序列化器:嵌套对象

官方文档原文

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

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

如果嵌套对象可以是 None 值(即非必需的),则应使用 required = False

class CommentSerializer(serializers.Serializer):
    user = UserSerializer(required=False)  # 可能是匿名用户
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

同样,如果嵌套对象是一个列表,则应使用 many = True

class CommentSerializer(serializers.Serializer):
    user = UserSerializer(required=False)
    edits = EditItemSerializer(many=True)  # 可以有多个编辑
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()




可写嵌套表示

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

在 shell 中检验一下:

>>> from myapp.serializer import UserSerializer, CommentSerializer

>>> serializer = CommentSerializer(data={'user': {'email': 'foobar', 'username': 'doe'}, 'content': 'baz'})

>>> serializer.is_valid()
False

>>> serializer.errors
ReturnDict([('user', {'email': ['Enter a valid email address.']}),
            ('created', ['This field is required.'])])




为嵌套表示书写 .create() 方法

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

我们有这样的 models,Profile 扩展了用户信息,新增 city 字段保存用户的籍贯,Profile 和 User 用一对一关系关联。

# models.py

from django.db import models

class Profile(models.Model):
    city = models.CharField(max_length=50)
    owner = models.OneToOneField('auth.User', related_name='user_profile')

我们在序列化器体现这种嵌套关系并重写 create() 方法,让创建实例时候同时把关联对象一起创建。

# serializers.py

from rest_framework import serializers
from myapp.models import Profile
from django.contrib.auth.models import User


class ProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = Profile
        fields = ('city', 'owner')


class UserSerializer(serializers.ModelSerializer):
    user_profile = ProfileSerializer()

    class Meta:
        model = User
        fields = ('username', 'email', 'user_profile')

    def create(self, validated_data):
        # 拿到 profile 相关参数
        profile_data = validated_data.pop('profile')
        # 创建 user 实例
        user = User.objects.create(**validated_data)
        # 创建 profile 实例
        Profile.objects.create(owner=user, **profile_data)

        return user

在 shell 中我们能这样创建一个 user 以及和它关联的 profile 对象:

>>> from myapp.serializer import UserSerializer

>>> validated_data = {  
    'username': 'user2', 
    'email': 'user2@django.com', 
    'profile': {'city': 'xiamen'}
}

>>> us = UserSerializer()
>>> us.create(validated_data = validated_data)




为嵌套关系定义.update() 方法

对于更新,你需要仔细考虑如何处理关联字段的更新。 例如,如果关联字段的值是 None,或者没有提供,那么会发生下面哪一项?

  • 在数据库中将关联字段设置成 NULL
  • 删除关联的实例。
  • 忽略数据并保留这个实例。
  • 抛出验证错误。

以下是我们以前的 UserSerializer 类中的 .update() 方法的示例。

# serializers.py

def update(self, instance, validated_data):
    profile_data = validated_data.pop('profile')

    profile = instance.user_profile

    instance.username = validated_data.get('username', instance.username)
    instance.email = validated_data.get('email', instance.email)
    instance.save()

    profile.city = profile_data.get('city', profile.city)
    profile.save()

    return instance

在 shell 中演示修改一个 User 实例:

>>> from myapp.models import Profile
>>> from myapp.serializer import UserSerializer

>>> validated_data = {  
    'email': 'new@django.com', 
    'profile': {'city': 'beijing'}
}

>>> us = UserSerializer()
>>> u = User.objects.get(pk=2) 
>>> us.update(instance = u, validated_data = validated_data)

因为嵌套创建和更新的行为可能不明确,并且可能需要相关模型之间的复杂依赖关系,所以 REST framework 要求你始终明确写入这些方法。默认的 ModelSerializer.create().update() 方法不包括对可写嵌套表示的支持。




在模型管理器类中保存相关的实例

在序列化类中保存多个相关实例的另一种方法是编写自定义模型管理器类。

例如,假设我们希望确保 User 实例和 Profile 实例始终作为一对创建。我们可能会编写一个类似下面的自定义管理器类:

# models.py

from django.db import models
from django.contrib.auth.models import User

class Profile(models.Model):
    city = models.CharField(max_length=50)
    owner = models.OneToOneField('auth.User', related_name='user_profile')


class UserManager(models.Manager):
    # 重写 create() 方法
    def create(self, username, email, city):
        user = User(username=username, email=email)
        user.save()

        profile = Profile(owner=user, city=city)
        profile.save()

        return user

# 指定 User 使用该管理器
User.objects = UserManager()

此管理器类现在更好地封装了 User 实例和 profile 实例,使它们始终在同一时间创建。

我们可以方便地用这种方法创建 User 和他关联的 profile 对象:

>>> User.objects.create(username='user3', email='user3@django.com', city='beijing')

现在可以重新编写序列化类上的 .create() 方法,以使用新的管理类方法。

# serializers.py

def create(self, validated_data):
    user =  User.objects.create(
        username = validated_data['username'],
        email = validated_data['email'],
        city = validated_data['profile']['city']
    )
    return user

在 shell 中演示创建一个 User 实例:

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,680评论 18 139
  • 官网 中文版本 好的网站 Content-type: text/htmlBASH Section: User ...
    不排版阅读 4,398评论 0 5
  • Serializers 序列化器允许将诸如查询集和模型实例之类的复杂数据转换为原生 Python 数据类型,然后可...
    lkning阅读 1,029评论 0 1
  • 专心研究各种工具软件,它能帮助我们高效的工作和生活,一次搞定,终生受益,全家分享。 “航班纵横”查看航班信息,还可...
    信儿315阅读 123评论 0 0
  • 我只是秋天见过落叶的人 我只是繁星领悟春树的人 我只是沙漠邂逅蜃楼的人 我只是在山上布道的人 我只是贩卖乡音 花衣...
    光白阅读 161评论 1 4