Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django custom form field change value in render and submit

I've implemented a custom form field for converting my model integer field, which represent meter units, to a form float field representing kilometer units. i.e. 3500 meters saved in my model integer field would display 3,5 in form float field, and when the form is sent it need to convert back to integer. In order to achieve this i divide the value by 1000 before display it and multiply it by 1000 when i save it.

The render part works fine (either by dividing by 1000 in widget render or rather prepare_value in form field).

The problem comes when the form throws an error and values need to be redisplayed. In this case the form value will be passed to it (which is the float one 3,5) and the value is redivided and is displayed as 0,0035. So i don't need to divide the value by 1000 again.

class KmInput(NumberInput):

    def render(self, name, value, attrs=None):
        try:
            value = str(float(value or 0) / 1000)
        except ValueError:
            pass
        return super(KmInput, self).render(name, value, attrs)

class MeterToKmField(forms.FloatField):
    widget = KmInput

    def __init__(self, max_value=None, *args, **kwargs):
        super(MeterToKmField, self).__init__(max_value, 0, *args, **kwargs)

    def to_python(self, value):
        result = super(MeterToKmField, self).to_python(value)
        value *= 1000
        return result

class DistanceForm(forms.ModelForm):
    distance = MeterToKmField(help_text="km")

    class Meta:
        model = Distance

Am i missing something?

UPDATE:

As Peter DeGlopper suggested, i've implemented _format_value in my custom widget, but i still get this method called when the form raise an error, making the value that is already divided by 1000, gets divided again.. Here's what i did:

class KmInput(NumberInput):

    def _format_value(self, value):
        try:
            return str(float(value or 0) / 1000)
        except ValueError:
            return value

class MeterToKmField(forms.FloatField):
    widget = KmInput

    def __init__(self, max_value=None, *args, **kwargs):
        super(MeterToKmField, self).__init__(max_value, 0, *args, **kwargs)

    def to_python(self, value):
        result = super(MeterToKmField, self).to_python(value)
        result *= 1000
        return result
like image 992
Yuri Heupa Avatar asked Jan 23 '26 17:01

Yuri Heupa


1 Answers

I had a similar problem displaying price fields that are internally stored as integers. This is what I learned:

You could overwrite the bounddata method in your field to return the initial rather than the entered data. The result of bound data still passes through prepare_value later, so you could implement this the same way you did before:

class MeterToKmField(forms.FloatField):
    def bound_data(self, data, initial):
        return initial

    def prepare_value(self,value):
        #...same as before, just divide by 1000

However this might not be what you want, as changes made by the user might be lost. My preferred solution therefore is based on the fact that the bound value in prepare_value is unicode and the unbound one is an integer. So you could do something like:

class MeterToKmField(forms.FloatField):
    def prepare_value(self, value):
        #python 2.x ,use str instead of basestring for python 3.x
        if isinstance(value, basestring):
            #in case of bound data, is already divided by 100
            return value
        else:
            #raw data
            return float(value)/ 1000

    #don't overwrite bound_data in this case
like image 59
Sara Avatar answered Jan 25 '26 07:01

Sara



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!