Mark Winterbottom Mark Winterbottom - 4 months ago 21
Python Question

Django creating a custom model field

I am trying to create a custom field in Django which will take a decimal currency value (example: £1.56) and save it in the database as an Integer (example: 156) to store currency values.

This is what I have so far (I have put fixed values to test)

class CurrencyField(models.DecimalField):
__metaclass__ = models.SubfieldBase

def get_internal_type(self):
return 'PositiveIntegerField'

def to_python(self, value):
print "CurrentField to_python"

return Decimal(value)/100

def get_db_prep_value(self, value, connection, prepared=False):
print "CurrentField get_db_prep_value"
if value is None:
return value

print type(value)

return int(value*100)


I am following the Python 1.7 documentation for creating custom model fields.

In the above code, the
get_db_prep_value
method is never being called when I save the model using this field. The
to_python
method is working fine (if I manually enter the value in the DB it will return the correct decimal), however it will never save the correct value.

I have also tried using
get_prep_value
and this has the same result.

How can I make it save the correct value?

Answer

Integer based MoneyField

I've been using this Django snippet for some time, but realized later it was introducing floating-point artifacts when converting to an integer before saving the the database.

Also, as of Django 1.9 SubfieldBase has been deprecated and we should use Field.from_db_value instead. So I've edited the code to fix both of these issues.

class CurrencyField(models.IntegerField):
  description = "A field to save dollars as pennies (int) in db, but act like a float"

  def get_db_prep_value(self, value, *args, **kwargs):
    if value is None:
      return None
    return int(round(value * 100))

  def to_python(self, value):
    if value is None or isinstance(value, float):
      return value
    try:
      return float(value) / 100
    except (TypeError, ValueError):
      raise ValidationError("This value must be an integer or a string represents an integer.")

  def from_db_value(self, value, expression, connection, context):
    return self.to_python(value)

  def formfield(self, **kwargs):
    from django.forms import FloatField
    defaults = {'form_class': FloatField}
    defaults.update(kwargs)
    return super(DollarField, self).formfield(**defaults)