djangodjango-modelsdjango-formsmodelformdjango-template-filters

Django Modelform DecimalField drop trailing 0s for display, up to 2 decimal places


I have a Modelform that contains multiple DecimalFields with decimal_places=4. I need 4 decimal places as that is a reflection of the underlying data. This is a financial utility where mean values might consume all 4 decimal places. Users should be allowed to enter up to 4 decimal places, and forms should be displayed with up to 4 decimal places. I am not looking for any sort of rounding or truncation.

What I want to do is limit the display of trailing 0s in modelforms being rendered in my template, up to a minimum of 2 trailing 0s, as these are dollars. For instance:

3.1234 would still remain as 3.1234
3.1230 would become 3.123
3.1200 would become 3.12
3.1000 would become 3.10
3.0000 would become 3.00

I have came up with the following inside my modelforms init method:

def __init__(self, *args, **kwargs):
    if self.initial:
        for field_name, field in self.fields.items():
            if isinstance(field, forms.DecimalField) and field.decimal_places == 4 and self.initial.get(field_name):
                self.initial[field_name] = self.initial[field_name].normalize()

Using the .normalize() drops ALL the trailing 0s (3.0000 -> 3), however, I want a minimum of 2. I thought of using .quantize(), to enforce a minimum of 2 decimal places, but this rounds values with more then 2 decimal places, which I do not want. I don't think I can use template filters, as I would need to render the form values separately. I am rendering my form fields as {{ form.fieldname }}.

If anyone can think of a solution for this, it would be much appreciated. <3


Solution

  • I ended up writing my own widget and applying it to the applicable fields in my form. Hope this helps if anyone else was experiencing this.

    class DollarDisplayTextInput(forms.TextInput):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
        def format_value(self, value):
            if value is not None and isinstance(value, Decimal):
            #Strip trailing 0s, leaving a minimum of 2 decimal places
                while (abs(value.as_tuple().exponent) > 2 and value.as_tuple().digits[-1] == 0):
                    value = Decimal(str(value)[:-1])
            return value