• Django REST Framework(DRF)_第四篇


    • DRF分页(总共三种)

      1. PageNumberPagination(指定第n页,每页显示n条数据)

        • 说明

        既然要用人家的那么我们就先来看下源码,这个分页类源码中举例通过参数指定第几页和每页显示的数据:http://api.example.org/accounts/?page=4&page_size=100,所以我们可以知道,请求参数的名称和数值应该都是我们可以自定义的.来,我们来看下面的几个参数:

        page_size = api_settings.PAGE_SIZE            # 这个是每页显示的数据,我们可以在全局定义,也可以在继承类只定义,比如每页10条记录,这里就设置10
        django_paginator_class = DjangoPaginator    # 这个是django分页的类,默认即可
        page_query_param = 'page'            # 第几页参数名称,我们也可以默认使用page,形象生动嘛
        page_size_query_param = None        # 这个是每页显示记录条数的参数名称,我们可以使用size或者page_size
        max_page_size = None                # 每页显示数据的最大值,所以page_size要小于这个,这个设置了就表示一页最多能显示多少条数据

        好了,参数看完了,那我们就来看下方法,实际上分页器核心就是调用paginate_queryset(self, queryset, request, view=None)的方法,返回的是list(self.page),也就是我们传入了queryset数据,并且配置好了页数等信息,最终就返回了第几页的n条数据对象的列表,然后我们再通过序列化之后,通过get_paginated_response(self, data)将序列化后的数据返回给前端即可.好了,我们开始写实例看下吧

        • 使用

          首先定义分页器类

          from rest_framework.pagination import PageNumberPagination
          
          
          class PageNumberPaginator(PageNumberPagination):
              page_size = 10  # 每页显示10条数据
              page_size_query_param = 'size'  # 每页显示条数的参数名称
              page_query_param = 'page'   # 页码参数名称,比如page=3&size=10 第三页显示10条
              max_page_size = 10  # 最大页码数量控制

          视图类如下:

          from .Paginators import PageNumberPaginator
          
          
          class BookView(APIView):
              """书籍相关视图"""
          
              def get(self, request):
                  book_queryset = Book.objects.all()
                  # 1.实例化PageNumberPaginator
                  page_obj = PageNumberPaginator()
                  # 2.调用paginate_queryset方法获取当前页的数据
                  page_data = page_obj.paginate_queryset(queryset=book_queryset, request=request, view=self)
                  # 3.将获取的数据放入序列化器中进行匹配
                  serializer_data = BookModelSerializer(page_data, many=True)
                  # 4.返回带超链接的且有上一页和下一页的数据
                  return page_obj.get_paginated_response(serializer_data.data)

          访问路径为: http://127.0.0.1:8000/app01/books/?page=1&size=2

      2. LimitOffsetPagination (偏移n个位置, 向后查看n条数据)

        这里就不做多的说明了,直接开始使用,源码跟上一个是类似的.

        class LimitOffsetPaginator(LimitOffsetPagination):
            default_limit = 2      # 向后查看两条数据
            limit_query_param = 'limit'     # 查看数据的参数名称
            offset_query_param = 'offset'   # 偏移参数名称, limit=2&offset=0 这个表示偏移0也就是从第一条数据开始显示,显示两条
            max_limit = 999                 # 一页查看最多999条数据

        视图类的话只需要修改实例化类那一行就行了page_obj = LimitOffsetPaginator(),其他都一样

      3. CursorPagination (加密游标的分页,只能有上一页和下一页)

        class CursorPaginator(CursorPagination):
            cursor_query_param = 'cursor'   # 参数名称
            page_size = 1                   # 每页显示的条数
            ordering = '-id'                # 根据id倒序排列

        视图类一样只需要修改实例化那一行 page_obj = CursorPaginator()

        访问的分页路径都是自动加密生成的,比如 http://127.0.0.1:8000/app01/books/?cursor=cD0y

        • ModelViewSet视图类使用分页组件

      如果是视图类是继承ModelViewSet写的呢?继承了ModelViewSet后,只需定义queryset和serializer_class序列化器即可,那么怎么玩呢?这个还是得看源码咯,来继续分析源码.

      客户端get请求,所以从ModelViewSet -> ListModelMixin -> list方法, list方法中page = self.paginate_queryset(queryset) ,查看paginate_queryset的方法,self指的是视图类,所以从头再找,在GenericViewSet下的GenericAPIView中发现 paginate_queryset的方法下执行了 self.paginator.paginate_queryset(queryset, self.request, view=self) ,此时paginator 方法中又执行了self._paginator = self.pagination_class(), 而pagination_class()就是去配置里面找分页组件类并实例化,因此我们只需要配置pagination_class = PageNumberPaginator即可.

      class BooksView(viewsets.ModelViewSet):
          queryset = Book.objects
          serializer_class = BookModelSerializer
          # 指定分页器组件
          pagination_class = PageNumberPaginator
    • 跨域

      • 简单介绍CORS跨域请求

        CORS即Cross Origin Resource Sharing 跨域资源共享,跨域请求分为两种,一种叫简单请求,一种是复杂请求

      • 简单请求和复杂请求

        简单请求满足下面要求:

        HTTP方法是其中方法之一: HEAD, GET,POST

        HTTP头信息不超出以下几种字段

          Accept, Accept-Language, Content-Language, Last-Event-ID

          Content-Type只能是下列类型中的一个

            application/x-www-from-urlencoded

            multipart/form-data

            text/plain

        任何一个不满足上述要求的请求,即会被认为是复杂请求~~

        复杂请求会先发出一个预请求,我们也叫预检,OPTIONS请求~~

      • 浏览器的同源策略

        上面介绍的是跨域,那么跨域到底为什么会产生呢?其实就是浏览器的同源策略导致的,也就是说浏览器会阻止非同源的请求,非同源指的是域名不同或者不同端口,而且浏览器只会阻止表单及ajax请求,并不会阻止src的请求(script,img标签的)

      • 解决跨域

        那么怎么解决跨域呢?

        • 方法一:通过jsonp

          jsonp的实现原理是根据浏览器不阻止src请求入手,也就是可以放在window.onload = function () {src="xxx"...}中,这样页面一加载,就会执行代码,但是这种方式太局限了,又不能发送其他类型请求还不能带请求头等,所以这种方法仅作了解

        • 方法二: 添加响应头

          因为每个请求都会遇到跨域问题,所以我们可以定义一个中间件,专门处理跨域

          from django.utils.deprecation import MiddlewareMixin
          
          
          class CorsMiddleWares(MiddlewareMixin):
              def process_response(self, request, response):
                  # 针对简单请求,允许的地址是所有
                  response['Access-Control-Allow-Origin'] = '*'
                  # 如果是复杂请求,一定会先发送option预检
                  if request.method == "OPTIONS":
                      # Content-Type也可以写成*,表示任何形式的头都允许
                      response["Access-Control-Allow-Headers"] = "Content-Type"
                      response["Access-Control-Allow-Methods"] = "DELETE, PUT, POST"
                  return response
    • ContentType的应用

      • 前戏

        contenttypes 是Django内置的一个应用,可以追踪项目中所有app和model的对应关系,并记录在ContentType表中。

      那么这张表有什么作用呢?接下来我举个例子,网上大部分人都是举该例子,就是商品优惠券和商品的例子,你想下,是不是每个商品都有能有优惠券,那么优惠券设计的结果就会像下面的结构一样,有没有发现如果我们新增一种商品就需要修改表结构,而且大部分字段都是null,所以我们需要修改下表结构

         id     name       food_id        cloth_id   ....
         1   通用优惠券      null          null
         2   苹果满减券       1            null
         3   衬衫满减券       null           1

      修改后的表结构及数据如下:

         id    name       table_id       object_id   ....
         1   通用优惠券      null          null
         2   苹果满减券       7(food表)      1(第一行苹果记录)
         3   衬衫满减券       8              1

      你看这样就算再多产品也不会出现空间浪费和表结构修改,而django中的ContentType表.

      • 应用
      contenttypes 应用
      
      通过使用contenttypes 应用中提供的特殊字段GenericForeignKey,我们可以很好的解决这个问题。只需要以下三步:  
      
      from django.contrib.contenttypes.models import ContentType
      在model中定义ForeignKey字段,并关联到ContentType表。通常这个字段命名为“content_type”
      
      在model中定义PositiveIntegerField字段(正整数),用来存储关联表中的主键。通常这个字段命名为“object_id”
      
      from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
      在model中定义GenericForeignKey字段,传入上述两个字段的名字。
      
      为了更方便查询商品的优惠券,我们还可以在商品类中通过GenericRelation字段定义反向关系

      因此我们在models里面先建立表关系

      from django.db import models
      from django.contrib.contenttypes.models import ContentType
      from django.contrib.contenttypes.fields import (
          GenericForeignKey, GenericRelation
      )
      
      
      class Food(models.Model):
          name = models.CharField(max_length=32)
          price = models.FloatField()
          coupon = GenericRelation(to='Coupon')    # 用来反向查询
      
          def __str__(self):
              return self.name
      
      
      class Clothes(models.Model):
          name = models.CharField(max_length=32)
          price = models.FloatField()
          coupon = GenericRelation(to='Coupon')
      
          def __str__(self):
              return self.name
      
      
      class Coupon(models.Model):
          """优惠券表模型"""
          name = models.CharField(max_length=32)
          # 外键关联ContentType,其实这个关系就是要定位到具体的产品表
          content_type = models.ForeignKey(to=ContentType, on_delete=models.CASCADE)
          # 定位到具体的产品对象,比如苹果的id
          object_id = models.PositiveIntegerField()
          # 不会生成字段,只是用于关联对象,方便我们通过content_object快速查询到具体产品记录,插入数据时,直接content_object = 产品对象即可,就不需要再一一给content_type和object_id这两个字段赋值了
          content_object = GenericForeignKey("content_type", "object_id")
      
          def __str__(self):
              return self.name

      表模型创建完成之后,录入基础数据后我们就可以通过字段进行查询了.

      class QueryView(View):
          def get(self, request):
              from .models import Food, Coupon, Clothes
      
              # 1.根据苹果立减卷找到苹果商品的价格
              # 首先通过苹果立减劵找到coupon的对象
              coupon_obj = Coupon.objects.filter(name='苹果立减卷').first()
              # 再通过content_object字段就可以直接找到对应的food表记录
              pg = coupon_obj.content_object
              # 从记录中取出价格字段
              pg_price = pg.price
      
              # 2.根据苹果找出所有苹果的优惠券
              # 先找到苹果的记录对象
              food_obj = Food.objects.get(name='苹果')
              # 再通过coupon进行反向查询出所有结果,因为是1对多,所以存在多个,用了all()
              coupon_queset = food_obj.coupon.all()
              print(coupon_queset)    # <QuerySet [<Coupon: 苹果满减券>, <Coupon: 苹果立减卷>]>
              
              return HttpResponse("OK")

      注意:ContentType只运用于1对多的关系!!!并且多的那张表中有多个ForeignKey字段。

  • 相关阅读:
    【255】◀▶IEW-Unit20
    【254】◀▶IEW-Unit19
    【253】◀▶IEW-Unit18
    【252】◀▶IEW-Unit17
    [LeetCode] Restore IP Address
    1030
    (Relax 数论1.8)POJ 1284 Primitive Roots(欧拉函数的应用: 以n为模的本原根的个数phi(n-1))
    新知识的遗忘数度真是可怕
    【自由谈】城域网IPv6过渡技术——4v6场景技术总结(1)
    Graph Databases—The NOSQL Phenomenon阅读笔记
  • 原文地址:https://www.cnblogs.com/leixiaobai/p/11258036.html
Copyright © 2020-2023  润新知