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>
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>
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:
-
Use a SelectDateWidget instead of the default DateInput (no JavaScript required):
class MyForm(forms.Form):
date = forms.DateField(widget=forms.SelectDateWidget())
-
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')))
-
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):
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">
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>
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']
{% render_field form.year type=”date” class=”form-control” placeholder=”mm/dd/yyyy” %}
How to change the format to dd/mm/yyyy ?
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>
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>
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:
-
Use a SelectDateWidget instead of the default DateInput (no JavaScript required):
class MyForm(forms.Form): date = forms.DateField(widget=forms.SelectDateWidget())
-
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')))
-
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 yoururls.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):
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">
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>
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']
How to change the format to dd/mm/yyyy ?