Stephen Gilmore

🐍 Render a date picker in Django form fields

Django October 22nd, 2023 3 minute read.

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:

Date:
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/")