验证器可以用于在不同类型字段之间重用验证逻辑。
大多数情况下,您在处理 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_date
、unique_for_month
和 unique_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)
注意:UniqueFor
类隐式地强制要求它们所应用的字段始终被视为必需字段。具有 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_field
或 serializer
一起作为附加参数被调用。
class MultipleOf:
requires_context = True
def __call__(self, value, serializer_field):
...