Джанго: Как я должен хранить денежную стоимость? - PullRequest
63 голосов
/ 06 января 2010

Я столкнулся с проблемой парадигмы здесь. Я не знаю, должен ли я хранить деньги в виде десятичной дроби (), или я должен хранить их в виде строки и конвертировать их в десятичную дробь самостоятельно. Я рассуждаю так:

PayPal требуется 2 десятичных знака , поэтому, если у меня есть продукт, который стоит даже 49 долларов, PayPal хочет, чтобы 49,00 встретились по проводам. Django DecimalField () не устанавливает десятичную сумму. Он хранит только максимальное количество знаков после запятой. Итак, если у вас там 49, а у вас установлено поле с двумя десятичными разрядами, оно все равно будет храниться как 49. Я знаю, что Django в основном выполняет приведение типов, когда десериализуется из базы данных в десятичное число (поскольку базы данных не имеют десятичных полей), поэтому меня не столько волнуют проблемы со скоростью, сколько проблемы дизайна этой проблемы. Я хочу сделать то, что лучше для расширяемости.

Или, что еще лучше, кто-нибудь знает, как настроить django DecimalField () для постоянного форматирования с использованием стиля форматирования TWO_PLACES.

Ответы [ 8 ]

61 голосов
/ 06 января 2010

Возможно, вы захотите использовать метод .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: Почему это поле пользовательской модели не работает должным образом?

18 голосов
/ 06 января 2010

Я думаю, вы должны сохранить его в десятичном формате и отформатировать его в формате 00.00 только , а затем отправить его в PayPal, например:

pricestr = "%01.2f" % price

Если вы хотите, вы можетедобавить метод к вашей модели:

def formattedprice(self):
    return "%01.2f" % self.price
13 голосов
/ 10 мая 2013

Моя поздняя версия для вечеринки, которая добавляет южные миграции.

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'])
12 голосов
/ 23 июля 2012

Деньги должны храниться в денежном поле, которого, к сожалению, не существует. Поскольку деньги - это двумерное значение (сумма, валюта).

Существует python-money lib, в которой есть много вилок, но я не нашел работающую.


Рекомендации:

python-money вероятно, лучшая вилка https://bitbucket.org/acoobe/python-money

django-money рекомендовано akumria: http://pypi.python.org/pypi/django-money/ (еще не пробовал).

10 голосов
/ 06 января 2010

Предлагаю избегать смешивания представления с хранилищем. Сохраните данные в виде десятичного значения с двумя знаками.

В слое пользовательского интерфейса отобразите его в форме, удобной для пользователя (поэтому, возможно, пропустите ".00").

Когда вы отправляете данные в PayPal, форматируйте их в соответствии с требованиями интерфейса.

4 голосов
/ 02 ноября 2012

Опираясь на ответ @ 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
3 голосов
/ 01 ноября 2012

По моему опыту, а также от других , деньги лучше всего хранить как комбинацию валюты и суммы в центах.

С ним очень легко работать и рассчитывать.

1 голос
/ 06 января 2010

Вы сохраняете его как DecimalField и вручную добавляете десятичные дроби, если вам нужно, как сказал Валя, используя базовые методы форматирования.

Вы даже можете добавить метод метода к своей модели продукта или транзакции, который будет отображать поле DecimalField в виде строки соответствующего формата.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...