2010-01-06 13 views
61

Qui sto incontrando un problema di paradigma. Non so se dovrei memorizzare i soldi come decimale(), o se dovrei archiviarlo come stringa e convertirlo in un decimale. Il mio ragionamento è questo:Django: Come dovrei memorizzare un valore in denaro?

PayPal richiede 2 cifre decimali, quindi se ho un prodotto che è di 49 dollari anche, PayPal vuole vedere 49,00 venire attraverso il filo. Django's DecimalField() non imposta un valore decimale. Memorizza solo una quantità massima di posti decimali. Quindi, se ne hai 49 e hai il campo impostato su 2 cifre decimali, lo memorizzerò comunque come 49. So che Django è fondamentalmente di tipo casting quando deserializza dal database in un decimale (dal momento che i database non ho campi decimali), quindi non sono completamente interessato ai problemi di velocità tanto quanto lo sono con i problemi di progettazione di questo problema. Voglio fare ciò che è meglio per l'estensibilità.

O, meglio ancora, qualcuno sa come configurare un django DecimalField() per formattare sempre con lo stile di formattazione TWO_PLACES.

+1

"poiché i database non hanno campi decimali"; Microsoft Sql Server ha sia tipi di dati "decimali" che "in denaro" http://msdn.microsoft.com/en-us/library/aa258271%28v=sql.80%29.aspx –

risposta

60

È possibile utilizzare il metodo .quantize(). Ciò arrotondare un valore decimale a un certo numero di posti, l'argomento che si fornisce specifica il numero di posti:

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

Si può anche prendere un argomento per specificare quali arrotondamento approccio che si desidera (diversi sistemi contabili potrebbero volere diversi arrotondamento). Maggiori informazioni nello Python docs.

Di seguito è riportato un campo personalizzato che produce automaticamente il valore corretto. Nota che questo è solo quando viene recuperato dal database e non ti aiuterà quando lo imposti da solo (finché non lo salvi sul db e lo recuperi di nuovo!).

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 

[modifica]

aggiunto __metaclass__, vedi Django: Why does this custom model field not behave as expected?

+0

Eccellente. Grazie a tutti. È tutto ciò che devi fare per avere un campo del modello personalizzato completamente funzionale? (cioè, funzionerà esattamente come DecimalField ad eccezione della formattazione). – orokusaki

+1

Inoltre, qual è la convenzione per la memorizzazione di campi personalizzati? Sto pensando che lo inserirò nella cartella del progetto root. – orokusaki

+0

Grazie, ho appena usato questo. Ho notato che dovevo ripetere gli attributi max_digits e decimal_places ogni volta che utilizzavo CurrencyField, quindi ho postato una risposta che si basa sul tuo per risolvere questo problema. –

18

Penso che si dovrebbe conservarlo in un formato decimale e formattarlo a 00.00 formato solo poi inviarlo a PayPal, come questo:

pricestr = "%01.2f" % price 

Se si desidera, è possibile aggiungere un metodo per la vostra modello:

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

Inoltre: memorizzare la valuta nel database così come l'importo. Il denaro non è solo un numero. –

+0

È vero. Grazie Mr Shiny e New – orokusaki

+6

Mr Shiny, non è certo necessario memorizzare la valuta nel database se si sa che tutte le valute saranno le stesse. In effetti, la maggior parte delle applicazioni non si occupa di più valute. –

10

Suggerisco di evitare di mescolare la rappresentazione con la memoria. Memorizza i dati come valore decimale con 2 posizioni.

Nel livello UI, visualizzarlo in un formato adatto all'utente (quindi forse omettere ".00").

Quando si inviano i dati su PayPal, formattarli come richiesto dall'interfaccia.

1

Lo si memorizza come decimale e si aggiungono manualmente i decimali se necessario, come ha detto Valya, utilizzando tecniche di formattazione di base.

È anche possibile aggiungere un metodo modello al prodotto o al modello di transazione che sputerà il decimale come una stringa opportunamente formattata.

11

Il denaro deve essere conservato in campo di denaro, che purtroppo non esiste. Poiché il denaro è un valore bidimensionale (importo, valuta).

C'è python-money lib, che ha molte forcelle, eppure non ne ho trovato uno funzionante.


Raccomandazioni:

pitone denaro probabilmente la migliore forcella https://bitbucket.org/acoobe/python-money

django-denaro raccomandate da akumria: http://pypi.python.org/pypi/django-money/ (havent provato quella ancora).

+0

Consiglia https://github.com/reinbach/django-money invece di akumria in quanto ha alcuni bugfix e installa tramite pip. –

3

Nella mia esperienza e anche da others, il denaro è memorizzato al meglio come combinazione di valuta e l'importo in centesimi.

È molto facile da gestire e calcolare con esso.

+0

Ricorda che non è vero per tutte le valute che 1 minore (ad es. Cento) equivale a 0.01 maggiore (dollaro). – ElmoVanKielmo

+0

Non avrei dovuto usare il termine "centesimi", ma più il più piccolo non più unità divisibile di quella valuta. –

+0

Tuttavia, in questo modo la tua app non supporterà le valute con un diverso rapporto tra maggiore e minore allo stesso tempo. – ElmoVanKielmo

4

Sulla base di @ Will_Hardy risposta, qui è così non c'è bisogno di specificare MAX_DIGITS e decimal_places ogni volta:

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 
+0

Ho un campo simile, ma io uso il pattern 'defaults = {...' e 'defaults.update (** kwargs)' . – orokusaki

13

mio ritardo alla versione partito che aggiunge migrazioni 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']) 
+0

Il percorso in "['^ application \ .fields \ .CurrencyField']" deve essere modificato in base al mio progetto? Grazie – pymarco

Problemi correlati