Django REST framework API 指南(10):序列化·欄位

wcode發表於2018-03-13

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

Serializer 欄位

Form 類中的每個欄位不僅負責驗證資料,還負責 “清洗” 它 — 將其規範化為一致的格式。

Django 文件

序列化欄位處理基本資料型別和其他資料型別(比如自定義的類)之間的轉換。它們還可以對資料進行驗證,以及從其父物件中檢索和設定值。


注意: 序列化欄位都宣告在 fields.py 中,但按照慣例,應該使用 from rest_framework import serializers ,並用 serializers.<FieldName> 的方式引用。


核心引數

每個序列化欄位類的建構函式都需要一些引數。某些欄位類需要附加特定於該欄位的引數,但應始終接受以下引數:

read_only

只讀欄位包含於輸出 API 中,不應該包含在需要建立或更新操作的輸入 API 中。在序列化類輸入中錯誤的包含 'read_only' 會被忽略。

將其設定為 True 可確保在序列化表示時使用該欄位,但在反序列化期間建立或更新例項時不使用該欄位。

預設為 False

write_only

將其設定為 True 以確保在更新或建立例項時可以使用該欄位,但在序列化表示時不包括該欄位。

預設為 False

required

如果在反序列化過程中沒有該提供欄位,通常會出現錯誤。如果在反序列化過程中不需要此欄位,則應該設定為 false。

將此設定為 False 還允許在序列化例項時從輸出中省略物件屬性或字典金鑰。如果金鑰不存在,它將不會包含在輸出表示中。

預設為 True

allow_null

如果把 None 傳遞給序列化欄位,通常會引發錯誤。如果 None 應被視為有效值,則將此關鍵字引數設定為 True

請注意,將此引數設定為 True 將意味著序列化輸出的預設值為 null,但並不意味著輸入反序列化的預設值。

預設為 False

default

如果設定,則會給出預設值,在沒有提供輸入值時,將使用該預設值。如果未設定,則預設行為是不填充該屬性。

部分更新操作時不應該使用 default。因為有些情況下,只有傳入資料中提供的欄位才會返回驗證值。

可以設定為函式或其他可呼叫的物件,在這種情況下,每次使用該值時都會對其進行呼叫。被呼叫時,它將不會收到任何引數。如果可呼叫物件具有 set_context 方法,那麼在每次將欄位例項作為引數獲取值之前都會呼叫該方法。這與驗證器的工作方式相同。

在序列化例項時,如果物件屬性或字典關鍵字不存在於例項中,將使用預設值。

請注意,設定預設值意味著該欄位不是必需的。同時包括 defaultrequired 的關鍵字引數都是無效的,會引發錯誤。

source

將用於填充欄位的屬性的名稱。可以是一個只接受 self 引數的方法,如 URLField(source='get_absolute_url'),或者使用點符號來遍歷屬性,如 EmailField(source='user.email')。在使用點符號時,如果在屬性遍歷期間任何物件不存在或為空,則可能需要提供預設值。

source ='*' 具有特殊含義,用於表示整個物件應該傳遞到該欄位。這對建立巢狀表示或對於需要訪問完整物件以確定輸出表示的欄位非常有用。

預設為該欄位的名稱。

validators

應該應用於傳入欄位輸入的驗證函式列表,該列表中的函式應該引發驗證錯誤或僅返回。驗證器函式通常應該引發 serializers.ValidationError ,但 Django 的內建 ValidationError 也支援與 Django 程式碼庫或第三方 Django 包中定義的驗證器相容。

error_messages

一個字典,key 是錯誤程式碼, value 是對應的錯誤資訊。

label

一個簡短的文字字串,可用作 HTML 表單欄位或其他描述性元素中欄位的名稱。

help_text

一個文字字串,可用作 HTML 表單欄位或其他描述性元素中欄位的描述。

initial

應該用於預填充 HTML 表單欄位的值。你可能會傳遞一個可呼叫物件,就像你對任何常規 Django Field 所做的一樣:

import datetime
from rest_framework import serializers
class ExampleSerializer(serializers.Serializer):
    day = serializers.DateField(initial=datetime.date.today)
複製程式碼

style

可用於控制渲染器渲染欄位的鍵值對的字典。

這裡有兩個例子是 'input_type''base_template'

# Use <input type="password"> for the input.
password = serializers.CharField(
    style={'input_type': 'password'}
)

# Use a radio input instead of a select input.
color_channel = serializers.ChoiceField(
    choices=['red', 'green', 'blue'],
    style={'base_template': 'radio.html'}
)
複製程式碼

Boolean 欄位

BooleanField

表示一個 boolean 值。

使用 HTML 編碼表單時需要注意,省略一個 boolean 值被視為將欄位設定為 False,即使它指定了 default=True 選項。這是因為 HTML 核取方塊通過省略該值來表示未選中的狀態,所以 REST framework 將省略看作是空的核取方塊。

請注意,將使用 required=False 選項生成預設的 BooleanField 例項(因為 Django models.BooleanField 始終為 blank=True)。如果想要更改此行為,請在序列化類上顯式宣告 BooleanField

對應與 django.db.models.fields.BooleanField.

簽名: BooleanField()

NullBooleanField

表示一個布林值,它也接受 None 作為有效值。

對應與 django.db.models.fields.NullBooleanField.

簽名: NullBooleanField()


字串欄位

CharField

表示文字。可以使用 max_lengthmin_length 驗證(或限定)文字的長短。

對應與 django.db.models.fields.CharFielddjango.db.models.fields.TextField.

簽名: CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True)

  • max_length - 驗證輸入所包含的字元數不超過這個數目。
  • min_length - 驗證輸入所包含的字元數不少於這個數目。
  • allow_blank - 如果設定為 True,則空字串應被視為有效值。如果設定為 False,那麼空字串被認為是無效的並會引發驗證錯誤。預設為 False
  • trim_whitespace - 如果設定為 True,則前後空白將被刪除。預設為 True

allow_null 選項也可用於字串欄位,儘管它相對於 allow_blank 來說不被推薦。同時設定 allow_blank=Trueallow_null=True 是有效的,但這樣做意味著字串表示允許有兩種不同型別的空值,這可能導致資料不一致和微妙的應用程式錯誤。

EmailField

表示文字,將文字驗證為有效的電子郵件地址。

對應與 django.db.models.fields.EmailField

簽名: EmailField(max_length=None, min_length=None, allow_blank=False)

RegexField

表示文字,用於驗證給定的值是否與某個正規表示式匹配。

對應與 django.forms.fields.RegexField.

簽名: RegexField(regex, max_length=None, min_length=None, allow_blank=False)

強制的 regex 引數可以是一個字串,也可以是一個編譯好的 Python 正規表示式物件。

使用 Django 的 django.core.validators.RegexValidator 進行驗證。

SlugField

一個根據模式 [a-zA-Z0-9_-]+ 驗證輸入的 RegexField

對應與 django.db.models.fields.SlugField.

簽名: SlugField(max_length=50, min_length=None, allow_blank=False)

URLField

一個根據 URL 匹配模式驗證輸入的 RegexField。完全合格的 URL 格式為 http://<host>/<path>

對應與 django.db.models.fields.URLField. 使用 Django 的 django.core.validators.URLValidator 進行驗證。

簽名: URLField(max_length=200, min_length=None, allow_blank=False)

UUIDField

確保輸入的欄位是有效的 UUID 字串。to_internal_value 方法將返回一個 uuid.UUID 例項。在輸出時,欄位將以規範的連字元格式返回一個字串,例如:

"de305d54-75b4-431b-adb2-eb6b9e546013"
複製程式碼

簽名: UUIDField(format='hex_verbose')

  • format: 確定 uuid 值的表示形式
    • 'hex_verbose' - 權威的十六進位制表示形式,包含連字元: "5ce0e9a5-5ffa-654b-cee0-1238041fb31a"
    • 'hex' - 緊湊的十六進位制表示形式, 不包含連字元:"5ce0e9a55ffa654bcee01238041fb31a"
    • 'int' - 128 位整數表示形式:"123456789012312313134124512351145145114"
    • 'urn' - RFC 4122 URN 表示形式: "urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a" 修改 format 僅影響表示值。所有格式都被 to_internal_value 接受。

FilePathField

一個其選項僅限於檔案系統上某個目錄中的檔名的欄位。

對應於 django.forms.fields.FilePathField.

簽名: FilePathField(path, match=None, recursive=False, allow_files=True, allow_folders=False, required=None, **kwargs)

  • path - FilePathField 應該從中選擇的目錄的絕對檔案系統路徑。
  • match - 用來過濾檔名的正規表示式,string 型別。
  • recursive - 指定是否應該包含路徑的所有子目錄。預設值是 False
  • allow_files - 是否應該包含指定位置的檔案。預設值為 True。這個引數或 allow_folders 必須是 True。(兩個屬性必須有一個為 true
  • allow_folders - 是否應該包含指定位置的資料夾。預設值是 False。這個引數或 allow_files 必須是 True。(兩個屬性必須有一個為 true

IPAddressField

確保輸入是有效的 IPv4 或 IPv6 字串。

對應於 django.forms.fields.IPAddressFielddjango.forms.fields.GenericIPAddressField.

簽名: IPAddressField(protocol='both', unpack_ipv4=False, **options)

  • protocol 將有效輸入限制為指定的協議。接受的值是 'both' (預設),'IPv4''IPv6' 。匹配不區分大小寫。
  • unpack_ipv4 解壓 IPv4 對映的地址,如 ::ffff:192.0.2.1。如果啟用此選項,則該地址將解壓到 192.0.2.1。 預設是禁用的。只能在 protocol 設定為 'both' 時使用。

數字欄位

IntegerField

表示整數。

對應於 django.db.models.fields.IntegerField, django.db.models.fields.SmallIntegerField, django.db.models.fields.PositiveIntegerFielddjango.db.models.fields.PositiveSmallIntegerField

簽名: IntegerField(max_value=None, min_value=None)

  • max_value 驗證所提供的數字不大於這個值。
  • min_value 驗證所提供的數字不小於這個值。

FloatField

表示浮點。

對應於 django.db.models.fields.FloatField.

簽名: FloatField(max_value=None, min_value=None)

  • max_value 驗證所提供的數字不大於這個值。
  • min_value 驗證所提供的數字不小於這個值。

DecimalField

表示十進位制,由 Python 用 Decimal 例項表示。

對應於 django.db.models.fields.DecimalField.

簽名: DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None)

  • max_digits 允許的最大位數。它必須是 None 或大於等於 decimal_places 的整數。
  • decimal_places 小數位數。
  • coerce_to_string 如果應返回字串值,則設定為 True ;如果應返回 Decimal 物件,則設定為 False 。預設值與 COERCE_DECIMAL_TO_STRING settings key 的值相同,除非被覆蓋,否則該值將為 True。如果序列化物件返回 Decimal 物件,則最終的輸出格式將由渲染器決定。請注意,設定 localize 將強制該值為 True
  • max_value 驗證所提供的數字不大於這個值。
  • min_value 驗證所提供的數字不小於這個值。
  • localize 設定為 True 以啟用基於當前語言環境的輸入和輸出本地化。這也會迫使 coerce_to_stringTrue 。預設為 False 。請注意,如果你在 settings 檔案中設定了 USE_L10N=True,則會啟用資料格式化。
  • rounding 設定量化到配置精度時使用的舍入模式。 有效值是 decimal 模組舍入模式。預設為 None

用法示例

若要驗證數字到999,精確到 2 位小數,應該使用:

serializers.DecimalField(max_digits=5, decimal_places=2)
複製程式碼

用10位小數來驗證數字不超過10億:

serializers.DecimalField(max_digits=19, decimal_places=10)
複製程式碼

這個欄位還接受一個可選引數,coerce_to_string。如果設定為 True,則表示將以字串形式輸出。如果設定為 False,則表示將保留為 Decimal 例項,最終表示形式將由渲染器確定。

如果未設定,則預設設定為與 COERCE_DECIMAL_TO_STRING setting 相同的值,除非另行設定,否則該值為 True


日期和時間欄位

DateTimeField

表示日期和時間。

對應於 django.db.models.fields.DateTimeField.

簽名: DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None)

  • format - 表示輸出格式的字串。如果未指定,則預設為與 DATETIME_FORMAT settings key 相同的值,除非設定,否則將為 'iso-8601'。設定為格式化字串則表明 to_representation 返回值應該被強制為字串輸出。格式化字串如下所述。將此值設定為 None 表示 Python datetime 物件應由 to_representation 返回。在這種情況下,日期時間編碼將由渲染器確定。
  • input_formats - 表示可用於解析日期的輸入格式的字串列表。 如果未指定,則將使用 DATETIME_INPUT_FORMATS 設定,該設定預設為 ['iso-8601']

DateTimeField 格式化字串。

格式化字串可以是明確指定的 Python strftime 格式,也可以是使用 ISO 8601 風格 datetime 的特殊字串 iso-8601 。(例如 '2013-01-29T12:34:56.000000Z'

當一個 None 值被用於格式化 datetime 物件時,to_representation 將返回,最終的輸出表示將由渲染器類決定。

auto_nowauto_now_add 模型欄位。

使用 ModelSerializerHyperlinkedModelSerializer 時,請注意,auto_now=Trueauto_now_add=True 的模型欄位預設情況下將使用 read_only=True

如果想覆蓋此行為,則需要在序列化類中明確宣告 DateTimeField。例如:

class CommentSerializer(serializers.ModelSerializer):
    created = serializers.DateTimeField()

    class Meta:
        model = Comment
複製程式碼

DateField

表示日期。

對應於 django.db.models.fields.DateField

簽名: DateField(format=api_settings.DATE_FORMAT, input_formats=None)

  • format - 表示輸出格式的字串。如果未指定,則預設為與 DATE_FORMAT settings key 相同的值,除非設定,否則將為 'iso-8601'。設定為格式化字串則表明 to_representation 返回值應該被強制為字串輸出。格式化字串如下所述。將此值設定為 None 表示 Python date 物件應由 to_representation 返回。在這種情況下,日期時間編碼將由渲染器確定。
  • input_formats - 表示可用於解析日期的輸入格式的字串列表。 如果未指定,則將使用 DATE_INPUT_FORMATS 設定,該設定預設為 ['iso-8601']

DateField 格式化字串

格式化字串可以是明確指定的 Python strftime 格式,也可以是使用 ISO 8601 風格 date 的特殊字串 iso-8601 。(例如 '2013-01-29'

TimeField

表示時間。

對應於 django.db.models.fields.TimeField

簽名: TimeField(format=api_settings.TIME_FORMAT, input_formats=None)

  • format - 表示輸出格式的字串。如果未指定,則預設為與 TIME_FORMAT settings key 相同的值,除非設定,否則將為 'iso-8601'。設定為格式化字串則表明 to_representation 返回值應該被強制為字串輸出。格式化字串如下所述。將此值設定為 None 表示 Python time 物件應由 to_representation 返回。在這種情況下,日期時間編碼將由渲染器確定。
  • input_formats - 表示可用於解析日期的輸入格式的字串列表。 如果未指定,則將使用 TIME_INPUT_FORMATS 設定,該設定預設為 ['iso-8601']

TimeField 格式化字串

格式化字串可以是明確指定的 Python strftime 格式,也可以是使用 ISO 8601 風格 time 的特殊字串 iso-8601 。(例如 '12:34:56.000000'

DurationField

表示持續時間。 對應於 django.db.models.fields.DurationField

這些欄位的 validated_data 將包含一個 datetime.timedelta 例項。該表示形式是遵循格式 '[DD] [HH:[MM:]]ss[.uuuuuu]' 的字串。

簽名: DurationField()


選擇欄位

ChoiceField

可以從一個有限的選擇中接受值的欄位。

如果相應的模型欄位包含 choices=… 引數,則由 ModelSerializer 自動生成欄位。

簽名: ChoiceField(choices)

  • choices - 有效值列表,或 (key, display_name) 元組列表。
  • allow_blank - 如果設定為 True,則空字串應被視為有效值。如果設定為 False,那麼空字串被認為是無效的並會引發驗證錯誤。預設是 False
  • html_cutoff - 如果設定,這將是 HTML 選擇下拉選單中顯示的選項的最大數量。可用於確保自動生成具有非常大可以選擇的 ChoiceField,而不會阻止模板的渲染。預設是 None.
  • html_cutoff_text - 指定一個文字指示器,在截斷列表時顯示,比如在 HTML 選擇下拉選單中已經截斷了最大數量的專案。預設就會顯示 "More than {count} items…"

Allow_blankallow_null 都是 ChoiceField 上的有效選項,但強烈建議只使用一個而不是兩個都用。對於文字選擇,allow_blank 應該是首選,allow_null 應該是數字或其他非文字選項的首選。

MultipleChoiceField

可以接受一組零、一個或多個值的欄位,從有限的一組選擇中選擇。採取一個必填的引數。 to_internal_value 返回一個包含選定值的 set

簽名: MultipleChoiceField(choices)

  • choices - 有效值列表,或 (key, display_name) 元組列表。
  • allow_blank - 如果設定為 True,則空字串應被視為有效值。如果設定為 False,那麼空字串被認為是無效的並會引發驗證錯誤。預設是 False
  • html_cutoff - 如果設定,這將是 HTML 選擇下拉選單中顯示的選項的最大數量。可用於確保自動生成具有非常大可以選擇的 ChoiceField,而不會阻止模板的渲染。預設是 None.
  • html_cutoff_text - 指定一個文字指示器,在截斷列表時顯示,比如在 HTML 選擇下拉選單中已經截斷了最大數量的專案。預設就會顯示 "More than {count} items…"

Allow_blankallow_null 都是 ChoiceField 上的有效選項,但強烈建議只使用一個而不是兩個都用。對於文字選擇,allow_blank 應該是首選,allow_null 應該是數字或其他非文字選項的首選。


檔案上傳欄位

解析和檔案上傳。

FileFieldImageField 類只適用於 MultiPartParserFileUploadParser 。大多數解析器,例如, e.g. JSON 不支援檔案上傳。 Django 的常規 FILE_UPLOAD_HANDLERS 用於處理上傳的檔案。

FileField

表示檔案。執行 Django 的標準 FileField 驗證。

對應於 django.forms.fields.FileField.

簽名: FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)

  • max_length - 指定檔名的最大長度。
  • allow_empty_file - 指定是否允許空檔案。
  • use_url - 如果設定為 True,則 URL 字串值將用於輸出表示。如果設定為 False,則檔名字串值將用於輸出表示。預設為 UPLOADED_FILES_USE_URL settings key 的值,除非另有設定,否則為 True

ImageField

表示圖片。驗證上傳的檔案內容是否匹配已知的圖片格式。

對應於 django.forms.fields.ImageField.

簽名: ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)

  • max_length - 指定檔名的最大長度。
  • allow_empty_file - 指定是否允許空檔案。
  • use_url - 如果設定為 True,則 URL 字串值將用於輸出表示。如果設定為 False,則檔名字串值將用於輸出表示。預設為 UPLOADED_FILES_USE_URL settings key 的值,除非另有設定,否則為 True

需要 Pillow 庫或 PIL 庫。 建議使用 Pillow 庫。 因為 PIL 已經不再維護。


複合欄位

ListField

驗證物件列表的欄位類。

簽名: ListField(child=<A_FIELD_INSTANCE>, min_length=None, max_length=None)

  • child - 應該用於驗證列表中的物件的欄位例項。如果未提供此引數,則列表中的物件將不會被驗證。
  • min_length - 驗證列表中包含的元素數量不少於這個數。
  • max_length - 驗證列表中包含的元素數量不超過這個數。

例如,要驗證整數列表:

scores = serializers.ListField(
   child=serializers.IntegerField(min_value=0, max_value=100)
)
複製程式碼

ListField 類還支援一種宣告式風格,允許編寫可重用的列表欄位類。

class StringListField(serializers.ListField):
    child = serializers.CharField()
複製程式碼

我們現在可以在我們的應用程式中重新使用我們自定義的 StringListField 類,而無需為其提供 child 引數。

DictField

驗證物件字典的欄位類。DictField 中的鍵總是被假定為字串值。

簽名: DictField(child=<A_FIELD_INSTANCE>)

  • child - 應該用於驗證字典中的值的欄位例項。如果未提供此引數,則對映中的值將不會被驗證。

例如,要建立一個驗證字串到字串對映的欄位,可以這樣寫:

document = DictField(child=CharField())
複製程式碼

你也可以像使用 ListField 一樣使用宣告式風格。例如:

class DocumentField(DictField):
    child = CharField()
複製程式碼

JSONField

驗證傳入的資料結構由有效 JSON 基元組成的欄位類。在其二進位制模式下,它將表示並驗證 JSON 編碼的二進位制字串。

簽名: JSONField(binary)

  • binary - 如果設定為 True,那麼該欄位將輸出並驗證 JSON 編碼的字串,而不是原始資料結構。預設是 False.

其他型別的欄位

ReadOnlyField

只是簡單地返回欄位的值而不進行修改的欄位類。

當包含與屬性相關的欄位名而不是模型欄位時,此欄位預設與 ModelSerializer 一起使用。

簽名: ReadOnlyField()

例如,如果 has_expiredAccount 模型中的一個屬性,則以下序列化程式會自動將其生成為 ReadOnlyField

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ('id', 'account_name', 'has_expired')
複製程式碼

HiddenField

不根據使用者輸入獲取值的欄位類,而是從預設值或可呼叫值中獲取值。

簽名: HiddenField()

例如,要包含始終提供當前時間的欄位作為序列化類驗證資料的一部分,則可以使用以下內容:

modified = serializers.HiddenField(default=timezone.now)
複製程式碼

如果需要根據某些預先提供的欄位值執行某些驗證,則通常只需要 HiddenField 類,而不是將所有這些欄位公開給終端使用者。

ModelField

可以繫結到任意模型欄位的通用欄位。ModelField 類將序列化/反序列化的任務委託給其關聯的模型欄位。該欄位可用於為自定義模型欄位建立序列化欄位,而無需建立新的自定義序列化欄位。

ModelSerializer 使用此欄位來對應自定義模型欄位類。

簽名: ModelField(model_field=<Django ModelField instance>)

ModelField 類通常用於內部使用,但如果需要,可以由 API 使用。為了正確例項化 ModelField,必須傳遞一個附加到例項化模型的欄位。例如:ModelField(model_field=MyModel()._meta.get_field('custom_field'))

SerializerMethodField

這是一個只讀欄位。它通過呼叫它所連線的序列化類的方法來獲得它的值。它可用於將任何型別的資料新增到物件的序列化表示中。

簽名: SerializerMethodField(method_name=None)

  • method_name - 要呼叫序列化物件的方法的名稱。如果不包含,則預設為 get_<field_name>.

method_name 引數引用的序列化方法應該接受一個引數(除了 self),這是要序列化的物件。它應該返回你想要包含在物件的序列化表示中的任何內容。例如:

from django.contrib.auth.models import User
from django.utils.timezone import now
from rest_framework import serializers

class UserSerializer(serializers.ModelSerializer):
    days_since_joined = serializers.SerializerMethodField()

    class Meta:
        model = User

    def get_days_since_joined(self, obj):
        return (now() - obj.date_joined).days
複製程式碼

自定義欄位

如果你想建立自定義欄位,則需要對 Field 進行子類化,然後重寫 .to_representation().to_internal_value() 方法中的一個或兩個。這兩個方法用於在初始資料型別和基本序列化資料型別之間進行轉換。基本資料型別通常是 number,string, boolean, date/time/datetimeNone 。它們也可以是任何僅包含其他基本物件的列表或字典。其他型別可能會支援,但具體取決於你使用的渲染器。

呼叫 .to_representation() 方法將初始資料型別轉換為基本的可序列化資料型別。

呼叫 to_internal_value() 方法將基本資料型別恢復為其內部 python 表示形式。如果資料無效,此方法應該引發 serializers.ValidationError 異常。

請注意,2.x 版本中存在的 WritableField 類不再存在。 應此,如果欄位需要支援資料輸入,則應該繼承 Field 並覆蓋 to_internal_value()

看幾個栗子

基本的自定義欄位

我們來看一個序列化代表 RGB 顏色值的類的例子:

class Color(object):
    """
    A color represented in the RGB colorspace.
    """
    def __init__(self, red, green, blue):
        assert(red >= 0 and green >= 0 and blue >= 0)
        assert(red < 256 and green < 256 and blue < 256)
        self.red, self.green, self.blue = red, green, blue

class ColorField(serializers.Field):
    """
    Color objects are serialized into 'rgb(#, #, #)' notation.
    """
    def to_representation(self, obj):
        return "rgb(%d, %d, %d)" % (obj.red, obj.green, obj.blue)

    def to_internal_value(self, data):
        data = data.strip('rgb(').rstrip(')')
        red, green, blue = [int(col) for col in data.split(',')]
        return Color(red, green, blue)
複製程式碼

預設情況下,欄位值被視為對映到物件的屬性。如果需要自定義欄位值的訪問方式,則需要覆蓋 .get_attribute() 和/或 .get_value()

讓我們建立一個可以用來表示被序列化物件的類名的欄位:

class ClassNameField(serializers.Field):
    def get_attribute(self, obj):
        # We pass the object instance onto `to_representation`,
        # not just the field attribute.
        return obj

    def to_representation(self, obj):
        """
        Serialize the object's class name.
        """
        return obj.__class__.__name__
複製程式碼

丟擲驗證錯誤

我們上面的 ColorField 類目前不執行任何資料驗證。為了表示無效資料,我們應該引發一個 serializers.ValidationError,如下所示:

def to_internal_value(self, data):
    if not isinstance(data, six.text_type):
        msg = 'Incorrect type. Expected a string, but got %s'
        raise ValidationError(msg % type(data).__name__)

    if not re.match(r'^rgb\([0-9]+,[0-9]+,[0-9]+\)$', data):
        raise ValidationError('Incorrect format. Expected `rgb(#,#,#)`.')

    data = data.strip('rgb(').rstrip(')')
    red, green, blue = [int(col) for col in data.split(',')]

    if any([col > 255 or col < 0 for col in (red, green, blue)]):
        raise ValidationError('Value out of range. Must be between 0 and 255.')

    return Color(red, green, blue)
複製程式碼

.fail() 方法是引發 ValidationError 的快捷方式,它從 error_messages 字典中接收訊息字串。例如:

default_error_messages = {
    'incorrect_type': 'Incorrect type. Expected a string, but got {input_type}',
    'incorrect_format': 'Incorrect format. Expected `rgb(#,#,#)`.',
    'out_of_range': 'Value out of range. Must be between 0 and 255.'
}

def to_internal_value(self, data):
    if not isinstance(data, six.text_type):
        self.fail('incorrect_type', input_type=type(data).__name__)

    if not re.match(r'^rgb\([0-9]+,[0-9]+,[0-9]+\)$', data):
        self.fail('incorrect_format')

    data = data.strip('rgb(').rstrip(')')
    red, green, blue = [int(col) for col in data.split(',')]

    if any([col > 255 or col < 0 for col in (red, green, blue)]):
        self.fail('out_of_range')

    return Color(red, green, blue)
複製程式碼

這種風格讓你的錯誤資訊更清晰,並且與程式碼分離,應該是首選。

使用 source='*'

這裡我們將舉一個具有 x_coordinatey_coordinate 屬性的平面 DataPoint 模型的示例。

class DataPoint(models.Model):
    label = models.CharField(max_length=50)
    x_coordinate = models.SmallIntegerField()
    y_coordinate = models.SmallIntegerField()
複製程式碼

使用自定義欄位和 source ='*',我們可以提供座標對的巢狀表示形式:

class CoordinateField(serializers.Field):

    def to_representation(self, obj):
        ret = {
            "x": obj.x_coordinate,
            "y": obj.y_coordinate
        }
        return ret

    def to_internal_value(self, data):
        ret = {
            "x_coordinate": data["x"],
            "y_coordinate": data["y"],
        }
        return ret


class DataPointSerializer(serializers.ModelSerializer):
    coordinates = CoordinateField(source='*')

    class Meta:
        model = DataPoint
        fields = ['label', 'coordinates']
複製程式碼

請注意,此示例不處理驗證。使用 source ='*' 的巢狀序列化類可以更好地處理座標巢狀,並且帶有兩個 IntegerField 例項,每個例項都有自己的 source 指向相關欄位。

然而,這個例子的關鍵點是:

  • to_representation 傳遞整個 DataPoint 物件, 並且會對映到所需的輸出。
>>> instance = DataPoint(label='Example', x_coordinate=1, y_coordinate=2)
>>> out_serializer = DataPointSerializer(instance)
>>> out_serializer.data
ReturnDict([('label', 'Example'), ('coordinates', {'x': 1, 'y': 2})])
複製程式碼
  • 除非我們的欄位是隻讀的,否則 to_internal_value 必須對映回適合更新目標物件的字典。使用 source='*'to_internal_value 的返回將更新根驗證的資料字典,而不是單個鍵。
>>> data = {
...     "label": "Second Example",
...     "coordinates": {
...         "x": 3,
...         "y": 4,
...     }
... }
>>> in_serializer = DataPointSerializer(data=data)
>>> in_serializer.is_valid()
True
>>> in_serializer.validated_data
OrderedDict([('label', 'Second Example'),
             ('y_coordinate', 4),
             ('x_coordinate', 3)])
複製程式碼

為了完整性,我們再次做同樣的事情,但是使用上面建議的巢狀序列化方法:

class NestedCoordinateSerializer(serializers.Serializer):
    x = serializers.IntegerField(source='x_coordinate')
    y = serializers.IntegerField(source='y_coordinate')


class DataPointSerializer(serializers.ModelSerializer):
    coordinates = NestedCoordinateSerializer(source='*')

    class Meta:
        model = DataPoint
        fields = ['label', 'coordinates']
複製程式碼

這裡,我們在 IntegerField 宣告中處理目標和源屬性對(xx_coordinateyy_coordinate )之間的對映。這是使用了 source ='*'NestedCoordinateSerializer

新的 DataPointSerializer 展現出與自定義欄位方法相同的行為。

序列化:

>>> out_serializer = DataPointSerializer(instance)
>>> out_serializer.data
ReturnDict([('label', 'testing'),
            ('coordinates', OrderedDict([('x', 1), ('y', 2)]))])
複製程式碼

反序列化:

>>> in_serializer = DataPointSerializer(data=data)
>>> in_serializer.is_valid()
True
>>> in_serializer.validated_data
OrderedDict([('label', 'still testing'),
             ('x_coordinate', 3),
             ('y_coordinate', 4)])
複製程式碼

雖然沒有編寫驗證,但是可以使用內建的驗證方式:

>>> invalid_data = {
...     "label": "still testing",
...     "coordinates": {
...         "x": 'a',
...         "y": 'b',
...     }
... }
>>> invalid_serializer = DataPointSerializer(data=invalid_data)
>>> invalid_serializer.is_valid()
False
>>> invalid_serializer.errors
ReturnDict([('coordinates',
             {'x': ['A valid integer is required.'],
              'y': ['A valid integer is required.']})])
複製程式碼

出於這個原因,可以首先嚐試巢狀序列化類方法。當巢狀序列化類變得不可行或過於複雜時,可以使用自定義欄位方法。

相關文章