10

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?

1 Answer 1

14

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(CurrencyField, self).formfield(**defaults)
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.