tags:

views:

17534

answers:

16

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

I have looked through http://www.djangoproject.com/documentation/forms/, and it brefly 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">
        <table>
            {% for f in form %}
               <tr> <td> {{ f.name }}</td> <td>{{ f }}</td> </tr>
            {% endfor %}
        </table>
        <input type="submit" name="submit" value="Add Product">
    </form>

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 url.py:

    (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...

+49  A: 

What you have to do to make this work:

  1. Define your own ModelForm subclass for your model (best to put it in forms.py 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 {{ form.media }} 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 {{ form.media }} 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 %}";
</script>

Thanks lupefiasco for this addition.

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.

Carl Meyer
Most helpful post online for doing this but having finally got date/time widget appear and populating field am now going to take it out because despite having required=False in the form field it now shows the message "Enter a valid date/time." for my not required field if I leave it blank. Back to jquery for me.
Is this the way to go on Django 1.1.1?
ign
@Ignacio I haven't tested this for quite a while, so YMMV. I'm not aware of any easier way that's been introduced since, in any case.
Carl Meyer
In Django 1.2 RC1 and later, you need to make a small change to the above. See: http://stackoverflow.com/questions/38601/using-django-time-date-widgets-in-custom-form/2818128#2818128
lupefiasco
+9  A: 

As the solution is hackish, I think using your own date/time widget with some JavaScript is more feasible.

zgoda
I agree. I tried doing this a few months ago with my django project. I eventually gave up and just added my own with jQueryUI. Took all of 10 minutes to get it working.
nbv4
For the record, I agree, I've upvoted this answer, and I've never actually used the technique in my answer in a production site ;-)
Carl Meyer
A: 

Can I use these widgets in my app (not admin)?

I tried this, but it's not working, I get a text input:

from ventas.models import *
from django.forms import ModelForm

from django.contrib.admin.widgets import AdminDateWidget, AdminSplitDateTime

class AvisoPublicitarioForm(ModelForm):
    fecha = forms.DateField(label=u"Fecha del aviso", input_formats=['%d-%m-%Y'], widget=AdminDateWidget())

    class Meta:
        model = AvisoPublicitario

Any clue?

Cecilia

A: 

You missed step 4, you need to add the javascript files.

A: 

Good help!

BUT: the link to /media/admin/js/core.js does not work! Changing to /media/js/core.js helped me very well.

Could you please change it in your post?!

As I explained in my answer, the correct URL depends on your Django settings (for ADMIN_MEDIA_URL in this case), so I can't change my post to anything that will work reliably for everyone.
Carl Meyer
A: 

Thanks for the hacky part. Adding the jsi18n and core.js scripts helped me get the calendar date widget yesterday. However, today it was not working, even though I hadn't modified any code. After about an hour, I finally figured out that I was logged in with admin privileges yesterday and not today.

So, my question is, how can I link the /admin/jsi18n/ without needing to be logged in as an admin? Currently, it is linking in the admin login page.

I guess I am going to have to override the /admin/jsi18n/ url to something, but I'm not sure what.

The last line in Carl's reply above shows how to get that working: (r'^my_admin/jsi18n', 'django.views.i18n.javascript_catalog'),That makes the javascript files from admin be available from outside.
Emil Stenström
+6  A: 

Yep, I ended up overriding the /admin/jsi18n/ url.

Here's what I added in my urls.py. Make sure it's above the /admin/ url

    (r'^admin/jsi18n', i18n_javascript),

And here is the i18n_javascript function I created.

from django.contrib import admin
def i18n_javascript(request):
  return admin.site.i18n_javascript(request)
Oh, very good point. I'll add this to my answer above so it's easier for people to find before they have trouble.
Carl Meyer
+5  A: 

Complementing the answer by Carl Meyer, I would like to comment that you need to put that header in some valid block (inside the header) within your template.

{% block extra_head %}

<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"/>

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

{{ form.media }}

{% endblock %}
Alex. S.
I also found this link useful: http://groups.google.ca/group/django-users/msg/1a40cf5f153cd23e
Alex. S.
A: 

No working in version 1.1 of Django SVN trunk, any help.

+2  A: 

I find myself referencing this post a lot, and found that the documentation defines a slightly less hacky way to override default widgets.

(No need to override the ModelForm's __init__ method)

However, you still need to wire your js and css appropriatly as Carl mentions.

forms.py

from django import forms
from my_app.models import Product
from django.contrib.admin import widgets                                       


class ProductForm(forms.ModelForm):
    mydate = forms.DateField(widget=widgets.AdminDateWidget)
    mytime = forms.TimeField(widget=widgets.AdminTimeWidget)
    mydatetime = forms.DateTimeField(widget=widgets.AdminSplitDateTime)

    class Meta:
        model = Product

Reference Field Types to find the default form fields.

monkut
I disagree that this approach is less hackish. It looks nicer, sure, but you are completely overriding the form fields generated by the model form, which could also erase options from the model fields, like help_text. Overriding __init__ only changes the widget and leaves the rest of the form field as it was.
Carl Meyer
A: 

Updated solution and workaround for SplitDateTime with required=False:

forms.py

from django import forms

class SplitDateTimeJSField(forms.SplitDateTimeField):
    def __init__(self, *args, **kwargs):
        super(SplitDateTimeJSField, self).__init__(*args, **kwargs)
        self.widget.widgets[0].attrs = {'class': 'vDateField'}
        self.widget.widgets[1].attrs = {'class': 'vTimeField'}  


class AnyFormOrModelForm(forms.Form):
    date = forms.DateField(widget=forms.TextInput(attrs={'class':'vDateField'}))
    time = forms.TimeField(widget=forms.TextInput(attrs={'class':'vTimeField'}))
    timestamp = SplitDateTimeJSField(required=False,)

form.html

<script type="text/javascript" src="/admin/jsi18n/"></script>
<script type="text/javascript" src="/admin_media/js/core.js"></script>
<script type="text/javascript" src="/admin_media/js/calendar.js"></script>
<script type="text/javascript" src="/admin_media/js/admin/DateTimeShortcuts.js"></script>

urls.py

(r'^admin/jsi18n/', 'django.views.i18n.javascript_catalog'),
Samuel Adam
I have also filled a ticket to fix the SplitDateTime widgetshttp://code.djangoproject.com/ticket/12303
Samuel Adam
A: 

I am using django 1.0 version and want to add an admindate widget in my form.I tried the above procedure but just got a textbox for the date field.Can you guess wats wrong and what can I do to get the widget

krishna
A: 

The below will also work as a last resort if the above failed

class PaymentsForm(forms.ModelForm):
   class Meta:
      model = Payments
   def __init__(self, *args, **kwargs):
   super(PaymentsForm, self).__init__(*args, **kwargs)
   self.fields['date'].widget = SelectDateWidget()

Same as

 class PaymentsForm(forms.ModelForm):
   date = forms.DateField(widget=SelectDateWidget())
   class Meta:
      model = Payments

put this in your forms.py from django.forms.extras.widgets import SelectDateWidget

A: 

I finally managed to get this widget working on the dev server, only to have it break on deployment. I finally decided it wasn't worth shoehorning into my site, and wrote my own widget. It's not as flexible, but it will probably work well for many: http://www.copiesofcopies.org/webl/?p=81

copiesofcopies
+4  A: 

Starting in Django 1.2 RC1, if you're using the Django admin date picker widge trick, the following has to be added to your template, or you'll see the calendar icon url being referenced through "/missing-admin-media-prefix/".

{% 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 %}";
</script>
lupefiasco
A: 

(I'm trying to comment on people suggesting to roll their own Calendar widget, but either I don't see the comment button, or I don't have enough rep.)

What happened to DRY? I think it would be best to re-use the admin widget, but perhaps it should be separated from admin, and easier to use. Thanks for this information anyways.

kapace