18

I am new at django.

I want to create a custom widget.

forms.py:

from project.widgets import MultiChoiceFilterWidget

class CustomSearchForm(FacetedSearchForm):
    TEST_COLORS = [
        u"Blau", u"Rot", u"Gelb"
    ]

    color = forms.MultipleChoiceField(
        label=_("Color"), choices=[(x, x) for x in TEST_COLORS],
        widget=MultiChoiceFilterWidget, required=False)

widget.py:

class MultiChoiceFilterWidget(forms.widgets.CheckboxSelectMultiple):
    template_name = 'project/widgets/filter.html'
    option_template_name = 'ptoject/widgets/filter_option.html'

project/widgets/filter.html:

 <h1>TEST</h1>

But it doesn't render the new template, instead it still renders the old way.

Can you give me some tips?

John Moutafis
  • 22,254
  • 11
  • 68
  • 112
Flo
  • 1,179
  • 3
  • 15
  • 43

3 Answers3

37

You will have to do the below steps to render your new widget template:

1) Add 'django.forms' to your INSTALLED_APPS;

2) Add FORM_RENDERER = 'django.forms.renderers.TemplatesSetting' to your settings.py.

More details : https://docs.djangoproject.com/en/2.0/ref/forms/renderers/#django.forms.renderers.TemplatesSetting

Milan
  • 434
  • 1
  • 4
  • 9
  • 3
    this should be the accepted answer. With these settings, you can just put your widget's `.html` files in `templates/django/forms/widgets` and it will use these before falling back to djangos defaults. – biodiv Oct 01 '19 at 09:33
  • 2
    @biodiv Once this these steps are done, you can put the replacement template instance anywhere your project looks for templates. Not just the mirrored original path it was looking for. – Rob Apr 04 '20 at 15:50
  • 2
    an dont forget the order of your INSTALLED_APPS. your app with the overrides should be before the django.forms app – thelittlebug Jun 09 '20 at 01:32
  • 1
    Just in case someone else makes my same silly mistake... make sure you put the files in `//templates/django/forms/widgets` and NOT `//templates//django/forms/widgets`. – Zmaster Nov 21 '20 at 18:17
  • 4
    Note that this will also affect django admin. – André Laszlo Mar 21 '21 at 13:02
19

Django version < 1.11:

The widget must implement the render method in order to render a different template:

from django.utils.safestring import mark_safe
from django.template.loader import render_to_string

class MultiChoiceFilterWidget(forms.widgets.CheckboxSelectMultiple):
    template_name = 'project/widgets/filter.html'

    def render(self, data):
        ...
        Do stuff with data
        ...
        return mark_safe(render_to_string(self.template_name))


Django version 1.11:

In the renderer's documentation, we can find the following:

New in Django 1.11:

In older versions, widgets are rendered using Python. All APIs described in this document are new.

And by having a look at the widget source code and specifically on how the Input widget extends the Widget class, we can see that you would only need to customize your widget as follows:

class MultiChoiceFilterWidget(forms.widgets.CheckboxSelectMultiple):
    template_name = 'project/widgets/filter.html'

Which is what you have already.

John Moutafis
  • 22,254
  • 11
  • 68
  • 112
4

If you only need to change the templates, redefining a complete widget is tedious for nothing. Since the widget is passed to the field as an instance, you can instanciate the base widget you want to use and change the templates afterward.

class CustomSearchForm(FacetedSearchForm):
TEST_COLORS = [
    u"Blau", u"Rot", u"Gelb"
]

color = forms.MultipleChoiceField(
    label=_("Color"), choices=[(x, x) for x in TEST_COLORS],
    widget=forms.widgets.CheckboxSelectMultiple, required=False)
color.widget.template_name = 'project/widgets/filter.html'
color.widget.option_template_name = 'project/widgets/filter_option.html'

If you need to pass custom data to your templates, then you will have to create a custom widget.

mbegoc
  • 51
  • 1
  • This is exactly what I needed because I didn't need custom widgets and simply wanted to override some widget templates on a case by case basis. However, I still had to use [this answer](https://stackoverflow.com/a/52184422/680521) for my custom templates to be found. – Stephen Blair Aug 24 '21 at 11:11