CodeMIRAI
0 XP

Forms

3 min read 4 runnable examples Quiz included

Django forms do three jobs: they render HTML inputs, check what the user sent, and turn the text into proper Python values, with error messages for anything wrong.

Defining and validating a form

Python
import django
from django.conf import settings

settings.configure()
django.setup()

from django import forms


class SignupForm(forms.Form):
    name = forms.CharField(max_length=50)
    email = forms.EmailField()
    age = forms.IntegerField(min_value=13)
    newsletter = forms.BooleanField(required=False)


good = SignupForm({"name": "Asha", "email": "asha@example.com", "age": "21"})
print(good.is_valid())
print(good.cleaned_data)

bad = SignupForm({"name": "", "email": "not-an-email", "age": "9"})
print(bad.is_valid())
for field, errors in bad.errors.items():
    print(f"{field}: {errors[0]}")
  • Data arrives as text, like "21". After is_valid(), cleaned_data holds proper Python values: age is the number 21, and the unticked newsletter is False.
  • form.errors maps each field to its error messages.
  • Every field is required unless you say required=False.

Custom validation

Python
import django
from django.conf import settings

settings.configure()
django.setup()

from django import forms


class RegisterForm(forms.Form):
    username = forms.CharField(min_length=3)
    password = forms.CharField(min_length=8)
    confirm = forms.CharField()

    def clean_username(self):
        username = self.cleaned_data["username"].lower()
        if username in {"admin", "root"}:
            raise forms.ValidationError("That username is reserved.")
        return username                       # what you return becomes the cleaned value

    def clean(self):
        cleaned = super().clean()
        if cleaned.get("password") != cleaned.get("confirm"):
            self.add_error("confirm", "The passwords don't match.")
        return cleaned


form = RegisterForm({"username": "Admin", "password": "secret-pass", "confirm": "secret-pas"})
print(form.is_valid())
print(dict(form.errors))

form = RegisterForm({"username": "Asha", "password": "secret-pass", "confirm": "secret-pass"})
print(form.is_valid(), form.cleaned_data["username"])
  • A clean_<field>() method checks one field. It can also change the value, as lower-casing the username does.
  • clean() checks fields together, such as two passwords matching, and attaches errors with add_error.

Rendering a form

Python
import django
from django.conf import settings

settings.configure()
django.setup()

from django import forms


class ContactForm(forms.Form):
    email = forms.EmailField(label="Your email")
    message = forms.CharField(widget=forms.Textarea(attrs={"rows": 3}))


print(ContactForm().as_p())

In a template, {{ form.as_p }} renders every field with its label, and Django adds the HTML attributes (required, type="email", maxlength) that match your validation. For full control of the layout, render fields one by one:

HTML
<form method="post">
  {% csrf_token %}
  {{ form.email.label_tag }} {{ form.email }}
  {% for error in form.email.errors %}<p class="error">{{ error }}</p>{% endfor %}
  <button>Send</button>
</form>

{% csrf_token %} is required in every POST form. It protects against cross-site request forgery, where another site tricks a logged-in user's browser into submitting your form.

ModelForm: a form built from a model

Python
import django
from django.conf import settings

settings.configure(
    INSTALLED_APPS=["django.contrib.contenttypes"],
    DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}},
)
django.setup()

from django import forms
from django.db import connection, models


class Course(models.Model):
    title = models.CharField(max_length=100)
    price = models.PositiveIntegerField()

    class Meta:
        app_label = "courses"


with connection.schema_editor() as editor:
    editor.create_model(Course)


class CourseForm(forms.ModelForm):
    class Meta:
        model = Course
        fields = ["title", "price"]


form = CourseForm({"title": "Django", "price": "-5"})
print(form.is_valid(), dict(form.errors))

form = CourseForm({"title": "Django", "price": "399"})
if form.is_valid():
    course = form.save()                       # creates the row
    print("Saved course", course.id, course.title, course.price)
print(Course.objects.count(), "course in the database")

A ModelForm takes its fields and validation from the model, here rejecting a negative price because the field is a PositiveIntegerField, and save() creates or updates the object.

Handling a form in a view

Python
from django.shortcuts import redirect, render

from .forms import CourseForm


def new_course(request):
    if request.method == "POST":
        form = CourseForm(request.POST)            # bind the submitted data
        if form.is_valid():
            form.save()
            return redirect("course_list")          # Post/Redirect/Get
    else:
        form = CourseForm()                        # an empty form for the first visit
    return render(request, "courses/new_course.html", {"form": form})

This shape appears in almost every Django project. On the first visit, show an empty form. When it's submitted, validate it: if it's valid, save and redirect, so refreshing can't submit twice; if not, render the same form again, which now shows the user's input with the error messages.

Finished this lesson?Mark it complete to earn +20 XP and move your progress on.