6

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>
djvg
  • 11,722
  • 5
  • 72
  • 103
Alex Ilasi
  • 63
  • 1
  • 3
  • This is the expected behavior: the `DateInput` is just a `text` box with an optional format: https://docs.djangoproject.com/en/3.0/ref/forms/widgets/#dateinput – Willem Van Onsem Apr 07 '20 at 10:26
  • 1
    I know that, but I was wondering how can I use the DatePicker widget. – Alex Ilasi Apr 07 '20 at 10:29
  • You could use the admin datepicker, as described [here](https://stackoverflow.com/q/38601) and [here](https://stackoverflow.com/q/660898). – djvg Sep 08 '21 at 17:14

4 Answers4

10

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">
djvg
  • 11,722
  • 5
  • 72
  • 103
1

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']
Craig P
  • 477
  • 6
  • 12
0

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>
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
0

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

StackTheEast
  • 101
  • 4