Skip to content

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.

python
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.

python
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.

html
<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.

WidgetHTML ElementCommon 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
python
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.

python
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.

html
<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.

python
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_data

ModelForms

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.

python
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.

python
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.

python
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.