Changing input type in a class based create view

Hi folks

I’ve created a widget to change date input type to a datepicker:

from django import forms

class DateInput(forms.DateInput):
input_type = 'date'

I can use this fine with models I define in forms.py but I was wondering if there’s a way to override the input type in a class based view in views.py. Specifically, CreateView:

class ActionCreate(LoginRequiredMixin, CreateView):
    model = Action
    fields = '__all__'

Any help, much appreciated.
Thanks

The widget used in a form is an attribute of the form, not of the view that is presenting that form.

So what you’re looking for is the form_class attribute of your view that will use your custom form.

1 Like

Yes as Ken says. Defining model and fields on a FormView are shortcuts meaning “create a Form class for me like this.” For any level of customization you need to create the form class yourself.

1 Like

Great, thanks guys. I’ll give that a crack.

Did you find a solution to this? Override Date Input in Create View?

@adamchainz: Sorry for necromancing, but I wanted to check whether this is still the best way. I have quite a few dates in my models, and <input type="date"> actually works quite well in mobile browsers, and it’d be nice not to have to add so much boilerplate to every form for a model with DateFields.

I had a look at the code for BaseModelForm, to try to find how it determines which widget to use, and see if I can make a custom version of that, but didn’t have any joy.

I also noticed that django/forms/templates/django/forms/widgets/date.html simply includes django/forms/widgets/input.html, so I tried overriding it, by copying input.html to django/forms/widgets/date.html under my app’s templates directory, and hardcoding the type attribute. But that didn’t work, either, presumably because something as core Djang as forms doesn’t look for templates in the same way.

Am I on the right track with either of these, or is there another way? Or will I just have to spoon-feed my custom widget to each date field by hand?

Thanks!

Overriding form widgets isn’t quite the same as overriding page templates.

In addition to creating your new widget template, you also should use the TemplatesSetting renderer.

See: Overriding built-in widget templates and TemplatesSetting

You might also want to read the thread at Overriding Widgets templates not working (following help), template loader not searching app dirs

I don’t think you need to touch templates. You should be able to make a custom base form class that always uses the custom widget, using formfield_callback per Overriding the default fields. Something like the following (untested):

from django import forms
from django.db import models

from example.models import NicheMuseum

class DateInput(forms.DateInput):
    """
    Subclass of Django’s DateInput widget to use <input type=date>.
    """
    input_type = "date"


def customize_fields(db_field, **kwargs):
    if isinstance(db_field, (models.DateField, models.DateTimeField)):
        kwargs["widget"] = DateInput
    return db_field.formfield(**kwargs)


class NicheMuseumForm(forms.ModelForm):
    class Meta:
        model = NicheMuseum
        fields = ["name", "created_at"]
        formfield_callback = urlfields_assume_https

Beautiful, that worked! Thank you! I just had to set a different input type for DateTimeFields (and set formfield_callback in my form class to customise_fields, not ‘urlfields_assume_https’ :slightly_smiling_face:):

class DateInput(forms.DateInput):
    """Django's DateInput widget with type="date"."""

    input_type = "date"


class DateTimeInput(forms.DateTimeInput):
    """Django's DateTimeInput widget with type="datetime-local"."""

    input_type = "datetime-local"


def customise_fields(db_field, **kwargs):
    """Set date and date/time widgets to `DateInput` or `DateTimeInput`."""
    if isinstance(db_field, models.DateField):
        kwargs["widget"] = DateInput
    if isinstance(db_field, models.DateTimeField):
        kwargs["widget"] = DateTimeInput
    return db_field.formfield(**kwargs)

It’s no bother to set formfield_callback in each form’s Meta class, but for the sake of completing my understanding, I’m curious: The following, unsurprisingly, doesn’t work—the formfield_callback doesn’t get set in the subclass’s Meta—but is it possible to write CustomModelForm so it would?

class CustomModelForm(forms.ModelForm):

    class Meta:
        formfield_callback = customise_fields


class NicheMuseumForm(CustomModelForm):

    class Meta:  # This doesn't inherit CustomModelForm.Meta
        model = NicheMuseum
        fields = ["name", "created_at"]

Glad to help. I may adapt this into a blog post.

oh yeah, sorry, I adapted from my example over at http://localhost:8080/tech/2023/12/07/django-fix-urlfield-assume-scheme-warnings/#modelform-classes-with-formfield-callback :slight_smile:

Yeah, it’s inconsistent with models - to inherit when subclassing, you need to inherit the Meta too, per these docs:

class CustomModelForm(forms.ModelForm):
    class Meta:
        formfield_callback = customise_fields


class NicheMuseumForm(CustomModelForm):
    class Meta(CustomModelForm.Meta):
        model = NicheMuseum
        fields = ["name", "created_at"]

You could also hack automatic setting of attributes with __init_subclass__, but that’s probably not worth it.

1 Like