Валидаторы

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

Django documentation

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

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

Валидация в REST-фреймворке

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

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

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

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

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

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

Пример

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

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. Мы видим, что ограничение уникальности явно обеспечивается валидатором на поле сериализатора.

Из-за этого более явного стиля REST framework включает несколько классов валидаторов, которые недоступны в основном Django. Эти классы подробно описаны ниже.


UniqueValidator

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

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

  • 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 required - Это набор запросов, в отношении которого должна быть обеспечена уникальность.

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

  • 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 required - Это набор запросов, в отношении которого должна быть обеспечена уникальность.

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

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

  • 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, вы, вероятно, просто будете полагаться на значения по умолчанию, которые REST framework генерирует для вас, но если вы используете 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 являются исключением из этого правила, так как они всегда предоставляют значение, даже если оно пропущено при вводе пользователем.


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

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

Два шаблона, которые вы можете использовать для такого рода проверки, включают:

  • Использование HiddenField. Это поле будет присутствовать в validated_data, но не будет использоваться в выходном представлении сериализатора.

  • Использование стандартного поля с read_only=True , но которое также включает аргумент default=…. Это поле будет использоваться в выходном представлении сериализатора, но не может быть задано непосредственно пользователем.

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

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

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

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

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

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

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

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

В этих случаях вы можете отключить автоматически создаваемые валидаторы, указав пустой список для атрибута serializer 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_<field_name> в ваш подкласс Serializer. Это документировано в разделе Serializer docs

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

Чтобы написать валидатор на основе класса, используйте метод __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 в качестве дополнительного аргумента.

requires_context = True

def __call__(self, value, serializer_field):
    ...
Вернуться на верх