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.

14 comments:

  1. Very helpful for a new Django user thanks!

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. This worked like a charm and was the clearest explanation I have found online so far. Thanks!

    ReplyDelete
  5. I found bug when U use a widgets property in modelform. I solve this by using send **kwargs to formfield constructor.
    Now make_custom_datefield look like that:

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

    ReplyDelete
  6. I think it is simpler to use the widgets meta in your ProjectForm.

    Example:

    class ProjectForm(ModelForm):
    class Meta:
    widgets = {
    'date': forms.DateInput(format='%Y-%m-%d', attrs={'class':'datePicker', 'readonly':'true'}),
    }

    ReplyDelete
  7. Thanks for the detailed conclusion to your(?) slashdot question. I wouldn't have been able to go from the general code on the answer to your final solution easily.

    ReplyDelete
  8. Thanks! Both yours and hleroy's comment helped me a lot. Django forms are kinda confusing if you want to go beyond the basics...

    ReplyDelete
  9. This comment has been removed by the author.

    ReplyDelete
  10. Hi,

    your code is very useful to me..
    please share the entire code.
    I am beginner of django understanding very useful to me.

    -Thanks
    -Ganesh.

    ReplyDelete
  11. Sleek! Worked right away! Though I customised jQuery UI's datepicker.

    So far I have placed the custom function in the models.py file in my app. Any idea what would be a better place to put this function?

    ReplyDelete
  12. Learning Django is not easy, if you want to do amazing things.
    I found your post looking for a jquery renderer for django forms.
    It is amazing how you gave a solution for a very common issue.
    Thank you for sharing it!!

    ReplyDelete
  13. Thank you for this! It came in very handy for me!

    ReplyDelete
  14. 2015 and this is exactly what I needed. Thank you! (django 1.8.2, bootstrap 3.3.5)

    For any others who happen across this, I've added a wee bit of js using bootstrap so that a nice clickable calendar icon appears in the form field. Looks awesome!

    http://pastebin.com/FXADF1Bt

    ReplyDelete