小能豆

Order of Serializer Validation in Django REST Framework

java

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:

  1. I have an endpoint that creates a user. As such, there is a 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.
  2. The user POSTs improper values for each of the fields mentioned above
  3. An implementation of validate has been made in the serializer (see below), catching the non-matching password and confirm_password fields

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.

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.


阅读 80

收藏
2023-11-23

共1个答案

小能豆

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.

2023-11-23