Josh Hunt Josh Hunt - 16 days ago 4x
Python Question

Using Django time/date widgets in custom form

How can I use the nifty JavaScript date and time widgets that the default admin uses with my custom view?

I have looked through the Django forms documentation, and it briefly mentions django.contrib.admin.widgets, but I don't know how to use it?

Here is my template that I want it applied on.

<form action="." method="POST">
{% for f in form %}
<tr> <td> {{ }}</td> <td>{{ f }}</td> </tr>
{% endfor %}
<input type="submit" name="submit" value="Add Product">

Also, I think it should be noted that I haven't really written a view up myself for this form, I am using a generic view. Here is the entry from the

(r'^admin/products/add/$', create_object, {'model': Product, 'post_save_redirect': ''}),

And I am relevantly new to the whole Django/MVC/MTV thing, so please go easy...


The growing complexity of this answer over time, and the many hacks required, probably ought to caution you against doing this at all. It's relying on undocumented internal implementation details of the admin, is likely to break again in future versions of Django, and is no easier to implement than just finding another JS calendar widget and using that.

That said, here's what you have to do if you're determined to make this work:

  1. Define your own ModelForm subclass for your model (best to put it in in your app), and tell it to use the AdminDateWidget / AdminTimeWidget / AdminSplitDateTime (replace 'mydate' etc with the proper field names from your model):

    from django import forms
    from my_app.models import Product
    from django.contrib.admin import widgets                                       
    class ProductForm(forms.ModelForm):
        class Meta:
            model = Product
        def __init__(self, *args, **kwargs):
            super(ProductForm, self).__init__(*args, **kwargs)
            self.fields['mydate'].widget = widgets.AdminDateWidget()
            self.fields['mytime'].widget = widgets.AdminTimeWidget()
            self.fields['mydatetime'].widget = widgets.AdminSplitDateTime()
  2. Change your URLconf to pass 'form_class': ProductForm instead of 'model': Product to the generic create_object view (that'll mean "from my_app.forms import ProductForm" instead of "from my_app.models import Product", of course).

  3. In the head of your template, include {{ }} to output the links to the Javascript files.

  4. And the hacky part: the admin date/time widgets presume that the i18n JS stuff has been loaded, and also require core.js, but don't provide either one automatically. So in your template above {{ }} you'll need:

    <script type="text/javascript" src="/my_admin/jsi18n/"></script>
    <script type="text/javascript" src="/media/admin/js/core.js"></script>

    You may also wish to use the following admin CSS (thanks Alex for mentioning this):

    <link rel="stylesheet" type="text/css" href="/media/admin/css/forms.css"/>
    <link rel="stylesheet" type="text/css" href="/media/admin/css/base.css"/>
    <link rel="stylesheet" type="text/css" href="/media/admin/css/global.css"/>
    <link rel="stylesheet" type="text/css" href="/media/admin/css/widgets.css"/>

This implies that Django's admin media (ADMIN_MEDIA_PREFIX) is at /media/admin/ - you can change that for your setup. Ideally you'd use a context processor to pass this values to your template instead of hardcoding it, but that's beyond the scope of this question.

This also requires that the URL /my_admin/jsi18n/ be manually wired up to the django.views.i18n.javascript_catalog view (or null_javascript_catalog if you aren't using I18N). You have to do this yourself instead of going through the admin application so it's accessible regardless of whether you're logged into the admin (thanks Jeremy for pointing this out). Sample code for your URLconf:

(r'^my_admin/jsi18n', 'django.views.i18n.javascript_catalog'),

Lastly, if you are using Django 1.2 or later, you need some additional code in your template to help the widgets find their media:

{% load adminmedia %} /* At the top of the template. */

/* In the head section of the template. */
<script type="text/javascript">
window.__admin_media_prefix__ = "{% filter escapejs %}{% admin_media_prefix %}{% endfilter %}";

Thanks lupefiasco for this addition.