While building APIs, the primary thing to setup is the validations for the API. Validators can be pretty useful for writing validations but sometimes there are a lot of custom validations to be supported.
If you have custom validations, you can override the validate
method in your serializer.
class SomeRandomSerializer(serializers.ModelSerializer):
class Meta:
model = SomeRandomModel
fields = [
'id',
'some_random_field_1',
'some_random_field_2',
'some_random_field_3',
]
def validate(self, data):
is_field_1_valid = data.get('some_random_field_1') == 100
if not is_field_1_valid:
raise serializers.ValidationError({
'some_random_field_1': [
'Some random field 1 should be equal to 100'
]
})
is_field_2_valid = data.get('some_random_field_2') == 200
if not is_field_2_valid:
raise serializers.ValidationError({
'some_random_field_2': [
'Some random field 2 should be equal to 200'
]
})
return data
def create(self, validated_data):
# Instance creation code comes here
pass
def update(self, instance, validated_data):
# Instance updation code comes here
pass
But the problem writing all your validations here is that if there are a lot of validations, your serializer will become very long. It will be better to move all our validations to a separate file so our serializer file will be clean.
Give me an example
Let's say we are building our Products
API and our project's folder structure is shown below.
/ app
/ serializers
product.py
/ validations
product.py
/ viewsets
product.py
models.py
urls.py
We have 3 different folders for our serializers, validation files, and viewset files. Hence, we created product.py
under each folder.
Our ProductSerializer
would look something like this. Check the validation method, we have directly called the validate
method from our custom-built ProductValidation
class.
# serializers/product.py
from app.models import Product
from app.validations.product import ProductValidation
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = [
'id',
'title',
'code',
'description',
'quantity'
]
def validate(self, data):
return ProductValidation(
data=data,
instance=self.instance).validate()
def create(self, validated_data):
# Product creation code comes here
pass
def update(self, instance, validated_data):
# Product updation code comes here
pass
The ProductValidation Class
class ProductValidation:
def __init__(self, data, instance):
self.data = data
self.instance = instance
self.is_update_case = instance is not None
def validate(self):
if self.is_update_case:
return self.__validate_UPDATE()
return self.__validate_CREATE()
def __validate_CREATE(self):
errors = {
**self.__validate_title_length(),
**self.__validate_code(),
**self.__validate_description_length(),
**self.__validate_quantity()
}
if errors:
raise serializers.ValidationError(errors)
return self.data
def __validate_UPDATE(self):
errors = {
**self.__validate_title_length(),
**self.__validate_code(),
**self.__validate_description_length(),
**self.__validate_quantity()
**self.__validate_JUST_FOR_UPDATE_CASE_1(),
**self.__validate_JUST_FOR_UPDATE_CASE_2(),
}
if errors:
raise serializers.ValidationError(errors)
return self.data
def __validate_title_length(self):
errors = dict()
title = self.data['title']
if len(title) <= 2:
errors.update({
'title': ['Title length should be greater than 2']
})
return errors
def __validate_code(self):
pass
def __validate_description_length(self):
pass
def __validate_quantity(self):
pass
def __validate_JUST_FOR_UPDATE_CASE_1(self):
pass
def __validate_JUST_FOR_UPDATE_CASE_2(self):
pass
- The constructor of
ProductValidation
needs two values. The first one is thedata
passed as in the body of the API and the second is the actualinstance
.- The instance will be None if it is a creation case else there will be the actual instance.
- Inside the constructor, the
is_update_case
value is set on the same principle.
- The
validate
method calls__validate_UPDATE
or the__validate_CREATE
method based on theis_update_case
boolean. **
can be used inside a dictionary to merge multiple dictionaries.- Inside the
__validate_CREATE
method, we build a dictionary oferrors
. - This dictionary calls multiple validations methods, each of them returning a dictionary of errors (if there are errors else an empty dictionary).
- If there are any errors, we raise
ValidationError
passing ourerrors
dictionary.
- Inside the
- The
__validate_UPDATE
is the same as the__validate_CREATE
method.- We created the
__validate_UPDATE
method because sometimes update APIs have some more validations than the create APIs.
- We created the
This makes adding new validations, checking for validation errors, or removing any validations very easy. Trust me, if you have tons of validations, this technique makes it very easy to maintain.
Thanks for reading!