Possible datetime-local bug

Hey all,

I have an issue with the datetime-local widget. I have a model, “Order”, with a single datetime field. I specify the datetime-local widget to be used. When I go to create an Order, the widget works as expected. This saves and shows up properly in a detail view for that order. But, when I go to update that order, the date is not pre-populated in the form. (Screenshots available here since new users are limited to 1 image per post)

A full git repo with a site/app replicating this can be found here. But, for convenience, I’ll post the relevant sections here:

models.py

from django.db import models

# Create your views here.

class Order(models.Model):
    intake_date_and_time = models.DateTimeField(blank=True, null=True)

views.py

from django.shortcuts import render
from django.views import generic, View
from django.urls import reverse
from .models import Order
from .forms import NewOrderForm

class CreateOrderView(generic.CreateView):
    model = Order
    template_name = "datetimepoc/create_order.html"
    form_class = NewOrderForm
    def get_success_url(self):
        return reverse('datetimepoc/index')

class UpdateOrderView(generic.UpdateView):
    model = Order
    fields = "__all__"
    template_name = "datetimepoc/create_order.html"
    form_class = NewOrderForm

    def get_success_url(self):
        return reverse('datetimepoc/list_orders')

class OrderDetailView(generic.DetailView):
    template_name = "datetimepoc/order_detail.html"
    model = Order

urls.py

from django.urls import path

from . import views

urlpatterns = [
    path('detail/<int:pk>', views.OrderDetailView.as_view(), name='detail'),
    path('create', views.CreateOrderView.as_view(), name='create_order'),
    path('update/<int:pk>', views.UpdateOrderView.as_view(), name='update_order'),

]

forms.py

from django import forms
from .models import Order
class NewOrderForm(forms.ModelForm):
    class Meta:
        model = Order
        widgets = {"intake_date_and_time": forms.DateTimeInput(attrs={'type':'datetime-local'}) }
        fields = '__all__'

order_detail.html

<h1>{{ object.intake_date_and_time }}</h1>

create_order.html

<form action="" method="post">

    {% csrf_token %}

    <table>

    {{ form.as_table }} 

</table>

    <input type="submit" value="Submit">

</form>

Is this a bug? Is there a way to get it so that the update form has the date pre-populated with the value put in at creation (or last update)?

It looks like you’re using your CreateOrderView for your update URL instead of your UpdateOrderView.

Thanks for pointing that out. I’ve now updated that in both the code in my original post and the github repo. The issue persists.

How are you navigating to that page? Are you entering the URL directly or are you getting there from a link on a different page? If so, what does the page (template and view) that you are navigating from looks like?

I’m entering the URL manually

I’ve done some research, and the issue is that the HTML input tag with type “datetime-local” requires that the value be supplied as YYYY-MM-DDThh:mm:ss. (See <input type="datetime-local"> - HTML: HyperText Markup Language | MDN) However, as a standard output format, Django is rendering that field as YYYY-MM-DD hh:mm:ss - with a space between the date and time.

It looks like that if you’re going to use that widget, you need to make that field render with the embedded “T”.

2 Likes

Ken, thank you for your help on this. It was the final piece to making this work. There’s quite a bit to it from figuring out how to create a custom model field, custom form field, custom widget, and custom rendering template. I haven’t seen a succinct guide on this whole stack, so I’ll post my complete solution:

Issue : Datetime fields in a django model are rendered as text boxes. This sucks for inputting dates and times.

This can be ‘fixed’ by creating a custom form and specifying attributes for the model:

forms.py

class NewOrderForm(forms.ModelForm):
    class Meta:
        model = Order
        widgets = {'intake_date_and_time': forms.DateTimeInput(attrs={'type':'datetime-local'})}
        fields = '__all__'

But

  1. This requires you to use a custom form and
  2. When the user goes to edit the datetime field, the values are not prepopulated. Django tries to give the datetime as value=“YYYY-MM-DD hh:mm:ss” whereas the html5 datetime-local widget requires “YYYY-MM-DDThh:mm:ss”.

To fix this, we set our model to use a custom model field, which references a custom form field, which references a custom widget that always uses the datetime-local html5 widget and renders the time using the T depicted above.

models.py

class Order(models.Model):
    intake_date_and_time = DateTimePickerModelField(blank=True, null=True)

<proj>/custom_datetime_classes.py

from django.db import models
from django import forms

class DateTimeInputPicker(forms.DateTimeInput):
    format_key = 'DATETIME_INPUT_FORMATS'
    template_name = 'django/forms/widgets/pickerwidget.html'

class DateTimePickerFormField(forms.DateTimeField):
    widget = DateTimeInputPicker

class DateTimePickerModelField(models.fields.DateTimeField):
    def formfield(self, **kwargs):
      return super().formfield(**{
        'form_class': DateTimePickerFormField,
        **kwargs,
       })

Note, it uses a custom template (this is where the rendering happens. The custom template is

<proj>/<app>/templates/django/forms/widgets / pickerwidget.html

<input type="datetime-local" name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value|slice:"0:10"}}T{{widget.value|slice:"11:"}}"{% endif %}{% include "django/forms/widgets/attrs.html" %}>

Finally, we have to update settings.py to find the new template

Add ‘django.forms’ to your INSTALLED_APPS ;

Add FORM_RENDERER = ‘django.forms.renderers.TemplatesSetting’ to your settings.py .