Serializers

Serializers

序列化程序允许将复杂数据(如 querysets 和 modle 实例)转换为原生的Python数据类型,然后可以将其简单地呈现为JSON,XML或其他内容类型。 序列化器还提供反序列化,允许解析的数据在首次验证传入数据后转换成复杂类型。

REST框架中的序列化工作与Django的Form和ModelForm类非常相似。 我们提供了一个Serializer类,它为您提供了强大的通用方法来控制响应的输出,以及一个ModelSerializer类,它为创建用于处理模型实例和查询结果的序列化程序提供了有用的快捷方式。

Declaring Serializers(声明序列化)

让我们从创建一个简单的对象开始,我们可以使用这个例子:

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')

我们将声明一个序列化器,我们可以使用它来序列化和反序列化与Comment对象相对应的数据。

声明一个序列化器看起来非常类似于声明一个表单:

from rest_framework import serializers

class CommentSerializer(serializers.Serializer):
    email = serializers.EmailField()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

Serializing objects

我们可以用CommentSerializer 来序列化一个comment或者comment的列表。再一次说,使用Serializer类和使用Form类很类似。

serializer = CommentSerializer(comment)
serializer.data
# {'email': 'leila@example.com', 'content': 'foo bar', 'created': '2016-01-27T15:17:10.375877'}

此时,我们将模型实例转换为Python原生数据类型。 要完成序列化过程,我们将数据呈现给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

反序列化也是类似的,首先我们将一个流解析为Python原生数据类型...

from django.utils.six import BytesIO
from rest_framework.parsers import JSONParser

stream = BytesIO(json)
data = JSONParser().parse(stream)

...然后我们将这些原生数据类型恢复到已验证数据的字典中。

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

如果我们希望能够根据验证的数据返回完整的对象实例,我们需要实现.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

如果你的对象实例对应于Django模型,您还需要确保这些方法将对象保存到数据库。 例如,如果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

现在反序列化数据时,我们可以根据验证的数据调用.save()返回一个对象实例。
comment = serializer.save()
调用.save()将创建一个新的实例或更新一个现有的实例,具体取决于在实例化序列化器类时是否传递了一个现有的实例:

# .save() will create a new instance.
serializer = CommentSerializer(data=data)

# .save() will update the existing `comment` instance.
serializer = CommentSerializer(comment, data=data)

.create()和.update()方法都是可选的。 你可以根据你的Serializer类的用例来实现它们之一,一个或两者。

Passing additional attributes to .save()

有时你会希望你的视图代码能够在保存实例时注入额外的数据。 此附加数据可能包括当前用户,当前时间或不是请求数据一部分的其他信息。

您可以通过在调用.save()时包含其他关键字参数来执行此操作。 例如:

serializer.save(owner=request.user)

当调用.create()或.update()时,任何其他关键字参数将被包含在validated_data参数中。

Overriding .save() directly.

在某些情况下,.create()和.update()方法名称可能无意义。 例如,在联系表单中,我们可能不会创建新的实例,而是发送电子邮件或其他消息。

在这些情况下,你可以直接选择覆盖.save(),因为它更可读和有意义。
例如:

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)

请注意,在上述情况下,我们现在必须直接访问serializer_date属性。

Validation

反序列化数据时,你始终需要在尝试访问经过验证的数据之前调用is_valid()或保存对象实例。 如果发生任何验证错误,则.errors属性将包含表示生成的错误消息的字典。 例如:

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.']}

字典中的每个键都将是字段名称,值将是与该字段对应的任何错误消息的字符串列表。 non_field_errors键也可能存在,并将列出任何一般验证错误。 可以使用NON_FIELD_ERRORS_KEY REST框架设置来定制non_field_errors键的名称。

当对项目列表进行反序列化时,将作为代表每个反序列化项目的字典列表返回错误。

Raising an exception on invalid data

.is_valid()方法接受一个可选的raise_exception标志,如果存在验证错误,它将引发一个serializers.ValidationError异常。

这些异常由REST框架提供的默认异常处理程序自动处理,默认情况下将返回HTTP 400错误请求响应。

# Return a 400 response if the data was invalid.
serializer.is_valid(raise_exception=True)

Field-level validation

.is_valid()方法接受一个可选的raise_exception标志,如果验证错误,它将引发一个serializers.ValidationError异常。

这些异常由REST框架提供的默认异常处理程序自动处理,默认情况下将返回HTTP 400错误请求响应。

# Return a 400 response if the data was invalid.
serializer.is_valid(raise_exception=True)

Field-level validation

您可以通过将.validate_ <field_name>方法添加到Serializer子类来指定自定义字段级验证。 这些类似于Django表单上的.clean_ <field_name>方法。

这些方法采用单个参数,即需要验证的字段值。

您的validate_ <field_name>方法应返回验证的值或提出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>在您的串行器上声明,参数required = False,则不会在不包含该字段的情况下执行此验证步骤。

Object-level validation

要执行需要访问多个字段的任何其他验证,请将一个名为.validate()的方法添加到Serializer子类。 该方法采用单个参数,它是字段值的字典。 如果需要,它应该引发ValidationError,或者只返回验证的值。 例如:

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

串行器上的各个字段可以包括验证器,通过在字段实例上声明它们,例如:

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])
    ...

序列化器类还可以包括应用于整套现场数据的可重用验证器。 这些验证器包含在内部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']
        )

有关更多信息,请参阅验证器文档。

Accessing the initial data and instance

将初始对象或查询集传递给序列化器实例时,该对象将以.instance形式提供。 如果没有初始对象被传递,那么.instance属性将为None。

将数据传递给串行器实例时,未修改的数据将作为.initial_data提供。 如果data关键字参数未被传递,则.initial_data属性将不存在。

Partial updates

默认情况下,序列化程序必须传递所有必填字段的值,否则会引发验证错误。 您可以使用部分参数以允许部分更新。

# Update `comment` with partial data
serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True)

Dealing with nested objects

以前的示例适用于处理只有简单数据类型的对象,但有时我们还需要能够表示更复杂的对象,其中对象的某些属性可能不是简单的数据类型,如字符串,日期或整数。

Serializer类本身就是一个Field类型,可以用来表示一个对象类型嵌套在另一个对象之间的关系。

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()

如果嵌套表示可以选择接受无值,则应将required = False标志传递给嵌套的序列化程序。

class CommentSerializer(serializers.Serializer):
    user = UserSerializer(required=False)  # May be an anonymous user.
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

类似地,如果嵌套表示应该是项目列表,则应该将许多= True标志传递给嵌套的序列化。

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

当处理支持反序列化数据的嵌套表示时,嵌套对象的任何错误都嵌套在嵌套对象的字段名下。

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属性将包括嵌套的数据结构。

Writing .create() methods for nested representations

如果您支持可写嵌套表示,则需要编写处理保存多个对象的.create()或.update()方法。

以下示例演示如何处理创建具有嵌套概要文件对象的用户。

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • serializer只需要定义好字段即可(序列化器的字段名称和对应的对象的字段想对应),任何方法都是非必须的。 序...
    xncode阅读 555评论 0 1
  • Django: csrf防御机制 csrf攻击过程 1.用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登...
    lijun_m阅读 1,052评论 0 0
  • //我所经历的大数据平台发展史(三):互联网时代 • 上篇http://www.infoq.com/cn/arti...
    葡萄喃喃呓语阅读 51,180评论 10 200
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,591评论 18 139
  • HTML表单 在HTML中,表单是 ... 之间元素的集合,它们允许访问者输入文本、选择选项、操作对象等等,然后将...
    兰山小亭阅读 3,409评论 2 14