In a previous tutorial, we saw how to create, render and validate forms. But we didn't talk too much about the validation process. Indeed, imagine that in the post form that we created last time we want our post title to be unique, how can we do that?
There are three types of cleaning methods, each one is triggered when you call the is_valid() method on the form:
In this tutorial, we will see how to customize the form validation process using the last two cleaning methods mentioned above.
Let's start by downloading the starter project by running the following command:
git clone https://github.com/Rouizi/dj-forms
cd dj-forms
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
Suppose that want to make sure that the title field of our PostForm is unique. We can write a clean_title() method that validate the title field like below:
# core/forms.py
from django import forms
from .models import Post
class PostForm(forms.Form):
# ...
def clean_title(self):
title = self.cleaned_data['title']
if Post.objects.filter(title=title).exists():
raise forms.ValidationError('A post with that title already exists.')
return title
I first created and submitted a form with the title "How to Use Forms in Django" and I tried to submit another form with the same title. Below you can see the error message that I get:
Suppose we add another field to our form (a checkbox field) to let the person who created the post to subscribe to the post, meaning that she will receive an email every time someone leaves a comment on her post.
But, in our form, the email field is not required, so we need to check that if the subscribe field is checked then the email is provided.
Since this validation is performed on more than one field, we need to use the form's clean() method. Below you can see how to do it:
# core/forms.py
# ...
class PostForm(forms.Form):
# ...
subscribe = forms.BooleanField(required=False,
help_text='We will send you an email every time someone comments your post')
def clean_title(self):
# ...
def clean(self):
# we don't need to do: cleaned_data = super().clean()
# because there is no validation logic in the parent class
super().clean()
print(self.cleaned_data)
subscribe = self.cleaned_data['subscribe']
email = self.cleaned_data['email']
if subscribe and not email:
# if `subscribe` is checked but there is no email we raise an error
raise forms.ValidationError('The email must be provided when subscribing to the post.')
You need to know that the self.cleaned_data in the clean method above only contains data that have been survived so far the validation.
So, for example, if we submit the form with a title that already exists in the database, the title will not be included in the cleaned_data dictionary.
The print function above will show us the following:
/home/rouizi/dj-forms/core/forms.py changed, reloading.
Watching for file changes with StatReloader
Performing system checks...
# ...
{'content': 'zefzef', 'activate': False, 'author': 'yacine', 'email': '', 'subscribe': True}
The title field is not included in the cleaned_data dictionary because it didn't survive the validation in the clean_title() method.
Ok, but what is the problem?
Well in our case there is no problem, but let's imagine another case where we want our post's title to be unique, and also we want that the title is not included in the content field.
If we write our clean method with the same logic as above:
# core/forms.py
# ...
class PostForm(forms.Form):
# ...
def clean_title(self):
# ...
def clean(self):
super().clean()
title = self.cleaned_data['title']
content = self.cleaned_data['content']
if title and content:
if title in content:
raise forms.ValidationError('The content must be different from the title.')
and submit the form with a title that already exists in the database and a content that contains the title, here is what we get:
So here is how to do it instead:
# ...
class PostForm(forms.Form):
# ...
def clean_title(self):
# ...
def clean(self):
super().clean()
# if the title is not in cleaned_data that means that
# it did not survive the validation in the clean_title() method
if 'title' or 'content' not in cleaned_data:
# there is nothing to do because the clean_title() method
# or other methods that are run before the clean() method
# has already raised an error
pass
else:
title = self.cleaned_data['title']
content = self.cleaned_data['content']
if title in content:
raise forms.ValidationError("You can't include the title in the content.")
We need to check if the title is in the cleaned_data dictionary, and if not we just pass. Otherwise, we can implement the same logic as before.
Django allows us to customize the validation process of a form with several methods, each one serving a different purpose. In this tutorial, we saw how to use the form's clean() method and the cleand_<fieldname>() method.
If you have a question or a remark, please use the comment section below. Also, if you find the content helpful, you can subscribe to the mailing list or follow me on social media for future posts.