• drf框架(四)


    二次封装 Response方法

    ​ 自定义APIResponse方法继承Response,重新 __ init _ _方法

    # 自定义APIResponse方法,便于以后每次使用Response
    
    from rest_framework.response import Response
    # 1.自定义一个类,继承 drf的 Response方法,重写该方法的 __init__方法
    class APIResponse(Response):
        # 格式化 data
        def __init__(self, status=0, msg='ok', results=None, http_status=None, headers=None, exception=False, **kwargs):
            data = {
               # json的response基础有数据状态码和数据状态信息
                'status': status,
                'msg': msg
            }
            if results is not None:  # 如果后台有响应数据返回
                data['results'] = results
    
            #  如后台有响应的其它信息(一切自定义数据),直接放到响应数据data中
            data.update(**kwargs)
            super().__init__(data=data, status=http_status, headers=headers, exception=exception)
    

    基表设置 (注意设置abstract = True)

    from django.db import models
    
    # 知识点(一)基表
    """ 
    如果数据库中有多张表都存在相同的字段,这时可以设置一个基表, 继承基表
    在 Model 类的内部配置 Meta 类并设置 abstract=True ,这就表示作为基表
    """
    class BaseModel(models.Model):
        create_time = models.DateTimeField(auto_now_add=True)
        is_delete = models.BooleanField(default=False)
        class Meta:
            # 基表必须设置 abstract,基表是给普通的 Model类继承使用的,设置abstract就不会完成数据库迁移完成建表
            abstract = True
    
    class Book(BaseModel):
        name = models.CharField(max_length=32)
        price = models.DecimalField(max_digits=5, decimal_places=2)
    
    class Publish(BaseModel):
        name = models.CharField(max_length=32)
        address = models.CharField(max_length=64)
    
    ...
    

    ORM多表关联操作

    外键所放位置
    	一对多:外键放在多的一方
    	多对多:外键放在常用的一方
    	一对一:外键放在不常用的一方(从逻辑正方向考虑,如作者和作者详情表(一对一关系),如果外键放在作者表中(常用表;此时被关联关系),一旦删除了详情表,作者也会被级联删除,但是详情删除应当对作者保留(因为存在有的作者没有设置详情),所以外键不应该放在作者表中;外键放在详情表中(不常用,此时是被关联关系),删除作者,会级联删除详情,因为作者删除后详情存在就无意义,和之前的一对一关系外键放置不一样,之前都是把外键放在查询频率高的表)
    

    模型层:models.py

    class BaseModel(models.Model):
        create_time = models.DateTimeField(auto_now_add=True)
        is_delete = models.BooleanField(default=False)
        class Meta:
            # 基表必须设置 abstract,基表是给普通的 Model类继承使用的,设置abstract就不会完成数据库迁移完成建表
            abstract = True
    
    # 知识点(二)表断关联
    class Book(BaseModel):
        name = models.CharField(max_length=32)
        price = models.DecimalField(max_digits=5, decimal_places=2)
    
        publish = models.ForeignKey(to='Publish', related_name='books',  db_constraint=False, on_delete=models.DO_NOTHING)
        # 重点:多对多外键实际在关系表中,ORM默认关系表中两个外键字段都是级联
        # ManyToManyField字段不提供设置on_delete, 如果想设置关系表级联,只能手动定义关系表
        authors = models.ManyToManyField(to='Author', related_name='books', db_constraint=False)
    	# 自定义序列化字段
        @property
        def publish_name(self):
            return self.publish.name
    
        @property
        def authors_list(self):
            temp_author_list = []
            for author in self.authors.all():
                temp_author_list.append({
                    'name': author.name,
                    'sex': author.get_sex_display(),
                    'phone': author.detail.phone
                })
            return temp_author_list
    
    
    class Publish(BaseModel):
        name = models.CharField(max_length=32)
        address = models.CharField(max_length=64)
    
    
    class Author(BaseModel):
        name = models.CharField(max_length=16)
        sex = models.IntegerField(choices=((0, '男'), (1, '女')), default=0)
    
    
    class AuthorDetail(BaseModel):
        phone = models.CharField(max_length=11, default=None)
        # 有作者可以没有详情,删除作者,详情一定会被级联删除
        # 外键字段为正向查询字段,related_name是反向查询字段
        author = models.OneToOneField(to='Author', related_name='detail', db_constraint=False, on_delete=models.CASCADE)
    
    """
    表断关联:
        1.表之间没有外键关联,但是有外键逻辑关联
        2.断关联后不会影响数据库查询效率,但是会极大提高数据库增删改效率(不影响增删改查操作)
        3.断关联一定要通过逻辑保证表之间数据的安全
        4.断关联:db_constraint=False,设置了这个参数就表示断关联
        5.级联关系:
            作者没了,详情也没: on_delete=models.CASCADE
            出版社没了,但书还是那个出版社出版的: on_delete=models.DO_NOTHING
            部门没了,员工没有部门(空部门): null=True, on_delete=models.SET_NULL
            部门没了,员工进入默认部门(默认值):default=0, on_delete=models.SET_DEFAULT
        6.重点:多对多外键实际在关系表中,ORM默认关系表中两个外键字段都是级联
        ManyToManyField字段不提供设置 on_delete, 如果想设置关系表级联,只能手动定义关系表
        7.外键字段为正向查询字段,related_name是反向查询字段
    """
    

    多表查询之单查、群查

    序列化层:api/serializer.py

    from rest_framework.serializers import ModelSerializer
    from . import models
    class BookModelSerializer(ModelSerializer):
        class Meta:
            model = models.Book
            fields = ['name', 'price', 'publish_name', 'authors_list']
            
    

    路由层:api/urls.py

    from django.conf.urls import url, include
    
    from . import models
    from . import views
    urlpatterns = [
    
        url(r'^books/$', views.BookAPIView.as_view()),
        url(r'^books/(?P<pk>d+)$', views.BookAPIView.as_view())
    ]
    

    视图层:views.py

    from rest_framework.views import APIView
    from . import models
    from .response import APIResponse
    from .serializer import BookModelSerializer
    
    class BookAPIView(APIView):
        def get(self, request, *args, **kwargs):
            pk = kwargs.get('pk')
            if pk:
                book_obj = models.Book.objects.filter(pk=pk).first()
                if not book_obj:
                    return APIResponse(1, '单查 error',http_status=400)
                book_data = BookModelSerializer(book_obj).data
                return APIResponse(results=book_data)
    		# 群查
            book_ser = models.Book.objects.filter(is_delete=False).all()
            book_list_data = BookModelSerializer(book_ser, many=True).data
            return APIResponse(results=book_list_data)
        
        def post(self,request,*args,**kwargs):
            #单增:传的数据是与model对应的一个字典
            # 群增:设计传递的是多个model对应的字典列表
            request_data = request.data
            if isinstance(request_data,dict):
                many = False
            elif isinstance(request_data,list):  #在postman中设计一个列表存入每条数据
                many = True
            else:
                return Response({
                    'status':1,
                    'msg':'数据错误'
                })
            book_ser = serializers.V2BookModelSerializer(data=request_data,many=many)  #反序列化
            book_ser.is_valid(raise_exception=True)
            book_result = book_ser.save()  #book_result是对象<class 'app01.models.Book'>,如果增加多个就是列表套一个个对象
    
         return Response({
                'status':0,
                'msg':'ok',
                'results':serializers.V2BookModelSerializer(book_result,many=many).data
            })
    
        #单删: 有pk   #在postman中通过路径传参
        #群删:有pks   {"pks": [1, 2, 3]}   #通过json传参
        def delete(self,request,*args,**kwargs):
            pk = kwargs.get('pk')
            if pk:
                pks = [pk]
            else:
                pks = request.data.get('pks')
            if models.Book.objects.filter(pk__in=pks,is_delete=False).update(is_delete=True):
                return Response({
                    'status':0,
                    'msg':'删除成功'
                })
            return Response({
                'status':1,
                'msg':'删除失败'
            })
    

    视图层注意点:(**)

    1.序列化数据最后必须要.data  (因为要传给前端)
    2.反序列化通过data传参,序列化通过instance传参(当你只传一个参数时,默认是instanca的参数)
    3.反序列化与序列化都能使用many=True,序列化和反序列化数据只要被[]嵌套都要写many=True
    

    instance/data/many/partial四个关键参数的用法总结

    instance/data/many/partial 影响序列化对象行为的四个关键参数。
    
    1.如果没有data参数,只有instance,那么就不存在反序列化校验一说,只有序列化对象instance。
    2.如果有data,没有instance,那么需要进行校验data,然后将data进行反序列化,得到validated_data,此时再通过序列化对象获取data,这个data和初始化提供的data可不一样,这个序列化validated_data后的data,比起初始化data,可能减少了无效的字段(序列化没有定义的字段)。
    3.如果又提供了instance 又提供了data, 那么只要有data或者部分data,那么data都要进行验证才能进行下面的save等操作,如果不经过is_valid过程,那么后面的获取序列化数据或者反序列化数据都会无效。
    4.many参数将直接影响序列化类的类型,如果是many=False,那么直接使用当前序列化类。如果many=True,将实例化一个ListSerializer类来序列化或者反序列化类。(这也是看源码是漏掉的地方,一直奇怪普通Serialiszer类怎么没看到对多对象序列化的特殊处理。查看BaseSerializer.__new__方法)或者class Meta:中定义了list_serializer_class指定的多对象序列化类。
    5.终于弄懂了,partial用于部分更新,为啥子要伴随instance,因为要指明给save用,在save操作时给那个instance部分更新。逻辑这回走到下面源码中的get_initial()获取要进行更新instance的字段数据。
    

    单整体改

    单指的是单独一条数据,整体指这条数据的所有字段都必须传值修改

    基于上述文章的代码修改,序列化层不用变,只修改views.py

    1) 单整体改,说明前台要提供修改的数据,那么数据就需要校验,校验的数据应该在实例化“序列化类对象”时,赋值给data
    2)修改,就必须明确被修改的模型类对象,并在实例化“序列化类对象”时,赋值给instance,必须赋值给instance
    3)整体修改,所有校验规则有required=True的字段,都必须提供,因为在实例化“序列化类对象”时,参数partial默认为False
    
    注:如果partial值设置为True,就是可以局部改
    1)单整体修改,一般用put请求:
    V2BookModelSerializer(
        instance=要被更新的对象, 
        data=用来更新的数据,
        partial=默认False,必须的字段全部参与校验
    )
    2)单局部修改,一般用patch请求:
    V2BookModelSerializer(
        instance=要被更新的对象, 
        data=用来更新的数据,
        partial=设置True,必须的字段都变为选填字段
    )
        注:partial设置True的本质就是使字段 required=True 校验规则失效
    
    请求方法:put   整体修改
    请求参数:pk从路由传参,修改的数据通过数据包json传递
    请求接口: http://127.0.0.1:8080/app01/books/pk/
    
    #单整体改  对/books/pk/  传的参数是与model对应的字典 {name|price|publish|authors}在json中传递
    def put(self,request,*args,**kwargs):
            request_data = request.data
            pk = kwargs.get('pk')
            #先获取要修改的对象
            try:
                old_book_obj = models.Book.objects.get(pk=pk,is_delete=False)
            except:
                #当输入不存在的pk
                return Response({
                    'status':1,
                    'msg':'参数错误'
                })
            book_ser = serializers.V2BookModelSerializer(instance=old_book_obj,data=request_data,partial=False)
            book_ser.is_valid(raise_exception=True)
            book_obj = book_ser.save()
    
            return Response({
                'status':0,
                'msg':'ok',
                'results':serializers.V2BookModelSerializer(book_obj).data
            })
    

    单整体修改注意点(******)

    1.需要修改的数据通过data传递
    2.需要修改的模型类对象必须传递给instance
    3.参数partial设置为False (默认是False,这里不设置也行)
    

    单局部修改

    单整体修改和单局部修改只有一点不相同:

    设置参数partial=True,就变成单局部修改,修改的参数就不必全部传,想修改什么数据就传什么数据
    

    单局部修改和群局部修改整合

    序列化层 serializer.py

    群改(重点)需要设置ListSerializer,创建BookListSerializer继承ListSerializer,重写update方法

    # 重点:ListSerializer与ModelSerializer建立关联的是:
    # ModelSerializer的Meta类的 - list_serializer_class
    class BookListSerializer(ListSerializer):   
      def update(self, instance, validated_data):  #需要重写update方法
            # print(instance)  # 要更新的对象们
            # print(validated_data)  # 更新的对象对应的数据们
            # print(self.child)  # 服务的模型序列化类 - V2BookModelSerializer
            for index, obj in enumerate(instance):
                self.child.update(obj, validated_data[index])
            return instance
        
    # 原模型序列化类变化
    class BookModelSerializer(ModelSerializer):
        class Meta:
            # ...
            # 群改,需要设置 自定义ListSerializer,重写群改的 update 方法
            list_serializer_class = V2BookListSerializer
    
    请求方式:patch
    请求参数:pk从路由获取,修改参数从数据包通过json格式传递  单局部修改: /books/pk/  pk通过路由传参,修改数据通过json传参  群局部修改: /books/ 修改的数据都是从json传递   eg:[{'pk':1,'name':'花果山'},{'pk':2,'price':3.33}]
    请求接口:http://127.0.0.1:8080/app01/books/pk/
    

    视图层:views.py

    #单局部改和群局部改整合
        #单局部改:对 /books/pk/   pk通过路由传参,修改数据选择传参,通过数据包json传递
        #群局部修改:/books/ 修改数据通过数据包传递,设置成列表格式  [{pk:1,name:123},{pk:3,price:7},{pk:7,publish:2}]
        def patch(self,request,*args,**kwargs):
            request_data = request.data  #数据包数据
            pk = kwargs.get('pk')
            # 将单改,群改的数据都格式化成 pks=[要需要的对象主键标识] | request_data=[每个要修改的对象对应的修改数据]
            if pk and isinstance(request_data,dict):  #单改
                pks = [pk,]
                request_data = [request_data,]
            elif not pk and isinstance(request_data,list):  #群改
                pks = []
                # 遍历前台数据[{pk:1, name:123}, {pk:3, price:7}, {pk:7, publish:2}],拿一个个字典
                for dic in request_data:
                    pk=dic.pop('pk',None)  #返回pk值
                    if pk:
                        pks.append(pk)
                    #pk没有传值
                    else:
                        return Response({
                            'status':1,
                            'msg':'参数错误'
                        })
            else:
                return Response({
                    'status': 1,
                    'msg': '参数错误'
                })
            # pks与request_data数据筛选,
            # 1)将pks中的没有对应数据的pk与数据已删除的pk移除,request_data对应索引位上的数据也移除
            # 2)将合理的pks转换为 objs
            objs = []
            new_request_data = []
            for index,pk in enumerate(pks):
                try:
                    #将pk合理的对象数据保存下来
                    book_obj = models.Book.objects.get(pk=pk)
                    objs.append(book_obj)
                    #对应索引的数据也保存下来
                    new_request_data.append(request_data[index])
                except:
                    # 重点:反面教程 - pk对应的数据有误,将对应索引的data中request_data中移除
                    #在for循环中不要使用删除
                    # index = pks.index(pk)
                    # request_data.pop(index)
                    continue
            #生成一个serializer对象
            book_ser = serializers.BookModelSerializer(instance=objs,data=new_request_data,partial=True,many=True)
            book_ser.is_valid(raise_exception=True)
            book_objs = book_ser.save()
    
    
            return Response({
                'status':0,
                'msg':'ok',
                'results':serializers.BookModelSerializer(book_objs,many=True).data
            })
    

    思路:

    1.先将单改,群改的数据都格式化成 pks=[要需要的对象主键标识] | request_data=[每个要修改的对象对应的修改数据]
    2.pks与request_data数据筛选,
          将pks中的没有对应数据的pk与数据已删除的pk移除,request_data对应索引位上的数据也移除
           将合理的pks转换为 objs
    

    注意点:

    1.本篇文章讲了局部修改patch,和整体修改put,都需要设置参数instance传入的是要修改的对象,data传入的是修改的数据2.群修改的话需要使用ListSerializer,重写update方法
    

    (#####)

    # 单整体改、群整体改
        def put(self, request, *args, **kwargs):
            """
            单整体改:前台提交字典,接口 /books/(pk)/
            群整体改:前台提交列表套字典,接口 /books/,注每一个字典都可以通过pk
            """
            pk = kwargs.get('pk')
            request_data = request.data
            if pk: # 单改
                try:
                    book_obj = models.Book.objects.get(pk=pk)
                except:
                    return APIResponse(1, 'pk error')
    
                # 修改和新增,都需要通过数据,数据依旧给data,修改与新增不同点,instance要被赋值为被修改对象
                book_ser = serializers.BookModelSerializer(instance=book_obj, data=request_data)
                book_ser.is_valid(raise_exception=True)
                book_obj = book_ser.save()
                return APIResponse(results=serializers.BookModelSerializer(book_obj).data)
            else:  # 群改
                if not isinstance(request_data, list) or len(request_data) == 0:
                    return APIResponse(1, 'data error', http_status=400)
    
                # [{pk:1,...}, {pk:3,...}, {pk:100,...}] => [obj1, obj3, obj100] + [{...}, {...}, {...}]
                # 要考虑pk对应的对象是否被删,以及pk没有对应的对象
                # 假设pk3被删,pk100没有 => [obj1] + [{...}]
    
                # 注:一定不要在循环体中对循环对象进行增删(影响对象长度)的操作
                obj_list = []
                data_list = []
                for dic in request_data:
                    # request_data可能是list,单内部不一定是dict
                    try:
                        pk = dic.pop('pk')
                        try:
                            obj = models.Book.objects.get(pk=pk, is_delete=False)
                            obj_list.append(obj)
                            data_list.append(dic)
                        except:
                            pass
                    except:
                        return APIResponse(1, 'data error', http_status=400)
    
                book_ser = serializers.BookModelSerializer(instance=obj_list, data=data_list, many=True)
                book_ser.is_valid(raise_exception=True)
                book_obj_list = book_ser.save()
                return APIResponse(results=serializers.BookModelSerializer(book_obj_list, many=True).data)
    
  • 相关阅读:
    108.Convert Sorted Array to Binary Search Tree
    111.Minimum Depth of Binary Tree
    118.Pascal's Triangle
    122.Best Time to Buy and Sell Stock II
    35.搜索插入位置
    OSI参考模型
    虚拟机访问tomcat
    linux输入ifconfig找不到IP的解决办法
    分层协议、协议、接口、服务
    Spring Boot项目的创建
  • 原文地址:https://www.cnblogs.com/chmily/p/11935110.html
Copyright © 2020-2023  润新知