Skip to content

Custom Field Validation

In Django REST Framework (DRF), you can implement custom validation logic within serializers by using the validate_<field_name> method or the validate method for object-level validation. Here's how you can do it:

1. Field-Level Validation

Field-level validation is done using the validate_<field_name> method. This method is called when validating the individual field, and it allows you to add custom validation logic specific to that field.

from rest_framework import serializers

class UserSerializer(serializers.Serializer):
    username = serializers.CharField(max_length=100)
    email = serializers.EmailField()

    def validate_username(self, value):
        if "@" in value:
            raise serializers.ValidationError("Username should not contain the '@' symbol.")
        return value

In this example, the validate_username method checks if the username contains the "@" symbol and raises a ValidationError if it does.

2. Object-Level Validation

Object-level validation is done using the validate method. This method allows you to validate the entire object as a whole, based on the interrelationship of multiple fields.

from rest_framework import serializers

class UserSerializer(serializers.Serializer):
    username = serializers.CharField(max_length=100)
    email = serializers.EmailField()
    password = serializers.CharField(write_only=True)
    password_confirm = serializers.CharField(write_only=True)

    def validate(self, data):
        if data['password'] != data['password_confirm']:
            raise serializers.ValidationError("Passwords do not match.")
        return data

In this example, the validate method checks if the password and password_confirm fields match. If they don't, a ValidationError is raised.

3. Using Custom Validators

You can also define reusable custom validators by creating standalone functions or classes and passing them to the serializer field.

from rest_framework import serializers

def validate_no_numbers(value):
    if any(char.isdigit() for char in value):
        raise serializers.ValidationError("This field should not contain numbers.")
    return value

class UserSerializer(serializers.Serializer):
    username = serializers.CharField(max_length=100, validators=[validate_no_numbers])
    email = serializers.EmailField()
from rest_framework import serializers

class NoSpecialCharactersValidator:
    def __call__(self, value):
        if any(not char.isalnum() for char in value):
            raise serializers.ValidationError("This field should not contain special characters.")
        return value

class UserSerializer(serializers.Serializer):
    username = serializers.CharField(max_length=100, validators=[NoSpecialCharactersValidator()])
    email = serializers.EmailField()

Summary

  • Field-Level Validation: Use validate_<field_name> for validation specific to a single field.
  • Object-Level Validation: Use the validate method for cross-field validation or when the validation depends on multiple fields.
  • Custom Validators: Create reusable custom validators as functions or classes and apply them to fields.

Example

Suppose you want to ensure that the published_year field cannot be in the future.

Serializers
class BookModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ['id', 'title', 'author', 'published_year']

    def validate_published_year(self, value):
        if value > 2024:
            raise serializers.ValidationError("The published year cannot be in the future.")
        return value

Now, if someone tries to create or update a book with a published_year beyond 2024, they’ll get a validation error.

Views
from rest_framework import status

class BookCreateView(APIView):
    def post(self, request):
        serializer = BookModelSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Question

when to use field-level validation and when to use object level validation

Field-level and object-level validation in Django REST Framework (DRF) serve different purposes and are used based on the context of your validation needs. Here’s a guide to help you decide when to use each type of validation:

Field-Level Validation

Use field-level validation when:
  1. Validation is Specific to a Single Field:

    • The validation logic pertains only to the data within that particular field. For example, checking if an email address is in a valid format, or if a username contains forbidden characters.
  2. Independent Validation Logic:

    • The validation does not depend on the values of other fields. For instance, ensuring a string field does not exceed a certain length.
  3. Error Messages are Specific to the Field:

    • You want to provide detailed and specific error messages related to that particular field. For example, ensuring that a password field meets certain complexity requirements.

from rest_framework import serializers

class UserSerializer(serializers.Serializer):
    username = serializers.CharField(max_length=100)

    def validate_username(self, value):
        if "@" in value:
            raise serializers.ValidationError("Username should not contain the '@' symbol.")
        return value

Object-Level Validation

Use object-level validation when:
  1. Validation Depends on Multiple Fields:

    • The validation logic involves checking the relationship between multiple fields. For example, ensuring that two password fields match, or verifying that a start date is before an end date.
  2. Cross-Field Constraints:

    • When constraints are based on the combined values of multiple fields. For instance, calculating an age from a date of birth and then validating it.
  3. Complex Validation Logic:

    • The validation logic is more complex and involves multiple fields or requires accessing the entire object’s data to determine if it meets the criteria.

from rest_framework import serializers

class UserSerializer(serializers.Serializer):
    username = serializers.CharField(max_length=100)
    password = serializers.CharField(write_only=True)
    password_confirm = serializers.CharField(write_only=True)

    def validate(self, data):
        if data['password'] != data['password_confirm']:
            raise serializers.ValidationError("Passwords do not match.")
        return data

Summary

  • Field-Level Validation: Use this when the validation logic is specific to one field and does not depend on other fields.
  • Object-Level Validation: Use this when the validation needs to consider multiple fields together or when the validation logic involves the entire object’s data.

Choosing the appropriate level of validation ensures that your API data is validated efficiently and accurately, helping to maintain data integrity and provide meaningful feedback to users.


Summary

  • Custom Field Validation: Add custom logic to ensure your data meets specific criteria.