Валидаторы


источник: - validators.py


Валидаторы

Валидаторы могут быть полезны для повторного использования логики проверки между различными типами полей.

Django documentation

В большинстве случаев, когда вы имеете дело с проверкой в DRF, вы просто полагаетесь на стандартную проверку полей, либо пишете явные методы проверки в сериализаторе или классах полей.

Однако иногда вам захочется поместить логику валидации в компоненты многократного использования, чтобы ее можно было легко повторно использовать в кодовой базе. Этого можно достичь с помощью функций валидатора и классов валидатора.

Валидация в DRF

Валидация в сериализаторах DRF обрабатывается немного иначе, чем валидация в классе Django ModelForm.

При использовании ModelForm валидация выполняется частично на форме, частично на экземпляре модели. При использовании DRF валидация выполняется полностью на классе сериализатора. Это выгодно по следующим причинам:

  • Обеспечивается правильное разделение обязанностей, что делает поведение вашего кода более очевидным.

  • Легко переключаться между использованием коротких классов ModelSerializer и явными классами Serializer. Любое поведение проверки, используемое для ModelSerializer, легко повторить.

  • Распечатка repr экземпляра сериализатора покажет вам, какие именно правила проверки он применяет. Нет никакого дополнительного скрытого поведения проверки, вызываемого на экземпляре модели.

При использовании ModelSerializer все это обрабатывается автоматически. Если вы хотите перейти к использованию классов Serializer, то вам необходимо явно определить правила валидации.

Пример

В качестве примера того, как DRF использует явную валидацию, возьмем простой класс модели, в котором есть поле с ограничением уникальности.

class CustomerReportRecord(models.Model):
    time_raised = models.DateTimeField(default=timezone.now, editable=False)
    reference = models.CharField(unique=True, max_length=20)
    description = models.TextField()

Вот базовый ModelSerializer, который мы можем использовать для создания или обновления экземпляров CustomerReportRecord:

class CustomerReportSerializer(serializers.ModelSerializer):
    class Meta:
        model = CustomerReportRecord

Если мы откроем оболочку Django с помощью manage.py shell, то теперь мы можем

>>> from project.example.serializers import CustomerReportSerializer
>>> serializer = CustomerReportSerializer()
>>> print(repr(serializer))
CustomerReportSerializer():
    id = IntegerField(label='ID', read_only=True)
    time_raised = DateTimeField(read_only=True)
    reference = CharField(max_length=20, validators=[<UniqueValidator(queryset=CustomerReportRecord.objects.all())>])
    description = CharField(style={'type': 'textarea'})

Интересным здесь является поле reference. Мы видим, что ограничение уникальности явно обеспечивается валидатором на поле сериализатора.

Из-за этого более явного стиля DRF включает несколько классов валидаторов, которые отсутствуют в основном Django. Эти классы подробно описаны ниже. Валидаторы DRF, как и их аналоги в Django, реализуют метод __eq__, позволяющий сравнивать экземпляры на равенство.


UniqueValidator

Этот валидатор может быть использован для обеспечения ограничения unique=True на поля модели. Он принимает один обязательный аргумент и необязательный аргумент messages:

  • queryset обязательно - Это набор запросов, в отношении которого должна быть обеспечена уникальность.

  • message - Сообщение об ошибке, которое должно быть использовано при неудачной проверке.

  • lookup - Поиск, используемый для нахождения существующего экземпляра с проверяемым значением. По умолчанию 'exact'.

Этот валидатор должен применяться к полям сериализатора, например, так:

from rest_framework.validators import UniqueValidator

slug = SlugField(
    max_length=100,
    validators=[UniqueValidator(queryset=BlogPost.objects.all())]
)

UniqueTogetherValidator

Этот валидатор можно использовать для наложения ограничений unique_together на экземпляры моделей. Он имеет два обязательных аргумента и один необязательный аргумент messages:

  • queryset обязательно - Это набор запросов, по которому должна быть обеспечена уникальность.

  • fields обязательно - Список или кортеж имен полей, которые должны составлять уникальный набор. Они должны существовать как поля в классе сериализатора.

  • message - Сообщение об ошибке, которое должно быть использовано при неудачной проверке.

Валидатор должен применяться к классам сериализатора, например, так:

from rest_framework.validators import UniqueTogetherValidator

class ExampleSerializer(serializers.Serializer):
    # ...
    class Meta:
        # ToDo items belong to a parent list, and have an ordering defined
        # by the 'position' field. No two items in a given list may share
        # the same position.
        validators = [
            UniqueTogetherValidator(
                queryset=ToDoItem.objects.all(),
                fields=['list', 'position']
            )
        ]

Примечание: Класс UniqueTogetherValidator всегда накладывает неявное ограничение на то, что все поля, к которым он применяется, всегда рассматриваются как обязательные. Поля со значениями default являются исключением из этого правила, так как они всегда предоставляют значение, даже если оно опущено при вводе пользователем.


UniqueForDateValidator

UniqueForMonthValidator

UniqueForYearValidator

Эти валидаторы могут использоваться для наложения ограничений unique_for_date, unique_for_month и unique_for_year на экземпляры модели. Они принимают следующие аргументы:

  • queryset обязательно - Это набор запросов, по которым будет проверяться уникальность.

  • field обязательно - Имя поля, по которому будет проверяться уникальность в заданном диапазоне дат. Оно должно существовать как поле в классе сериализатора.

  • date_field обязательно - Имя поля, которое будет использоваться для определения диапазона дат для ограничения уникальности. Оно должно существовать как поле в классе сериализатора.

  • message - Сообщение об ошибке, которое должно быть использовано при неудачной проверке.

Валидатор должен применяться к классам сериализатора, например, так:

from rest_framework.validators import UniqueForYearValidator

class ExampleSerializer(serializers.Serializer):
    # ...
    class Meta:
        # Blog posts should have a slug that is unique for the current year.
        validators = [
            UniqueForYearValidator(
                queryset=BlogPostItem.objects.all(),
                field='slug',
                date_field='published'
            )
        ]

Поле даты, которое используется для проверки, всегда должно присутствовать в классе сериализатора. Вы не можете просто положиться на класс модели default=..., потому что значение, используемое по умолчанию, будет сгенерировано только после выполнения валидации.

Есть несколько стилей, которые вы можете использовать для этого, в зависимости от того, как вы хотите, чтобы ваш API вел себя. Если вы используете ModelSerializer, вы, вероятно, просто будете полагаться на значения по умолчанию, которые генерирует для вас DRF, но если вы используете Serializer или просто хотите более явного контроля, используйте один из стилей, продемонстрированных ниже.

Использование с записываемым полем даты.

Если вы хотите, чтобы поле даты было доступно для записи, единственное, что стоит отметить, это то, что вы должны убедиться, что оно всегда доступно во входных данных, либо задав аргумент default, либо установив required=True.

published = serializers.DateTimeField(required=True)

Использование с полем даты, доступным только для чтения.

Если вы хотите, чтобы поле даты было видно, но не редактировалось пользователем, то установите read_only=True и дополнительно задайте аргумент default=....

published = serializers.DateTimeField(read_only=True, default=timezone.now)

Использование со скрытым полем даты.

Если вы хотите, чтобы поле даты было полностью скрыто от пользователя, используйте HiddenField. Этот тип поля не принимает пользовательский ввод, а вместо этого всегда возвращает значение по умолчанию в validated_data в сериализаторе.

published = serializers.HiddenField(default=timezone.now)

Примечание: Классы UniqueFor<Range>Validator накладывают неявное ограничение на то, что поля, к которым они применяются, всегда рассматриваются как обязательные. Поля со значениями default являются исключением из этого правила, так как они всегда предоставляют значение, даже если оно опущено при вводе пользователем.



Примечание: HiddenField() не появляется в сериализаторе partial=True (при выполнении запроса PATCH). Это поведение может измениться в будущем, следите за обновлениями на github discussion.


Расширенные значения полей по умолчанию

Валидаторы, применяемые к нескольким полям в сериализаторе, иногда могут потребовать ввода поля, которое не должно предоставляться клиентом API, но которое доступно в качестве входных данных для валидатора. Для этих целей используйте HiddenField. Это поле будет присутствовать в validated_data, но не будет использоваться в выходном представлении сериализатора.

Примечание: Использование поля read_only=True исключается из записываемых полей, поэтому оно не будет использовать аргумент default=.... Смотрите 3.8 объявление.

DRF включает в себя несколько параметров по умолчанию, которые могут быть полезны в данном контексте.

CurrentUserDefault

Класс по умолчанию, который можно использовать для представления текущего пользователя. Чтобы использовать его, request должен быть предоставлен как часть контекстного словаря при инстанцировании сериализатора.

owner = serializers.HiddenField(
    default=serializers.CurrentUserDefault()
)

CreateOnlyDefault

Класс по умолчанию, который можно использовать только для установки аргумента по умолчанию во время операций создания. При обновлении поле опускается.

Он принимает один аргумент, который является значением по умолчанию или вызываемым модулем, который должен использоваться при создании.

created_at = serializers.DateTimeField(
    default=serializers.CreateOnlyDefault(timezone.now)
)

Ограничения валидаторов

Есть несколько неоднозначных случаев, когда вам придется явно обрабатывать валидацию, а не полагаться на классы сериализаторов по умолчанию, которые генерирует ModelSerializer.

В таких случаях вы можете отключить автоматически сгенерированные валидаторы, указав пустой список для атрибута сериализатора Meta.validators.

Необязательные поля

По умолчанию валидация unique_together принудительно требует, чтобы все поля были required=True. В некоторых случаях вы можете захотеть явно применить required=False к одному из полей, и тогда желаемое поведение валидации будет неоднозначным.

В этом случае, как правило, необходимо исключить валидатор из класса сериализатора, а всю логику проверки написать явно либо в методе .validate(), либо в представлении.

Например:

class BillingRecordSerializer(serializers.ModelSerializer):
    def validate(self, attrs):
        # Apply custom validation either here, or in the view.

    class Meta:
        fields = ['client', 'date', 'amount']
        extra_kwargs = {'client': {'required': False}}
        validators = []  # Remove a default "unique together" constraint.

Обновление вложенных сериализаторов

При применении обновления к существующему экземпляру валидаторы уникальности исключают текущий экземпляр из проверки уникальности. Текущий экземпляр доступен в контексте проверки уникальности, поскольку он существует как атрибут сериализатора, будучи изначально переданным с помощью instance=... при инстанцировании сериализатора.

В случае операций обновления для вложенных сериализаторов нет возможности применить это исключение, поскольку экземпляр недоступен.

Опять же, вероятно, вам захочется явно удалить валидатор из класса сериализатора и написать код для ограничения валидации явно, в методе .validate() или в представлении.

Отладка сложных случаев

Если вы не уверены в том, какое именно поведение будет генерировать класс ModelSerializer, обычно полезно запустить manage.py shell и распечатать экземпляр сериализатора, чтобы вы могли проверить поля и валидаторы, которые он автоматически генерирует для вас.

>>> serializer = MyComplexModelSerializer()
>>> print(serializer)
class MyComplexModelSerializer:
    my_fields = ...

Также имейте в виду, что в сложных случаях часто бывает лучше явно определить свои классы сериализатора, а не полагаться на поведение ModelSerializer по умолчанию. Это потребует немного больше кода, но гарантирует, что результирующее поведение будет более прозрачным.


Написание пользовательских валидаторов

Вы можете использовать любой из существующих валидаторов Django или написать свой собственный валидатор.

Валидаторы, основанные на функциях

Валидатором может быть любой вызываемый объект, который при неудаче вызывает ошибку serializers.ValidationError.

def even_number(value):
    if value % 2 != 0:
        raise serializers.ValidationError('This field must be an even number.')

Валидация на уровне поля

Вы можете задать пользовательскую проверку на уровне полей, добавив методы .validate_<имя_поля> в подкласс Serializer. Это описано в документации по сериализаторам

На основе класса

Чтобы написать валидатор на основе класса, используйте метод __call__. Валидаторы на основе классов полезны тем, что позволяют параметризировать и повторно использовать поведение.

class MultipleOf:
    def __init__(self, base):
        self.base = base

    def __call__(self, value):
        if value % self.base != 0:
            message = 'This field must be a multiple of %d.' % self.base
            raise serializers.ValidationError(message)

Доступ к контексту

В некоторых сложных случаях вы можете захотеть, чтобы валидатору передавалось поле сериализатора, с которым он используется, в качестве дополнительного контекста. Вы можете сделать это, установив атрибут requires_context = True для класса валидатора. Тогда метод __call__ будет вызван с полем serializer_field или serializer в качестве дополнительного аргумента.

class MultipleOf:
    requires_context = True

    def __call__(self, value, serializer_field):
        ...

Last updated