Serializers
序列化程序允許將復(fù)雜數(shù)據(jù)(如 querysets 和 modle 實(shí)例)轉(zhuǎn)換為原生的Python數(shù)據(jù)類型膛锭,然后可以將其簡(jiǎn)單地呈現(xiàn)為JSON,XML或其他內(nèi)容類型蝗岖。 序列化器還提供反序列化,允許解析的數(shù)據(jù)在首次驗(yàn)證傳入數(shù)據(jù)后轉(zhuǎn)換成復(fù)雜類型榔至。
REST框架中的序列化工作與Django的Form和ModelForm類非常相似抵赢。 我們提供了一個(gè)Serializer類,它為您提供了強(qiáng)大的通用方法來(lái)控制響應(yīng)的輸出唧取,以及一個(gè)ModelSerializer類铅鲤,它為創(chuàng)建用于處理模型實(shí)例和查詢結(jié)果的序列化程序提供了有用的快捷方式。
Declaring Serializers(聲明序列化)
讓我們從創(chuàng)建一個(gè)簡(jiǎn)單的對(duì)象開始枫弟,我們可以使用這個(gè)例子:
from datetime import datetime
class Comment(object):
def __init__(self, email, content, created=None):
self.email = email
self.content = content
self.created = created or datetime.now()
comment = Comment(email='leila@example.com', content='foo bar')
我們將聲明一個(gè)序列化器邢享,我們可以使用它來(lái)序列化和反序列化與Comment對(duì)象相對(duì)應(yīng)的數(shù)據(jù)。
聲明一個(gè)序列化器看起來(lái)非常類似于聲明一個(gè)表單:
from rest_framework import serializers
class CommentSerializer(serializers.Serializer):
email = serializers.EmailField()
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField()
Serializing objects
我們可以用CommentSerializer 來(lái)序列化一個(gè)comment或者comment的列表淡诗。再一次說骇塘,使用Serializer類和使用Form類很類似。
serializer = CommentSerializer(comment)
serializer.data
# {'email': 'leila@example.com', 'content': 'foo bar', 'created': '2016-01-27T15:17:10.375877'}
此時(shí)韩容,我們將模型實(shí)例轉(zhuǎn)換為Python原生數(shù)據(jù)類型款违。 要完成序列化過程,我們將數(shù)據(jù)呈現(xiàn)給json群凶。
from rest_framework.renderers import JSONRenderer
json = JSONRenderer().render(serializer.data)
json
# b'{"email":"leila@example.com","content":"foo bar","created":"2016-01-27T15:17:10.375877"}'
Deserializing objects
反序列化也是類似的插爹,首先我們將一個(gè)流解析為Python原生數(shù)據(jù)類型...
from django.utils.six import BytesIO
from rest_framework.parsers import JSONParser
stream = BytesIO(json)
data = JSONParser().parse(stream)
...然后我們將這些原生數(shù)據(jù)類型恢復(fù)到已驗(yàn)證數(shù)據(jù)的字典中。
serializer = CommentSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# {'content': 'foo bar', 'email': 'leila@example.com', 'created': datetime.datetime(2012, 08, 22, 16, 20, 09, 822243)}
Saving instances
如果我們希望能夠根據(jù)驗(yàn)證的數(shù)據(jù)返回完整的對(duì)象實(shí)例,我們需要實(shí)現(xiàn).create()和update()方法之一或兩者赠尾。 例如:
class CommentSerializer(serializers.Serializer):
email = serializers.EmailField()
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField()
def create(self, validated_data):
return Comment(**validated_data)
def update(self, instance, validated_data):
instance.email = validated_data.get('email', instance.email)
instance.content = validated_data.get('content', instance.content)
instance.created = validated_data.get('created', instance.created)
return instance
如果你的對(duì)象實(shí)例對(duì)應(yīng)于Django模型力穗,您還需要確保這些方法將對(duì)象保存到數(shù)據(jù)庫(kù)。 例如气嫁,如果Comment是Django模型睛廊,那么這些方法可能如下所示:
def create(self, validated_data):
return Comment.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.email = validated_data.get('email', instance.email)
instance.content = validated_data.get('content', instance.content)
instance.created = validated_data.get('created', instance.created)
instance.save()
return instance
現(xiàn)在反序列化數(shù)據(jù)時(shí),我們可以根據(jù)驗(yàn)證的數(shù)據(jù)調(diào)用.save()返回一個(gè)對(duì)象實(shí)例杉编。
comment = serializer.save()
調(diào)用.save()將創(chuàng)建一個(gè)新的實(shí)例或更新一個(gè)現(xiàn)有的實(shí)例,具體取決于在實(shí)例化序列化器類時(shí)是否傳遞了一個(gè)現(xiàn)有的實(shí)例:
# .save() will create a new instance.
serializer = CommentSerializer(data=data)
# .save() will update the existing `comment` instance.
serializer = CommentSerializer(comment, data=data)
.create()和.update()方法都是可選的咆霜。 你可以根據(jù)你的Serializer類的用例來(lái)實(shí)現(xiàn)它們之一邓馒,一個(gè)或兩者。
Passing additional attributes to .save()
有時(shí)你會(huì)希望你的視圖代碼能夠在保存實(shí)例時(shí)注入額外的數(shù)據(jù)蛾坯。 此附加數(shù)據(jù)可能包括當(dāng)前用戶光酣,當(dāng)前時(shí)間或不是請(qǐng)求數(shù)據(jù)一部分的其他信息。
您可以通過在調(diào)用.save()時(shí)包含其他關(guān)鍵字參數(shù)來(lái)執(zhí)行此操作脉课。 例如:
serializer.save(owner=request.user)
當(dāng)調(diào)用.create()或.update()時(shí)救军,任何其他關(guān)鍵字參數(shù)將被包含在validated_data參數(shù)中。
Overriding .save() directly.
在某些情況下倘零,.create()和.update()方法名稱可能無(wú)意義唱遭。 例如,在聯(lián)系表單中呈驶,我們可能不會(huì)創(chuàng)建新的實(shí)例拷泽,而是發(fā)送電子郵件或其他消息。
在這些情況下袖瞻,你可以直接選擇覆蓋.save()司致,因?yàn)樗勺x和有意義。
例如:
class ContactForm(serializers.Serializer):
email = serializers.EmailField()
message = serializers.CharField()
def save(self):
email = self.validated_data['email']
message = self.validated_data['message']
send_email(from=email, message=message)
請(qǐng)注意聋迎,在上述情況下脂矫,我們現(xiàn)在必須直接訪問serializer_date屬性。
Validation
反序列化數(shù)據(jù)時(shí)霉晕,你始終需要在嘗試訪問經(jīng)過驗(yàn)證的數(shù)據(jù)之前調(diào)用is_valid()或保存對(duì)象實(shí)例庭再。 如果發(fā)生任何驗(yàn)證錯(cuò)誤,則.errors屬性將包含表示生成的錯(cuò)誤消息的字典牺堰。 例如:
serializer = CommentSerializer(data={'email': 'foobar', 'content': 'baz'})
serializer.is_valid()
# False
serializer.errors
# {'email': [u'Enter a valid e-mail address.'], 'created': [u'This field is required.']}
字典中的每個(gè)鍵都將是字段名稱佩微,值將是與該字段對(duì)應(yīng)的任何錯(cuò)誤消息的字符串列表。 non_field_errors鍵也可能存在萌焰,并將列出任何一般驗(yàn)證錯(cuò)誤哺眯。 可以使用NON_FIELD_ERRORS_KEY REST框架設(shè)置來(lái)定制non_field_errors鍵的名稱。
當(dāng)對(duì)項(xiàng)目列表進(jìn)行反序列化時(shí)扒俯,將作為代表每個(gè)反序列化項(xiàng)目的字典列表返回錯(cuò)誤奶卓。
Raising an exception on invalid data
.is_valid()方法接受一個(gè)可選的raise_exception標(biāo)志一疯,如果存在驗(yàn)證錯(cuò)誤,它將引發(fā)一個(gè)serializers.ValidationError異常夺姑。
這些異常由REST框架提供的默認(rèn)異常處理程序自動(dòng)處理墩邀,默認(rèn)情況下將返回HTTP 400錯(cuò)誤請(qǐng)求響應(yīng)。
# Return a 400 response if the data was invalid.
serializer.is_valid(raise_exception=True)
Field-level validation
.is_valid()方法接受一個(gè)可選的raise_exception標(biāo)志盏浙,如果驗(yàn)證錯(cuò)誤眉睹,它將引發(fā)一個(gè)serializers.ValidationError異常。
這些異常由REST框架提供的默認(rèn)異常處理程序自動(dòng)處理废膘,默認(rèn)情況下將返回HTTP 400錯(cuò)誤請(qǐng)求響應(yīng)竹海。
# Return a 400 response if the data was invalid.
serializer.is_valid(raise_exception=True)
Field-level validation
您可以通過將.validate_ <field_name>方法添加到Serializer子類來(lái)指定自定義字段級(jí)驗(yàn)證。 這些類似于Django表單上的.clean_ <field_name>方法丐黄。
這些方法采用單個(gè)參數(shù)斋配,即需要驗(yàn)證的字段值。
您的validate_ <field_name>方法應(yīng)返回驗(yàn)證的值或提出serializers.ValidationError灌闺。 例如:
from rest_framework import serializers
class BlogPostSerializer(serializers.Serializer):
title = serializers.CharField(max_length=100)
content = serializers.CharField()
def validate_title(self, value):
"""
Check that the blog post is about Django.
"""
if 'django' not in value.lower():
raise serializers.ValidationError("Blog post is not about Django")
return value
注意:如果您的<field_name>在您的串行器上聲明艰争,參數(shù)required = False,則不會(huì)在不包含該字段的情況下執(zhí)行此驗(yàn)證步驟桂对。
Object-level validation
要執(zhí)行需要訪問多個(gè)字段的任何其他驗(yàn)證甩卓,請(qǐng)將一個(gè)名為.validate()的方法添加到Serializer子類。 該方法采用單個(gè)參數(shù)蕉斜,它是字段值的字典猛频。 如果需要,它應(yīng)該引發(fā)ValidationError蛛勉,或者只返回驗(yàn)證的值鹿寻。 例如:
from rest_framework import serializers
class EventSerializer(serializers.Serializer):
description = serializers.CharField(max_length=100)
start = serializers.DateTimeField()
finish = serializers.DateTimeField()
def validate(self, data):
"""
Check that the start is before the stop.
"""
if data['start'] > data['finish']:
raise serializers.ValidationError("finish must occur after start")
return data
Validators
串行器上的各個(gè)字段可以包括驗(yàn)證器,通過在字段實(shí)例上聲明它們诽凌,例如:
def multiple_of_ten(value):
if value % 10 != 0:
raise serializers.ValidationError('Not a multiple of ten')
class GameRecord(serializers.Serializer):
score = IntegerField(validators=[multiple_of_ten])
...
序列化器類還可以包括應(yīng)用于整套現(xiàn)場(chǎng)數(shù)據(jù)的可重用驗(yàn)證器毡熏。 這些驗(yàn)證器包含在內(nèi)部Meta類上聲明它們,如下所示:
class EventSerializer(serializers.Serializer):
name = serializers.CharField()
room_number = serializers.IntegerField(choices=[101, 102, 103, 201])
date = serializers.DateField()
class Meta:
# Each room only has one event per day.
validators = UniqueTogetherValidator(
queryset=Event.objects.all(),
fields=['room_number', 'date']
)
有關(guān)更多信息侣诵,請(qǐng)參閱驗(yàn)證器文檔痢法。
Accessing the initial data and instance
將初始對(duì)象或查詢集傳遞給序列化器實(shí)例時(shí),該對(duì)象將以.instance形式提供杜顺。 如果沒有初始對(duì)象被傳遞财搁,那么.instance屬性將為None。
將數(shù)據(jù)傳遞給串行器實(shí)例時(shí)躬络,未修改的數(shù)據(jù)將作為.initial_data提供尖奔。 如果data關(guān)鍵字參數(shù)未被傳遞,則.initial_data屬性將不存在。
Partial updates
默認(rèn)情況下提茁,序列化程序必須傳遞所有必填字段的值淹禾,否則會(huì)引發(fā)驗(yàn)證錯(cuò)誤。 您可以使用部分參數(shù)以允許部分更新茴扁。
# Update `comment` with partial data
serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True)
Dealing with nested objects
以前的示例適用于處理只有簡(jiǎn)單數(shù)據(jù)類型的對(duì)象铃岔,但有時(shí)我們還需要能夠表示更復(fù)雜的對(duì)象,其中對(duì)象的某些屬性可能不是簡(jiǎn)單的數(shù)據(jù)類型峭火,如字符串毁习,日期或整數(shù)。
Serializer類本身就是一個(gè)Field類型卖丸,可以用來(lái)表示一個(gè)對(duì)象類型嵌套在另一個(gè)對(duì)象之間的關(guān)系纺且。
class UserSerializer(serializers.Serializer):
email = serializers.EmailField()
username = serializers.CharField(max_length=100)
class CommentSerializer(serializers.Serializer):
user = UserSerializer()
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField()
如果嵌套表示可以選擇接受無(wú)值,則應(yīng)將required = False標(biāo)志傳遞給嵌套的序列化程序坯苹。
class CommentSerializer(serializers.Serializer):
user = UserSerializer(required=False) # May be an anonymous user.
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField()
類似地,如果嵌套表示應(yīng)該是項(xiàng)目列表摇天,則應(yīng)該將許多= True標(biāo)志傳遞給嵌套的序列化粹湃。
class CommentSerializer(serializers.Serializer):
user = UserSerializer(required=False)
edits = EditItemSerializer(many=True) # A nested list of 'edit' items.
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField()
Writable nested representations
當(dāng)處理支持反序列化數(shù)據(jù)的嵌套表示時(shí),嵌套對(duì)象的任何錯(cuò)誤都嵌套在嵌套對(duì)象的字段名下泉坐。
serializer = CommentSerializer(data={'user': {'email': 'foobar', 'username': 'doe'}, 'content': 'baz'})
serializer.is_valid()
# False
serializer.errors
# {'user': {'email': [u'Enter a valid e-mail address.']}, 'created': [u'This field is required.']}
類似地为鳄,.validated_data屬性將包括嵌套的數(shù)據(jù)結(jié)構(gòu)。
Writing .create() methods for nested representations
如果您支持可寫嵌套表示腕让,則需要編寫處理保存多個(gè)對(duì)象的.create()或.update()方法孤钦。
以下示例演示如何處理創(chuàng)建具有嵌套概要文件對(duì)象的用戶。
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer()
class Meta:
model = User
fields = ('username', 'email', 'profile')
def create(self, validated_data):
profile_data = validated_data.pop('profile')
user = User.objects.create(**validated_data)
Profile.objects.create(user=user, **profile_data)
return user