🐍 Render a date picker in Django form fields
Every time I add a date field on a form, I want a date picker to show up on the rendered page, but by default, Django renders a plain text box.
For example, instead of:
I want:
I have to search back to an old project or spend 10-15 minutes asking some combination of Google and ChatGPT. But after this blog post, never again!
Render a Date Field for a Model Form
In a Django Model Form, a DateInput widget is specified with a type=date
attribute in the Meta class of rhte Form definition.
from django import forms
from django.forms.widgets import DateInput
from blog.models import MyModel
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ("title", "date")
widgets = {"date": DateInput(attrs={"type": "date"})}
Render a Date Field for a Form
With a "regular" form in Django, the widget is specified as part of the field definition.
from django import forms
from django.utils import timezone
class MyForm(forms.Form):
title = forms.CharField()
hire_date = forms.DateField(
initial=timezone.localtime(timezone.now()),
widget=forms.widgets.DateInput(attrs={"type": "date"}),
)
What about date-time fields?
Just switch the type attrs from date
to datetime-local
.
widget=forms.widgets.DateInput(attrs={"type": "datetime-local"})
But I was hoping for separate date and time fields...
There's a widget for that too! Use SplitDateTimeWidget
instead of DateTimeWidget
and you'll get a fieldset that looks something like this:
widgets = {
"date": forms.SplitDateTimeWidget(
date_attrs={"type": "date"},
time_attrs={"type": "time"},
)
}
Important note on Model Forms with SplitDateTimeWidget
There is one small and very important line in the Django Docs about the SplitDateTimeWidget:
Must be used with SplitDateTimeField rather than DateTimeField
This means that using the SplitDateTimeWidget with a ModelForm requires a little extra work.
Let's use this Log
model with a date time field as an example:
from django.db import models
from django.utils import timezone
class Log:
date = models.DateTimeField(default=timezone.now)
To use this in a model form, the field needs to be defined explicitly. It won't work with the model.DateTimeField's default widget.
from django import forms
from django.utils import timezone
class LogForm(forms.ModelForm):
date = forms.SplitDateTimeField(
initial=timezone.now,
widget=forms.SplitDateTimeWidget(
date_attrs={"type": "date"},
time_attrs={"type": "time"},
),
)
class Meta:
model = Log
fields = ["date"]
And then finally, say you needed to define a dictionary of data for testing the form, you'd need something like:
test_data = {
"date_0": "2023-11-06",
"date_1": "21:00:00",
}
# Post the form data
response = client.post(f"/log/form/", data=test_data)
# Validate redirect to success url
assertRedirects(response, "/success/url/")