Django Forms & Validation0%

Django Forms & Validation

Beginner12 min read•Updated: Jul 11, 2026
Study MaterialsCheatsheet

Django Forms & Validation

Web forms are the primary vector for user input and data mutation in web applications. Writing manual HTML forms and raw validation logic in views leads to security vulnerabilities (such as SQL injection, XSS, and unvalidated payloads). Django's Form framework provides declarative form definitions, automatic HTML widget rendering, server-side data cleaning and validation, and tight integration with models via ModelForm.


1. Declarative Forms vs ModelForms

  • forms.Form: Used for arbitrary input processing not tied directly to a single database table (e.g. contact forms, password reset requests, search filters).
  • forms.ModelForm: Automatically maps fields from a Django model, inferring validation rules, field types, and default widgets while providing an automatic .save() method.

2. Implementing an Enterprise ModelForm

Python
# articles/forms.py
from django import forms
from .models import Article
 
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ["title", "content", "category", "tags", "status", "is_featured"]
widgets = {
"title": forms.TextInput(attrs={
"class": "w-full px-3 py-2 bg-slate-800 border border-slate-700 rounded text-white text-sm",
"placeholder": "Enter article headline...",
}),
"content": forms.Textarea(attrs={
"class": "w-full px-3 py-2 bg-slate-800 border border-slate-700 rounded text-white text-sm h-40",
"placeholder": "Write your markdown or text here...",
}),
"status": forms.Select(attrs={
"class": "w-full px-3 py-2 bg-slate-800 border border-slate-700 rounded text-white text-sm",
}),
}
 
# 1. Field-specific custom validation: clean_<fieldname>
def clean_title(self):
title = self.cleaned_data.get("title", "").strip()
if len(title) < 5:
raise forms.ValidationError("Headline must be at least 5 characters long.")
if "clickbait" in title.lower():
raise forms.ValidationError("Clickbait headlines are not permitted by editorial policy.")
return title
 
# 2. Cross-field validation: clean()
def clean(self):
cleaned_data = super().clean()
status = cleaned_data.get("status")
is_featured = cleaned_data.get("is_featured")
 
# Business rule: Draft articles cannot be featured on homepage
if status == "draft" and is_featured:
raise forms.ValidationError(
"You cannot set a draft article as featured. Please publish the article first."
)
 
return cleaned_data

3. Handling Forms in Views

The canonical pattern for processing form submissions:

Python
# articles/views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from .forms import ArticleForm
from .models import Article
 
@login_required
def create_article_view(request):
if request.method == "POST":
# 1. Bind incoming POST data and files to form
form = ArticleForm(request.POST, request.FILES)
# 2. Validate input
if form.is_valid():
# commit=False creates model instance without saving to database yet
article = form.save(commit=False)
article.author = request.user # Inject authenticated author
article.save()
form.save_m2m() # Save Many-to-Many tags relationships!
return redirect("articles:detail", slug=article.slug)
else:
# GET request: instantiate empty form
form = ArticleForm()
 
return render(request, "articles/article_form.html", {"form": form})

4. The Critical commit=False and save_m2m() Rule

When using form.save(commit=False), Django instantiates the model object without writing to the database, allowing you to attach additional attributes (like author).

However, Many-to-Many relationships cannot be saved until the parent object has a database ID. Therefore, after calling article.save(), you must call form.save_m2m() to persist tags or related categories!


Interactive Quiz

Practice Quiz

Question 1
What method executes form validation and populates the cleaned_data dictionary?
Question 2
What naming convention does Django enforce for field-specific custom validation methods?
Question 3
Why is form.save(commit=False) commonly used in views?
Question 4
Why must form.save_m2m() be called after saving an instance with commit=False?
Question 5
What is the purpose of the widgets dictionary inside a ModelForm's Meta class?
Next Lesson

Template Engine Basics

Continue learning with hands-on practice, examples, and exercises in the upcoming topic.

Related Lessons

Previous LessonNext Lesson
Groups and RolesTemplate Engine Basics

Practice Quiz

Test your understanding of this lesson with 5 questions. Each question has one correct answer.

PrevNext