Saturday, March 26, 2011

Using jQuery UI's date picker on all django forms

After getting over the initial learning curves of django's forms, I've begun to really appreciate the level of customizability the developers have incorporated into the API.  By default, when sending a model to a form, any DateField members are converted to form DateField instances, which translates to a simple textfield.  This isn't very attractive and quickly leads to formatting complaints from users (and supervisors). There's another widget django provides for picking dates--three comboboxes for month, date, and year--but it's not a very elegant solution.  I thought it would be nice if, by default, all my forms could use jQuery UI's datepicker.  Here's what I did.

First, after some searching online, it seemed several people have experienced similar issues.  I happened to run across this answer on stackoverflow for customizing widgets for all forms.

Here's my final code.

def make_custom_datefield(f):
    formfield = f.formfield()
    if isinstance(f, models.DateField):
        formfield.widget.format = '%m/%d/%Y'
        formfield.widget.attrs.update({'class':'datePicker', 'readonly':'true'})
    return formfield

class ProjectForm(ModelForm):
    formfield_callback = make_custom_datefield
    class Meta:
        model = Project

First, on ProjectForm I specify a custom callback function for handling form fields.  This is actually really nice as I can apply this code to any form I want instead of marking individual fields for a form or applying the entire fix to every single model.

In make_custom_datefield I do a quick check to see if it's a model DateField instance.  If it is, I do some modifying.  First, I change the format of the widget so incoming data from the model match jQuery's format.  It might be possible to modify jQuery to match Django, but whatever.  Then I add on two custom attributes to the widget.  Both of these directly map to html attributes of the input tag.

<input class="datePicker" readonly="true" type="text" id="id_dateDue" />

The datePicker class is important so I can mark this input as a jQuery calendar in the browser.  I also mark it as readonly so users can't modify the date with bad formats.  Marking the input as read-only is a double-edged sword as it also prevents users from quickly entering dates months or years away.

Once that is working, I just need to add some code to my page that will mark all datePicker instances in javascript as being datepicker widgets.

// on page load

$(function() {
$( ".datePicker" ).datepicker();

And that's it!  I can now make jQuery UI datepickers the default widget on any django form I choose.