• 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)
    

       这句代码的意思是,当有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 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]
    }
    
  • 相关阅读:
    burp用命令行一键启动出现错误“a java exception has occurred”的解决
    fofa语法
    使用LaZagne获取各种密码
    获取网站ico图标以及使用shodan的http.favicon.hash方法搜同ico站点
    使用nmap-converter.py整理nmap导出的xml记录为xls文件
    使用wafw00f工具识别waf类型
    反射型XSS中的POST型复现
    锐捷统一上网行为管理与审计系统信息泄露漏洞复现(CNVD-2021-14536)
    软件工程 数组
    c语言4
  • 原文地址:https://www.cnblogs.com/Yunya-Cnblogs/p/13920080.html
Copyright © 2020-2023  润新知