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,**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,**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,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","author_list")  # 返回外键,出版社的名字,返回多对多外键,作者的列表

   并且,还指定了extra_kwargs参数,用于指定哪些参数是序列化时用,哪些参数是反序列化时用:

        extra_kwargs = {
            "publish":{"write_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,UpdateModelMixin):
    queryset = models.Book.objects.filter(delete_status=False)  # 查询未删除的数据
    serializer_class = BookModelSerializer

    def get(self,request,**kwargs):
        pk = kwargs.get("pk")
        if not pk:  # 获取所有
            return self.list(request)
        # 获取单条
        return self.retrieve(request,pk)

    def post(self,**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,**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_price:xx}]
        # 处理传入的数据  对象列表[book1,book2]  修改的数据列表[{book_name:xx,{book_name:xx,book_price:xx}]
        book_list = []
        modify_data = []
        for item in request.data:
            # {book_id:1,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 in enumerate(validated_data)  传入id,和要修改的数据
        book_ser.is_valid(raise_exception=True)
        book_ser.save()
        return Response(book_ser.data)

    def delete(self,**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": "新书,单条","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","authors": [
        1
    ]
},{
    "book_name":"新书2,多条","book_price": "13.00","authors": [
        2,3
    ]
},{
    "book_name":"新书3,多条","book_price": "63.00","authors": [
        3,4
    ]
}
]

# 返回格式:

[
    {
        "pk": 18,"book_name": "新书1,多条","author_list": [
            {
                "author_name": "云崖","author_gender": "男"
            }
        ]
    },{
        "pk": 19,"book_name": "新书2,多条","author_list": [
            {
                "author_name": "小屁孩","author_gender": "女"
            },{
                "author_name": "东仙人",{
        "pk": 20,"book_name": "新书3,多条","author_list": [
            {
                "author_name": "东仙人","author_gender": "男"
            },{
                "author_name": "云散人","author_gender": "女"
            }
        ]
    }
]

   修改数据:

# 修改一条 http://127.0.0.1:8000/api/books/1/  patch请求
# 请求的数据格式:
{
    "book_name":"修改新书","publish": 3,3
    ]
}

# 返回格式:
{
    "pk": 1,"book_name": "修改新书","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":"修改新书",4
    ]
	},{
    "pk": 2,"book_name":"修改新书2",2
    ]
	}
]

# 返回格式:
[
    {
        "pk": 1,"author_gender": "女"
            }
        ]
    },{
        "pk": 2,"book_name": "修改新书2",{
                "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]
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


注:所有源代码均实测运行过。所有源代码均已上传CSDN,请有需要的朋友自行下载。
继承APIView和ViewSetMixin;作用也与APIView基本类似,提供了身份认证、权限校验、流量管理等。ViewSet在开发接口中不经常用。
一、Django介绍Python下有许多款不同的 Web 框架。Django是重量级选手中最有代表性的一位。许多成功的网站和APP都基于Django。Django 是一个开放源代码的 Web 应用框架,由 Python 写成。Django 遵守 BSD 版权,初次发布于 2005 年 7 月, 并于 2008 年 9 月发布了第一个正式版本 1.0 。Django学习线路Django 采用了 MVT 的软件设计模式,即模型(Model),视图(View)和模板(Template)。这个MVT模式并
本文从nginx快速掌握到使用,gunicorn快速掌握到使用,实现小白快速搭建django项目,并对可能出现的报错进行了分析
uniapp微信小程序订阅消息发送服务通知
Django终端打印SQL语句 1 Setting配置: 2 默认python 使用的MysqlDB连接,Python3 支持支持pymysql 所有需要在app里面的__init__加上下面配置:
url: re_path('authors/$', views.AuthorView.as_view()), re_path('book/(?P\d+)/$', vie
前提 关于html寻找路线: template 如果在各个APP中存在, Django 会优先找全局template 文件下的html文件,如果全局下的template文件没有相关的html Djan
// GET请求request.GET // POST请求request.POST // 处理文件上传请求request.FILES // 处理如checkbox等多选 接受列表request.get
from bs4 import BeautifulSoup#kindeditordef kindeditor(request): s = ''' <li><s
view.py 配置 html 配置
from django.http import JsonResponse JsonResponse 里面代码会加这一个响应头 kwargs.setdefault('content_type&#
#下面两种是基于QuerySet查询 也就是说SQL中用的jion连表的方式查询books = models.UserInfo.objects.all() print(type(books)) &gt
return HttpResponse("OK") 返回一个字符串 return redirect("/index/") 返回URL return render
from django.http import JsonResponse JsonResponse 里面代码会加这一个响应头 kwargs.setdefault('content_type&#
浏览器有一个很重要的概念——同源策略(Same-Origin Policy)。所谓同源是指,域名,协议,端口相同。不同源的客户端脚本(javascript、ActionScript)在没明确授权的情况
自动发送 > 依赖jQuery文件 实例-->GET请求: 手动发送 > 依赖浏览器XML对象(也叫原生ajax) Ajax主要就是使用 【XmlHttpRequest】对象来完成请
#下面两种是基于QuerySet查询 也就是说SQL中用的jion连表的方式查询books = models.UserInfo.objects.all() print(type(books)) &gt
// GET请求request.GET // POST请求request.POST // 处理文件上传请求request.FILES // 处理如checkbox等多选 接受列表request.get
return HttpResponse("OK") 返回一个字符串 return redirect("/index/") 返回URL return render