Django custom form fields and widgets

In table 6-2 you saw the wide variety of built-in Django form fields, from basic text and number types, to more specialized text types (e.g. CSV, pre-defined options), including file and directory types. But as extensive as these built-in form fields are, in certain circumstances it can be necessary to build custom form fields.

Similarly, in table 6-2 you learned how all Django forms fields are linked to Django widgets which define the HTML produced by a form field. While in previous sections (e.g. listing 6-25 and listing 6-26) you learned how it's possible to use a form field's widget property to override its default widget with custom properties (e.g. a CSS class attribute) or assign it a different widget altogether (e.g. a forms.Textarea widget to a forms.CharField field), in certain circumstances, playing around with Django's built-in widgets (i.e. adding attributes or switching one built-in widget for another) can be insufficient for complex HTML form inputs.

Up next, you'll learn how to customize both Django form fields and widgets.

Customize Django form fields, widgets or both ?

As you've learned throughout this chapter, there's a fuzzy relationship between a form field and a form widget, which can make it hard to determine which one to customize when built-in options become insufficient.

As a rule of thumb, if you need to constantly change a form field's data or validation logic, you should use a custom form field. If you need to constantly change a form field's HTML output (e.g. form tags, CSS classes, JavaScript events), you should use a custom widget.

Create custom form fields

The good news about creating custom form fields is you don't have to write everything from scratch, compared to other custom Django constructs (e.g. a custom template filter or custom context processor). Since Django's built-in form fields are sub-classes that inherit their behavior from the forms.Form class, you can further sub-class a built-in form field into a more specialized form field. Therefore, a custom form field can start with a basic set of functionalities present in a built-in form field, which you can then customize as required.

For example, if you find yourself creating forms with the built-in forms.FileField and constantly customizing it in a certain way (e.g. for certain file sizes or types), you can create a custom form field (e.g. PdfFileField, MySpecialFileField) that inherits the behavior from forms.FileField, customize it and use the custom form field directly in your forms.

Listing 6-29 illustrates a custom form field that inherits its behavior from the built-in forms.ChoiceField form field.

Listing 6-29. Django custom form field inherits behavior from forms.ChoiceField

class GenderField(forms.ChoiceField):
      def __init__(self, *args, **kwargs):
            super(GenderField, self).__init__(*args, **kwargs)
            self.error_messages = {"required":"Please select a gender, it's required"}
            self.choices = ((None,'Select gender'),('M','Male'),('F','Female'))

As you can see in listing 6-29, the GenderField class inherits its behavior from the built-in forms.ChoiceField field class, giving it automatic access to the same behaviors and features as this latter class. Next, the __init__ method -- used to initialize instances of the class -- a call to super() is made to ensure the initialization process of the parent class (i.e. forms.ChoiceField) is made and immediately after values are assigned to the error_messages and choices fields.

The error_messages and choices fields in listing 6-29 might look familiar because they're arguments typically used in the built-in forms.ChoiceField and are part of Django's standard form field arguments described earlier in this chapter. You can similarly add any other argument supported by form fields (e.g. self.required, self.widget) so the custom form field is created with behaviors set by form field arguments.

Once you have a custom form field like the one in listing 6-29, you can use it to declare a form field in a Django form class (e.g. gender = <pkg_location>.GenderField() vs. gender = forms.ChoiceField(error_messages={...},choices=...)). As you can see, custom form fields are a great choice if you're doing repetitive customizations on built-in form fields.

Customize built-in widgets

In listing 6-25 and listing 6-26 you already explored some customizations associated with Django's built-in widgets. For example, in listing 6-25 you saw how it's possible to customize the default built-in widget assigned to form fields, while in listing 6-26 you saw how it's possible to add custom attributes to built-in widgets. In this section you'll learn how to globally customize built-in widgets.

Looking back at table 6-2, you can see for example the forms.widget.TextInput() widget produces an HTML output like <input type="text" ...> and similarly all the other widgets in table 6-2 produce their own specific HTML output.

Given the high-expectations set forth by many front-end designs, producing this type of basic boilerplate HTML output can be a non-starter for a lot of Django projects. For example, if you want to tightly integrate JavaScript jQuery or ReactJS logic into HTML forms, customizing the default HTML produced by Django's built-in widgets can be a necessity. In these circumstances, the ideal approach is to produce custom HTML for Django's built-in widgets, forgoing the use of the default markup defined by Django in its out-of-the-box state.

The first thing you need to know about customizing Django's built-in widgets is where Django keeps its default built-in widgets. Django builds the HTML output for its built-in widgets from the Django templates located inside the django/forms/templates/django/forms/widgets/ directory in the main Django distribution (e.g. if your Python installation is located at /python/coffeehouse/lib/python3.5/site-packages/, append this directory path to locate the widget templates)

If you look inside this last directory, you'll find templates (e.g. input.html, radio.html) for each built-in Django widget. Be aware all these widgets templates don't use plain HTML, but instead use Django template syntax (e.g. {% include %} tags, {% if %} conditionals ) to favor code re-use. If you're unfamiliar with Django template syntax, look over chapter three.

Now, while you could directly modify the templates in this location to alter the HTML output produced by each widget, don't do this. The recommended approach to customize the output for each built-in widget is to include custom built-in widgets on a project basis. Therefore the first step to customize Django's built-in widgets is to build custom built-in widgets and make them part of a Django project.

Tip Copy all the built-in widgets in the Django distribution (i.e. the templates inside django/forms/templates/django/forms/widgets/ ) to your project and modify them as needed.

Since built-in widgets are Django templates, they need to be placed in a project directory where they can be discovered. This means custom built-in widgets must be placed in a project directory that's part of the DIRS list declared in the TEMPLATES variable in settings.py. In most cases, a project declares a directory named templates -- as part of DIRS -- that also contains a project's templates -- but you can use any directory so long as it's declared as part of DIRS -- see the section 'Template search paths' in chapter three for more details on DIRS.

Besides using a directory that's part of the DIRS list to locate widget templates, Django also expects to locate custom built-in widgets in the same path it uses for its default built-in widgets (i.e. those included in the Django distribution).

Therefore, if you have a project directory named templates as part of the DIRS list, inside this templates directory you'll need to create the same directory path django/forms/widgets/ and inside this last widgets sub-directory place the custom built-in widgets (e.g. to customize the built-in input.html widget located in the Django distribution at django/forms/templates/django/forms/widgets/input.html you would create a project version at <project_dir>/templates/django/forms/widgets/input.html, this way the latter input.html template takes precedence over the default built-in distribution template).

Once you have the custom built-in widgets set up in your project, you need to make two configuration changes to your project's settings.py The first configuration requires you add the FORM_RENDERER variable, as follows:

FORM_RENDERER = 'django.forms.renderers.TemplatesSetting' 

By default, Django built-in widgets are loaded through a standalone Django template renderer -- django.forms.renderers.DjangoTemplates -- which is unrelated to a project's main TEMPLATES configuration containing the DIRS list with a project's templates. Because you now placed custom built-in widgets inside a directory declared as part of the DIRS list, you need to configure Django to use a project's main TEMPLATES configuration as part of the form rendering process. By setting the FORM_RENDERER variable to django.forms.renderers.TemplatesSetting, Django also inspects paths in the DIRS list of the main TEMPLATES configuration for built-in widgets. The last sub-section on custom widgets provides additional details on the FORM_RENDERER variable.

Finally, because you're overriding the django.forms package in a project to get custom built-in widgets, you must also declare the django.forms package as part of the INSTALLED_APPS list in settings.py.

Create custom form widgets

Custom form widgets are used for cases where you need to keep Django's built-in widget functionality as-is, but still need to customize the HTML output produced by widgets.

Tip Before creating custom form widgets, look closely at the built-in widgets in table 6-2 and the widgets in the sidebar 'Django hidden built-in widgets'. You may be able to find what you're looking for without the need to create custom widgets.

Similar to custom form fields, custom form widgets have the advantage of being class-based and can therefore inherit their behavior from built-in widgets. For example, if you find yourself constantly customizing the built-in forms.widgets.TextInput widget in a certain way (e.g. HTML attributes or JavaScript events), you can create a custom widget (e.g. CustomerWidget, EmployeeWidget) that inherits its behavior from forms.widgets.TextInput, customize it and use the custom widget directly in form fields.

For example, let's say you're constantly modifying Django text widgets to include the HTML 5 placeholder attribute -- used to give end-users a hint about the purpose of an input form field and which goes away when a user focuses on a field. Listing 6-30 illustrates a custom form widget which generates the HTML 5 placeholder attribute based on the field name attribute assigned to a widget.

Listing 6-30. Django custom form widget inherits behavior from forms.widgets.Input

class PlaceholderInput(forms.widgets.Input):
      template_name = 'about/placeholder.html'
      input_type = 'text'
      def get_context(self, name, value, attrs):
            context = super(PlaceholderInput, self).get_context(name, value, attrs)
            context['widget']['attrs']['maxlength'] = 50
            context['widget']['attrs']['placeholder'] = name.title()
            return context
Note The forms.widgets.Input widget is a more general purpose widget than forms.widgets.TextInput and does not include text input behaviors, hence it's often the preferred choice to build custom widgets due to its basic feature set. It's worth mentioning forms.widgets.Input is the parent widget of forms.widgets.TextInput, forms.widgets.NumberInput, forms.widgets.EmailInput and other input widgets described in table 6-2.

As you can see in listing 6-30, the PlaceholderInput class inherits its behavior from the built-in forms.widgets.Input widget class, giving it access to the same behaviors and features as this latter class.

Next, are two class fields. The template_name field defines the backing template for the custom widget which points to 'about/placeholder.html'-- note that if you omit the template_name field, the parent class template is used (i.e. for the forms.widgets.Input widget the template is django/forms/widgets/input.html). The input_type field is a requirement for forms.widgets.Input sub-classes and is used to assign the HTML input type attribute, values can include: text, number, email, url, password or any other valid HTML input type value.

Inside the custom widget class is the get_context() method, which is used to set the context for the backing widget template -- just like standard Django templates. In this case, a call is made to super() to ensure the context for the parent widget class template (i.e. forms.widgets.Input) is set and immediately after a pair of attributes -- maxlength and placeholder -- are set on the widget context in the ['widget']['attrs'] dictionary for use inside the widget template. Note the value for maxlength is fixed and the value for placeholder is taken from the field name attribute and converted to a title with Python's standard title method. Finally, the get_context() method returns the updated context reference to pass it to the widget template.

Now lets take a look at the widget template 'about/placeholder.html' in listing 6-31.

Listing 6-31. Django custom form widget inherits behavior from forms.widgets.Input

# about/placeholder.html
<input type="{{ widget.type }}"
          name="{{ widget.name }}"
	  {% if widget.value != None %}
	    value="{{ widget.value }}"
	  {% endif %}
	  {% include "django/forms/widgets/attrs.html" %} />


# django/forms/widgets/attrs.html
{% for name, value in widget.attrs.items %}
    {% if value is not False %} {{ name }}
         {% if value is not True %}="{{ value }}"{% endif %}
    {% endif %}
{% endfor %}

You can see in listing 6-31 the HTML input tag is generated with values from the widget dictionary set through the get_context method. The values for widget.type, widget.name and widget.value are all set behind the scenes in the parent class (i.e. forms.widgets.Input). In addition, notice the HTML input tag uses the built-in widget template django/forms/widgets/attrs.html shown at the bottom of listing 6-31. This last widget template loops over all the elements in the widgets.attrs context dictionary and generates the input attributes. Since the widgets.attrs context dictionary is modified in listing 6-30 to include the maxlength and placeholder attributes, the input tag is generated with these additional attributes (e.g. <input type="text" name="email" maxlength="50" placeholder="Email">).

Now let's take a step back to discuss the location of the 'about/placeholder.html' widget template. By default, Django custom widgets are only searched for inside the templates folders in all Django apps defined in INSTALLED_APPS. This means that in order for Django to find the custom 'about/placeholder.html' widget template, it must be placed inside any project app's templates folder (e.g. given a project app named about, the custom widget should be located under templates/about/placeholder.html , where the templates folder is at the same level of an app's models.py and views.py files). It's possible to define custom widgets on a global directory, but I'll discuss this in the last sub-section on custom widgets.

Finally, once you define the custom widget in the right location, you can use it as part of a Django form field (e.g. email=forms.EmailField(widget=<pkg_location>.PlaceholderInput) ) to generate an HTML input tag with a default placeholder attribute.

Custom form widget configuration options

In the previous two sections on customizing form widgets, I didn't mention a variety of configuration options in order to avoid getting sidetracked from the main task at hand. But now that you know how to customize Django's built-in widgets and how to create custom form widgets, I can provide you with these additional details.

The first topic is related to finding and loading custom widget templates. Django defines a form renderer through the FORM_RENDERER variable in settings.py. Table 6-4 describes the three different values supported by the FORM_RENDERER variable.

Table 6-4 FORM_RENDERER values that influence finding and loading custom widget templates

Form renderer class Description
django.forms.renderers.DjangoTemplates (Default) Searches and loads widget templates from the built-in django/forms/templates/ directory (i.e. the distribution). Searches and loads widget templates from all the templates directories inside apps declared in INSTALLED_APPS.
django.forms.renderers.JinjaTemplates Searches and loads widget templates from the built-in django/forms/jinja2/ directory (i.e. the distribution). Searches and loads widget templates from all the jinja2 directories inside apps declared in INSTALLED_APPS.
django.forms.renderers.TemplatesSetting Searches and loads widget templates based on a project's TEMPLATES configuration (e.g. its DIRS values). NOTE: This renderer requires you declare the django.forms package as part of INSTALLED_APPS

As you can see in table 6-4, there's even a Jinja template renderer to allow you to customize widgets backed by Jinja templates, in addition to other renderers used in the previous sections.

In listing 6-30 you learned how custom widget classes use fields (e.g. template_name, input_type) to specify certain behaviors. Fields in widgets classes are highly dependent on the parent widget class. For example, although template_name is valid for all built-in widgets, more specialized built-in widgets can accept additional fields. If in doubt, consult the fields supported for the built-in widget[4] used as a widget's parent class.

In listing 6-30 you also learned how to access and modify a widget's template context through the get_context() method. Although in listing 6-30 you only added a couple of widget attributes to the context['widget']['attrs'] dictionary, the parent context['widget'] dictionary is a large data structure that stores all data associated with a widget, where you can inclusively update a widget's field values. The following snippet illustrates the contents of the context['widget'] for a form field using the PlaceholderInput widget class from listing 6-30:

{'widget':
    {'attrs': {'placeholder': 'Email', 'maxlength': 50},
     'name': 'email',
     'is_hidden': False,
     'type': 'text',
     'value': None,
     'template_name': 'about/placeholder.html',
     'required': True
     }
}

As you can see, in addition to the HTML input attributes stored under the attrs key, there are other widget field keys (e.g. name, is_hidden) that are made available in a template to render the final output. This also means there's nothing limiting you from adding custom data keys to the context['widget'] dictionary inside the get_context() method to integrate them as part of the final template layout (e.g. 'react':{<react_data>}, 'jquery':{<jquery_data>}).

  1. https://docs.djangoproject.com/en/1.11/ref/forms/widgets/#built-in-widgets