Mastering form validation in Django: A complete guide

Form validation is an important part of every web application. It enables you to verify that the data entered by the user is correct and satisfies the requirements of your application.

Django forms are robust, powerful, versatile, and expandable. The ability to combine forms, models, and views enables us to complete a lot of work with little effort.

In this tutorial, we will learn.

1. Model Form with default validators.

2. How to add custom form field validators.

3. Overriding the clean() function for validation.

4. One Form tip and trick.

Let's create a Product model.

# models.py

from django.db import models

class Product(models.Model):
    title = models.CharField(null=False, blank=False, max_length=25)
    slug = models.CharField(unique=True, max_length=20)
    quantity = models.PositiveIntegerField()
    price = models.DecimalField(max_digits=5, decimal_places=2)

    def __str__(self):
        return self.title

Let's create a ModelForm "ProductForm" now. 

# forms.py

from django import forms

from .models import Product

class ProductForm(forms.ModelForm):

    class Meta:
        model = Product
        fields = '__all__'

This code defines a ProductForm class that subclasses Django's ModelForm class. ModelForm is a useful class that allows you to create forms from Django models. This means that the ProductForm class will have a form field for each of the model fields defined in the Product model.

Finally, in the Meta class, we specify that the ProductForm should use the Product model and include all fields from the model. This will create form fields for each field in the Product model.

Now we will write a code to display our form.

# views.py

from django.shortcuts import render
from django.views.generic import CreateView

from .models import Product
from .forms import ProductForm


class ProductCreate(CreateView):
    model = Product
    form_class = ProductForm
    template_name = 'products/create_product.html'


#urls.py

from django.urls import path

from .views import ProductCreate

urlpatterns = [
   path('create/', ProductCreate.as_view(), name="create_product")
]

Now let's write a code in create_product.html

<!-- create_product.html -->
<form action="" method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Submit">
</form>

Go to your URL and you will see the following:

1. Model Form with default validators.

We have a ProductForm which is a ModelForm. And in our model form, we are using all fields of our model.

All ModelForm rely on the default field validation rules of our model.

For example: In our price field we have specified max_digits=5. So if we put more than 5 digits then it should throw the validation error.

price = models.DecimalField(max_digits=5, decimal_places=2)

Although Django provides us with a tonne of fantastic defaults or built-in form validation like MaxLengthValidator, MinLengthValidator, in actuality, the defaults are never sufficient. We are aware of this, hence we learn how to create custom validators in the next step.

2. How to add custom form field validators.

Here we are applying the validtor on field level. Let's say you want that every title of a product should start with "Amazing". like Amazing Shoes, Amazing Joggers, etc. 

We can solve this problem with a simple custom validator. Create a new file validators.py in your app. 

# validators.py

from django.core.exceptions import ValidationError


def validate_amazing(value):
    """Raise a ValidationError if the value doesn't start with the
    word 'Amazing'.
    """
    if not value.startswith('Amazing'):
        msg = 'Must start with Amazing'
        raise ValidationError(msg)

The validate_amazing function is a custom validator that raises a ValidationError if the given value doesn't start with the word 'Amazing'. This validator can be used to ensure that the title field of a Product always starts with the word 'Amazing'.

We can add this validator in both models or forms according to our requirements.

a. Adding validate_amazing in models.py

Here we are adding the validate_amazing validator in the title field by specifying validators=[validate_amazing].

# models.py

from django.db import models

from .validators import validate_amazing

class Product(models.Model):
    # new
    title = models.CharField(
        null=False, blank=False, max_length=25, validators=[validate_amazing]
        )
    slug = models.CharField(unique=True, max_length=20)
    quantity = models.PositiveIntegerField()
    price = models.DecimalField(max_digits=5, decimal_places=2)

    def __str__(self):
        return self.title

b. Adding validate_amazing in forms.py

Now we will learn how to add validators in forms.

# forms.py

from django import forms

from .models import Product
from .validators import validate_amazing

class ProductForm(forms.ModelForm):

    # new
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['title'].validators.append(validate_amazing)

    class Meta:
        model = Product
        fields = '__all__'

In the __init__ method of the ProductForm class, we are appending a custom validator called validate_amazing to the list of validators for the title field.

3. Overriding the clean() function for validation.

After the default and custom field validators are run, Django provides the second stage of validation through the clean() method and clean_<field_name>() methods.

a. clean(): Since the clean() method isn't exclusive to any one particular field, it is used to validate two or more fields against one another.

b. clean_<field_name>(): The clean_<field_name>() methods are similar to the clean() method, but are specific to individual fields on the form. These methods are called after the clean() method and allow you to perform custom validation on individual form fields.

Let's explore each one by one.

a. clean().

# forms.py

from django import forms

from .models import Product


class ProductForm(forms.ModelForm):

    class Meta:
        model = Product
        fields = '__all__'

    def clean(self):
        cleaned_data = super().clean()
        slug = cleaned_data.get('slug', '')
        title = cleaned_data.get('title', '')

        # slug and title should be same example
        if slug != title.lower():
            msg = "slug and title should be same"
            raise forms.ValidationError(msg)
        return cleaned_data

cleaned_data variable is a dictionary that contains the cleaned and validated data from the form. The code then retrieves the slug and title fields from the cleaned_data dictionary.

Then it compares the value of the slug field with the lowercase version of the title field. If the slug and the lowercase title are not the same, the code raises a validation error with the message "slug and title should be same". This error message will be displayed to the user if the form fails validation. Finally, the clean method returns the cleaned_data dictionary, which contains the cleaned and validated data from the form.

b. clean_<field_name>()

# forms.py

from django import forms

from .models import Product
from .validators import validate_amazing


class ProductForm(forms.ModelForm):

    class Meta:
        model = Product
        fields = '__all__'


    def clean_quantity(self):
        quantity = self.cleaned_data['quantity']
        if quantity > 100:
            msg = 'Quantity should be less than 100'
            raise forms.ValidationError(msg)
        return quantity

This method first retrieves the value of the quantity field from the form's cleaned data, which is a dictionary-like object containing the form data that has been validated.

Next, the method checks if the quantity value is greater than 100. If it is, then the method raises a ValidationError with the message "Quantity should be less than 100". This error will be displayed to the user, informing them that their input is invalid and must be corrected.

If the quantity value is less than or equal to 100, then the method simply returns the value of quantity, indicating that it is valid.

4. One form tip and trick.

If you want to specify required in your form field. Then don't do this. This is a repeated duplicate code.

# forms.py

# Don't do this

from django import forms
from .models import Product


class ProductForm(forms.ModelForm):
    # Don't do this - This is a duplication of the model field!
    title = forms.CharField(required=True)
    # Don't do this - This is a duplicationof the model field!
    price = forms.DecimalField(required=True)

    class Meta:
        model = Product
        fields = '__all__'

The ProductForm class defines title and price fields that are duplicates of the title and price fields that already exist in the Product model. This is unnecessary and can lead to confusion and errors.

Let me show you the correct way.

# forms.py

# Correct way

from django import forms
from .models import Product


class ProductForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['title'].required = True
        self.fields['price'].required = True

    class Meta:
        model = Product
        fields = '__all__'

The ProductForm class overrides the __init__ method of the ModelForm class. This method is called when an instance of the ProductForm class is created. The __init__ method first calls the __init__ method of the parent ModelForm class using super(), which allows the parent class to initialize itself.

Next, the __init__ method sets the required attribute of the title and price fields to True. This means that when the form is rendered, these fields will be marked as required and the user will have to enter a value for them in order to submit the form.

That's it. If you find it useful then show some love and support by clapping or by buying me coffee. And don't forget to share it with your friends. In case of any problem, you can comment here or you can also directly approach me through my LinkedIn.

Related Articles.

How to add class and other attributes to django form fields

How to add date input widget in django forms

I would greatly appreciate it if you subscribe to my YouTube channel, Let's Code More

 ⋅  1 comment has been posted.
    April 2, 2024, 11:30 a.m. - Arun  
    Most loving article. Thank's
    Reply

Your comment

Required for comment verification
claps