How to use a DatePicker in a ModelForm in django?

Question:

I am using django 3.0 and I am trying to display a datepicker widget in my ModelForm, but I can’t figure out how (all I can get is text field). I have tried looking for some solutions, but couldn’t find any. This is how my Model and my ModelForm look like:

class Membership(models.Model):
  start_date = models.DateField(default=datetime.today, null=True)
  owner = models.ForeignKey(Client, on_delete=models.CASCADE, null=True)
  type = models.ForeignKey(MembershipType, on_delete=models.CASCADE, null=True)

class MembershipForm(ModelForm):
  class Meta:
    model = Membership
    fields = ['owner', 'start_date', 'type']
    widgets = {
        'start_date': forms.DateInput
    }

And this is my html:

<form class="container" action="" method="POST">
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
Asked By: Alex Ilasi

||

Answers:

This is the expected behavior. A DateInput widget [Django-doc] is just a <input type="text"> element with an optional format parameter.

You can make use of a package, like for example django-bootstrap-datepicker-plus [pypi]
, and then define a form with the DatePickerInput:

from bootstrap_datepicker_plus import DatePickerInput

class MembershipForm(ModelForm):
  class Meta:
    model = Membership
    fields = ['owner', 'start_date', 'type']
    widgets = {
        'start_date': DatePickerInput
    }

In the template you will need to render the media of the form and load the bootstrap css and javascript:

{% load bootstrap4 %}
{% bootstrap_css %}
{% bootstrap_javascript jquery='full' %}
{{ form.media }}

<form class="container" action="" method="POST">
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
Answered By: Willem Van Onsem

Although @willem-van-onsem’s answer is great, there are a few alternatives that do not require additional dependencies.

A few options, in order of increasing effort:

  1. Use a SelectDateWidget instead of the default DateInput (no JavaScript required):

    class MyForm(forms.Form):
        date = forms.DateField(widget=forms.SelectDateWidget())
    
  2. Use the browser’s built-in date picker, by implementing a customized widget that uses the HTML <input type="date"> element (no JavaScript required):

    class MyDateInput(forms.widgets.DateInput):
        input_type = 'date'
    
    class MyForm(forms.Form):
        date = forms.DateField(widget=MyDateInput())
    

    or, alternatively:

    class MyForm(forms.Form):
        date = forms.DateField(widget=forms.DateInput(attrs=dict(type='date')))
    
  3. Use the date picker from django.contrib.admin, as described here in detail. In short, there are a few things you would need:

    from django.contrib.admin.widgets import AdminDateWidget
    ...
    class MyForm(forms.Form):
        date = forms.DateField(widget=AdminDateWidget())
    

    then, to make this work, add the following dependencies to your template <head>:

    <link rel="stylesheet" type="text/css" href="{% static 'admin/css/widgets.css' %}" />
    <script src="{% static 'admin/js/core.js' %}"></script>
    <script src="{% url 'admin:jsi18n' %}"></script>  {# see note below #}
    {{ form.media }}  {# this adds 'calendar.js' and 'DateTimeShortcuts.js' #}   
    

    Now there’s one catch: the admin:jsi18n url only works for users with admin access, so you may need to replace this and define an alternative path in your urls.py, e.g.:

    from django.views import i18n
    ...
    urlpatterns = [
        ...,
        path('jsi18n/', i18n.JavaScriptCatalog.as_view(), name='jsi18n'),
    ]
    

Finally, here’s what the widgets look like (on firefox):

date picker examples

Personally I like the second option best. It also allows us to specify initial, minimum and maximum values (in django you can do this e.g. using the attrs argument). Here’s a quick snippet to show the HTML element in action:

<input type="date" value="2021-09-09" min="2021-09-09">

Answered By: djvg

As other have said, this is expected since its just a special text field.

An alternative I prefer is using django-widget-tweaks as this pushes front-end customizations back to your template instead of editing forms.py on the backend. Saving/testing is also faster since the app doesn’t have to reload with each save.

Install to your environment:

pip install django-widget-tweaks

Add to installed apps:

INSTALLED_APPS = [
   ...
   "widget_tweaks",
]

Add to your template:

{% extends 'app/base.html' %}
{% load widget_tweaks %}

Use render_field with input tag attributes to customize your field. Eg below using bootstrap 5. Notice how we can specify attributes such as type and class within the template tag:

<div class="col-2">
   <label for="{{ form.date.id_for_label }}" class="col-form-label">{{ form.year.label }}</label>
</div>
<div class="col-4">
   {% render_field form.year type="date" class="form-control" placeholder="mm/dd/yyyy" %}
<div>

widget tweaks date

Answered By: StackTheEast

Django 4.0. Leaving this here incase it helps someone else.
This will set the minimum date and default value to today’s date and should be used in forms.py. In my case I use crispy forms in my .html to render the field.

from datetime import date

today = date.today()
    
class DateForm(forms.ModelForm):
   target_Date = forms.DateField(widget=forms.TextInput(attrs={'min': today, 'value': today, 'type': 'date'}), required=True)

class Meta:
    model = DateForm
    fields = ['target_Date']
Answered By: Craig P
{% render_field form.year type=”date” class=”form-control” placeholder=”mm/dd/yyyy” %}

How to change the format to dd/mm/yyyy ?

Answered By: cyborg_upskill