Django: ¿Cómo debo almacenar un valor monetario?


Me estoy topando con un problema de paradigma aquí. No se si debo almacenar dinero como un Decimal(), o si debo almacenarlo como una cadena y convertirlo a un decimal yo mismo. Mi razonamiento es este:

PayPal requiere 2 decimales, así que si tengo un producto que es 49 dólares incluso, PayPal quiere ver 49.00 venir a través del cable. DecimalField() de Django no establece una cantidad decimal. Solo almacena una cantidad máxima de decimales. Así que, si tienes 49 ahí, y tienes el campo establecido en 2 decimales, todavía lo almacenará como 49. Sé que Django es básicamente tipo casting cuando deserializa de nuevo de la base de datos en un Decimal (ya que las bases de datos no tienen campos decimales), por lo que no estoy completamente preocupado con los problemas de velocidad tanto como lo estoy con los problemas de diseño de este problema. Quiero hacer lo mejor para la extensibilidad.

O, mejor aún, ¿alguien sabe cómo configurar un django DecimalField () para que formatee siempre con el formato TWO_PLACES estilo.

Author: orokusaki, 2010-01-06

8 answers

Es posible que desee utilizar el método .quantize(). Esto redondeará un valor decimal a un cierto número de lugares, el argumento que proporcione especifica el número de lugares:

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

También puede tomar un argumento para especificar qué enfoque de redondeo desea (diferentes sistemas de contabilidad podrían querer redondeo diferente). Más información en los documentos de Python .

A continuación se muestra un campo personalizado que produce automáticamente el valor correcto. Tenga en cuenta que esto es solo cuando se recupera de la base de datos, y no le ayudará cuando lo configure usted mismo (hasta que lo guarde en la base de datos y lo recupere de nuevo!).

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

[editar]

Agregado __metaclass__, vea Django: ¿Por qué este campo de modelo personalizado no se comporta como se espera?

 61
Author: Will Hardy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:00

Creo que debes almacenarlo en un formato decimal y formatearlo en formato 00.00 solo luego enviarlo a PayPal, así:

pricestr = "%01.2f" % price

Si lo desea, puede agregar un método a su modelo:

def formattedprice(self):
    return "%01.2f" % self.price
 18
Author: Valentin Golev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-06 15:13:05

Mi versión tardía para el partido que añade migraciones del Sur.

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'])
 13
Author: so_,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-09 22:06:29

El dinero debe almacenarse en el campo dinero, que lamentablemente no existe. Dado que el dinero es un valor bidimensional (cantidad, moneda).

Existe python-money lib, que tiene muchas bifurcaciones, pero no he encontrado ninguna que funcione.


Recomendaciones:

Python-money probablemente la mejor bifurcación https://bitbucket.org/acoobe/python-money

Django-money recomendado por akumria: http://pypi.python.org/pypi/django-money / (no probado que uno todavía).

 11
Author: aisbaa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-24 22:41:31

Sugiero evitar mezclar la representación con el almacenamiento. Almacene los datos como un valor decimal con 2 lugares.

En la capa de interfaz de usuario, mostrarlo en una forma que sea adecuada para el usuario (por lo que tal vez omitir el ".00").

Cuando envíe los datos a PayPal, formatéelos según lo requiera la interfaz.

 10
Author: Aaron Digulla,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-06 15:14:10

Basándose en la respuesta de @Will_Hardy, aquí está para que no tenga que especificar max_digits y decimal_places cada vez:

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
 4
Author: Dave Aaron Smith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-01 21:43:22

En mi experiencia y también de otros, el dinero se almacena mejor como combinación de moneda y la cantidad en centavos.

Es muy fácil de manejar y calcular con él.

 3
Author: Andre Bossard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-01 11:22:57

Se almacena como un campo decimal y se agregan manualmente los decimales si es necesario, como dijo Valya, utilizando técnicas básicas de formato.

Incluso puede agregar un Método de Modelo a su modelo de producto o transacción que escupirá el campo decimal como una cadena con el formato adecuado.

 1
Author: M. Ryan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-06 15:17:00