Skip to content

Model Forms

Visitors need a way to submit an adoption application from the site. Our AdoptionApplication model already declares the fields an application has, and the rules for each.

A form is a class that declares fields, renders them as HTML inputs, and validates submitted data. It converts the strings a browser sends into Python values, and rejects values that fail validation.

We don't want to declare the same fields and rules a second time, so we'll use a ModelForm, a form that builds its fields from a model's fields.

The Application Form

Create animals/forms.py:

animals/forms.py
from django import forms

from .models import AdoptionApplication


class AdoptionApplicationForm(forms.ModelForm):
    class Meta:
        model = AdoptionApplication
        fields = ["applicant_name", "email", "phone", "message"]
        labels = {
            "applicant_name": "Your name",
            "message": "Tell us about your home",
        }

The inner Meta class holds the form's options:

  • model: The model the form builds its fields from. Saving the form creates a row of it.
  • fields: The model fields that become form fields. The animal comes from the page's URL, and employees set the status in the admin, so neither is on the form.
  • labels: Labels to show in place of the ones made from each field's name, such as "Applicant name".

Validating Submitted Data

Start the shell with uv run manage.py shell, and give the form data that breaks its rules:

Python REPL
>>> from animals.forms import AdoptionApplicationForm
>>> form = AdoptionApplicationForm({"applicant_name": "", "email": "not-an-email"})
>>> form.is_valid()
False
>>> form.errors
{'applicant_name': ['This field is required.'], 'email': ['Enter a valid email address.']}

A form created with data is bound to that data. is_valid() validates it, and errors holds the messages for each field that failed.

Python REPL
>>> form = AdoptionApplicationForm({"applicant_name": "Maria", "email": "maria@example.com"})
>>> form.is_valid()
True
>>> form.cleaned_data
{'applicant_name': 'Maria', 'email': 'maria@example.com', 'phone': '', 'message': ''}

When the data is valid, cleaned_data holds the validated values.

is_valid() also calls the model's full_clean(), as the admin's forms do, so the validators of the form's fields and the model's clean() run too.

Forms Without a Model

A form that doesn't save a row subclasses forms.Form and declares its own fields. The exercise's branch includes one for the list page's search. It's sent with GET, so its values are in the URL and a page of results can be bookmarked.