验证器对于在不同类型的字段之间重用验证逻辑非常有用。—— Django 文档
大多数情况下,您在 REST framework 中处理验证时,只需依赖默认的字段验证,或者在序列化器或字段类上编写显式的验证方法。
但是,有时您需要将验证逻辑放入可重用的组件中,以便可以在整个代码库中轻松地重用它。这可以通过使用验证器函数和验证器类来实现。
REST framework 中的验证 (Validation in 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
如果我们使用 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 framework 包含了一些在核心 Django 中不可用的验证器类。下面详细介绍这些类。
UniqueValidator
该验证器可用于在模型字段上强制实施 unique=True 约束。它需要一个必需的参数和一个可选的 messages 参数:
queryset必须 - 这是强制执行唯一性的查询集。message- 验证失败时使用的错误消息。lookup- 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') ) ]
注意:UniqueTogetherValidation 类始终施加一个隐式约束,即它所应用的所有字段都是按必须处理的。具有 default 值的字段是个例外,因为它们总是提供一个值,即使在用户输入中省略。
UniqueForDateValidator
UniqueForMonthValidator
UniqueForYearValidator
这些验证器可用于在模型实例上强制执行 unique_for_date,unique_for_month 和 unique_for_year 约束。他们有以下参数:
queryset必须 - 这是强制执行唯一性的查询集。fields必须 - 将验证给定日期范围中唯一性的字段名。这必须作为序列化类中的字段存在。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 或只想更明确的控制,请使用下面演示的样式。
使用可写日期字段 (Using with a writable date field.)
如果您希望日期字段可写,唯一值得注意的是您应确保输入数据始终可用,或者通过设置 default 参数,或者设置 required = True。
published = serializers.DateTimeField(required=True)
使用只读日期字段 (Using with a read-only date field.)
如果你希望日期字段可见,但用户无法编辑,请设置 read_only=True 并另外设置 default=... 参数。
published = serializers.DateTimeField(read_only=True, default=timezone.now)
该字段对用户不可写,但默认值仍将传递给 validated_data。
使用隐藏日期字段 (Using with a hidden date field.)
如果您希望日期字段对用户完全隐藏,请使用 HiddenField。该字段类型不接受用户输入,而是始终将其默认值返回到序列化器中的 validated_data。
published = serializers.HiddenField(default=timezone.now)
注意:UniqueFor<Range>Validation 类强加一个隐式约束,即它所应用的字段都按必须处理的。具有 default值的字段是个例外,因为它们总是提供一个值,即使在用户输入中省略。
高级字段默认值 (Advanced field defaults)
在序列化器中跨多个字段应用的验证器有时需要 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) )
验证器的限制 (Limitations of validators)
有一些模棱两可的情况,您需要显式处理验证,而不是依赖于 ModelSerializer 生成的默认序列化器类。
在这些情况下,您可能希望通过为序列化器 Meta.validators 属性指定一个空列表来禁用自动生成的验证器。
可选字段 (Optional fields)
默认情况下 "unique together" 验证强制所有字段都是 required=True。在某些情况下,您可能希望显式的将 required=False 应用到其中一个字段,在这种情况下,期望的验证行为是不明确的。
在这种情况下,您通常需要从序列化器类中排除验证器,而不是显式地编写任何验证逻辑,无论是在 .validate() 方法中,还是在视图中。
举个栗子:
class BillingRecordSerializer(serializers.ModelSerializer): def validate(self, data): # 在这里或视图中应用自定义验证。 class Meta: fields = ('client', 'date', 'amount') extra_kwargs = {'client': {'required': False}} validators = [] # 删除默认的 "unique together" 约束。
更新嵌套的序列化器 (Updating nested serializers)
将更新应用于现有实例时,唯一性验证器将从唯一性检查中排除当前实例。当前实例在唯一性检查的上下文中可用,因为它作为序列化器中的属性存在,最初在实例化序列化器时使用 instance=... 传递。
在嵌套序列化器上进行更新操作时,无法应用此排除,因为该实例不可用。
同样,您可能希望从序列化器类中显式删除验证器,并在 .validate() 方法或视图中显式地为验证约束编写代码。
调试复杂案例 (Debugging complex cases)
如果您不确定 ModelSerializer 类将生成什么行为,那么运行 manage.py shell 通常是个好主意,并打印序列化器的实例,以便您可以检查自动生成的字段和验证器。
>>> serializer = MyComplexModelSerializer() >>> print(serializer) class MyComplexModelSerializer: my_fields = ...
还要记住,对于复杂的情况,通常最好显式地定义序列化器类,而不是依赖于默认的 ModelSerializer 行为。这涉及更多代码,但确保结果行为更透明。
编写自定义验证器 (Writing custom validators)
您可以使用任何 Django 现有的验证器,也可以编写自己的自定义验证器。
基于函数 (Function based)
验证器可以是任何可调用函数,在失败时引发 serializers.ValidationError。
def even_number(value): if value % 2 != 0: raise serializers.ValidationError('This field must be an even number.')
字段级验证 (Field-level validation)
您可以通过将 .validate_<field_name> 方法添加到 Serializer 子类来指定自定义字段级验证。这在 Serializer 文档中有记录
基于类 (Class-based)
要编写基于类的验证器,请使用 __call__ 方法。基于类的验证器非常有用,因为它们允许您参数化和重用行为。
class MultipleOf(object): 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)
使用 set_context() (Using set_context())
在某些高级情况下,您可能希望将验证器传递给作为附加上下文使用的序列化器字段。您可以通过在基于类的验证器上声明 set_context 方法来实现。
def set_context(self, serializer_field): # 确定这是更新还是创建操作。 # 在 `__call__` 中,我们可以使用该信息来修改验证行为。 self.is_update = serializer_field.parent.instance is not None
