validators.py

验证器

验证器对于在不同类型的字段之间重用验证逻辑很有用。

Django 文档

在 REST 框架中处理验证时,大多数情况下你只需依赖默认字段验证,或在序列化器或字段类上编写显式验证方法。

但是,有时你希望将验证逻辑放入可重用组件中,以便在整个代码库中轻松重用它。这可以通过使用验证器函数和验证器类来实现。

REST 框架中的验证

Django REST 框架序列化器中的验证与 Django 的 ModelForm 类的验证工作方式略有不同。

使用 ModelForm 时,验证部分在表单上执行,部分在模型实例上执行。使用 REST 框架时,验证完全在序列化器类上执行。这有以下优点

  • 它引入了适当的关注点分离,使你的代码行为更加明显。
  • 在使用快捷方式 ModelSerializer 类和使用显式 Serializer 类之间轻松切换。对 ModelSerializer 使用的任何验证行为都易于复制。
  • 打印序列化器实例的 repr 将准确显示它应用的验证规则。在模型实例上没有调用额外的隐藏验证行为。

当你使用 ModelSerializer 时,所有这些都将自动为你处理。如果你想改为使用 Serializer 类,则需要显式定义验证规则。

示例

作为一个 REST 框架如何使用显式验证的示例,我们将采用一个具有唯一性约束字段的简单模型类。

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

如果我们使用 manage.py shell 打开 Django 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框架包含了一些核心Django中不可用的验证器类。这些类在下面详细介绍。REST框架验证器与其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_dateunique_for_monthunique_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框架为您生成的默认值,但如果您使用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 讨论 中的更新。


高级字段默认值

有时,应用于序列化器中多个字段的验证器可能需要 API 客户端不应提供的字段输入,但 可以 作为验证器的输入提供。为此,请使用 HiddenField。此字段将显示在 validated_data 中,但 不会 用于序列化器输出表示。

注意: 使用 read_only=True 字段会排除在可写字段之外,因此它不会使用 default=… 参数。查看 3.8 公告

REST 框架包含一些在此上下文中可能很有用的默认值。

CurrentUserDefault

可用于表示当前用户的默认类。为了使用此类,必须在实例化序列化器时将“request”作为上下文词典的一部分提供。

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

CreateOnlyDefault

可用于仅在创建操作期间设置默认参数的默认类。在更新期间,此字段将被省略。

它接受一个参数,该参数是在创建操作期间应使用的默认值或可调用对象。

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

验证器的限制

在某些模棱两可的情况下,您需要显式处理验证,而不是依赖 ModelSerializer 生成的默认序列化器类。

在这些情况下,您可能希望禁用自动生成的验证器,方法是为序列化器 Meta.validators 属性指定一个空列表。

可选字段

默认情况下,“唯一性组合”验证强制要求所有字段 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.')

字段级验证

您可以通过向 Serializer 子类添加 .validate_<field_name> 方法来指定自定义字段级验证。这在 序列化器文档 中有记录

基于类

要编写基于类的验证器,请使用 __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 属性来实现此目的。然后,将使用 serializer_fieldserializer 作为附加参数调用 __call__ 方法。

class MultipleOf:
    requires_context = True

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