To customize the way a field renders, you need to define a widget as well.
The following code shows some responsibilities of widgets in django :
- render the form tag ( superclass TextInput takes care of this )
- format the value for displaying inside the form field.
there's an gotcha here : the value's class may vary, since you may render cleaned values coming from the field, or uncleaned data coming from a form POST. Hence the test if we have a numeric value in _format_value ; if it's a string, just leave it as-is.
- tell if the value has changed, compared to initial data. Very important when you use formsets with default values.
Please note that the widget's code is inspired by widgets.TimeInput from the sourcecode of django, which helps being consistent.
from django import forms
from django.forms import widgets
from django.forms.util import ValidationError
class HourWidget(widgets.TextInput):
def _format_value(self, value):
if isinstance(value, float) or isinstance(value, int):
import math
hours = math.floor(value)
minutes = (value - hours) * 60
value = "%d:%02d" % (hours, minutes)
return value
def render(self, name, value, attrs=None):
value = self._format_value(value)
return super(HourWidget, self).render(name, value, attrs)
def _has_changed(self, initial, data):
return super(HourWidget, self)._has_changed(self._format_value(initial), data)
class HourField(forms.Field):
widget = HourWidget
def clean(self, value):
super(HourField, self).clean(value)
import re
match = re.match("^([0-9]{1,2}):([0-9]{2})$", value)
if not match:
raise ValidationError("Please enter a valid hour ( ex: 12:34 )")
groups = match.groups()
hour = float(groups[0])
minutes = float(groups[1])
if minutes >= 60:
raise ValidationError("Invalid value for minutes")
return hour + minutes/60
Please note that 1:20 becomes 1:19, which is due to the loss of precision induced by the use of a float. You might want to change the data type if you don't want to lose some precision.