Using Django time/date widgets in custom form
Asked Answered
T

18

184

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

Toffic answered 1/9, 2008 at 23:22 Comment(4)
jqueryui.com/demos/datepickerBradway
trentrichardson.com/examples/timepickerBradway
For date widgets, there are a few simple alternatives. I posted some examples hereProlix
An alternative to this is to use HTML5 date/time pickers that work in modern browsers without any Javascript - https://mcmap.net/q/137528/-how-can-i-use-html5-date-time-widgets-with-django-39-s-splitdatetimefieldAvunculate
H
169

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

Hargrove answered 2/9, 2008 at 6:10 Comment(11)
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.Alvertaalves
Is this the way to go on Django 1.1.1?Cedillo
@Cedillo 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.Hargrove
In Django 1.2 RC1 and later, you need to make a small change to the above. See: #39101Kopple
Do you know if this still applies to Django 1.4? Although I'm convinced to go with jQuery's datepicker, I'm curious if the Django team might have made their widget more publicly available. (I suspect not, as this QA seems to be the most documentation around)Dugald
There has been no movement towards making the admin widgets more easily reusable outside the admin, and I wouldn't guess that there will be any. It would be a lot of work, and there are much higher priority items to work on. (I'm part of the Django core team, btw).Hargrove
this is great answer and it worked for me in django version 1.4. but recently i needed to convert django 1.4 to 1.5. now its giving me error that adminmedia is not valid library. kindly help me how to solve my problemEnfield
You should probably move that very last paragraph directly to the top of your answer.Nisen
Shouldn't you use the standard form Media class instead of hacking the template manually?Cobalt
You're right that it is easier/safer to use a 3rd party JS date/time picker, but there are many to choose from. I suggest flatpickr as it exists as a standalone package and is very simple to set up.Ashanti
Doesn't use of the AdminSplitDateTime widget require using SplitDateTimeField? Using this solution, I get the error described in https://mcmap.net/q/137529/-django-splitdatetime-widget-throws-39-list-39-object-has-no-attribute-39-strip-39 (this answer was written a long time ago, so maybe this is only an issue for Django >= 2)Priest
N
67

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

Nationalize answered 16/9, 2008 at 13:39 Comment(2)
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.Dibromide
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 ;-)Hargrove
B
14

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 appropriately 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.SplitDateTimeField(widget=widgets.AdminSplitDateTime)

    class Meta:
        model = Product

Reference Field Types to find the default form fields.

Booze answered 8/9, 2009 at 6:42 Comment(1)
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.Hargrove
M
13

My head code for 1.4 version(some new and some removed)

{% block extrahead %}

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

<script type="text/javascript" src="/admin/jsi18n/"></script>
<script type="text/javascript" src="{{ STATIC_URL }}admin/js/core.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}admin/js/admin/RelatedObjectLookups.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}admin/js/jquery.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}admin/js/jquery.init.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}admin/js/actions.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}admin/js/calendar.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}admin/js/admin/DateTimeShortcuts.js"></script>

{% endblock %}
Matriarch answered 12/7, 2012 at 7:6 Comment(3)
awesome . i was trying this from past 2 weeks and this help me thanks alotEnfield
using {{ STATIC_URL }} or {% load staticfiles %} with {% static 'admin/js/core.js' %}... is required for Django 1.4+, as the whole MEDIA_URL namespace was deprecated and removed. the /admin/jsi18n resolves properly if you have ^admin/ mapped in server/urls.pyBostwick
nice stuff, useful in the case of Django 1.4Federalist
U
12

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)
Unscratched answered 2/1, 2009 at 22:53 Comment(1)
Oh, very good point. I'll add this to my answer above so it's easier for people to find before they have trouble.Hargrove
K
10

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>
Kopple answered 12/5, 2010 at 11:3 Comment(0)
L
8

For Django >= 2.0

Note: Using admin widgets for date-time fields is not a good idea as admin style-sheets can conflict with your site style-sheets in case you are using bootstrap or any other CSS frameworks. If you are building your site on bootstrap use my bootstrap-datepicker widget django-bootstrap-datepicker-plus.

Step 1: Add javascript-catalog URL to your project's (not app's) urls.py file.

from django.views.i18n import JavaScriptCatalog

urlpatterns = [
    path('jsi18n', JavaScriptCatalog.as_view(), name='javascript-catalog'),
]

Step 2: Add required JavaScript/CSS resources to your template.

<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
<script type="text/javascript" src="{% static '/admin/js/core.js' %}"></script>
<link rel="stylesheet" type="text/css" href="{% static '/admin/css/widgets.css' %}">
<style>.calendar>table>caption{caption-side:unset}</style><!--caption fix for bootstrap4-->
{{ form.media }}        {# Form required JS and CSS #}

Step 3: Use admin widgets for date-time input fields in your forms.py.

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

class ProductCreateForm(forms.ModelForm):
    class Meta:
        model = Product
        fields = ['name', 'publish_date', 'publish_time', 'publish_datetime']
        widgets = {
            'publish_date': widgets.AdminDateWidget,
            'publish_time': widgets.AdminTimeWidget,
            'publish_datetime': widgets.AdminSplitDateTime,
        }
Lidia answered 17/8, 2018 at 10:36 Comment(1)
As described in the other answer, another answer (stackoverflow.com/a/1392329) I believe the use of AdminSplitDateTime requires the use of SplitDateTimeField---otherwise, you'll run into issues when submitting (https://mcmap.net/q/137529/-django-splitdatetime-widget-throws-39-list-39-object-has-no-attribute-39-strip-39)Priest
K
7

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 %}
Klarrisa answered 5/4, 2009 at 20:2 Comment(1)
I also found this link useful: groups.google.ca/group/django-users/msg/1a40cf5f153cd23eKlarrisa
L
5

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

Labio answered 7/3, 2010 at 16:9 Comment(1)
it gives me error 'DateField' object has no attribute 'needs_multipart_form'Descriptive
D
5

Another simple solution for Django 3 (3.2) in 2021 ;) cause andyw's solution doesn't work in Firefox...

{% load static %}

{% block extrahead %}
{{ block.super }}
<script type="text/javascript" src="{% static 'admin/js/cancel.js' %}"></script>
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/forms.css' %}">
<script src="{% url 'admin:jsi18n' %}"></script>
<script src="{% static 'admin/js/jquery.init.js' %}"></script>
<script src="{% static 'admin/js/core.js' %}"></script>
{{ form.media }}
{% endblock %}

<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Сохранить">
</form>

and you'll able to use your form. example:

from django.contrib.admin import widgets
date_time = forms.SplitDateTimeField(widget=widgets.AdminSplitDateTime)
Deloris answered 20/7, 2021 at 8:40 Comment(0)
B
4

Here's another 2020 solution, inspired by @Sandeep's. Using the MinimalSplitDateTimeMultiWidget found in this gist, in our Form as below, we can use modern browser date and time selectors (via eg 'type': 'date'). We don't need any JS.

class EditAssessmentBaseForm(forms.ModelForm):
    class Meta:
        model = Assessment
        fields = '__all__'

    begin = DateTimeField(widget=MinimalSplitDateTimeMultiWidget())

enter image description here

Bartizan answered 10/9, 2020 at 5:21 Comment(8)
AttributeError: 'NoneType' object has no attribute 'copy' Do you have working app example?Salina
I initialized it using this code: date_created = forms.DateTimeField(widget=MinimalSplitDateTimeMultiWidget(attrs={'type': 'date'})) But it did not workBeeswing
you need to provide more info @BeeswingBartizan
@ABHAYKOTAL @Beeswing replace the code with this ` date_attrs = attrs or {} time_attrs = attrs or {}`Superhuman
I updated the gist to reflect @DanieleStoDomingo's fixBartizan
This works great in Apr 2021. Easiest way to get what you needDecrease
Thanks for pointing this out. Although browser support for <input type="datetime-local"> is not ideal yet, it is quite good for "date" and "time" types, according to this.Prolix
An updated solution - https://mcmap.net/q/137528/-how-can-i-use-html5-date-time-widgets-with-django-39-s-splitdatetimefieldAvunculate
P
3

What about just assigning a class to your widget and then binding that class to the JQuery datepicker?

Django forms.py:

class MyForm(forms.ModelForm):

  class Meta:
    model = MyModel

  def __init__(self, *args, **kwargs):
    super(MyForm, self).__init__(*args, **kwargs)
    self.fields['my_date_field'].widget.attrs['class'] = 'datepicker'

And some JavaScript for the template:

  $(".datepicker").datepicker();
Photochromy answered 4/2, 2012 at 6:50 Comment(1)
#661429Photochromy
S
1

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'),
Spindly answered 2/12, 2009 at 14:29 Comment(1)
I have also filled a ticket to fix the SplitDateTime widgets code.djangoproject.com/ticket/12303Spindly
B
1

I use this, it's great, but I have 2 problems with the template:

  1. I see the calendar icons twice for every filed in template.
  2. And for TimeField I have 'Enter a valid date.'

    Here is a screenshot of the Form

models.py

from django.db import models
    name=models.CharField(max_length=100)
    create_date=models.DateField(blank=True)
    start_time=models.TimeField(blank=False)
    end_time=models.TimeField(blank=False)

forms.py

from django import forms
from .models import Guide
from django.contrib.admin import widgets

class GuideForm(forms.ModelForm):
    start_time = forms.DateField(widget=widgets.AdminTimeWidget)
    end_time = forms.DateField(widget=widgets.AdminTimeWidget)
    create_date = forms.DateField(widget=widgets.AdminDateWidget)
    class Meta:
        model=Guide
        fields=['name','categorie','thumb']
Blandish answered 21/7, 2018 at 19:26 Comment(0)
N
0

In Django 10. myproject/urls.py: at the beginning of urlpatterns

  from django.views.i18n import JavaScriptCatalog

urlpatterns = [
    url(r'^jsi18n/$', JavaScriptCatalog.as_view(), name='javascript-catalog'),
.
.
.]

In my template.html:

{% load staticfiles %}

    <script src="{% static "js/jquery-2.2.3.min.js" %}"></script>
    <script src="{% static "js/bootstrap.min.js" %}"></script>
    {# Loading internazionalization for js #}
    {% load i18n admin_modify %}
    <script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
    <script type="text/javascript" src="{% static "/admin/js/jquery.init.js" %}"></script>

    <link rel="stylesheet" type="text/css" href="{% static "/admin/css/base.css" %}">
    <link rel="stylesheet" type="text/css" href="{% static "/admin/css/forms.css" %}">
    <link rel="stylesheet" type="text/css" href="{% static "/admin/css/login.css" %}">
    <link rel="stylesheet" type="text/css" href="{% static "/admin/css/widgets.css" %}">



    <script type="text/javascript" src="{% static "/admin/js/core.js" %}"></script>
    <script type="text/javascript" src="{% static "/admin/js/SelectFilter2.js" %}"></script>
    <script type="text/javascript" src="{% static "/admin/js/admin/RelatedObjectLookups.js" %}"></script>
    <script type="text/javascript" src="{% static "/admin/js/actions.js" %}"></script>
    <script type="text/javascript" src="{% static "/admin/js/calendar.js" %}"></script>
    <script type="text/javascript" src="{% static "/admin/js/admin/DateTimeShortcuts.js" %}"></script>
Net answered 9/10, 2016 at 17:39 Comment(0)
B
0

My Django Setup : 1.11 Bootstrap: 3.3.7

Since none of the answers are completely clear, I am sharing my template code which presents no errors at all.

Top Half of template:

{% extends 'base.html' %}
{% load static %}
{% load i18n %}

{% block head %}
    <title>Add Interview</title>
{% endblock %}

{% block content %}

<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
<script type="text/javascript" src="{% static 'admin/js/core.js' %}"></script>
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/forms.css' %}"/>
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/widgets.css' %}"/>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" >
<script type="text/javascript" src="{% static 'js/jquery.js' %}"></script>

Bottom Half:

<script type="text/javascript" src="/admin/jsi18n/"></script>
<script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.min.js' %}"></script>
<script type="text/javascript" src="{% static 'admin/js/jquery.init.js' %}"></script>
<script type="text/javascript" src="{% static 'admin/js/actions.min.js' %}"></script>
{% endblock %}
Bi answered 11/3, 2019 at 10:22 Comment(0)
D
0

June 3, 2020 (All answers didn't worked, you can try this solution I used. Just for TimeField)

Use simple Charfield for time fields (start and end in this example) in forms.

forms.py

we can use Form or ModelForm here.

class TimeSlotForm(forms.ModelForm):
    start = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'HH:MM'}))
    end = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'HH:MM'}))

    class Meta:
        model = TimeSlots
        fields = ('start', 'end', 'provider')

Convert string input into time object in views.

import datetime
def slots():
    if request.method == 'POST':
        form = create_form(request.POST)
        if form.is_valid():                
            slot = form.save(commit=False)
            start = form.cleaned_data['start']
            end = form.cleaned_data['end']
            start = datetime.datetime.strptime(start, '%H:%M').time()
            end = datetime.datetime.strptime(end, '%H:%M').time()
            slot.start = start
            slot.end = end
            slot.save()
Darnley answered 3/6, 2020 at 19:55 Comment(0)
T
0

The most cleanest way (tested with Django 3.2):

from django.contrib import admin
from django.contrib.admin.widgets import AdminSplitDateTime
from django import forms


class MyCustomForm(forms.ModelForm):
    custom_datetime = forms.SplitDateTimeField(widget=AdminSplitDateTime())

    class Meta:
        model = MyModel
        fields = ("custom_datetime",)


class MyModelAdmin(admin.ModelAdmin):
    def my_custom_admin_view(self, request):
        form = MyCustomForm()
        admin_form = admin.helpers.AdminForm(form, [], {}, model_admin=self)
        media = self.media + admin_form.media
        context = {
            "media": media,
            "form": form,
        }
        return render(request, "my_custom_template.html", context)

In the my_custom_template.html:

{% extends "admin/base.html" %}

{% block extrastyle %}
{{ block.super }}
{{ media }}
{% endblock %}

{% block content %}
<form method="post">
{{ form.as_p  }}
<input type="submit" value="Submit" />
</form>
{% endblock %}
Tiphany answered 26/10, 2023 at 13:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.