Forms
Handling HTML forms manually involves a lot of repetitive work. You have to write the HTML, extract the submitted data from the request, validate it, display error messages, and repopulate the form if something goes wrong. Django's form framework handles all of this for you securely and efficiently.
Creating a Basic Form
You define a form in Django much like you define a model. You create a class that inherits from django.forms.Form and define the fields you want to collect.
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(max_length=100)
email = forms.EmailField()
message = forms.CharField(widget=forms.Textarea)To display and process this form, your view needs to handle two different scenarios. When a user first visits the page, the request is a GET request, and you should display an empty form. When the user submits the form, the request is a POST request, and you need to validate the incoming data.
from django.shortcuts import render
from django.http import HttpResponseRedirect
from .forms import ContactForm
def contact_view(request):
if request.method == "POST":
form = ContactForm(request.POST)
if form.is_valid():
# Process the data in form.cleaned_data
return HttpResponseRedirect("/thanks/")
else:
form = ContactForm()
return render(request, "contact.html", {"form": form})CSRF Protection
Django has built-in protection against Cross-Site Request Forgery (CSRF) attacks. This security feature ensures that the form submission is actually coming from your own website and not a malicious third party.
To make this work, you must include the {% csrf_token %} template tag inside any form that uses the POST method. If you forget this tag, Django will reject the form submission and display a 403 Forbidden error.
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Send</button>
</form>Widgets
A widget is Django's representation of an HTML input element. While a form field handles the validation logic, the widget handles the HTML rendering. Django automatically assigns a default widget to every field type. For example, a CharField uses a standard text input, while a BooleanField uses a checkbox.
You can override these defaults when defining your form. This is especially useful when you want to add CSS classes or change the input type.
| Widget | HTML Element | Common Use Case |
|---|---|---|
TextInput | <input type="text"> | Default for short text fields |
Textarea | <textarea> | Long-form text and messages |
PasswordInput | <input type="password"> | Hiding sensitive text |
CheckboxInput | <input type="checkbox"> | True or false selections |
Select | <select> | Choosing a single option from a dropdown |
class StylingForm(forms.Form):
username = forms.CharField(
widget=forms.TextInput(attrs={"class": "form-control", "placeholder": "Enter username"})
)Form Assets (Media)
Often, a custom widget needs more than just HTML to function correctly. Advanced widgets, like date pickers or rich text editors, usually rely on specific CSS stylesheets and JavaScript files. Django calls these static files "form assets," and it allows you to bundle them directly within your form or widget definitions.
To associate assets with a form or widget, you define an inner Media class.
from django import forms
class CalendarWidget(forms.TextInput):
class Media:
css = {
"all": ["pretty.css"],
}
js = ["animations.js", "actions.js"]
class EventForm(forms.Form):
title = forms.CharField()
date = forms.DateField(widget=CalendarWidget)When you render this form in a template, you can output its associated CSS and JavaScript links using {{ form.media }}. Django automatically manages the dependencies, ensuring that if multiple fields use the same widget, its assets are only included once.
<head>
{{ form.media }}
</head>
<body>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Submit</button>
</form>
</body>Validation
Django automatically validates your data based on the field types you choose. An EmailField automatically checks for a valid email address structure. However, you often need to enforce custom business rules.
To add custom validation for a single field, you write a method named clean_<fieldname>(). To add validation that relies on multiple fields comparing data against each other, you override the general clean() method.
from django import forms
from django.core.exceptions import ValidationError
class RegistrationForm(forms.Form):
username = forms.CharField()
password = forms.CharField(widget=forms.PasswordInput)
password_confirm = forms.CharField(widget=forms.PasswordInput)
def clean_username(self):
data = self.cleaned_data["username"]
if "admin" in data.lower():
raise ValidationError("You cannot use 'admin' in your username.")
return data
def clean(self):
cleaned_data = super().clean()
password = cleaned_data.get("password")
password_confirm = cleaned_data.get("password_confirm")
if password and password_confirm and password != password_confirm:
raise ValidationError("Passwords do not match.")
return cleaned_dataModelForms
If you are creating a form designed to add or update a database model, you do not need to redefine all your fields manually. A ModelForm automatically generates a form based on your model's structure. It also provides a convenient save() method that writes the validated data directly to the database.
from django import forms
from .models import Article
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ["title", "content", "author"]Formsets
Sometimes you need to display and process multiple instances of the same form on a single page. Formsets manage this complexity for you. They ensure all forms are processed together and handle extra hidden fields required to track the total number of submitted forms.
If you are working with database models, you can use a modelformset_factory to generate a formset that creates or updates multiple database records at once.
from django.forms import modelformset_factory
from django.shortcuts import render
from .models import Article
def manage_articles(request):
ArticleFormSet = modelformset_factory(Article, fields=("title", "content"))
if request.method == "POST":
formset = ArticleFormSet(request.POST)
if formset.is_valid():
formset.save()
else:
formset = ArticleFormSet()
return render(request, "manage_articles.html", {"formset": formset})Inline Formsets
While a standard ModelFormSet manages multiple instances of a single model, an inline formset manages a parent model and multiple related child models at the same time. This is useful when dealing with Foreign Key relationships, such as an Author and their related Book records.
Django provides the inlineformset_factory function to handle this specific relationship automatically.
from django.forms import inlineformset_factory
from django.shortcuts import render
from .models import Author, Book
def manage_books(request, author_id):
author = Author.objects.get(pk=author_id)
BookInlineFormSet = inlineformset_factory(Author, Book, fields=("title", "published_date"))
if request.method == "POST":
formset = BookInlineFormSet(request.POST, instance=author)
if formset.is_valid():
formset.save()
else:
formset = BookInlineFormSet(instance=author)
return render(request, "manage_books.html", {"formset": formset})By passing instance=author to the formset, Django automatically links any new books created in the formset to that specific author and correctly updates existing ones.
