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?
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With