Пользовательские валидаторы в BaseSerializer

Я хочу использовать пользовательские валидаторы в BaseSerializer.

Первая попытка:

Настраиваемый валидатор:

class UserIdExists:
def __init__(self):
    self.MESSAGE_INCORRECT_USER = 'No user with this id.'

def __call__(self, user_id):
    exists = User.objects.filter(id=user_id).exists()
    if not exists:
            message = self.MESSAGE_INCORRECT_USER
            raise serializers.ValidationError(message, code='invalid')

и BaseSerializer:

class QuestionBaseSerializer(serializers.BaseSerializer):

   def to_internal_value(self, data):
      user_id = data.get('user_id')

      user_validator = UserIdExists()
      user_validator(user_id=user_id)

В случае ошибки валидации я получал сообщение:

'["No user with this id."]'

Но я бы хотел получить такое сообщение:

'{"user_id": "No user with this id."}'

Second attempt: Custom validator:

class UserIdExists:
    def __init__(self):
        self.MESSAGE_INCORRECT_USER = 'No user with this id.'

    def __call__(self, user_id, field=None):
        exists = User.objects.filter(id=user_id).exists()
        if not exists:
            if field:
                message = {field: self.MESSAGE_INCORRECT_USER}
                raise serializers.ValidationError(message, code='invalid')

        else:
            if not field:
                message = self.MESSAGE_INCORRECT_USER
                raise serializers.ValidationError(message, code='invalid')

И BaseSerializer:

class QuestionBaseSerializer(serializers.BaseSerializer):

    def to_internal_value(self, data):
        user_id = data.get('user_id')

        user_validator = UserIdExists()
        user_validator(user_id=user_id, field='user_id')

Я получаю ожидаемое сообщение:

'{"user_id": "No user with this id."}'

Am I doing the right thing? Can it be done more correctly?

Вместо того, чтобы использовать to_internal_value, лучше использовать функциональные возможности, созданные специально для валидации, так будет проще расширять/поддерживать, потому что все, на чем вам придется сосредоточиться - это логика валидации, а не то, как создать внутреннее значение. Для валидации полей принято использовать:

  • Атрибут поля сериализатора validators (список валидаторов для запуска для поля)
  • Метод сериализатора validate_field_name()
  • Метод сериализатора validate()

Здесь для упрощения объяснения мы будем утверждать, что разрешены только нечетные номера user_id.

class UserIdExists:
    def __init__(self):
        self.MESSAGE_INCORRECT_USER = 'No user with this id.'

    def __call__(self, user_id):
        if user_id % 2 == 0:
            raise serializers.ValidationError("No user with this id.")
        return user_id


class QuestionBaseSerializer(serializers.Serializer):
    user_id = serializers.IntegerField(
        validators=[UserIdExists()]
    )


class MathQuestionSerializer(QuestionBaseSerializer):
    description = serializers.CharField()
>>> from my_app.serializers import MathQuestionSerializer
>>>
>>> serializer = MathQuestionSerializer(data={'user_id': 2, 'description': "What is an imaginary number?"})
>>> serializer.is_valid(raise_exception=True)
Traceback (most recent call last):
    raise ValidationError(self.errors)
rest_framework.exceptions.ValidationError: {'user_id': [ErrorDetail(string='No user with this id.', code='invalid')]}
>>> 
>>> serializer = MathQuestionSerializer(data={'user_id': 3, 'description': "What is an imaginary number?"})
>>> serializer.is_valid(raise_exception=True)
True

Как видите, наследование от QuestionBaseSerializer сработало, потому что оно правильно проверило поле user_id, автоматически убедившись, что оно не является четным числом.

Вы можете дополнительно настроить валидацию, выбрав 2-й стиль с помощью реализации validate_field_name():

class QuestionBaseSerializer(serializers.Serializer):
    user_id = serializers.IntegerField()

    def validate_user_id(self, user_id: int) -> int:
        validator = UserIdExists()
        return validator(user_id)


class MathQuestionSerializer(QuestionBaseSerializer):
    description = serializers.CharField()

    def validate_user_id(self, user_id: int) -> int:
        # Let's call the usual validation of even-odd numbers
        user_id_validated = super().validate_user_id(user_id)

        # Now, let's add additional validation of small numbers
        if user_id_validated < 10:
            raise serializers.ValidationError("No user with this id.")
        return user_id_validated
>>> serializer = MathQuestionSerializer(data={'user_id': 2, 'description': "What is an imaginary number?"})
>>> serializer.is_valid(raise_exception=True)
Traceback (most recent call last):
    raise ValidationError(self.errors)
rest_framework.exceptions.ValidationError: {'user_id': [ErrorDetail(string='No user with this id.', code='invalid')]}
>>> 
>>> serializer = MathQuestionSerializer(data={'user_id': 3, 'description': "What is an imaginary number?"})
>>> serializer.is_valid(raise_exception=True)
Traceback (most recent call last):
    raise ValidationError(self.errors)
rest_framework.exceptions.ValidationError: {'user_id': [ErrorDetail(string='No user with this id.', code='invalid')]}
>>> 
>>> serializer = MathQuestionSerializer(data={'user_id': 14, 'description': "What is an imaginary number?"})
>>> serializer.is_valid(raise_exception=True)
Traceback (most recent call last):
    raise ValidationError(self.errors)
rest_framework.exceptions.ValidationError: {'user_id': [ErrorDetail(string='No user with this id.', code='invalid')]}
>>> 
>>> serializer = MathQuestionSerializer(data={'user_id': 15, 'description': "What is an imaginary number?"})
>>> serializer.is_valid(raise_exception=True)
True

Как видите, проверка правильно проверила, является ли это число нечетным (базовая проверка) и больше 10 (проверка подкласса).

Вернуться на верх