4

I am working with a django model which stores currency values as integers. i.e. GBP22.35 gets stored as 2235.

When that model gets rendered as a form, I need to be able to associate a widget with that integer field so can be edited as though it were a float value (i.e. to two decimal places - 22.35) and is validated as such. form.save() then needs to save the native integer value to the db.

I've tried creating a custom FormField/Widget pair which involves dividing by 100 in the widgets render and multiplying back up in the fields to_python method, but it all goes awry if there is an error on the form. The widget keeps re-dividing the value.

I realise this could be avoided by using float/decimal model fields but that is not an option in this case.

Is this something people have done before? Any pointers? Thanks.

3
  • Why not use FloatField ?? Ok, I see you don't have that option. Commented Mar 11, 2014 at 13:11
  • 1
    @RaydelMiranda Storing currency data in a FloatField is never an acceptable option. Commented Mar 11, 2014 at 13:19
  • Using firebird as a db. Commented Mar 11, 2014 at 14:12

1 Answer 1

2

You could implement a subclass of IntegerField that handles that conversion behind the scenes:

import decimal
from django.db import models
from django.core import exceptions
from django.utils.translation import ugettext_lazy as _

class CentsField(models.IntegerField):
    empty_strings_allowed = False
    default_error_messages = {
        'invalid': _("'%(value)s' value must be a decimal number."),
    }
    description = _("Fixed-point number")

    __metaclass__ = models.SubfieldBase

    def to_python(self, value):
        if value is None or isinstance(value, decimal.Decimal):
            return value
        try:
            if isinstance(value, int):
                return decimal.Decimal(value) / 100
            else:
                return decimal.Decimal(value)
        except decimal.InvalidOperation:
            raise exceptions.ValidationError(
                self.error_messages['invalid'],
                code='invalid',
                params={'value': value},
            )

    def get_prep_value(self, value):
        return int(value * 100)

Should behave as DecimalField in your Django code, but as IntegerField in your database.

Update: simpler implementation derived from IntegerField instead of DecimalField; added validation as implemented in DecimalField.to_python

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.