Django REST framework - 验证器

验证器可以用于在不同类型字段之间重用验证逻辑。
大多数情况下,您在处理 REST framework 中的验证时,仅依赖默认字段验证,或编写序列化器或字段类的显式验证方法即可。
但是,有时您可能希望将验证逻辑放置在可重用的组件中,以便在代码库中轻松地重复使用。这可以通过使用验证器函数和验证器类来实现。

REST framework 中的验证

Django REST framework 序列化器中的验证与 Django 的 ModelForm 类中的验证方式有所不同。
ModelForm 中,验证部分在表单上进行,部分在模型实例上进行。而在 REST framework 中,验证完全在序列化器类上进行。这具有以下优势:

  • 它引入了明确的职责分离,使代码行为更加显而易见。
  • 在使用快捷 ModelSerializer 类和使用显式 Serializer 类之间切换变得容易。为 ModelSerializer 使用的任何验证行为都很容易复制。
  • 打印序列化器实例的 repr 将显示它应用的确切验证规则。没有额外的隐藏验证行为在模型实例上被调用。

如果您使用 ModelSerializer,所有这些都将自动为您处理。如果您想切换到使用 Serializer 类,则需要显式定义验证规则。

示例

以下是一个简单的模型类,该类的字段具有唯一性约束。

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 shell 中打开它(使用 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 中不可用的验证器类。这些类将在下面详细介绍。REST framework 验证器(如其 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 项目属于一个父列表,并且通过 '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:
        # 博客文章的 slug 应在当年唯一。
        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)

注意UniqueForValidator 类隐式地强制要求它们所应用的字段始终被视为必需字段。具有 default 值的字段是此规则的例外,因为它们即使在用户输入中被省略时也会提供一个值。

注意HiddenField()partial=True 序列化器(当进行 PATCH 请求时)中不会出现。

高级字段默认值

有时,在序列化器的多个字段上应用的验证器可能需要一个字段输入,该输入不应由 API 客户端提供,但在验证器中是可用的。为此目的,请使用 HiddenField。此字段将出现在 validated_data 中,但 不会 被用在序列化器的输出表示中。

REST framework 包含几个在这种上下文中可能有用的默认值。

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):
        # 在这里应用自定义验证,或者在视图中应用。

    class Meta:
        fields = ['client', 'date', 'amount']
        extra_kwargs = {'client': {'required': False}}
        validators = []  # 移除默认的“唯一组合”约束。

更新嵌套序列化器

当对现有实例进行更新时,唯一性验证器将从唯一性检查中排除当前实例。由于在实例化序列化器时使用了 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('该字段必须是偶数。')

字段级验证

您可以通过在序列化器子类中添加 .validate_ 方法来指定自定义字段级验证。这在序列化器文档中有详细说明。

基于类的

要编写一个基于类的验证器,请使用 __call__ 方法。基于类的验证器是有用的,因为它们允许您参数化和重用行为。

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

    def __call__(self, value):
        if value % self.base != 0:
            message = '该字段必须是 %d 的倍数。' % self.base
            raise serializers.ValidationError(message)

访问上下文

在某些高级情况下,您可能希望验证器能够作为附加上下文传递序列化器字段。您可以通过在验证器类上设置 requires_context = True 属性来实现这一点。此时,__call__ 方法将与 serializer_fieldserializer 一起作为附加参数被调用。

class MultipleOf:
    requires_context = True

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

你可能感兴趣的:(pythondjango)