前言

   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)

   这句代码的意思是,当有manyTrue时代表这是多条操作,它将不会实例化你的自定义序列化器,因为你的自定义序列化器都是针对单条记录的,转而它会实例化一个叫做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 高级使用的更多相关文章

  1. drf框架 - 序列化组件 | ModelSerializer (查,增,删,改)

    ModelSerializer 序列化准备: 配置 settings.py # 注册rest_framework框架 INSTALLED_APPS = [ ... 'rest_framework' ] ...

  2. drf序列化高级、自定义只读只写、序列化覆盖字段、二次封装Response、数据库查询优化(断关联)、十大接口、视图家族

    目录 自定义只读 自定义只写 序列化覆盖字段 二次封装Response 数据库关系分析 断外键关联关系 ORM操作外键关系 ORM四种关联关系 基表 系列化类其他配置(了解) 十大接口 BaseSer ...

  3. 4)drf序列化组件 Serializer(偏底层)、ModelSerializer(重点)、ListModelSerializer(辅助群改)

    知识点:Serializer(偏底层).ModelSerializer(重点).ListModelSerializer(辅助群改) 一.Serializer 偏底层 一般不用 理解原理 1.序列化准备 ...

  4. 序列化之Serializer类与ModelSerializer类的使用

    序列化之Serializer类的使用(5星) 作用: 序列化,序列化器会把模型对象转换成字典,经过response以后变成json字符串 反序列化,把客户端发送过来的数据,经过request以后变成字 ...

  5. MySQL高级知识- MySQL的架构介绍

    [TOC] 1.MySQL 简介 概述 MySQL是一个关系型数据库管理系统,由瑞典MySQL AB公司开发,目前属于Oracle公司. MySQL是一种关联数据库管理系统,将数据保存在不同的表中,而 ...

  6. PayPal高级工程总监:读完这100篇论文 就能成大数据高手(附论文下载)

    100 open source Big Data architecture papers for data professionals. 读完这100篇论文 就能成大数据高手 作者 白宁超 2016年 ...

  7. 马哥linux运维初级+中级+高级 视频教程 教学视频 全套下载(近50G)

    马哥linux运维初级+中级+高级 视频教程 教学视频 全套下载(近50G)目录详情:18_02_ssl协议.openssl及创建私有CA18_03_OpenSSH服务及其相关应用09_01_磁盘及文 ...

  8. JS高级前端开发群加群说明及如何晋级

    JS高级前端开发群加群说明 一.文章背景: 二. 高级群: 三. 加入方式: 四. 说明:   一.文章背景: 去年年初建了几个群,在不经意间火了,一直排在“前端开发”关键字搜索结果第一名.当然取得这 ...

  9. C#高级知识点&(ABP框架理论学习高级篇)——白金版

    前言摘要 很早以前就有要写ABP高级系列教程的计划了,但是迟迟到现在这个高级理论系列才和大家见面.其实这篇博客很早就着手写了,只是楼主一直写写停停.看看下图,就知道这篇博客的生产日期了,谁知它的出厂日 ...

随机推荐

  1. 深入了解Vue.js组件笔记

    1.组件注册 Vue.component('name',{}) 创建的组件都是全局组件,它们在注册之后可以用在任何新创建的Vue根实例(new Vue)的模板中.第一个参数是组件的名字,第二个参数是一 ...

  2. 利用new Object方式创建对象

    var obj = new Object();      //创建了一个空的对象obj.uname = 'zhangsanfeng';obj.name = 18;       //字面量方式创建对象不 ...

  3. Salesforce LWC学习(二十七) File Upload

    本篇参考: https://developer.salesforce.com/docs/component-library/bundle/lightning-file-upload/documenta ...

  4. Java知识系统回顾整理01基础03变量06变量的作用域

    一.变量根据所处的位置对应不同的名称 变量处于不同的位置,有不同的名称 名称分别是  字段,属性 参数 局部变量 不同名称的变量,其作用域是不一样的 二.字段,属性,Field 当一个变量被声明在类下 ...

  5. 在程序开发中,++i 与 i++的区别在哪里?

    哈哈哈! 从大学开始又忘了...蜜汁问题哈 参考来源:https://www.zhihu.com/question/19811087/answer/80210083 i++ 与 ++i 的主要区别有两 ...

  6. S3C2440 LCD驱动(FrameBuffer)实例开发<二>(转)

    开发板自带的LCD驱动是基于platform总线写的,所以如果要使其它的LCD能够在自己的开发板上跑起来,那么就先了解platform驱动的架构,下面简单记录下自己看platform驱动时体会,简单的 ...

  7. 多测师讲解python _函数中参数__高级讲师肖sir

    函数中讲解参数: 形参和实参的认识 函数无参数的调用 函数单个参数的调用 函数多个参数的调用 # #调试函数给默认参数传新值,则函数使用新值 # 注意:当多种参数同时出现在函数中,默认参数要放在最后的 ...

  8. day53 Pyhton 前端04

    内容回顾: 盒子: 内边距:padding,解决内部矛盾,内边距的增加整个盒子也会增加 外边距:margin,解决外部矛盾,当来盒子都有外边距的时候,取两者最大值 边框:border border-c ...

  9. linux(centos8):zabbix配置邮件报警(监控错误日志)(zabbix5.0)

    一,zabbix5.0发邮件报警的准备工作: zabbix5.0在linux平台上的安装:参见这一篇: https://www.cnblogs.com/architectforest/p/129125 ...

  10. Luogu-2480 古代猪文

    我们首先来概括一下题意,其实就是给定 \(n,g\),求: \[g^{\sum_{k\nmid n} C_n^{\frac{n}{k}}}\operatorname{mod} 999911659 \] ...