驗證器對於在不同類型的字段之間重用驗證邏輯非常有用。—— 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
