Django REST Framework 学习笔记(九):序列化模块 serialziers 之 ListSerializer类

  • Title(EN): Django REST Framework Learning Notes (9): the ListSerializer class in serialziers module
  • Author: dog2

基本信息

  • 源码:rest_framework.serializers
  • 官方文档
    • API Guide - Serializers
    • API Guide - Serializer fields
    • API Guide - Serializer relations
  • 本文demo代码Github

DRF常用序列化类主要有

  • rest_framework.serialziers.Serializer
  • rest_framework.serialziers.ModelSerializer
  • rest_framework.serialziers.ListSerializer

本篇介绍rest_framework.serialziers.ListSerializer,将继续以图书管理系统实例说明。

使用场景之群改群增群改

当一个序列化器在带有many=True选项被序列化时,将创建一个ListSerializer实例,该序列化器类将成为ListSerializer类的子类。 当你需要自定义多个对象的行为时(比如群增,群改),你需要手动定制ListSerializer类的一些行为。 可以通过在自定义序列化器的Meta类下面的list_serializer_class来绑定你需要的的ListSerializer

示例代码

群改需要设置ListSerializer,创建BookListSerializer继承ListSerializer,重写update方法

序列化层 serializer.py

1
2
3
4
5
6
7
8
9
10
11
12
13
# 重点:ListSerializer与ModelSerializer建立关联的是: 在ModelSerializer的Meta类中设置   list_serializer_class
class BookListSerializer(ListSerializer):
def update(self, instance, validated_data): # print(instance) # 要更新的对象们
# print(validated_data) # 更新的对象对应的数据们
# print(self.child) # 服务的模型序列化类 - V3BookModelSerializer
for index, obj in enumerate(instance):
self.child.update(obj, validated_data[index])
return instance

class V3BookModelSerializer(ModelSerializer):
class Meta:
# 群改,list_serializer_class是固定的key写法,直接转入BookListSerializer类的 update 方法
list_serializer_class = BookListSerializer

之所以需要实现update方法,群改时会走 rest_framework.serializers.ListSerializer中的update函数,而该函数并未被实现。

而群增不需要重写create方法,因为源码中rest_framework.serializers.ListSerializer走的就是ModelSerializercreate方法:

1
2
3
4
def create(self, validated_data):
return [
self.child.create(attrs) for attrs in validated_data
]

视图层 views.py

在视图层将 单局部改和群局部改整合,思路如下:

  1. 单局部改:对 v3/books/pk/ pk通过路由传参,修改数据选择传参,通过数据包json传递
  2. 群局部修改:v3/books/ 修改数据通过数据包传递,设置成列表格式 [{pk:1,name:123},{pk:3,price:7},{pk:7,publish:2}]
  3. 先将单改,群改的数据都格式化成 pks=[要需要的对象主键标识]request_data=[每个要修改的对象对应的修改数据]
  4. pksrequest_data数据筛选,将pks中的没有对应数据的pk与数据已删除的pk移除,request_data对应索引位上的数据也移除,将合理的pks转换为objs

Source Code

1
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
from rest_framework.views import APIView
from rest_framework.response import Response
from . import models, serializers


class V3Book(APIView):
# 单局部改和群局部改整合
# 单局部改:对 v3/books/pk/ pk通过路由传参,修改数据选择传参,通过数据包json传递
# 群局部修改:v3/books/ 修改数据通过数据包传递,设置成列表格式 [{pk:1,name:123},{pk:3,price:7},{pk:7,publish:2}]
def patch(self, request, *args, **kwargs):
request_data = request.data # 数据包数据
pk = kwargs.get('pk')
# 将单改,群改的数据都格式化成 pks=[要需要的对象主键标识] | request_data=[每个要修改的对象对应的修改数据]
if pk and isinstance(request_data, dict): # 单改
pks = [pk, ]
request_data = [request_data, ]
elif not pk and isinstance(request_data, list): # 群改
pks = []
# 遍历前台数据[{pk:1, name:123}, {pk:3, price:7}, {pk:7, publish:2}],拿一个个字典
for dic in request_data:
pk = dic.pop('pk', None) # 返回pk值
if pk:
pks.append(pk)
# pk没有传值
else:
return Response({
'status': 1,
'msg': '参数错误'
})
else:
return Response({
'status': 1,
'msg': '参数错误'
})
# pks与request_data数据筛选,
# 1)将pks中的没有对应数据的pk与数据已删除的pk移除,request_data对应索引位上的数据也移除
# 2)将合理的pks转换为 objs
objs = []
new_request_data = []
for index, pk in enumerate(pks):
try:
# 将pk合理的对象数据保存下来
book_obj = models.Book.objects.get(pk=pk, is_delete=False)
objs.append(book_obj)
# 对应索引的数据也保存下来
new_request_data.append(request_data[index])
except:
# 重点:反面教程 - pk对应的数据有误,将对应索引的data中request_data中移除
# 在for循环中不要使用删除
# index = pks.index(pk)
# request_data.pop(index)
continue
# 生成一个serializer对象
book_ser = serializers.V3BookModelSerializer(
instance=objs, data=new_request_data, partial=True, many=True)
book_ser.is_valid(raise_exception=True)
book_objs = book_ser.save()

return Response({
'status': 0,
'msg': 'ok',
'results': serializers.V3BookModelSerializer(book_objs, many=True).data
})

ListSerializer使用要点小结

views.py

1
ser_obj = ModelSerializer(instance=model_obj,data=model_data,partial=True,many=True)

serializer.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 实现群改功能,反序列化情况下的create、update就不再调用ModelSerializer的
# 而是调用 ModelSerializer.Meta.list_serializer_class 指向的 ListSerializer 类的create、update
# ListSerializer默认只实现了群增的create,要实现群改,必须重写update

  #自定义序列化类,重写update方法
class MyListSerializer(ListSerializer):
def update(self, instance, validated_data):
# print(instance) # 要更新的对象们: [obj1, obj2, ...]
# print(validated_data) # 更新的对象对应的数据们: [{}, {}, ...]
# print(self.child) # 服务的模型序列化类 - V2BookModelSerializer
for index, obj in enumerate(instance):
self.child.update(obj, validated_data[index])
return instance

class MyModelSerializer(ModelSerializer):
class Meta:
list_serializer_class = MyListSerializer

# 将两者类建立关联,在MyListSerializer中就可以用self.child拿到# MyModelSerializer,进而使用MyModelSerializer中封装好的方法
cls.Meta.list_serializer_class.child = cls

源码补充分析

分析一下修改为什么要用instance传参。

修改之后数据使用save()保存,从视图的save()点击进去查看源码,下面是BaseSerializer类中的save,而该save未被实现。

因此接着查看save方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

def save(self, **kwargs):
# ...

validated_data = [
dict(list(attrs.items()) + list(kwargs.items()))
for attrs in self.validated_data
]

if self.instance is not None:
self.instance = self.update(self.instance, validated_data)
assert self.instance is not None, (
'`update()` did not return an object instance.'
)
else:
self.instance = self.create(validated_data)
assert self.instance is not None, (
'`create()` did not return an object instance.'
)

return self.instance

instance存在就走update方法,修改数据(PUT/PATCH)时都应传入instance,所以单改群改都需要传入data(反序列化用)和instance(序列化用)参数。