views:

1290

answers:

2

As an exercise I am trying to create a custom django widget for a 24 hour clock. The widget will is a MultiWidget - a select box for each field.

I am trying to follow docs online (kinda sparse) and looking at the Pro Django book, but I can't seem to figure it out. Am I on the right track? I can save my data from the form, but when I prepopulate the form, the form doesn't have the previous values.

It seems the issue is that the decompress() methods 'value' argument is always empty, so I have nothing to interpret.

from django.forms import widgets

import datetime

class MilitaryTimeWidget(widgets.MultiWidget):
    """
    A widget that displays 24 hours time selection.
    """
    def __init__(self, attrs=None):
        hours = [ (i, "%02d" %(i)) for i in range(0, 24) ]
        minutes = [ (i, "%02d" %(i)) for i in range(0, 60) ]
        _widgets = (
            widgets.Select(attrs=attrs, choices=hours), 
            widgets.Select(attrs=attrs, choices=minutes),
            )
        super(MilitaryTimeWidget, self).__init__(_widgets, attrs)

    def decompress(self, value):
        print "******** %s" %value
        if value:
            return [int(value.hour), int(value.minute)]
        return [None, None]

    def value_from_datadict(self, data, files, name):
        hour = data.get("%s_0" %name, None)
        minute = data.get("%s_1" %name, None)
        if hour and minute:
            hour = int(hour)
            minute = int(minute)
            return datetime.time(hour=hour, minute=minute)
        return None

In my form, I am calling the widget like:

arrival_time = forms.TimeField(label="Arrival Time", required=False, widget=MilitaryTimeWidget())
A: 

I can't reproduce the problem:

>>> class MyForm(forms.Form):
...     t = forms.TimeField(widget=MilitaryTimeWidget())
...
>>> print MyForm(data={'t_0': '13', 't_1': '34'})
******** 13:34:00
<tr><th><label for="id_t_0">T:</label></th><td><select name="t_0" id="id_t_0">
<option value="0">00</option>
[...]
<option value="13" selected="selected">13</option>
[...]
<option value="23">23</option>
</select><select name="t_1" id="id_t_1">
<option value="0">00</option>
[...]
<option value="34" selected="selected">34</option>
[...]
<option value="59">59</option>
</select></td></tr>

Check that your request.POST is correct.

As a sidenote, are you sure this widget gives good usability? Four mouse clicks and possible scrolling of the minutes combobox...

akaihola
I think my problem is that I am not trying to populate the form using the POST data but instead trying to populate using one of my models.Since I am missing the arrival_time_0 and arrival_time_1 (the names given to each select widget) in the data I pass in, it isnt being populated.
ashchristopher
It seems rather heavy-handed and non-pythonic to purposfully split that data in my view before passing into the form. Thoughts?
ashchristopher
+2  A: 

Note this line in the docstring for MultiWidget:

You'll probably want to use this class with MultiValueField.

That's the root of your problem. You might be able to get the single-widget-only approach working (Marty says it's possible in Pro Django, but I've never tried it, and I think it's likely to be more work), but in that case your widget shouldn't be a subclass of MultiWidget.

What you need to do (if you want to follow the MultiWidget/MultiValueField path) is:

  • remove your value_from_datadict method
  • define a subclass of MultiValueField with a definition of the compress() method which does the task you're currently doing in value_from_datadict() (transforming a list of numbers into a datetime.time object)
  • set your Widget as the default one for your custom form Field (using the widget class attribute)
  • either create a custom model Field which returns your custom form Field from its formfield() method, or use your custom form field manually as a field override in a ModelForm.

Then everything will Just Work.

Carl Meyer
Looks like django.forms.fields.SplitDateTimeField and django.forms.widgets.SplitDateTimeWidget are good models to base on.
akaihola