Hello! Welcome to our next lesson.
In the last session, we focused on the backend logic inside a view. You learned how to process POST requests, validate the submitted data using form.is_valid(), and save a new object to the database. We also discussed what happens when validation fails: Django re-renders the template, passing it a "bound" form object that now contains both the user's input and a list of errors.
Today, we'll shift our focus to the frontend—the template. Our goal is to display form fields and validation errors in a template. You'll learn how to take that form object from the view and render it into a clean, user-friendly HTML form that provides clear feedback when something goes wrong. This is where your existing knowledge of HTML and CSS will come in handy as we work to create a good user experience.
From View to Template: Rendering the Form
Once your view passes the form object to the template's context, you have several ways to render it. The method you choose depends on how much control you need over the final HTML output.
Let's start with the official Django documentation to get an overview of the rendering process.
Working with forms | Django documentation
The Django documentation, "Working with forms", provides a great introduction to the different ways a form can be rendered. It covers the basics, which we will build upon.
Please read the section "Working with form templates". Pay attention to the simple {{ form }} rendering method and the note that you still have to provide the <form> tags and submit button yourself.
The Quick and Easy Methods
As you just read, Django provides several "shortcut" methods to render a form object automatically. These are great for rapid prototyping or simple forms.
{{ form.as_p }}: Wraps each field in a<p>(paragraph) tag. This is a common default.{{ form.as_ul }}: Wraps each field in an<li>(list item) tag. You'll need to provide the surrounding<ul>tags.{{ form.as_table }}: Wraps each field in<tr>and<td>tags. You'll need to provide the surrounding<table>tags. In fact, simply using{{ form }}defaults to{{ form.as_table }}.
A typical template using as_p would look like this:
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Submit</button>
</form>
While these methods are fast, they offer little control over the HTML attributes, like adding CSS classes for styling. For your portfolio project, you'll want more fine-grained control.
Taking Control: Manual Form Rendering
To fully control the layout and styling, you need to render the form's fields manually. The most common way to do this is by looping through the fields in your template. This allows you to inject your own HTML structure and CSS classes around each label, input, help text, and error message.

The basic structure for manually rendering a form looks like this:
<form method="post">
{% csrf_token %}
{# Display errors that are not tied to a specific field #}
{{ form.non_field_errors }}
{# Loop through all visible fields in the form #}
{% for field in form.visible_fields %}
<div class="form-group">
{# Display the field's label #}
{{ field.label_tag }}
{# Display any errors for this specific field #}
{{ field.errors }}
{# Render the input widget itself #}
{{ field }}
{# Display the field's help text, if any #}
{% if field.help_text %}
<small class="help-text">{{ field.help_text }}</small>
{% endif %}
</div>
{% endfor %}
<button type="submit">Submit</button>
</form>
Let's break down the key template variables:
form.non_field_errors: Use this to display errors that apply to the form as a whole (e.g., "The username and password do not match").{% for field in form.visible_fields %}: This loop gives you access to each field object one by one.{{ field.label_tag }}: Renders the<label>for the field.{{ field.errors }}: If validation fails, this will render a<ul>containing the error messages for that specific field. If there are no errors, it renders nothing.{{ field }}: Renders the actual<input>,<select>, or<textarea>widget.
To see a detailed practical example of how to implement this, let's turn to an excellent article that walks through the entire process.
How to Render Django Form Manually
The article "How to Render Django Form Manually" by Vitor Freitas provides a clear, step-by-step guide from basic rendering to full manual control.
Please read the section that starts after the initial view setup, beginning with "Understanding the Rendering Process" and ending just before "Accessing the Form Fields Individually". This section shows how to mimic Django's automatic rendering using a manual loop, giving you full control.
Improving User Experience: Displaying Errors and Repopulating Data
Two things are critical for a good user experience when a form has errors:
- Clearly show the user what they did wrong.
- Don't erase their correct input! Make them fix only what's broken.
Our manual rendering loop already handles the first point with {{ field.errors }}. The second point is handled automatically by Django. When you render a bound form (a form with submitted data), the {{ field }} tag will render the input widget with its value attribute already set to what the user previously entered.
Let's watch a video that puts these pieces together: looping through fields to display errors and seeing how Django repopulates the form.
Render Django Form Manually - Make Custom Form with Form Validations | Code Band
This video from Code Band demonstrates how to manually render fields and display their corresponding errors right below the input.
Watch from 09:39 to 14:40. The first part (until 12:15) shows how to loop through field.errors in the template. The second part (from 12:15) demonstrates how to ensure the user's submitted values are repopulated in the form fields when validation fails.
Applying Styles for a Professional Look
Manually rendering a form gives you the power to add CSS classes, which is essential for integrating a framework like Bootstrap. However, adding a class directly to {{ field }} isn't possible. You can't write {{ field class="form-control" }}.
There are two great ways to solve this.
1. Using a Third-Party Package: django-widget-tweaks
This is a very popular and lightweight package that gives you a template filter to add or modify HTML attributes on your form fields directly in the template. It's perfect for adding classes.
The simpleisbetterthancomplex.com article you looked at earlier has a fantastic section on this.
How to Render Django Form Manually
Let's continue with the same article to see how to integrate Bootstrap for styling.
Read the sections "Using Django Widget Tweaks" and "Rendering Bootstrap 4 Forms". Focus on how {% render_field %} is used to add the form-control class and how conditional logic ({% if field.errors %}) is used to add is-invalid for styling error states.
Using widget_tweaks, your field rendering might look like this:
{% load widget_tweaks %}
{% if field.errors %}
{% render_field field class="form-control is-invalid" %}
{% else %}
{% render_field field class="form-control" %}
{% endif %}
2. Using a Modern Django Feature: as_field_group (Django 5.0+)
More recently, Django introduced a built-in way to render a field with its label, widget, errors, and help text all together in a structured way, making it easier to style. The as_field_group method provides a cleaner, built-in alternative to a full manual loop for many use cases.
Django 5.0 Just Made Forms Even Better
This short video by Dennis Ivy introduces the as_field_group method and clearly shows how much cleaner it can make your templates.
Watch from 00:48 to 03:24. Notice how field.as_field_group replaces the need to manually render field.label_tag, field, field.errors, and field.help_text separately.
This modern approach simplifies your template significantly while still allowing for good structure and styling.
Test your understanding!
Imagine you are rendering a title field in your template. Write a <div> that contains the following:
- The field's label.
- The input field itself. It should have the class
form-control. If the field has errors, it should also have the classis-invalid. - A
divwith the classinvalid-feedbackthat loops through and displays each error for thetitlefield.
You can use standard Django template tags or the django-widget-tweaks syntax.
Show answer
Here is a solution using standard Django template logic and django-widget-tweaks.
{% load widget_tweaks %}
<div class="form-group">
{{ form.title.label_tag }}
{# Add the 'is-invalid' class only if there are errors #}
{% if form.title.errors %}
{% render_field form.title class="form-control is-invalid" %}
{% else %}
{% render_field form.title class="form-control" %}
{% endif %}
{# Loop through errors and display them #}
{% for error in form.title.errors %}
<div class="invalid-feedback">
{{ error }}
</div>
{% endfor %}
</div>
Conclusion
You now have a comprehensive toolkit for rendering Django forms in your templates. You can choose the method that best fits your needs, from quick automatic rendering to full manual control for custom styling.
Key Takeaways:
- Django offers quick rendering methods like
{{ form.as_p }}for simple use cases. - For full control, loop through
{% for field in form %}to manually structure your HTML. - Display field-specific errors with
{{ field.errors }}and form-wide errors with{{ form.non_field_errors }}. - When validation fails, Django automatically repopulates the form with the user's submitted data, improving usability.
- For styling (e.g., with Bootstrap), use a tool like
django-widget-tweaks'srender_fieldtag or Django 5.0'sas_field_groupmethod to add classes and other attributes to your input fields.
Preview of the Next Lesson:
So far, we've written our views as functions. This is a great way to learn, but as your application grows, you might notice you're writing similar logic (like handling GET vs. POST) over and over. In the next lesson, we will explore a more powerful and reusable pattern by learning how to refactor function-based views into generic Class-Based Views (CBVs) like ListView and DetailView. This will help you write more concise, organized, and maintainable code.