Валидаторы

Валидаторы

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

Django documentation

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

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

Валидация в REST framework

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

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

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

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

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

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

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

На основе функций

A validator may be any callable that raises a serializers.ValidationError on failure.

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

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

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

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

requires_context = True
def __call__(self, value, serializer_field):
...