Situation
While working with validation in the Django REST Framework’s ModelSerializer
, I have noticed that the Meta.model
fields are always validated, even when it does not necessarily make sense to do so. Take the following example for a User
model’s serialization:
password
field and a confirm_password
field. If the two fields do not match, the user cannot be created. Likewise, if the requested username
already exists, the user cannot be created.validate
has been made in the serializer (see below), catching the non-matching password
and confirm_password
fieldsImplementation of validate
:
def validate(self, data):
if data['password'] != data.pop('confirm_password'):
raise serializers.ValidationError("Passwords do not match")
return data
Problem
Even when the ValidationError
is raised by validate
, the ModelSerializer
still queries the database to check to see if the username
is already in use. This is evident in the error-list that gets returned from the endpoint; both the model and non-field errors are present.
Consequently, I would like to know how to prevent model validation until after non-field validation has finished, saving me a call to my database.
Attempt at solution
I have been trying to go through the DRF’s source to figure out where this is happening, but I have been unsuccessful in locating what I need to override in order to get this to work.
In Django REST Framework, the model validation is performed during the .is_valid()
call on the serializer, which includes both the field validation and the model-level validation.
If you want to delay the model-level validation until after your custom validation is performed, you can override the is_valid
method in your serializer. Here’s an example:
from rest_framework import serializers
class YourSerializer(serializers.ModelSerializer):
confirm_password = serializers.CharField(write_only=True)
class Meta:
model = YourModel
fields = ['username', 'password', 'confirm_password']
def validate(self, data):
if data['password'] != data.pop('confirm_password'):
raise serializers.ValidationError("Passwords do not match")
return data
def is_valid(self, raise_exception=False):
# Run the regular field validation first
valid = super().is_valid(raise_exception=False)
# If the field validation is successful, perform model validation
if valid:
# Perform your custom model validation here
if self.validated_data['password'] == 'some_invalid_value':
self._errors['password'] = self.error_class(["Invalid password"])
valid = not bool(self.errors)
if raise_exception and not valid:
self._raise_errors()
return valid
In this example, the is_valid
method is overridden. It first calls the regular is_valid
method to perform the field-level validation. If the field validation is successful, it then performs your custom model-level validation. If any errors are found during the model-level validation, they are added to the _errors
attribute.
Make sure to adapt this example to your specific use case and model structure. The key is to override the is_valid
method and perform the model-level validation after the field validation.