 序列化使用
序列化使用
  前言
序列化器有两大主要功能! 序列化 + 数据校验.
当前篇幅主要阐述序列化, 序列化的 核心奥义 就是从数据库获取 QuerySet"多条"或数据对象"单条" 转换成 Json格式 的数据!!
该篇博文就是单纯的进行序列化!!
注: - 在学习分页的时候领悟意识到的.BookSerializer(instance=xxx, many=True)
当many=True时, 填入的 xxx 只要是可迭代的 里面包含着一个或多个对象 即可.  也就是说:
1> xxx的值可以是queryset对象
 (但一定要注意 Book.object.filter(id=1) 虽然只有一条数据,但结果也是queryset对象)  
2> xxx的值也可以是 [obj,obj,obj] 包含一个或多个obj的列表.
# 快速使用
牢记! drf序列化器序列化的三大步骤:
step1: 从数据库中获取数据
step2: 转换成json格式
step3: 返回给前端
# Serializer
在序列化器中需自己写需要序列化的字段.
urls.py 根路由
from django.urls import path, re_path
from api import views
urlpatterns = [
    path('api/<str:version>/depart/', views.DepartView.as_view()),
]
2
3
4
5
6
api/models.py
from django.db import models
class Depart(models.Model):
    title = models.CharField(verbose_name="部门", max_length=32)
    order = models.IntegerField(verbose_name="顺序")
    count = models.IntegerField(verbose_name="人数")
    
"""
手动往该表中添加两条数据
    title  order  count
1   技术部  1      10
2   运营部  2      11
"""
"""
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
回顾一点知识. Django ORM的单表查询.
 特别注意: 下述的queryset对象以及depart_object对象都不能直接通过json.dumps进行序列化!!
 因为json模块的序列化,只支持python内置的数据类型, eg:int/str/list/dict支持  哪怕是datatime/decimal也不支持
1. 所有数据
queryset = models.Depart.objects.all()
queryset       --> <QuerySet [<Depart: Depart object (1)>, <Depart: Depart object (2)>]>
type(queryset) --> <class 'django.db.models.query.QuerySet'>
2. 单条数据
depart_object = models.Depart.objects.all().first()
depart_object       --> Depart object (1)
type(depart_object) --> <class 'api.models.Depart'>  
2
3
4
5
6
7
8
9
api/views.py
from rest_framework import serializers
from rest_framework.response import Response
from rest_framework.views import APIView
from api import models
class DepartSerializer(serializers.Serializer):
    # ★ 此处的title、count与ORM数据表中的字段名是对应的!! (SerializerMethodField类型的字段对象除外)
    #   这里写了ORM数据表中的哪些字段,利用该序列化器序列化后展示的结果就只有这些字段. 
    #   !! 不是数据库中的数据表的字段名哈,是ORM数据表中的! 为啥? 
    #      1> 这两个地方的字段名有些是不一样的. 一对多外键字段,会字段加_id,多对多是虚拟字段,自动创建第三张表..等
    #.     2> 序列化可以直接用多对多的虚拟对象 (后面会讲到的!)
    title = serializers.CharField()
    count = serializers.IntegerField()
class DepartView(APIView):
    def get(self, *args, **kwargs):
        # 情况1. 数据库获取单条数据进行序列化 默认many=False
        """
        type(ser.data) --:> <class 'rest_framework.utils.serializer_helpers.ReturnDict'>
        ser.data       --:> {'title': '技术部', 'count': 10}
        """
        # depart_object = models.Depart.objects.all().first()
        # ser = DepartSerializer(instance=depart_object)
        # 情况2. 数据库获取多条数据进行序列化 many=True
        """
        type(ser.data) --:> <class 'rest_framework.utils.serializer_helpers.ReturnList'>
        # OrderedDict 本质就是一个字典
        ser.data       --:> [OrderedDict([('title', '技术部'), ('count', 10)]), 
                             OrderedDict([('title', '运营部'), ('count', 11)])]
        """
        queryset = models.Depart.objects.all()
        ser = DepartSerializer(instance=queryset, many=True)
        context = {"status": True, "data": ser.data}
        return Response(context)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
POSTMAN GET请求 http://127.0.0.1:8001/api/v1/depart/ . 结果如下:
{
    "status": true,
    "data": [
        {
            "title": "技术部",
            "count": 10
        },
        {
            "title": "运营部",
            "count": 11
        }
    ]
}
2
3
4
5
6
7
8
9
10
11
12
13
# ModelSerializer
Serializer字段得自己写,不方便 就有了ModelSerializer.
可以发现, 对数据表中的字段进行序列化, 使用ModelModelSerializer是要比Serializer更简洁一些的!

同样的, 利用PostMan模拟前端发送请求. 结果如下:

# 应用场景
在上面快速使用中,数据库中的Depart表的字段都是些很简单的CharField、IntegerField.
在真实的业务场景中, 序列化器序列化数据常见场景示例是远不止这一点!
■ choices属性的字段、时间字段、一对多的外键字段、多对多的外键字段 在经过序列化后,如何按照我们的心意进行展示?
■ 如果还要格外展示一些数据库表中没有的字段,我们又该如何在序列化器中自定义该字段?
"快速使用"小节 只是序列化知识的抛砖引玉. 带着上述的疑问, 继续我们苦逼的探究! OS: 学到现在是真的困.
 ★ 此小节的知识点学完,我们就能处理数据库中的任何表结构.
 也就是说,当我们获取到 对象 or QuerySet 时,就可以利用这些知识将其换换成Json格式的数据并返回!
说明一点, 该“应用场景”小节中的示例 使用的视图类的代码都是一样的, 有些示例省略了, 望周知.
class UserView(APIview):
    def get(self, *args, **kwargs):
        """获取数据-序列化-返回"""
        queryset = models.UserInfo.objects.all()
        ser = UserSerializer(instance=queryset, many=True)
        context = {"status": True, "data": ser.data}
        return Response(context)
2
3
4
5
6
7
# 朴实的序列化
数据库中存的是啥就展示啥. 是多么的朴实无华.
- Depart部门表
  id  title  order  count
- UserInfo用户表
  id  name  age  gender  depart_id  ctime
Depart:UserInfo = 1:n  ForeignKey外键在UserInfo中,外键字段名为depart
2
3
4
5
6
7
数据库 - 路由 - 视图(序列化器、drf视图类)

Depart表和UserInfo表中添加的数据
 注意: 如果通过pycharm连接sqlite数据库手动添加数据的话, 时间数据会被sqlite自动转换成时间戳, 是sqlite的问题不是代码的问题.

通过PostMan模拟前端请求接口, 得到后端返回的数据.

# 开始定制
fields、source、自定义方法
# fields - 指定字段
只展示UserInfo表中 name、age、gender字段. id、ctime、depart字段就不展示啦!
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserInfo
        # fields = "__all__"
        fields = ["name", "age", "gender"]
        
"""
{
    "status": true,
    "data": [
        {
            "name": "武沛齐",
            "age": 20,
            "gender": 1
        },
        {
            "name": "小沈",
            "age": 18,
            "gender": 2
        }
    ]
}
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# source - choices
显示有choices属性的字段的文本内容, 需要 <基于source> 做字段的自定制!
1> gender字段显示的不是数字, 是文本内容!!
class UserSerializer(serializers.ModelSerializer):
    # 序列化过程中,拿到一个对象/一行记录 此处source的值是get_gender_display So,会执行 obj.get_gender_display()
    # 注意:需要加括号时会自动帮忙加括号的.
    # obj.get_gender_display() 拿到的是 字符串数据,"男"/"女" 所以,此处是CharField
    """初略的阐述下底层原理:
    底层会 循环 从数据库拿到的 queryset对象/多条数据. 然后点语法拿到对象里的字段在数据库中的值.
    for obj in queryset:
        obj.name
        obj.gender                # 在数据库中真正存储的是整型
        obj.get_gender_display()  # 拿到gender字段对应存储在内存中的文本内容
    """
    gender = serializers.CharField(source="get_gender_display")
    class Meta:
        model = models.UserInfo
        fields = ["name", "age", "gender"]
        
"""
{
    "status": true,
    "data": [
        {
            "name": "武沛齐",
            "age": 20,
            "gender": "男"     # -- "gender": 1
        },
        {
            "name": "小沈",
            "age": 18,
            "gender": "女"     # -- "gender": 2
        }
    ]
}
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2> 既想要gender字段在数据库中存储的数字又想要该字段在内存中的文本内容.
class UserSerializer(serializers.ModelSerializer):
    gender_text = serializers.CharField(source="get_gender_display")
    class Meta:
        model = models.UserInfo
        fields = ["name", "age", "gender", "gender_text"]
"""
{
    "status": true,
    "data": [
        {
            "name": "武沛齐",
            "age": 20,
            "gender": 1,
            "gender_text": "男"
        },
        {
            "name": "小沈",
            "age": 18,
            "gender": 2,
            "gender_text": "女"
        }
    ]
}
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# source - 外键
depart 外键字段在 数据库的api_userinfo表中 存储的是数字, 并不是关联表中的数据.
所以 朴实的序列化的结果中 该字段 展示的是数字.
如何让结果中, 该字段展示关联表中的数据呢? 同样的, 需要 <基于source> 做字段的自定制!
class UserSerializer(serializers.ModelSerializer):
    gender = serializers.CharField(source="get_gender_display")
    # depart.title 跨到api_depart表,拿到关联的一行数据 通过点语法拿到想要的值
    # Ps:在进行数据库设计时, 部门表:用户表 = 1:n 不要纠结该设计是否合理.
    # 提醒: A表外键字段跨到B表.B表外键字段跨到C表.C表字段 -- 连续跨多张表
    depart = serializers.CharField(source="depart.title")
    class Meta:
        model = models.UserInfo
        fields = ["name", "age", "gender", "depart"]
        
        
"""
{
    "status": true,
    "data": [
        {
            "name": "武沛齐",
            "age": 20,
            "gender": "男",
            "depart": "技术部"   # -- "depart": 1
        },
        {
            "name": "小沈",
            "age": 18,
            "gender": "女",
            "depart": "运营部"   # -- "depart": 2
        }
    ]
}
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# source - 时间
朴实的序列化中 时间的显示 年-月-日 时:分:秒
若我只想显示 年月日, 如何操作? 同样的, 需要 <基于source> 做字段的自定制!
create_datetime = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S")
class UserSerializer(serializers.ModelSerializer):
    gender = serializers.CharField(source="get_gender_display")
    depart = serializers.CharField(source="depart.title")
    # "%Y-%m-%d" 是时间里的占位符.
    ctime = serializers.DateTimeField(format="%Y-%m-%d")
    class Meta:
        model = models.UserInfo
        fields = ["name", "age", "gender", "depart", "ctime"]
        
"""
{
    "status": true,
    "data": [
        {
            "name": "武沛齐",
            "age": 20,
            "gender": "男",         # -- "gender": 1,
            "depart": "技术部",      # -- "depart": 1,
            "ctime": "2020-08-21"   # -- "ctime": "2020-08-21T11:47:28.215258Z"
        },
        {
            "name": "小沈",
            "age": 18,
            "gender": "女",         # -- "gender": 2,
            "depart": "运营部",      # -- "depart": 2,
            "ctime": "2021-08-24"   # -- "ctime": "2021-08-24T11:47:28.230410Z"
        }
    ]
}
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 自定义方法/额外字段
细品前面的 指定字段、choice、外键、时间这些定制的显示结果, 不难发现, 其与数据库字段是强关联的.
若现在我想随便定义一个字段返回, 如何操作呢? 通过 自定义方法 来实现!
自定义的xxx字段会自动触发get_xxx这个函数.该函数返回什么,xxx字段的值就是什么.
class UserSerializer(serializers.ModelSerializer):
    xxx = serializers.SerializerMethodField()
    """
    for obj in queryset:
        obj.name
        obj.gender
        obj.get_gender_display()
        obj.depart.title
        get_xxx(obj)  # -- obj中是没有xxx的,它底层会执行get_xxx方法,将obj当作参数传给该方法.
    """
    def get_xxx(self, obj):
        return "{}-{}-{}".format(obj.name, obj.age, obj.get_gender_display())
    class Meta:
        model = models.UserInfo
        fields = ["name", "xxx"]
        
        
"""
{
    "status": true,
    "data": [
        {
            "name": "武沛齐",
            "xxx": "武沛齐-20-男"
        },
        {
            "name": "小沈",
            "xxx": "小沈-18-女"
        }
    ]
}
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 外键字段
学会前面的定制 + 此处的多对多字段的处理. 工作中序列化相关的常见业务就都能解决啦.
# 实验数据准备
在原来的基础上, 数据库添加一张Tag表.
# -- 部门:用户 = 1:n  ForeignKey外键在用户表,外键字段名为depart
#    标签:用户 = n:n  ManyToManyField外键在用户表,外键字段名为tag,是虚拟字段!
from django.db import models
class Depart(models.Model):
    title = models.CharField(verbose_name="部门", max_length=32)
    order = models.IntegerField(verbose_name="顺序")
    count = models.IntegerField(verbose_name="人数")
class Tag(models.Model):
    caption = models.CharField(verbose_name="标签", max_length=32)
class UserInfo(models.Model):
    name = models.CharField(verbose_name="姓名", max_length=32)
    age = models.IntegerField(verbose_name="年龄")
    gender = models.SmallIntegerField(
        verbose_name="性别", choices=((1, "男"), (2, "女")))
    depart = models.ForeignKey(
        verbose_name="部门", to="Depart", on_delete=models.CASCADE)
    ctime = models.DateTimeField(
        verbose_name="创建时间", auto_now_add=True)
    tag = models.ManyToManyField(verbose_name="标签", to="Tag")  # 会自动帮忙生成第三张表
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

先看看朴实无华的多对多字段结果是怎样的:
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserInfo
        fields = ["name", "tag"]
2
3
4

敲黑板! tag字段虽说是虚拟字段, 但它是可以直接被fields属性指定进行序列化的!!
 这就可以反向证明序列化器类里写的那些字段是与ORM数据表中的字段名是对应的!!而不是数据库中数据表的字段. 
需求: 上面的操作可以将每个用户关联的所有标签都拿到并展示, 但我并不想展示的是数字, 如何是好?
# 方式一: 自定义方法
基于SerializerMethodField自定义方法对关联表数据进行序列化 .
该示例中涉及到了正反向的查询. 还好我复习总结了.. (´▽`)
class UserSerializer(serializers.ModelSerializer):
    tag = serializers.SerializerMethodField()
    """
    - 用户表的depart字段是ForeignKey,跨到部门表拿到的是一条数据;
    - 用户表的tag字段是ManyToManyField,跨到标签表拿到该用户所关联的所有标签数据,是一个queryset对象,可包含多条数据!
    伪代码:
    for obj in queryset:  # 此处queryset是用户表的所有数据
        obj.name
        obj.gender
        obj.get_gender_display()  # choice类型的字段
        obj.depart.title  # depart
        get_xxx(obj)      # 自定义方法
        obj.tag.all()     # 当前用户关联的tag表的所有数据,其结果是一个queryset对象! <QuerySet [Tag对象,Tag对象]>
                          # 回顾下ORM的查询就知道咋回事了!
    """
    def get_tag(self, obj):
        # 此处的obj是用户表的一条记录 
        # 对象.虚拟字段.all() 涉及到n:n ManyToManyField字段的查询. 
        # obj.tag跨到Tag表,此时可以看着1条用户记录对应多条标签记录,接着通过‘.all()‘取了出来.
        tag_queryset = obj.tag.all()  
        return [{"id": tag.id, "caption": tag.caption} for tag in tag_queryset]
    class Meta:
        model = models.UserInfo
        fields = ["name", "tag"]  
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
结果如下:
{
    "status": true,
    "data": [
        {
            "name": "武沛齐",
            "tag": [
                {
                    "id": 1,
                    "caption": "暖男"
                },
                {
                    "id": 5,
                    "caption": "长跑健将"
                }
            ]
        },
        {
            "name": "小沈",
            "tag": [
                {
                    "id": 2,
                    "caption": "渣男"
                },
                {
                    "id": 5,
                    "caption": "长跑健将"
                }
            ]
        }
    ]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 方式二: 嵌套
基于嵌套的序列化类实现.
注意! 嵌套主要针对 fk、m2m, 只有这两个用嵌套才有意义!
# - 当然DepartSerializer、TagSerializer序列化器里也可以自定制,这里从简. 知识是固定的,业务是灵活的,根据需求自由搭配嘛.
class DepartSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Depart
        fields = "__all__"
class TagSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Tag
        fields = "__all__"
class UserSerializer(serializers.ModelSerializer):
    # ★★★ 敲黑板!! 当前用户关联部门,对应的部门记录是一条,当前用户关联标签,对应的标签记录是多条.So,多条的需要加上many=True.
    depart = DepartSerializer()
    tag = TagSerializer(many=True)
    class Meta:
        model = models.UserInfo
        fields = ["name", "depart", "tag"]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
结果如下:
{
    "status": true,
    "data": [
        {
            "name": "武沛齐",
            "depart": {
                "id": 1,
                "title": "技术部",
                "order": 1,
                "count": 10
            },
            "tag": [
                {
                    "id": 1,
                    "caption": "暖男"
                },
                {
                    "id": 5,
                    "caption": "长跑健将"
                }
            ]
        },
        {
            "name": "小沈",
            "depart": {
                "id": 2,
                "title": "运营部",
                "order": 2,
                "count": 11
            },
            "tag": [
                {
                    "id": 2,
                    "caption": "渣男"
                },
                {
                    "id": 5,
                    "caption": "长跑健将"
                }
            ]
        }
    ]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 继承
该知识点用的不多, 但得了解知道.
class Base(serializers.Serializer):  # -- 注意这里是serializers.Serializer,不是ModelSerializer
    more = serializers.SerializerMethodField()
    def get_more(self, obj):
        return "123"
class UserSerializer(serializers.ModelSerializer, Base):
    class Meta:
        model = models.UserInfo
        fields = ["name", "more"]  # -- "xx"是从父类Base中拿到的!
        
"""
{
    "status": true,
    "data": [
        {
            "name": "武沛齐",
            "more": "123"
        },
        {
            "name": "小沈",
            "more": "123"
        }
    ]
}
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
