A Django form is a Python class. In a template it becomes HTML. Let’s look at the rendering approaches: from automatic to fully manual.
Passing a Form to a Template
# forms.py
from django import forms
class CommentForm(forms.Form):
name = forms.CharField(max_length=100, label="Name")
email = forms.EmailField(label="Email")
text = forms.CharField(widget=forms.Textarea, label="Comment")
# views.py
from django.shortcuts import render, redirect
from .forms import CommentForm
def add_comment(request, post_pk):
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
# save data
return redirect('post-detail', pk=post_pk)
else:
form = CommentForm()
return render(request, 'blog/comment_form.html', {'form': form})
Automatic Rendering
The fastest approach — let Django render the fields itself:
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
| Method | HTML wrapper |
|---|---|
{{ form.as_p }} |
Each field in a <p> |
{{ form.as_div }} |
Each field in a <div> (Bootstrap-friendly) |
{{ form.as_table }} |
Table with <tr>/<td> |
{{ form.as_ul }} |
List with <li> |
Limitation: automatic rendering doesn’t add Bootstrap classes — you’ll need to style via CSS or widgets.
Manual Rendering: Full Control
Manual rendering gives you complete control over each field’s HTML:
<form method="post" novalidate>
{% csrf_token %}
<div class="mb-3">
{{ form.name.label_tag }}
{{ form.name }}
{% if form.name.errors %}
<div class="invalid-feedback d-block">
{{ form.name.errors }}
</div>
{% endif %}
</div>
<div class="mb-3">
{{ form.email.label_tag }}
{{ form.email }}
{% if form.email.errors %}
<div class="invalid-feedback d-block">
{{ form.email.errors }}
</div>
{% endif %}
</div>
<div class="mb-3">
{{ form.text.label_tag }}
{{ form.text }}
{% if form.text.errors %}
<div class="invalid-feedback d-block">
{{ form.text.errors }}
</div>
{% endif %}
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
{{ form.name }} renders the <input>, {{ form.name.label_tag }} renders the <label>, {{ form.name.errors }} renders validation errors.
Bootstrap Classes via Widgets
To avoid adding styles in the template, set classes on the form itself:
# forms.py
class CommentForm(forms.Form):
name = forms.CharField(
max_length=100,
label="Name",
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Your name',
})
)
email = forms.EmailField(
label="Email",
widget=forms.EmailInput(attrs={'class': 'form-control'})
)
text = forms.CharField(
label="Comment",
widget=forms.Textarea(attrs={
'class': 'form-control',
'rows': 4,
})
)
Now {{ form.as_p }} already outputs fields with the form-control class.
Looping Over Fields
For forms with many fields, iterate over them:
<form method="post">
{% csrf_token %}
{% for field in form %}
<div class="mb-3">
{{ field.label_tag }}
{{ field }}
{% if field.help_text %}
<small class="form-text text-muted">{{ field.help_text }}</small>
{% endif %}
{% for error in field.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
</div>
{% endfor %}
{{ form.non_field_errors }}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
form.non_field_errors — errors not tied to a specific field (e.g. from form.clean()).
Example: DevBlog Login Form
{% extends 'base.html' %}
{% block title %}Log In — DevBlog{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-5">
<h1 class="mb-4">Log In</h1>
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label for="{{ form.username.id_for_label }}" class="form-label">
Username
</label>
{{ form.username }}
{{ form.username.errors }}
</div>
<div class="mb-3">
<label for="{{ form.password.id_for_label }}" class="form-label">
Password
</label>
{{ form.password }}
{{ form.password.errors }}
</div>
{% if form.non_field_errors %}
<div class="alert alert-danger">{{ form.non_field_errors }}</div>
{% endif %}
<button type="submit" class="btn btn-primary w-100">Log In</button>
</form>
</div>
</div>
{% endblock %}
💬 Comments (0)
No comments yet
Be the first to share your opinion about this article!