Django REST framework API 指南(12):驗證器

官方原文連接
本系列文章 github 地址
轉載請註明出處python

驗證器

大多數狀況下,您在 REST framework 中處理驗證時,只需依賴默認的字段驗證,或者在序列化類或字段類上編寫明確的驗證方法。git

可是,有時你會但願將驗證邏輯放置到可重用組件中,以便在整個代碼庫中輕鬆地重用它。這能夠經過使用驗證器函數和驗證器類來實現。github

REST framework 中的驗證

Django REST framework 序列化器中的驗證與 Django ModelForm 類中驗證的工做方式有點不一樣。shell

使用 ModelForm,驗證一部分在表單上執行,一部分在模型實例上執行。使用 REST framework ,驗證徹底在序列化類上執行。這是有優點的,緣由以下:django

  • 它使問題適當的分離,讓代碼行爲變的更加清晰。
  • 使用快捷的 ModelSerializer 類和使用顯式的 Serializer 類能夠輕鬆切換。任何用於 ModelSerializer 的驗證行爲都很容易複製。
  • 打印序列化類實例的 repr 將會顯示它應用的驗證規則。在模型實例上沒有額外的隱藏驗證行爲(由於全在序列化類上)。

當你使用 ModelSerializer 時,全部驗證都會自動爲你處理。若是你想要改成使用 Serializer 類,那麼你須要明肯定義驗證規則。api

舉個栗子

做爲 REST framework 如何使用顯式驗證的示例,咱們將採用一個簡單的模型類,該類具備惟一性約束的字段。ide

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 shellpost

>>> 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 字段。咱們能夠看到惟一性約束由序列化字段上的驗證器明確執行。ui

因爲這種更明確的風格,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_dateunique_for_monthunique_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_dateunique_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
複製代碼
相關文章
相關標籤/搜索