【问题标题】:Django: How should I store a money value?Django:我应该如何存储货币价值?
【发布时间】:2010-01-06 15:09:00
【问题描述】:

我在这里遇到了一个范式问题。我不知道是否应该将钱存储为 Decimal(),或者是否应该将其存储为字符串并自己将其转换为小数。我的理由是这样的:

PayPal 需要 2 位小数,所以如果我有一个 49 美元的产品,PayPal 希望看到 49.00 的电汇。 Django 的 DecimalField() 没有设置小数。它仅存储最大小数位数。所以,如果你有 49,并且你将字段设置为小数点后 2 位,它仍然会将它存储为 49。我知道 Django 在从数据库反序列化回十进制时基本上是类型转换(因为数据库没有小数字段),所以我并不完全关心速度问题,就像我关心这个问题的设计问题一样。我想做最好的可扩展性。

或者,更好的是,有谁知道如何将 django DecimalField() 配置为始终使用 TWO_PLACES 格式化样式。

【问题讨论】:

标签: python django django-models decimal currency


【解决方案1】:

您可能想要使用.quantize() 方法。这会将十进制值四舍五入到一定位数,您提供的参数指定位数:

>>> from decimal import Decimal
>>> Decimal("12.234").quantize(Decimal("0.00"))
Decimal("12.23")

它也可以带一个参数来指定你想要的四舍五入方法(不同的会计系统可能需要不同的四舍五入)。更多信息在Python docs

下面是一个自动生成正确值的自定义字段。请注意,这仅在从数据库中检索时使用,并且在您自己设置时不会帮助您(直到您将其保存到数据库并再次检索它!)。

from django.db import models
from decimal import Decimal
class CurrencyField(models.DecimalField):
    __metaclass__ = models.SubfieldBase

    def to_python(self, value):
        try:
           return super(CurrencyField, self).to_python(value).quantize(Decimal("0.01"))
        except AttributeError:
           return None

[编辑]

添加了__metaclass__,见Django: Why does this custom model field not behave as expected?

【讨论】:

  • 优秀。谢谢whrde。这就是拥有一个功能齐全的自定义模型字段所需要做的一切吗? (即,除了格式之外,这是否与 DecimalField 完全相同)。
  • 另外,存储自定义字段的约定是什么?我想我会把它放在根项目文件夹中。
  • 谢谢,我刚用这个。我注意到每次使用 CurrencyField 时都必须重复 max_digits 和 decimal_places 属性,因此我发布了一个基于您的答案来解决这个问题。
  • 我希望你能解释 quantize 的基本原理,因为你的回答和 python 文档都没有让我清楚地知道它的用途。
  • 好的,我已经为.quantize()添加了一个快速解释
【解决方案2】:

我认为您应该将其存储为十进制格式并将其格式化为 00.00 格式然后将其发送到 PayPal,如下所示:

pricestr = "%01.2f" % price

如果需要,您可以在模型中添加方法:

def formattedprice(self):
    return "%01.2f" % self.price

【讨论】:

  • 还有:将货币和金额存储在数据库中。金钱不仅仅是一个数字。
  • 确实如此。谢谢闪亮先生和新人
  • Shiny 先生,如果您知道所有货币都将是相同的,那么您当然不需要将货币存储在数据库中。事实上,大多数应用程序并不处理多种货币。
【解决方案3】:

我迟到了加入南方迁移的派对版本。

from decimal import Decimal
from django.db import models

try:
    from south.modelsinspector import add_introspection_rules
except ImportError:
    SOUTH = False
else:
    SOUTH = True

class CurrencyField(models.DecimalField):
    __metaclass__ = models.SubfieldBase

    def __init__(self, verbose_name=None, name=None, **kwargs):
        decimal_places = kwargs.pop('decimal_places', 2)
        max_digits = kwargs.pop('max_digits', 10)

        super(CurrencyField, self). __init__(
            verbose_name=verbose_name, name=name, max_digits=max_digits,
            decimal_places=decimal_places, **kwargs)

    def to_python(self, value):
        try:
            return super(CurrencyField, self).to_python(value).quantize(Decimal("0.01"))
        except AttributeError:
            return None

if SOUTH:
    add_introspection_rules([
        (
            [CurrencyField],
            [],
            {
                "decimal_places": ["decimal_places", { "default": "2" }],
                "max_digits": ["max_digits", { "default": "10" }],
            },
        ),
    ], ['^application\.fields\.CurrencyField'])

【讨论】:

  • “['^application\.fields\.CurrencyField']”中的路径是否必须根据我的项目进行更改?谢谢
【解决方案4】:

钱应该存储在money field中,遗憾的是它不存在。由于货币是二维值(金额、货币)。

python-money 库,它有很多分支,但我还没有找到工作。


建议:

python-money 可能是最好的 fork https://bitbucket.org/acoobe/python-money

django-money 由 akumria 推荐:http://pypi.python.org/pypi/django-money/(还没有尝试过)。

【讨论】:

【解决方案5】:

我建议避免将表示与存储混合。将数据存储为 2 位的十进制值。

在UI层中,以适合用户的形式显示(所以可以省略“.00”)。

当您将数据发送到 PayPal 时,按照界面要求对其进行格式化。

【讨论】:

    【解决方案6】:

    根据我的经验以及others 的经验,最好将钱存储为货币和美分金额的组合。

    使用它很容易处理和计算。

    【讨论】:

    • 请注意,1 个次要货币(例如美分)不等于 0.01 个主要货币(美元)。
    • 我不应该使用“美分”这个词,而是该货币的最小不可分割单位。
    • 不过,这样您的应用程序将不会同时支持主要和次要比率不同的货币。
    • @ElmoVanKielmo 你能举一个这种货币的例子吗?假设一种货币对一种主要货币有 1000 个次要货币(1minor=0.001major),可以将货币精度存储为元数据。
    • @pjotr_dolphin 日本货币,例如 1 rin = 0.001 日元。你是对的,正是出于这个原因,货币精度应该存储为元数据
    【解决方案7】:

    基于@Will_Hardy 的回答,您不必每次都指定 max_digits 和 decimal_places:

    from django.db import models
    from decimal import Decimal
    
    
    class CurrencyField(models.DecimalField):
      __metaclass__ = models.SubfieldBase
    
      def __init__(self, verbose_name=None, name=None, **kwargs):
        super(CurrencyField, self). __init__(
            verbose_name=verbose_name, name=name, max_digits=10,
            decimal_places=2, **kwargs)
    
      def to_python(self, value):
        try:
          return super(CurrencyField, self).to_python(value).quantize(Decimal("0.01"))
        except AttributeError:
          return None
    

    【讨论】:

    • 我有一个类似的字段,但我使用defaults = {...defaults.update(**kwargs) 模式。
    【解决方案8】:

    您将其存储为 DecimalField 并根据需要手动添加小数,正如 Valya 所说,使用基本的格式化技术。

    您甚至可以将模型方法添加到您的产品或交易模型中,将 DecimalField 输出为适当格式的字符串。

    【讨论】:

      【解决方案9】:

      我是 Django 的第一手,也可以存储货币价值。

      还有另一种存储货币值的可能性:只需将货币值转换为其次要表示并将该整数值存储在数据库中。如果数据库后端的四舍五入可以根据货币算术需求进行调整,那么这应该可以。

      在检索一个值时,只需将其读回并根据需要将其表示为字符串,这通常涉及将整数转换为字符串后在正确的位置放置一个小数分隔符。

      看起来某些中间件完全期望这种行为Convert decimal to integer without losing monetary value

      【讨论】:

        【解决方案10】:

        更好的办法是以次要货币单位(可能的最小单位)存储资金。对于欧元和美元,次要货币单位是美分。基于这个概念,有一个名为 valuta 的 Python(和 Django)包。

        直接使用货币类别

        import valuta
        
        valuta.EUR.convert_to_currency_units(1_000)  # 1_000 is the price in cents
        # 10.0
        

        使用 (ISO-4217) 货币代码的字符串表示形式

        from valuta.shortcuts import convert_to_currency_units
        
        convert_to_currency_units("EUR", 1_000)  # 1_000 is the price in cents
        # 10.0
        

        Django 集成

        模型定义

        from django.db import models
        from valuta.contrib.django_integration.models import CurrencyField
        
        class Product(models.Model):
        
            price = models.IntegerField()  # Amount in minor currency units
            price_with_tax = models.IntegerField()  # Amount in minor currency units
            currency = CurrencyField(amount_fields=["price", "price_with_tax"])
        

        样本数据

        import valuta
        from product.models import Product
        product = Product.objects.create(
            price=1_000,  # Price in cents
            price_with_tax=1_200,  # Price in cents
            currency=valuta.EUR.uid,
        )
        

        使用魔术方法将次要货币(美分)单位转换为主要货币单位(欧元)

        product.price_in_currency_units()  # Price in euros
        # 10.0
        product.price_with_tax_in_currency_units()  # Price in euros
        # 12.0
        

        如果需要在模板中显示:

        product.price_display_in_currency_units()  # Price in euros
        # '10.00'
        product.price_with_tax_display_in_currency_units()  # Price in euros
        # '12.00'
        

        现在试试另一种货币(日元)。

        product_jpy = Product.objects.create(
            price=1_000,  # Price in cents
            price_with_tax=1_200,  # Price in cents
            currency=valuta.JPY.uid,
        )
        
        product.price_in_currency_units()  # Price in JPY
        # 10.0
        product.price_with_tax_in_currency_units()  # Price in JPY
        # 12.0
        
        product.price_display_in_currency_units()  # Price in JPY
        # '10'
        product.price_with_tax_display_in_currency_units()  # Price in JPY
        # '12'
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-07-02
          • 2016-06-21
          • 1970-01-01
          • 2011-03-16
          • 2017-11-21
          相关资源
          最近更新 更多