Django Form Validation

Introduction

In this article, I will show you how form validation works in Django. I will also show you how to write your own custom validators. Understanding how this process works is very important because most web applications use forms to grab information from users. This article assumes that you have some experience in Django, and we'll be using Django version 4 and Python 3.10.

What is Form Validation

Forms are generally used for capturing user input. To make sure that the data which the user enters into the form meets the application's requirements, we put in place checks and balances: validators. This is necessary because you can't trust that users will always enter the correct data. The mechanism through which this is done is the validation process. Say you have a checkout form that requires a user to enter their credit card number, you want to make sure that the user does not enter any letters in the field. You are going to need to do some form validation to force the user to only enter numbers into the credit card input field.

Django provides us with the flexibility and tools to ensure that our forms work the way expect them to. It's also worth pointing out that form validation in Django can be done at the field and class levels.

class ContactForm(forms.Form):
    name = forms.CharField()
    username = forms.CharField()
    email = forms.EmailField()

The ContactForm above is the example we are going to work with. It is a very basic form. Now, if we want a user to enter their username in lowercase, we can do that with a custom validator. I say custom validator because the CharField field does not do that by default. Django form fields have default validators but sometimes these aren't enough for our needs, so we have to write our own — which we are going to do now.

Field Level Validation

Because we only want one field, username, to be in lowercase, the validator we write will only be applicable to that one field. In order to make sure that Django runs our custom validators, we create a method in the format clean_<fieldname>(). This is how Django runs any field-specific custom validations we write. In our case that is going to beclean_username().

from django.core.exceptions import ValidationError

class ContactForm(forms.Form):
    name = forms.CharField()
    username = forms.CharField()
    email = forms.EmailField()

    def clean_username(self):
        username = self.cleaned_data.get("username")
        if username.lower() != username:
            raise ValidationError(f"{username} is not inlowercase.")
        return username

In the code snippet above we implemented a custom validator for our username field. When Django cleans our forms, it puts the cleaned fields in a dictionary called cleaned_data. You first have to get the value of the field from the cleaneddata dictionary, which is what the username = self.cleaned_data.get("username") line does. Then we converted the value to lowercase and compared it to the actual value the user entered. If the value is not in lowercase, we raise a ValidationError and tell the user that what they entered is not lowercase. If the username was lowercase, we just returned the value (this just means that the field was valid).

When you implement a clean_<fieldname> method, you have to return the field: return username

The ValidationError exception that we raised will not crash the program, rather Django grabs the exception and spits it back out to the user as a form field error:

{username} is not inlowercase.

Remember, when the form has errors, Django rerenders the form back to the user with the errors. Because this error is field-specific, it will refer to the username field. There are non-field errors also which are not specific to a field but rather linked to multiple fields — which we will see shortly.

The clean_<fieldname>() method above is one way to write custom field validation. Another way is to write a separate function or callable and then append it to the validators argument of the field. This way you can write one validator for multiple fields. Let's see how we can do that:

from django.core.exceptions import ValidationError

def validate_lowercase(value):
    if value.lower() != value:
        raise ValidationError(f"{value} is not lowercase.")

class ContactForm(forms.Form):
    name = forms.CharField()
    username = forms.CharField(validators=[validate_lowercase])
    email = forms.EmailField()

As you can see, this is also another way we can write custom validators. The validate_lowecarse() function does the same thing the clean_username() field does, however, the only difference is that the validate_lowercase() function is reusable. If we want to force the user to enter their name in lowercase as well, we can reuse the validate_lowercase() function in the same way we did with the username field, like so:

name = forms.CharField(validators=validate_lowercase])

We are able to do this because when Django is cleaning a form field, it runs the field's validators, and by specifying validators=validate_lowercase], Django will append the validator to the field's list of validators and runs it.

Say for instance you have a validator you want to be run on all your form fields, it would be very repetitive to specify it on every field — especially if you have many fields in your form. You can do all that sorcery inside the form class's __init__() method, like so:

from django.core.exceptions import ValidationError

def validate_lowercase(value):
    if value.lower() != value:
        raise ValidationError(f"{value} is not lowercase.")


class ContactForm(forms.Form):
    name = forms.CharField()
    username = forms.CharField()
    email = forms.EmailField()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields:
            self.fields[field].validators.append(validate_lowercase)

Class Level Validation

As we've already mentioned, form validation sometimes spans multiple input fields. This means that the validity of one field depends on one or more fields. To do this, Django allows you to implement a clean() method. This clean() method is run after all the individual fields have been cleaned. Say you only want a user to enter either a name or a username but not both:

from django.core.exceptions import ValidationError

def validate_lowercase(value):
    if value.lower() != value:
        raise ValidationError(f"{value} is not lowercase.")

class ContactForm(forms.Form):
    name = forms.CharField()
    username = forms.CharField(validators=[validate_lowercase])
    email = forms.EmailField()

    def clean(self):
        cleaned_data = super().clean()
        name = cleaned_data.get('name')
        username = cleaned_data.get('username')
        if name and username:
            raise ValidationError("You can only enter name or username.")

By overriding the clean() method, we call super().clean() to get the dictionary where all the fields are stored, in a variable we named cleaned_data. We accessed both name and username, compared them and raised an exception if both fields were present.

ModelForms also work in much the same way, the only difference being that data is run against a database model or alters it.

Conclusion

In this article, we saw how to validate forms in Django. We showed how to do so at the field and class levels as well. We've also shown how to write our own custom validators and append them to the individual form fields. We showed as well how multiple-field validation works as well.

Thank you for reading! If you have any questions, I'd be happy to help.