Django: How should I store a money value?

orokusaki picture orokusaki · Jan 6, 2010 · Viewed 36k times · Source

I'm running into a paradigm problem here. I don't know whether I should store money as a Decimal(), or if I should store it as a string and convert it to a decimal myself. My reasoning is this:

PayPal requires 2 decimal places, so if I have a product that is 49 dollars even, PayPal wants to see 49.00 come across the wire. Django's DecimalField() doesn't set a decimal amount. It only stores a maximum decimal places amount. So, if you have 49 in there, and you have the field set to 2 decimal places, it'll still store it as 49. I know that Django is basically type casting when it deserializes back from the database into a Decimal (since Databases don't have decimal fields), so I'm not completely concerned with the speed issues as much as I am with the design issues of this problem. I want to do what's best for extensibility.

Or, better yet, does anyone know how to configure a django DecimalField() to always format with the TWO_PLACES formatting style.

Answer

Will Hardy picture Will Hardy · Jan 6, 2010

You might want to use the .quantize() method. This will round a decimal value to a certain number of places, the argument you provide specifies the number of places:

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

It can also take an argument to specify what rounding approach you want (different accounting systems might want different rounding). More info in the Python docs.

Below is a custom field that automatically produces the correct value. Note that this is only when it is retrieved from the database, and wont help you when you set it yourself (until you save it to the db and retrieve it again!).

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

[edit]

added __metaclass__, see Django: Why does this custom model field not behave as expected?