web-dev-qa-db-fra.com

Django: Comment dois-je stocker une valeur monétaire?

Je rencontre un problème de paradigme ici. Je ne sais pas si je dois stocker de l'argent en tant que Decimal (), ou si je dois le stocker en tant que chaîne et le convertir moi-même en décimal. Mon raisonnement est le suivant:

Paypal nécessite 2 décimales, donc si j'ai un produit qui coûte même 49 dollars, Paypal veut voir 49,00 tomber sur le fil. Le DecimalField () de Django ne définit pas de quantité décimale. Il ne stocke qu'un nombre maximal de décimales. Donc, si vous en avez 49 et que le champ est défini sur 2 décimales, il le stockera toujours sous la forme 49. Je sais que Django est essentiellement du type cast lorsqu'il désérialise en retour de la base de données dans une décimale (car les bases de données n'ont pas de champs décimaux), donc je ne suis pas complètement préoccupé par les problèmes de vitesse autant que par les problèmes de conception de ce problème. Je veux faire ce qui est le mieux pour l'extensibilité.

Ou, mieux encore, quelqu'un sait-il comment configurer un Django DecimalField () pour toujours formater avec le style de formatage TWO_PLACES.

63
orokusaki

Vous souhaiterez peut-être utiliser la méthode .quantize(). Cela arrondira une valeur décimale à un certain nombre de places, l'argument que vous fournissez spécifie le nombre de places:

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

Il peut également prendre un argument pour spécifier l'approche d'arrondi souhaitée (différents systèmes comptables peuvent souhaiter des arrondis différents). Plus d'informations dans les documents Python .

Vous trouverez ci-dessous un champ personnalisé qui produit automatiquement la valeur correcte. Notez que ce n'est que lorsqu'il est récupéré de la base de données, et ne vous aidera pas lorsque vous le définissez vous-même (jusqu'à ce que vous l'enregistriez dans la base de données et le récupériez à nouveau!).

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

[Éditer]

ajoutée __metaclass__, voir Django: Pourquoi ce champ de modèle personnalisé ne se comporte-t-il pas comme prévu?

61
Will Hardy

Je pense que vous devriez le stocker dans un format décimal et le formater au format 00.00 seulement puis l'envoyer à Paypal, comme ceci:

pricestr = "%01.2f" % price

Si vous le souhaitez, vous pouvez ajouter une méthode à votre modèle:

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

Ma fin à la version fête qui ajoute les migrations du Sud.

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
so_

L'argent devrait être stocké dans un champ d'argent, qui n'existe malheureusement pas. Puisque l'argent est une valeur bidimensionnelle (montant, devise).

Il y a python-money lib, qui a beaucoup de fourches, mais je n'en ai pas trouvé de fonctionnel.


Recommandations:

python-money probablement la meilleure fourchette https://bitbucket.org/acoobe/python-money

Django-money recommandé par akumria: http://pypi.python.org/pypi/Django-money/ (havent encore essayé).

12
aisbaa

Je suggère d'éviter de mélanger représentation et stockage. Stockez les données sous forme de valeur décimale avec 2 places.

Dans la couche d'interface utilisateur, affichez-le sous une forme qui convient à l'utilisateur (alors peut-être omettez le ".00").

Lorsque vous envoyez les données à Paypal, formatez-les comme l'interface l'exige.

10
Aaron Digulla

En s'appuyant sur la réponse de @ Will_Hardy, la voici pour que vous n'ayez pas à spécifier max_digits et decimal_places à chaque fois:

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
Dave Aaron Smith

D'après mon expérience et aussi de autres , l'argent est mieux stocké sous forme de combinaison de devise et de montant en cents.

Il est très facile à manipuler et à calculer avec.

3
Andre Bossard

Vous le stockez en tant que DecimalField et ajoutez manuellement les décimales si vous en avez besoin, comme l'a dit Valya, en utilisant des techniques de formatage de base.

Vous pouvez même ajouter une méthode de modèle à votre produit ou modèle de transaction qui crachera le DecimalField sous la forme d'une chaîne correctement formatée.

1
M. Ryan