drf ModelSerializer高级使用
前言
ModelSerializer
中还具有一些高级用法,如批量更新、批量删除、批量创建等。
但是批量过来的数据格式都需要与前端做好协商,什么样的数据格式是单条操作,什么样的数据格式是批量操作。
如下,对于单条操作而言,只需要传入编号即可,而批量操作则需要在请求体中传入[]
以及被操作的主键编号。
模型表
书籍表、出版社表、作者表、作者详情表
作者和作者详情表是一对一关系
书籍和出版社表是一对多关系
书籍和作者是多对多关系
以上关系均是为ORM查询方便所提供,但是在物理表中并没有确切关联
由于这些表中的数据具有商业价值,所以我们要给它设定一个字段名为delete_status
,代表是否逻辑删除(真实并不会删除)。
初此之外还有create_time
代表该记录的创建时间,以及last_update_time
代表这张表的最后更新时间。
所以我们可以给这三张字段抽出一张抽象表,用于被其他表继承:
from django.db import models
# Create your models here.
class BaseModel(models.Model):
delete_status = models.BooleanField(default=False) # 默认不删除
create_time = models.DateTimeField(auto_now_add=True) # 新增记录时自动插入
last_update_time = models.DateTimeField(auto_now=True) # 更新记录时自动插入
class Meta:
abstract = True # 抽象类,不会创建真实表,用于继承
class Book(BaseModel):
book_id = models.AutoField(primary_key=True)
book_name = models.CharField(max_length=32,verbose_name="书籍名称")
book_price = models.DecimalField(max_digits=5,decimal_places=2,verbose_name="书籍价格")
publish = models.ForeignKey(to="Publish",on_delete=models.DO_NOTHING,db_constraint=False)
# 逻辑一对多,实际表没有关联,删除出版社书不受影响
authors = models.ManyToManyField(to="Author",db_constraint=False)
# 逻辑多对多,实际表没有任何关联
class Meta:
verbose_name_plural = "书籍表"
def __str__(self):
return self.book_name
@property
def publish_name(self): # 模拟字段,用于序列化时显示出版社名称
return self.publish.publish_name
@property
def author_list(self): # 模拟字段,用于序列化时显示多个作者的名字和性别
author_list = self.authors.all()
return [
{"author_name":author.author_name,"author_gender":author.get_author_gender_display()} for author in author_list
]
class Publish(BaseModel):
publish_id = models.AutoField(primary_key=True)
publish_name = models.CharField(max_length=32,verbose_name="出版社名称")
publish_addr = models.CharField(max_length=32,verbose_name="出版社地址")
def __str__(self):
return self.publish_name
class Author(BaseModel):
author_id = models.AutoField(primary_key=True)
author_name = models.CharField(max_length=32,verbose_name="作者姓名")
author_gender = models.IntegerField(choices=[(1,"男"),(2,"女")])
author_detail = models.OneToOneField(to="AuthorDetail",db_constraint=False,on_delete=models.CASCADE)
# 逻辑一对一,实际上没有什么联系
class AuthorDetail(BaseModel):
author_phone = models.CharField(max_length=11)
# 二、表断关联
# 1、表之间没有外键关联,但是有外键逻辑关联(有充当外键的字段)
# 2、断关联后不会影响数据库查询效率,但是会极大提高数据库增删改效率(不影响增删改查操作)
# 3、断关联一定要通过逻辑保证表之间数据的安全,不要出现脏数据,代码控制
# 4、断关联
# 5、级联关系
# 作者没了,详情也没:on_delete=models.CASCADE
# 出版社没了,书还是那个出版社出版:on_delete=models.DO_NOTHING
# 部门没了,员工没有部门(空不能):null=True, on_delete=models.SET_NULL
# 部门没了,员工进入默认部门(默认值):default=0, on_delete=models.SET_DEFAULT
详解序列器
进行序列化时,如何区分是创建、更新以及查询单条或多条呢?这其实涉及到ModelSerializer
的参数问题。
def __init__(self, instance=None, data=empty, **kwargs):
如果只传入data
参数,代表这是新增一条,则情况如下:
方法\属性\钩子 | 状态 |
---|---|
is_valid() | 可用 |
initial_data() | 可用 |
validated_data | 仅在调用is_valid()之后可用 |
errors | 仅在调用is_valid()之后可用 |
data | 仅在调用is_valid()之后可用 |
如果没有传递data
参数,则会发生如下情况:
方法\属性\钩子 | 状态 |
---|---|
is_valid() | 不可用 |
initial_data() | 不可用 |
validated_data | 不可用 |
errors | 不可用 |
data | 可用 |
我们要注意传递参数的情况如下:
查询单条,只会传入instance
创建一条,只会传入data
更新一条,会传入instance以及data
上面没有涉及到查询多条,那么在查询多条时我们会传递进many
参数,在内部会执行这样一段代码:
def __new__(cls, *args, **kwargs):
# We override this method in order to automatically create
# `ListSerializer` classes instead when `many=True` is set.
if kwargs.pop('many', False):
return cls.many_init(*args, **kwargs)
return super().__new__(cls, *args, **kwargs)
这句代码的意思是,当有many
为True
时代表这是多条操作,它将不会实例化你的自定义序列化器,因为你的自定义序列化器都是针对单条记录的,转而它会实例化一个叫做ListSerializer
的类,该类是内置的,并且该类支持批量创建。
下面是ListSerializer
的部分源码,其中child
代表你自定义的序列化器:
class ListSerializer(BaseSerializer):
child = None # 代表你自己写的序列化器
many = True
default_error_messages = {
'not_a_list': _('Expected a list of items but got type "{input_type}".'),
'empty': _('This list may not be empty.')
}
def __init__(self, *args, **kwargs):
self.child = kwargs.pop('child', copy.deepcopy(self.child)) # 自己写的序列化器
self.allow_empty = kwargs.pop('allow_empty', True)
assert self.child is not None, '`child` is a required argument.'
assert not inspect.isclass(self.child), '`child` has not been instantiated.'
super().__init__(*args, **kwargs)
self.child.bind(field_name='', parent=self)
继续向下看它的源码,可以发现它是支持批量创建的,但是不支持批量更新。
def update(self, instance, validated_data):
raise NotImplementedError(
"Serializers with many=True do not support multiple update by "
"default, only multiple create. For updates it is unclear how to "
"deal with insertions and deletions. If you need to support "
"multiple update, use a `ListSerializer` class and override "
"`.update()` so you can specify the behavior exactly."
)
def create(self, validated_data):
return [
self.child.create(attrs) for attrs in validated_data
]
如果我们对自己的序列化器做一个ListSerializer
,则可以继承原生的ListSerializer
,并且指定好当有many
参数传递时,实例化的是我们自己写的ListSerializer
即可。如下所示:
from rest_framework.serializers import ModelSerializer
from rest_framework.serializers import ListSerializer
from app01 import models
# 写一个类,继承ListSerializer,重写update方法实现批量更新
class BookListSerializer(ListSerializer):
def update(self,instance,validated_data):
return [
self.child.update(instance[i],attrs) for i,attrs in enumerate(validated_data)
]
class BookModelSerializer(ModelSerializer):
class Meta:
list_serializer_class = BookListSerializer # 使用many参数后后用指定的类进行实例化
model = models.Book
# depth=0 # 序列化的关联层级
fields = ("pk","book_name","book_price","authors","publish","publish_name","author_list")
# 使用property来拿到展示的数据
extra_kwargs = {
"publish":{"write_only":True}, # 不展示,但是新增或更新需要指定
"publish_name":{"read_only":True}, # 仅用于展示
"authors":{"write_only":True},
"author_list":{"read_only":True},
}
总结如下:
1.我们自己写的序列化器都只能对单条进行操作
2.如果传入many为True时,则会自动序列化一个内部的ListSerializer类,它支持批量创建多条,但是不支持批量更新
3.在自定义序列器中使用list_serializer_class类属性即可指定在进行批量操纵时用哪一个类进行实例化
序列化与反序列化
查看上面的代码,你可以发现在model
中的很多地方都用了property
来对实例方法进行装饰。
它其实是为了配合序列化做的,因为我们在自定以序列化器中的fields
属性里将他们进行加入了,如下所示:
@property
def publish_name(self): # 模拟字段,用于序列化时显示出版社名称
return self.publish.publish_name
@property
def author_list(self): # 模拟字段,用于序列化时显示多个作者的名字和性别
author_list = self.authors.all()
return [
{"author_name":author.author_name,"author_gender":author.get_author_gender_display()} for author in author_list
]
fields = ("pk","book_name","book_price","authors","publish","publish_name","author_list") # 返回外键,出版社的名字,返回多对多外键,作者的列表
并且,还指定了extra_kwargs
参数,用于指定哪些参数是序列化时用,哪些参数是反序列化时用:
extra_kwargs = {
"publish":{"write_only":True}, # 不展示,但是新增或更新需要指定
"publish_name":{"read_only":True}, # 仅用于展示
"authors":{"write_only":True},
"author_list":{"read_only":True},
}
这其实也要与前端沟通好,我序列化丢给你的数据是字符串,但是你要创建时必须给我把诸如出版社、作者等信息的pk
放进来才行。
大概意思就是,我丢给你字符串让用户看,你创建或更新时我不要字符串,我要pk
主键。
此外,还有一个参数叫做depth
,它规定了在序列化时是否连同外键一起进行序列化,并且序列化的层级是多少,一般看一看就行了。
接口书写
规定,对于单条操作,直接放在?
请求地址后面。放入pk
即可。
多条操作,你需要放入请求body
中,以[]
形式进行传递。
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import CreateModelMixin,ListModelMixin,RetrieveModelMixin,UpdateModelMixin
from rest_framework.response import Response
from app01.serializer import *
from app01 import models
class BookAPI(GenericAPIView,CreateModelMixin,ListModelMixin,RetrieveModelMixin,UpdateModelMixin):
queryset = models.Book.objects.filter(delete_status=False) # 查询未删除的数据
serializer_class = BookModelSerializer
def get(self,request,*args,**kwargs):
pk = kwargs.get("pk")
if not pk: # 获取所有
return self.list(request)
# 获取单条
return self.retrieve(request,pk)
def post(self,request,*args,**kwargs):
# 新增一条
if isinstance(request.data,dict):
return self.create(request) # 自动返回
# 新增多条
elif isinstance(request.data,list):
# 现在执行我们自己定义的ListSerializer,因为传入many=True.由于继承原生的ListSerializer,它自己有create方法
book_ser = self.get_serializer(data=request.data,many=True)
book_ser.is_valid(raise_exception=True) # 序列化失败直接抛出异常
book_ser.save()
return Response(data=book_ser.data)
def patch(self, request, *args, **kwargs):
pk = kwargs.get("pk")
print(pk)
if pk:
return self.update(request,pk)
# 改多个
# 前端传递数据格式[{book_id:1,book_name:xx,book_price:xx},{book_id:1,book_name:xx,book_price:xx}]
# 处理传入的数据 对象列表[book1,book2] 修改的数据列表[{book_name:xx,book_price:xx},{book_name:xx,book_price:xx}]
book_list = []
modify_data = []
for item in request.data:
# {book_id:1,book_name:xx,book_price:xx} 取出pk,不允许修改pk
pk = item.pop("pk")
book_obj = models.Book.objects.get(pk=pk)
book_list.append(book_obj)
modify_data.append(item)
book_ser = BookModelSerializer(instance=book_list,data=modify_data,many=True,partial=True) # parital允许局部修改,这个主要针对put,patch本身就是True
# 处理时:
# self.child.update(instance[i],attrs) for i,attrs in enumerate(validated_data) 传入id,和要修改的数据
book_ser.is_valid(raise_exception=True)
book_ser.save()
return Response(book_ser.data)
def delete(self,request,*args,**kwargs):
pk = kwargs.get("pk")
pks = [] # 用于获取要删除的id,全部放入列表中
if pk:
# 删一个
pks.append(pk)
else:
pks = request.data.get("pks")
result = models.Book.objects.filter(pk__in=pks,delete_status=False).update(delete_status=True)
if result:
return Response(data="删除%s条记录成功"%len(pks))
else:
return Response(data="删除失败,没有要删除的数据")
数据格式
新增数据:
# 新增一条 http://127.0.0.1:8000/api/books/ POST请求
# 请求的数据格式:
{
"book_name":"新书,单条",
"book_price": "123.00",
"publish": 1,
"authors": [
1,2
]
}
# 返回格式:
{
"pk": 12,
"book_name": "新书,单条",
"book_price": "123.00",
"publish_name": "北京出版社",
"author_list": [
{
"author_name": "云崖",
"author_gender": "男"
},
{
"author_name": "小屁孩",
"author_gender": "女"
}
]
}
# 新增多条 http://127.0.0.1:8000/api/books/ POST请求
# 请求的数据格式:
[
{
"book_name":"新书1,多条",
"book_price": "88.00",
"publish": 1,
"authors": [
1
]
},
{
"book_name":"新书2,多条",
"book_price": "13.00",
"publish": 1,
"authors": [
2,3
]
},
{
"book_name":"新书3,多条",
"book_price": "63.00",
"publish": 1,
"authors": [
3,4
]
}
]
# 返回格式:
[
{
"pk": 18,
"book_name": "新书1,多条",
"book_price": "88.00",
"publish_name": "北京出版社",
"author_list": [
{
"author_name": "云崖",
"author_gender": "男"
}
]
},
{
"pk": 19,
"book_name": "新书2,多条",
"book_price": "13.00",
"publish_name": "北京出版社",
"author_list": [
{
"author_name": "小屁孩",
"author_gender": "女"
},
{
"author_name": "东仙人",
"author_gender": "男"
}
]
},
{
"pk": 20,
"book_name": "新书3,多条",
"book_price": "63.00",
"publish_name": "北京出版社",
"author_list": [
{
"author_name": "东仙人",
"author_gender": "男"
},
{
"author_name": "云散人",
"author_gender": "女"
}
]
}
]
修改数据:
# 修改一条 http://127.0.0.1:8000/api/books/1/ patch请求
# 请求的数据格式:
{
"book_name":"修改新书",
"book_price": "123.00",
"publish": 3,
"authors": [
2,3
]
}
# 返回格式:
{
"pk": 1,
"book_name": "修改新书",
"book_price": "123.00",
"publish_name": "西京出版社",
"author_list": [
{
"author_name": "小屁孩",
"author_gender": "女"
},
{
"author_name": "东仙人",
"author_gender": "男"
}
]
}
# 修改多条 http://127.0.0.1:8000/api/books/ patch请求
# 请求的数据格式:
[
{
"pk": 1,
"book_name":"修改新书",
"book_price": "123.00",
"publish": 1,
"authors": [
3,4
]
},
{
"pk": 2,
"book_name":"修改新书2",
"book_price": "123.00",
"publish": 1,
"authors": [
1,2
]
}
]
# 返回格式:
[
{
"pk": 1,
"book_name": "修改新书",
"book_price": "123.00",
"publish_name": "北京出版社",
"author_list": [
{
"author_name": "东仙人",
"author_gender": "男"
},
{
"author_name": "云散人",
"author_gender": "女"
}
]
},
{
"pk": 2,
"book_name": "修改新书2",
"book_price": "123.00",
"publish_name": "北京出版社",
"author_list": [
{
"author_name": "云崖",
"author_gender": "男"
},
{
"author_name": "小屁孩",
"author_gender": "女"
}
]
}
]
删除数据:
# 删除一条 http://127.0.0.1:8000/api/books/1/ delete请求
# 删除多条 http://127.0.0.1:8000/api/books/ delete请求
# 请求数据格式:
{
"pks":
[1,2,3]
}