官方原文連結
本系列文章 github 地址
轉載請註明出處
驗證器
大多數情況下,您在 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
複製程式碼
現在使用 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
- 用於查詢已驗證值的現有例項。預設為'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')
)
]
複製程式碼
注意: UniqueTogetherValidation
類總是施加一個隱式約束,即它所應用的所有欄位都是按需處理的。具有 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'
)
]
複製程式碼
我解釋下,上面例子的意思是,在
published
日期所在的年份中,slug
欄位的值必須唯一,注意,不是要和published
完全相等的日期,而是年份相等。unique_for_date
,unique_for_month
同理。
用於驗證的日期欄位應該始終存在於序列化類中。你不能簡單地依賴模型類 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)
複製程式碼
該欄位對使用者不可寫,但預設值仍將傳遞給 validated_data
。
與隱藏的日期欄位一起使用。
如果你希望日期欄位對使用者完全隱藏,請使用 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
可用於在 create 操作期間僅設定預設引數的預設類。在 update 期間,該欄位被省略。
它接受一個引數,這是在 create 操作期間應該使用的預設值或可呼叫引數。
created_at = serializers.DateTimeField(
read_only=True,
default=serializers.CreateOnlyDefault(timezone.now)
)
複製程式碼
驗證器的限制
有一些不明確的情況,你需要顯示處理驗證,而不是依賴 ModelSerializer
生成的預設序列化類。
在這些情況下,你可能希望通過為序列化類 Meta.validators
屬性指定一個空列表來禁用自動生成的驗證器。
可選欄位
預設情況下 "unique together" 驗證強制所有欄位都是 required=True
。在某些情況下,你可能希望顯式將 required=False
應用於其中一個欄位,在這種情況下,驗證所需的行為是不明確的。
在這種情況下,你通常需要從序列化類中排除驗證器,並且在 .validate()
方法中或在檢視中顯式地編寫驗證邏輯。
例如:
class BillingRecordSerializer(serializers.ModelSerializer):
def validate(self, data):
# 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(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()
在一些高階的情況下,你可能想要在驗證器中獲取正在被驗證的序列化欄位。這時,你可以通過在基於類的驗證器上宣告 set_context
方法來完成此操作。
def set_context(self, serializer_field):
# Determine if this is an update or a create operation.
# In `__call__` we can then use that information to modify the validation behavior.
self.is_update = serializer_field.parent.instance is not None
複製程式碼