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

66 投票
10 回答
39796 浏览
提问于 2025-04-15 17:42

我遇到了一个思维方式的问题。我不确定应该把钱存储为Decimal(),还是把它存储为字符串,然后自己转换成小数。我的想法是这样的:

PayPal要求金额有两位小数,所以如果我有一个产品价格是49美元,PayPal希望看到的是49.00这个格式。Django的DecimalField()并不会设置小数的具体数值。它只是存储最多可以有多少位小数。所以,如果你存储的是49,并且这个字段设置为2位小数,它仍然会存储为49。我知道Django在从数据库反序列化回Decimal时,基本上是在进行类型转换(因为数据库没有小数字段),所以我并不太担心速度问题,更关注的是这个设计上的问题。我想做出最适合扩展性的选择。

或者,更好的是,有人知道怎么配置Django的DecimalField(),让它总是以两位小数的格式显示吗?

10 个回答

13

我这个晚来的版本添加了South迁移功能。

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

我觉得你应该把它存储为小数格式,然后在发送给PayPal之前,格式化成00.00的样子,像这样:

pricestr = "%01.2f" % price

如果你愿意的话,可以在你的模型里添加一个方法:

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

你可能想用 .quantize() 这个方法。它可以把小数值四舍五入到你指定的小数位数,传入的参数就是你想保留的小数位数。

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

这个方法还可以接受一个参数,用来指定你想要的四舍五入方式(不同的会计系统可能需要不同的四舍五入方法)。更多信息可以查看 Python文档

下面是一个自定义字段,它会自动生成正确的值。注意,这个功能只在从数据库中取出数据时有效,自己设置的值不会自动调整(直到你把它保存到数据库,再取出来的时候才会调整!)。

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: 为什么这个自定义模型字段的表现和预期不一样?

撰写回答