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.models import *
class BookAPI(GenericAPIView,CreateModelMixin,ListModelMixin,RetrieveModelMixin,UpdateModelMixin):
queryset = 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")
if not 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("book_id")
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]
}
ModelSerializer 高级使用的更多相关文章
- drf框架 - 序列化组件 | ModelSerializer (查,增,删,改)
ModelSerializer 序列化准备: 配置 settings.py # 注册rest_framework框架 INSTALLED_APPS = [ ... 'rest_framework' ] ...
- drf序列化高级、自定义只读只写、序列化覆盖字段、二次封装Response、数据库查询优化(断关联)、十大接口、视图家族
目录 自定义只读 自定义只写 序列化覆盖字段 二次封装Response 数据库关系分析 断外键关联关系 ORM操作外键关系 ORM四种关联关系 基表 系列化类其他配置(了解) 十大接口 BaseSer ...
- 4)drf序列化组件 Serializer(偏底层)、ModelSerializer(重点)、ListModelSerializer(辅助群改)
知识点:Serializer(偏底层).ModelSerializer(重点).ListModelSerializer(辅助群改) 一.Serializer 偏底层 一般不用 理解原理 1.序列化准备 ...
- 序列化之Serializer类与ModelSerializer类的使用
序列化之Serializer类的使用(5星) 作用: 序列化,序列化器会把模型对象转换成字典,经过response以后变成json字符串 反序列化,把客户端发送过来的数据,经过request以后变成字 ...
- MySQL高级知识- MySQL的架构介绍
[TOC] 1.MySQL 简介 概述 MySQL是一个关系型数据库管理系统,由瑞典MySQL AB公司开发,目前属于Oracle公司. MySQL是一种关联数据库管理系统,将数据保存在不同的表中,而 ...
- PayPal高级工程总监:读完这100篇论文 就能成大数据高手(附论文下载)
100 open source Big Data architecture papers for data professionals. 读完这100篇论文 就能成大数据高手 作者 白宁超 2016年 ...
- 马哥linux运维初级+中级+高级 视频教程 教学视频 全套下载(近50G)
马哥linux运维初级+中级+高级 视频教程 教学视频 全套下载(近50G)目录详情:18_02_ssl协议.openssl及创建私有CA18_03_OpenSSH服务及其相关应用09_01_磁盘及文 ...
- JS高级前端开发群加群说明及如何晋级
JS高级前端开发群加群说明 一.文章背景: 二. 高级群: 三. 加入方式: 四. 说明: 一.文章背景: 去年年初建了几个群,在不经意间火了,一直排在“前端开发”关键字搜索结果第一名.当然取得这 ...
- C#高级知识点&(ABP框架理论学习高级篇)——白金版
前言摘要 很早以前就有要写ABP高级系列教程的计划了,但是迟迟到现在这个高级理论系列才和大家见面.其实这篇博客很早就着手写了,只是楼主一直写写停停.看看下图,就知道这篇博客的生产日期了,谁知它的出厂日 ...
随机推荐
- 深入了解Vue.js组件笔记
1.组件注册 Vue.component('name',{}) 创建的组件都是全局组件,它们在注册之后可以用在任何新创建的Vue根实例(new Vue)的模板中.第一个参数是组件的名字,第二个参数是一 ...
- 利用new Object方式创建对象
var obj = new Object(); //创建了一个空的对象obj.uname = 'zhangsanfeng';obj.name = 18; //字面量方式创建对象不 ...
- Salesforce LWC学习(二十七) File Upload
本篇参考: https://developer.salesforce.com/docs/component-library/bundle/lightning-file-upload/documenta ...
- Java知识系统回顾整理01基础03变量06变量的作用域
一.变量根据所处的位置对应不同的名称 变量处于不同的位置,有不同的名称 名称分别是 字段,属性 参数 局部变量 不同名称的变量,其作用域是不一样的 二.字段,属性,Field 当一个变量被声明在类下 ...
- 在程序开发中,++i 与 i++的区别在哪里?
哈哈哈! 从大学开始又忘了...蜜汁问题哈 参考来源:https://www.zhihu.com/question/19811087/answer/80210083 i++ 与 ++i 的主要区别有两 ...
- S3C2440 LCD驱动(FrameBuffer)实例开发<二>(转)
开发板自带的LCD驱动是基于platform总线写的,所以如果要使其它的LCD能够在自己的开发板上跑起来,那么就先了解platform驱动的架构,下面简单记录下自己看platform驱动时体会,简单的 ...
- 多测师讲解python _函数中参数__高级讲师肖sir
函数中讲解参数: 形参和实参的认识 函数无参数的调用 函数单个参数的调用 函数多个参数的调用 # #调试函数给默认参数传新值,则函数使用新值 # 注意:当多种参数同时出现在函数中,默认参数要放在最后的 ...
- day53 Pyhton 前端04
内容回顾: 盒子: 内边距:padding,解决内部矛盾,内边距的增加整个盒子也会增加 外边距:margin,解决外部矛盾,当来盒子都有外边距的时候,取两者最大值 边框:border border-c ...
- linux(centos8):zabbix配置邮件报警(监控错误日志)(zabbix5.0)
一,zabbix5.0发邮件报警的准备工作: zabbix5.0在linux平台上的安装:参见这一篇: https://www.cnblogs.com/architectforest/p/129125 ...
- Luogu-2480 古代猪文
我们首先来概括一下题意,其实就是给定 \(n,g\),求: \[g^{\sum_{k\nmid n} C_n^{\frac{n}{k}}}\operatorname{mod} 999911659 \] ...