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:
ModelSerializer
Meta.model
User
password
confirm_password
username
validate
Implementation 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.
ValidationError
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.
.is_valid()
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:
is_valid
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.
_errors
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.