Rendering form fields as group in Django

Table of Contents

Field groups in Django

A welcome addition in Django 5.0, the concept of field groups makes a lot easier to customize the layout of our Django forms.

In the past, to take control of field layout, developers had to render field labels, help text, errors and fields one by one (or resort to use an external library).

Field group solve that, as we will see in a moment.

In this brief post I base my examples over a Django library app, with a model named Book. You can look up the structure in this repo.

Customizing the form renderer

As a first step to opt-in into Django form field groups, we need to customize the form rendered. This can be done by creating your custom renderer in a file named renderers.py, ideally in a common app in your project.

The custom form rendered must inherit from TemplatesSetting in order to make the field template swappable:

from django.forms.renderers import TemplatesSetting


class FormRenderer(TemplatesSetting):
field_template_name = "common/field.html"

The renderer can then be used in settings.py:

INSTALLED_APPS = [
...
# Remember always to include django.forms
'django.forms',
'common'
]

FORM_RENDERER = 'common.renderers.FormRenderer'

Pay attention: in order to be able to customize the field group template in the following section, django.forms must be in our INSTALLED_APPS!

Customizing the form field group template project-wide

Now that we declared our custom form renderer, we can proceed to customize the form field template by creating a new file named field.html in common/templates/common:

{% if field.use_fieldset %}
<fieldset{% if field.help_text and field.auto_id and "aria-describedby" not in field.field.widget.attrs %} aria-describedby="{{ field.auto_id }}_helptext"{% endif %}>
{% if field.label %}{{ field.legend_tag }}{% endif %}
{% else %}
{% if field.label %}{{ field.label_tag }}{% endif %}
{% endif %}
{% if field.help_text %}<div class="helptext"{% if field.auto_id %} id="{{ field.auto_id }}_helptext"{% endif %}>{{ field.help_text|safe }}</div>{% endif %}
{{ field }}
{{ field.errors }}
{% if field.use_fieldset %}</fieldset>{% endif %}

This template is taken from Django itself.

You can now enrich it with any desired change, or leave it as it is.

Let's now create the view to handle the form.

Rendering fields: the create view

With the template in place, we can now create a Django view to display the form. I use a CreateView in this example, which generates its form automatically. If you need more customization over widgets or over the validation, you can declare your own ModelForm.

Here's the view to create books:

class BookCreateView(CreateView):
model = Book
fields = ["title", "authors"]
success_url = "/"

Note: your custom field template will work well even with a regular Django Form.

With the view and the form in place, we can render the form in our template.

Rendering fields: the form template

We are now ready to render our Django form in a template. Here it is:

{% extends "library/base.html" %}

{% block content %}
<form novalidate method="post">
{% csrf_token %}
<div class="form-group">
{{ form.title.as_field_group }}
</div>
<div class="form-group">
{{ form.authors.as_field_group }}
</div>
<button type="submit" class="btn btn-primary">Save</button>
</form>
{% endblock %}

As you can see, we no longer need to call field attributes one by one to group the field into a containing HTML element.

We can just call form.field_name.as_field_group:

        ...
<div class="form-group">
{{ form.title.as_field_group }}
</div>
...

Isn't that almost magic?

Calling form.field_name.as_field_group will render the label, any help text, the field error list, and the field itself.

Here's the HTML for the title field after a form submit:

<div class="form-group">
<label for="id_title">Title:</label>
<ul class="errorlist"><li>This field is required.</li></ul>
<input type="text" name="title" maxlength="100" required="" aria-invalid="true" id="id_title">
</div>

Note: if you don't see any effect after customizing the field template in your project, check that django.forms is declared in INSTALLED_APPS.

Reusable form templates

Keep in mind that you can not only customize the field template, but also take control of the form template, if you need to do so. Check reusable form templates for more.

Conclusion

Reusable form field groups in Django make a lot easier to take control of form field rendering without reaching for external libraries.

Available since Django 5.0 form fields can render as a group, and have their template overridden in different ways.

This can happen at various levels as described also in the documentation.

Thanks for reading!

Valentino Gagliardi

Hi! I'm Valentino! I'm a freelance consultant with a wealth of experience in the IT industry. I spent the last years as a frontend consultant, providing advice and help, coaching and training on JavaScript, testing, and software development. Let's get in touch!