aldesabido aldesabido - 3 years ago 136
Python Question

Automatically round Django's DecimalField according to the max_digits and decimal_places attributes before calling save()

I want to automatically round Django's DecimalField according to the max_digits and decimal_places attributes before calling save() function in ModelForm.

currently using the following:


  • django 1.8

  • python 2.7



What I have tried so far.

https://djangosnippets.org/snippets/10554/




models.py

amount = models.DecimalField(max_digits = 19, decimal_places = 2)


views.py

P.S. gonna apply it in different fields and in different models

data = {"amount" : 100.1234,"name":"John Doe",...}
form = My_form(data)
if form.is_valid(): //the error throws from here.
form.save()
else:
raise ValueError(form.errors)


forms.py

I plan to clean the fields in clean() function and do the rounding off of all decimal fields but when I try to print the raw_data, there's no 'amount field'.

class My_form(forms.ModelForm):
Class Meta:
model = My_model
fields = ('amount','name')
def clean(self):
raw_data = self.cleaned_data
print(raw_data) //only prints {'name' : 'John Doe'}

Answer Source

You are mainly getting the error because forms.DecimalField has separate validators from models.DecimalField:

data = {'amount': 1.12345 }

class NormalForm(forms.Form):
    amount = forms.DecimalField(max_digits = 19, decimal_places = 2)

normal_form = NormalForm(data)
normal_form.is_valid()  # returns False
normal_form.cleaned_data  # returns {}

and forms.DecimalField is used by default for forms for models with fields of class models.DecimalField. You could do something like this:

from django import forms
from django.db import models
from decimal import Decimal

def round_decimal(value, places):
    if value is not None:
        # see https://docs.python.org/2/library/decimal.html#decimal.Decimal.quantize for options
        return value.quantize(Decimal(10) ** -places)
    return value

class RoundingDecimalFormField(forms.DecimalField):
    def to_python(self, value):
        value = super(RoundingDecimalFormField, self).to_python(value)
        return round_decimal(value, self.decimal_places)

class RoundingDecimalModelField(models.DecimalField):
    def to_python(self, value):
        # you could actually skip implementing this
        value = super(RoundingDecimalModelField, self).to_python(value)
        return round_decimal(value, self.decimal_places)

    def formfield(self, **kwargs):
        defaults = { 'form_class': RoundingDecimalFormField }
        defaults.update(kwargs)
        return super(RoundingDecimalModelField, self).formfield(**kwargs)

Now anywhere you are using models.DecimalField, use RoundingDecimalModelField instead. Any form you use with those models will now also use the custom form field.

class RoundingForm(forms.Form):
    amount = RoundingDecimalFormField(max_digits = 19, decimal_places = 2)

data = {'amount': 1.12345 }

rounding_form = RoundingForm(data)
rounding_form.is_valid()  # returns True
rounding_form.cleaned_data  # returns {'amount': Decimal('1.12')}
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download