This website is made possible by displaying online advertisements to our visitors.
Please consider supporting us by disabling your ad blocker. Thank you for your support.
This website is made possible by displaying online advertisements to our visitors.
Please consider supporting us by disabling your ad blocker.

How to Customize the Validation of Forms in Django

July 5 2021 Yacine Rouizi
Django Form
How  to Customize the Validation of Forms in Django

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:

  • The clean() method on a field: this method will call the to_python() and validate() methods. This method is used to clean a specific type of form's field, for example, you have created a MultiEmailField field and you want this field to contain emails separated by commas. Please refer to the documentation for more information.
  • The clean_<fieldname>() method: Use this method when you want to do a cleaning on a form field attribute unrelated to the type of the field. For example, you want to make sure that a particular field is unique.
  • The clean() method on the form: Use this method when you want to perform validation on fields that depend on each other.

In this tutorial, we will see how to customize the form validation process using the last two cleaning methods mentioned above.

Download the code

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

Cleaning and Validating a Single Field

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:

Cleaning the title field

Cleaning 2 or more Fields that depend on each other

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.')

Cleaning the email and subscribe field

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:

Key error for the title field

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.

Conclusion

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.

Leave a comment

(Your email address will not be published)