Getting Started with Forms
In this tutorial, you will build a functional contact form using the Django forms system. You will learn how to define fields, validate user input, and handle the form lifecycle from raw data to cleaned Python objects.
What You Will Build
You will create a ContactForm that collects a user's email, a subject, and a message. By the end, you will be able to:
- Define a form using declarative syntax.
- Bind data to the form and trigger validation.
- Implement custom field-level and form-level validation.
- Render the form into HTML.
Prerequisites
To follow this tutorial, you should have a Django project environment ready. The core classes we will use are located in django/forms/forms.py and django/forms/fields.py.
Step 1: Define the Form Class
Django forms are defined as Python classes that inherit from django.forms.forms.Form. You define the form's fields as class attributes using various Field subclasses.
Create a new file (e.g., forms.py) and add the following code:
from django.forms import Form, CharField, EmailField, ValidationError
from django.forms.widgets import Textarea
class ContactForm(Form):
subject = CharField(max_length=100)
message = CharField(widget=Textarea)
sender = EmailField()
cc_myself = CharField(required=False)
In this snippet:
CharFieldandEmailFieldare subclasses ofField(fromdjango.forms.fields).- The
widgetargument (likeTextarea) determines how the field is rendered in HTML. required=Falseallows thecc_myselffield to be empty.
Step 2: Instantiate and Bind Data
A form instance is either unbound (no data) or bound (associated with a dictionary of data). When you pass a dictionary to the constructor, the BaseForm.__init__ method sets self.is_bound to True.
# An unbound form (used for displaying an empty form)
unbound_form = ContactForm()
# A bound form (used for processing submitted data)
data = {
'subject': 'Hello',
'message': 'This is a test message.',
'sender': 'invalid-email', # This will cause a validation error
'cc_myself': 'on'
}
bound_form = ContactForm(data)
Step 3: Validate the Form
To check if the submitted data is valid, call the is_valid() method. This method triggers the full_clean() process defined in BaseForm, which runs each field's clean() method.
if bound_form.is_valid():
# Access the cleaned, validated data
subject = bound_form.cleaned_data['subject']
print(f"Valid subject: {subject}")
else:
# Access the error dictionary
print(bound_form.errors)
# Output: {'sender': ['Enter a valid email address.']}
When is_valid() returns True, the cleaned_data attribute contains a dictionary of the validated data converted to Python objects (e.g., an EmailField ensures the string is a valid email format).
Step 4: Add Custom Validation
Sometimes you need logic that goes beyond built-in field validation. You can add methods to your form class to perform custom checks.
Field-specific validation
To validate a specific field, define a method named clean_<fieldname>().
class ContactForm(Form):
# ... (fields from Step 1)
def clean_subject(self):
data = self.cleaned_data['subject']
if "urgent" not in data.lower():
raise ValidationError("All subjects must contain the word 'urgent'!")
return data
Form-wide validation
To perform validation that involves multiple fields, override the clean() method.
class ContactForm(Form):
# ...
def clean(self):
cleaned_data = super().clean()
cc_myself = cleaned_data.get("cc_myself")
subject = cleaned_data.get("subject")
if cc_myself and subject and "help" not in subject:
raise ValidationError(
"You can only CC yourself if the subject contains 'help'."
)
return cleaned_data
Step 5: Render the Form
Django provides several ways to render a form into HTML. The BaseForm class includes mixins that allow you to output the form in different formats.
In your template (or for testing in a shell):
form = ContactForm()
print(form.as_p())
The as_p() method wraps each field in a <p> tag. Other options include as_ul() and as_table(). Each field is rendered as a BoundField, which combines the Field logic with the actual data and errors.
Complete Working Result
Here is your final ContactForm with custom validation:
from django.forms import Form, CharField, EmailField, ValidationError
from django.forms.widgets import Textarea
class ContactForm(Form):
subject = CharField(max_length=100)
message = CharField(widget=Textarea)
sender = EmailField()
cc_myself = CharField(required=False)
def clean_subject(self):
subject = self.cleaned_data.get('subject')
if len(subject) < 5:
raise ValidationError("Subject is too short.")
return subject
def clean(self):
cleaned_data = super().clean()
sender = cleaned_data.get('sender')
if sender and sender.endswith("@blocked.com"):
raise ValidationError("This domain is blocked.")
return cleaned_data
# Usage example:
f = ContactForm({'subject': 'Hi', 'message': 'Test', 'sender': 'user@blocked.com'})
if not f.is_valid():
print(f.errors.as_json())
Next Steps
Now that you can create and validate basic forms, explore ModelForms in django/forms/models.py to automatically generate forms from your database models, or look into Formsets to handle multiple forms on a single page.