Django:我应该如何存储货币价值?

时间:2010-01-06 15:09:00

标签: python django django-models decimal currency

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

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

或者,更好的是,有没有人知道如何配置django DecimalField()以始终使用TWO_PLACES格式样式进行格式化。

10 个答案:

答案 0 :(得分:61)

您可能希望使用.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?

答案 1 :(得分:18)

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

pricestr = "%01.2f" % price

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

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

答案 2 :(得分:13)

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

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'])

答案 3 :(得分:12)

钱应存放在货币领域,遗憾的是不存在。由于货币是二维价值(金额,货币)。

python-money lib,有很多分叉,但我找不到工作。


建议:

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

akumria推荐的

django-money http://pypi.python.org/pypi/django-money/(尚未尝试过那个)。

答案 4 :(得分:10)

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

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

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

答案 5 :(得分:4)

在@ 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

答案 6 :(得分:3)

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

用它来处理和计算非常容易。

答案 7 :(得分:1)

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

您甚至可以向产品或事务模型添加模型方法,该方法将DecimalField作为格式正确的字符串吐出。

答案 8 :(得分:0)

更好的主意是以较小的货币单位(可能的最小单位)存储货币。对于欧元和美元,次要货币单位是美分。基于这个概念,有一个名为 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'

答案 9 :(得分:-1)

我是Django的第一手入门者,也存储金钱值。

还有另一种存储货币值的可能性:只需将货币值转换为其次要表示形式,然后将该整数值存储在数据库中。如果可以将db后端的舍入调整为货币运算需要,则应该可以。

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

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