Skip to main content

Django Form Templates

When working with Django forms, one of the most powerful features is how easily they integrate with Django's template system. This integration allows you to render forms as HTML, customize their appearance, and handle user input elegantly.

Introduction to Django Form Templates

Django forms provide a bridge between your Python code and HTML interfaces. While you can create forms in pure HTML, Django forms offer several advantages:

  • Automatic validation
  • Easy rendering
  • Consistent styling
  • CSRF protection
  • Built-in error handling

In this tutorial, we'll explore how to display Django forms in templates and customize their appearance to create user-friendly interfaces.

Basic Form Rendering

Let's start with the most basic way to render a form in a template. First, let's define a simple form in forms.py:

python
# myapp/forms.py
from django import forms

class ContactForm(forms.Form):
name = forms.CharField(max_length=100)
email = forms.EmailField()
message = forms.CharField(widget=forms.Textarea)

Now, in your view, you'll pass this form to the template:

python
# myapp/views.py
from django.shortcuts import render
from .forms import ContactForm

def contact_view(request):
form = ContactForm()
return render(request, 'contact.html', {'form': form})

In your template, you can render the entire form with just a few lines:

html
<!-- templates/contact.html -->
<form method="post">
{% csrf_token %}
{{ form }}
<button type="submit">Submit</button>
</form>

This will render the form with the default styling, which includes:

  • Labels for each field
  • Input elements with appropriate HTML types
  • Any validation attributes

Form Rendering Options

Django provides several ways to render forms in templates:

1. Rendering the entire form at once

html
{{ form }}

Output:

html
<label for="id_name">Name:</label>
<input type="text" name="name" maxlength="100" required id="id_name">
<label for="id_email">Email:</label>
<input type="email" name="email" required id="id_email">
<label for="id_message">Message:</label>
<textarea name="message" required id="id_message"></textarea>

2. Rendering as a table

html
{{ form.as_table }}

Output:

html
<tr>
<th><label for="id_name">Name:</label></th>
<td><input type="text" name="name" maxlength="100" required id="id_name"></td>
</tr>
<tr>
<th><label for="id_email">Email:</label></th>
<td><input type="email" name="email" required id="id_email"></td>
</tr>
<tr>
<th><label for="id_message">Message:</label></th>
<td><textarea name="message" required id="id_message"></textarea></td>
</tr>

Remember to wrap this in a <table> element in your template:

html
<form method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<button type="submit">Submit</button>
</form>

3. Rendering as paragraphs

html
{{ form.as_p }}

Output:

html
<p>
<label for="id_name">Name:</label>
<input type="text" name="name" maxlength="100" required id="id_name">
</p>
<p>
<label for="id_email">Email:</label>
<input type="email" name="email" required id="id_email">
</p>
<p>
<label for="id_message">Message:</label>
<textarea name="message" required id="id_message"></textarea>
</p>

4. Rendering as an unordered list

html
{{ form.as_ul }}

Output:

html
<li>
<label for="id_name">Name:</label>
<input type="text" name="name" maxlength="100" required id="id_name">
</li>
<li>
<label for="id_email">Email:</label>
<input type="email" name="email" required id="id_email">
</li>
<li>
<label for="id_message">Message:</label>
<textarea name="message" required id="id_message"></textarea>
</li>

Remember to wrap this in a <ul> element in your template:

html
<form method="post">
{% csrf_token %}
<ul>
{{ form.as_ul }}
</ul>
<button type="submit">Submit</button>
</form>

Rendering Individual Fields

For more control over the layout, you can render fields individually:

html
<form method="post">
{% csrf_token %}

<div class="form-group">
<label for="{{ form.name.id_for_label }}">Your Name:</label>
{{ form.name }}
{% if form.name.errors %}
<div class="error">{{ form.name.errors }}</div>
{% endif %}
</div>

<div class="form-group">
<label for="{{ form.email.id_for_label }}">Your Email:</label>
{{ form.email }}
{% if form.email.errors %}
<div class="error">{{ form.email.errors }}</div>
{% endif %}
</div>

<div class="form-group">
<label for="{{ form.message.id_for_label }}">Your Message:</label>
{{ form.message }}
{% if form.message.errors %}
<div class="error">{{ form.message.errors }}</div>
{% endif %}
</div>

<button type="submit">Send Message</button>
</form>

This approach gives you complete control over:

  • HTML structure around each field
  • Custom labels
  • Error message placement and styling
  • Adding additional elements or instructions

Looping Through Form Fields

For consistent styling with less repetition, you can loop through all fields:

html
<form method="post">
{% csrf_token %}

{% for field in form %}
<div class="form-group">
<label for="{{ field.id_for_label }}">{{ field.label }}:</label>
{{ field }}
{% if field.help_text %}
<small class="form-text text-muted">{{ field.help_text }}</small>
{% endif %}
{% if field.errors %}
<div class="error">{{ field.errors }}</div>
{% endif %}
</div>
{% endfor %}

<button type="submit">Submit</button>
</form>

Styling Django Forms

Django forms come with minimal default styling. Here are some approaches to style them:

1. Adding CSS Classes

You can add CSS classes when defining the form:

python
class ContactForm(forms.Form):
name = forms.CharField(
max_length=100,
widget=forms.TextInput(attrs={'class': 'form-control'})
)
email = forms.EmailField(
widget=forms.EmailInput(attrs={'class': 'form-control'})
)
message = forms.CharField(
widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 5})
)

2. Using a Form Renderer

Django's form renderer system allows you to customize how forms are rendered globally:

python
# settings.py
FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'

# Then create templates in templates/django/forms/widgets/

3. Using CSS Frameworks

Here's how to integrate Django forms with Bootstrap:

html
<form method="post">
{% csrf_token %}

{% for field in form %}
<div class="form-group mb-3">
<label for="{{ field.id_for_label }}" class="form-label">
{{ field.label }}
</label>
{{ field|add_class:"form-control" }}
{% if field.help_text %}
<small class="form-text text-muted">{{ field.help_text }}</small>
{% endif %}
{% if field.errors %}
{% for error in field.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
{% endfor %}

<button type="submit" class="btn btn-primary">Submit</button>
</form>

Note: The add_class filter requires the django-widget-tweaks package:

bash
pip install django-widget-tweaks

Then add it to your INSTALLED_APPS:

python
# settings.py
INSTALLED_APPS = [
# ...
'widget_tweaks',
# ...
]

Handling Form Submission

Let's complete the picture by handling form submission:

python
# myapp/views.py
from django.shortcuts import render, redirect
from .forms import ContactForm

def contact_view(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
# Process the data
name = form.cleaned_data['name']
email = form.cleaned_data['email']
message = form.cleaned_data['message']

# Do something with the data (e.g., send email)
# ...

return redirect('success')
else:
form = ContactForm()

return render(request, 'contact.html', {'form': form})

In your template, you'll now need to display validation errors:

html
<form method="post">
{% csrf_token %}

{% if form.non_field_errors %}
<div class="alert alert-danger">
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}

{% for field in form %}
<div class="form-group">
<label for="{{ field.id_for_label }}">{{ field.label }}:</label>
{{ field }}
{% if field.errors %}
<div class="error">
{% for error in field.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
{% endfor %}

<button type="submit">Submit</button>
</form>

Real-World Example: User Registration Form

Let's create a complete user registration form with validation and styling:

python
# forms.py
from django import forms
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError

class UserRegistrationForm(forms.Form):
username = forms.CharField(
max_length=150,
widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Choose a username'})
)
email = forms.EmailField(
widget=forms.EmailInput(attrs={'class': 'form-control', 'placeholder': 'Enter your email'})
)
password1 = forms.CharField(
label="Password",
widget=forms.PasswordInput(attrs={'class': 'form-control', 'placeholder': 'Create a password'})
)
password2 = forms.CharField(
label="Confirm Password",
widget=forms.PasswordInput(attrs={'class': 'form-control', 'placeholder': 'Repeat your password'})
)

def clean_username(self):
username = self.cleaned_data.get('username')
if User.objects.filter(username=username).exists():
raise ValidationError("This username is already taken.")
return username

def clean_email(self):
email = self.cleaned_data.get('email')
if User.objects.filter(email=email).exists():
raise ValidationError("This email is already registered.")
return email

def clean(self):
cleaned_data = super().clean()
password1 = cleaned_data.get('password1')
password2 = cleaned_data.get('password2')

if password1 and password2 and password1 != password2:
self.add_error('password2', "The two password fields didn't match.")

return cleaned_data

View:

python
# views.py
from django.shortcuts import render, redirect
from django.contrib.auth.models import User
from django.contrib.auth import login
from .forms import UserRegistrationForm

def register(request):
if request.method == 'POST':
form = UserRegistrationForm(request.POST)
if form.is_valid():
user = User.objects.create_user(
username=form.cleaned_data['username'],
email=form.cleaned_data['email'],
password=form.cleaned_data['password1']
)
login(request, user)
return redirect('home')
else:
form = UserRegistrationForm()

return render(request, 'register.html', {'form': form})

Template:

html
<!-- templates/register.html -->
{% extends 'base.html' %}

{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h2 class="text-center">Create an Account</h2>
</div>
<div class="card-body">
<form method="post">
{% csrf_token %}

{% if form.non_field_errors %}
<div class="alert alert-danger">
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}

{% for field in form %}
<div class="mb-3">
<label for="{{ field.id_for_label }}" class="form-label">
{{ field.label }}
</label>
{{ field }}
{% if field.errors %}
{% for error in field.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
{% endif %}
{% if field.help_text %}
<small class="form-text text-muted">{{ field.help_text }}</small>
{% endif %}
</div>
{% endfor %}

<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">Register</button>
</div>
</form>
</div>
<div class="card-footer text-center">
<p>Already have an account? <a href="{% url 'login' %}">Login</a></p>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

Summary

Django forms and templates work together to create a powerful system for handling user input:

  1. Form Definition: Create form classes in Python that define fields and validation rules.
  2. Template Integration: Pass form instances to templates from views.
  3. Rendering Options: Choose between automatic rendering ({{ form }}, {{ form.as_p }}, etc.) or manual field rendering for more control.
  4. Customization: Style forms using CSS, widget attributes, or third-party libraries.
  5. Validation: Handle form submission and display error messages in your templates.

This integration lets you build complex, user-friendly forms with minimal code while maintaining strong validation and security features.

Additional Resources and Exercises

Resources

Exercises

  1. Basic Form: Create a contact form that saves messages to a database model.
  2. Custom Styling: Apply custom CSS to make a form match your site's design.
  3. Complex Form: Build a multi-step form using Django's form wizard.
  4. Form with File Upload: Create a form that includes file uploads and displays previews.
  5. Form with Dynamic Fields: Create a form where some fields appear or disappear based on other field values using JavaScript.

By mastering Django form templates, you'll be able to create intuitive, user-friendly interfaces for your web applications while maintaining strong validation and security.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)