Handling User Input with Django Forms
1. Why Not Just Read request.POST Directly?
When a user submits an HTML form, the data arrives in request.POST — a dictionary of raw strings. You could read it directly, but doing so is dangerous: there is no type conversion, no validation, and no protection against malformed input. Django Forms solve all three problems in one structured layer.
2. Creating a Django Form
Create a forms.py file inside your blog app:
# blog/forms.py
from django import forms
class CommentForm(forms.Form):
# Each field defines the data type, constraints, and widget to render.
name = forms.CharField(
max_length=100,
widget=forms.TextInput(attrs={'placeholder': 'Your name'})
)
email = forms.EmailField(
widget=forms.EmailInput(attrs={'placeholder': '[email protected]'})
)
body = forms.CharField(
widget=forms.Textarea(attrs={'rows': 4, 'placeholder': 'Your comment...'})
)
3. Processing a Form in a View
A view that handles a form must deal with two cases: the initial page load (GET request, show a blank form) and the form submission (POST request, validate and process the data). This pattern is called the GET/POST split:
# blog/views.py
from django.shortcuts import render, redirect
from .forms import CommentForm
def add_comment(request, post_slug):
if request.method == 'POST':
# Bind the form to the submitted data
form = CommentForm(request.POST)
if form.is_valid():
# form.cleaned_data is a dictionary of validated, type-cast values
name = form.cleaned_data['name']
email = form.cleaned_data['email']
body = form.cleaned_data['body']
# ... save the comment to the database here ...
return redirect('post_detail', slug=post_slug)
# If form is invalid, fall through and re-render with errors shown
else:
# GET request: show an empty form
form = CommentForm()
return render(request, 'blog/add_comment.html', {'form': form})
4. Rendering the Form in a Template
<!-- templates/blog/add_comment.html -->
{% extends "blog/base.html" %}
{% block content %}
<h1>Leave a Comment</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Submit</button>
</form>
{% endblock %}
{% csrf_token %} injects a hidden security token into every POST form. Django validates this token on submission to prevent Cross-Site Request Forgery attacks — an attacker on another website cannot forge form submissions to your site because they cannot know this token. Never omit it.
{{ form.as_p }} renders every field wrapped in a <p> tag, including error messages when validation fails. You can also render fields individually for full layout control:
<div class="field">
<label for="{{ form.name.id_for_label }}">Your Name</label>
{{ form.name }}
{% if form.name.errors %}
<ul class="errors">
{% for error in form.name.errors %}<li>{{ error }}</li>{% endfor %}
</ul>
{% endif %}
</div>
5. ModelForm — The Shortcut for Model-Based Forms
If your form maps directly to a model (which it usually does), use a ModelForm instead. It auto-generates fields from the model definition, so you do not duplicate code:
# blog/forms.py
from django import forms
from .models import Post
class PostForm(forms.ModelForm):
class Meta:
model = Post
# List the fields to include in the form
fields = ['title', 'slug', 'body', 'published']
widgets = {
'body': forms.Textarea(attrs={'rows': 10}),
}
# blog/views.py — create a new post
from django.contrib.auth.decorators import login_required
from .forms import PostForm
@login_required
def create_post(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False) # Don't save to DB yet
post.author = request.user # Attach the logged-in user
post.save() # Now save
return redirect('post_detail', slug=post.slug)
else:
form = PostForm()
return render(request, 'blog/create_post.html', {'form': form})
commit=False is a powerful ModelForm feature: it creates the model instance in memory without writing to the database, letting you modify fields (like setting author) before the final save().