Django rest framework(四)-缓存、限流、过滤、分页

本文为学习官方文档时所做笔记,可以看做是官方文档的全文翻译

缓存Caching

  • REST框架中的缓存可以很好地与Django中提供的缓存工具配合工作

    from django.utils.decorators import method_decorator
    from django.views.decorators.cache import cache_page
    from django.views.decorators.vary import vary_on_cookie
    
    from rest_framework.response import Response
    from rest_framework.views import APIView
    from rest_framework import viewsets
    
    
    class UserViewSet(viewsets.ViewSet):
    
        # Cache requested url for each user for 2 hours
        @method_decorator(cache_page(60*60*2))
        @method_decorator(vary_on_cookie)
        def list(self, request, format=None):
            content = {
                'user_feed': request.user.get_user_feed()
            }
            return Response(content)
    
    
    class PostView(APIView):
    
        # Cache page for the requested url
        @method_decorator(cache_page(60*60*2))
        def get(self, request, format=None):
            content = {
                'title': 'Post title',
                'body': 'Post content'
            }
            return Response(content)
    

    注意:cache_page只缓存响应码为200的GETHEAD请求

限流Throttling

基本信息

  • 功能类似于permissions,用来决定是否应该对请求进行授权,用于控制客户端可以对API发出的请求的速率。

  • 与权限一样,可以使用多个Throttles ;除了可以对请求速率进行限制还可以对存储服务的带宽进行限制,也可以对某些服务(如付费服务等)进行访问次数的限制

  • 与权限和身份验证一样,REST框架中的Throttles总是定义为列表。

  • 运行视图函数的主体之前,会检查列表中的每个Throttle。如果任一Throttle检查失败,都会引发exceptions.Throttled ,视图将不再往下运行。

  • 配置

    • 全局配置:

      REST_FRAMEWORK = {
          'DEFAULT_THROTTLE_CLASSES': [
              'rest_framework.throttling.AnonRateThrottle',
              'rest_framework.throttling.UserRateThrottle'
          ],
          'DEFAULT_THROTTLE_RATES': {
              'anon': '100/day',
              'user': '1000/day'
          }
      }
      
      • DEFAULT_THROTTLE_CLASSES:默认Throttle引擎
      • DEFAULT_THROTTLE_RATES:速率限制,可以使用second, minute, hourday 作为单位时间
    • 单独配置

      from rest_framework.response import Response
      from rest_framework.throttling import UserRateThrottle
      from rest_framework.views import APIView
      
      class ExampleView(APIView):
          throttle_classes = [UserRateThrottle]
      
          def get(self, request, format=None):
              content = {
                  'status': 'request was permitted'
              }
              return Response(content)
      

      使用装饰器

      @api_view(['GET'])
      @throttle_classes([UserRateThrottle])
      def example_view(request, format=None):
          content = {
              'status': 'request was permitted'
          }
          return Response(content)
      
  • 工作原理:请求头中的X-Forwarded-For 和WSGI变量REMOTE_ADDR 用于标识被Throttle的客户端IP地址

常用API

  • AnonRateThrottle :如果希望对每个用户进行简单的全局速率限制,则适用,用来限制未经身份验证的用户 ,传入请求的IP地址用于生成用于节流的惟一标识,允许的请求速率由以下选项之一确定(按优先顺序)

    • rate 属性,通过重写AnonRateThrottle 类并设置该rate属性来设置
    • DEFAULT_THROTTLE_RATES
  • UserRateThrottle :限制用户的请求速率 ,用户的id用于生成用于Throttle的惟一标识。未经身份验证的请求将退回到使用传入请求的IP地址来生成一个惟一的标识来进行控制。 允许的请求速率由以下选项之一确定(按优先顺序)

    • rate 属性,通过重写UserRateThrottle 类并设置该rate属性来设置
    • DEFAULT_THROTTLE_RATES

    一个API可能同时有多个UserRateThrottles ,为此,重写UserRateThrottle并为每个类设置唯一的“范围”。

    class BurstRateThrottle(UserRateThrottle):
        scope = 'burst'
    
    class SustainedRateThrottle(UserRateThrottle):
        scope = 'sustained'
    

    接着配置

    REST_FRAMEWORK = {
        'DEFAULT_THROTTLE_CLASSES': [
            'example.throttles.BurstRateThrottle',
            'example.throttles.SustainedRateThrottle'
        ],
        'DEFAULT_THROTTLE_RATES': {
            'burst': '60/min',
            'sustained': '1000/day'
        }
    }
    
  • ScopedRateThrottle :可用于限制对API特定部分的访问,只有在被访问的视图包含throttle_scope属性时,此 API才对该视图生效。通过将请求的“范围”与惟一的用户id或IP地址连接起来,将形成惟一的throttle key

    class ContactListView(APIView):
        throttle_scope = 'contacts'
        ...
    
    class ContactDetailView(APIView):
        throttle_scope = 'contacts'
        ...
    
    class UploadView(APIView):
        throttle_scope = 'uploads'
        ...
    

    配置

    REST_FRAMEWORK = {
        'DEFAULT_THROTTLE_CLASSES': [
            'rest_framework.throttling.ScopedRateThrottle',
        ],
        'DEFAULT_THROTTLE_RATES': {
            'contacts': '1000/day',
            'uploads': '20/day'
        }
    }
    

    用户请求ContactListViewContactDetailView 将被限制到1000/day;请求UploadView 将被限制到20/day

自定义Throttle

  • 重写BaseThrottle 并实现allow_request(self, request, view) 方法,如果允许请求,该方法应该返回True,否则返回False ;也可以选择性的实现.wait() 方法,该方法返回的是下一个请求之前建议的等待秒数,只有当allow_request()返回了False时,wait()方法才会被调用,如果实现了wait()方法并对请求进行了限制,那么响应头中将包含一个Retry-After属性。

    import random
    
    class RandomRateThrottle(throttling.BaseThrottle):
        def allow_request(self, request, view):
            return random.randint(1, 10) != 1
    

过滤Filtering

REST框架的通用列表视图的默认行为是返回模型管理器的整个查询集,但通常都需要对整个查询集进行限制。一般情况下对重写get_queryset() ,重写此方法允许您以多种不同的方式自定义视图返回的查询集。

过滤

  • 根据当前登陆用户request.user 进行数据过滤

    from myapp.models import Purchase
    from myapp.serializers import PurchaseSerializer
    from rest_framework import generics
    
    class PurchaseList(generics.ListAPIView):
        serializer_class = PurchaseSerializer
    
        def get_queryset(self):
            """
            This view should return a list of all the purchases
            for the currently authenticated user.
            """
            user = self.request.user
            return Purchase.objects.filter(purchaser=user)
    
  • 根据URL的传参进行过滤

    #URL
    url('^purchases/(?P<username>.+)/$', PurchaseList.as_view())
    
    #视图部分:根据URL传进来的username进行过滤
    class PurchaseList(generics.ListAPIView):
        serializer_class = PurchaseSerializer
    
        def get_queryset(self):
            """
            This view should return a list of all the purchases for
            the user as determined by the username portion of the URL.
            """
            username = self.kwargs['username']
            return Purchase.objects.filter(purchaser__username=username)
    
  • 根据URL的查询参数进行过滤

    #查询URL
    http://example.com/api/purchases?username=denvercoder9
    
    class PurchaseList(generics.ListAPIView):
        serializer_class = PurchaseSerializer
    
        def get_queryset(self):
            """
            Optionally restricts the returned purchases to a given user,
            by filtering against a `username` query parameter in the URL.
            """
            queryset = Purchase.objects.all()
            username = self.request.query_params.get('username', None)
            if username is not None:
                queryset = queryset.filter(purchaser__username=username)
            return queryset
    

通用过滤

除了能够覆盖默认的查询集之外,REST框架还包括对通用过滤后端的支持,它允许您轻松地构造复杂的搜索和过滤器。 另外,在browsable APIadmin API中支持以HTML控件的形式显示过滤器。

Filter Example

  • 设置过滤引擎

    • 全局默认配置DEFAULT_FILTER_BACKENDS

      REST_FRAMEWORK = {
          'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
      }
      
    • 通用视图单独设置

      import django_filters.rest_framework
      from django.contrib.auth.models import User
      from myapp.serializers import UserSerializer
      from rest_framework import generics
      
      class UserListView(generics.ListAPIView):
          queryset = User.objects.all()
          serializer_class = UserSerializer
          filter_backends = [django_filters.rest_framework.DjangoFilterBackend]
      

    注意:django-filter是第三方包

  • 过滤和对象查找 :如果过滤器后端为视图单独配置,那么除了用于筛选列表视图外,它还将用于筛选满足条件的用单个对象

    http://example.com/api/products/4675/?category=clothing&max_price=10.00
    

    例如,上面的URL查询将返回一个满足条件的对象而不是一个查询集

  • 重写初始queryset:您可以同时使用get_queryset() 和通用过滤

    #product中有一个关联user的多对多关系字段,名为purchase
    
    class PurchasedProductsList(generics.ListAPIView):
        """
        Return a list of all the products that the authenticated
        user has ever purchased, with optional filtering.
        返回经过身份验证的用户曾经购买的所有产品的列表,并带有可选的ProductFilter过滤。
        """
        model = Product
        serializer_class = ProductSerializer
        filterset_class = ProductFilter
    
        def get_queryset(self):
            user = self.request.user
            return user.purchase_set.all()
    

常用API

  • DjangoFilterBackenddjango-filter提供的过滤引擎,为REST框架定制高度的字段过滤

    • 安装

      pip install django-filter
      
    • 配置:前文已经说明

    • 过滤:简单的基于相同的过滤

      class ProductList(generics.ListAPIView):
          queryset = Product.objects.all()
          serializer_class = ProductSerializer
          filter_backends = [DjangoFilterBackend]
          filterset_fields = ['category', 'in_stock'] #自动创建一个FilterSet类
      

      查询URL

      http://example.com/api/products?category=clothing&in_stock=True
      
    • 对于更复杂的查询,可以为每个视图指定一个FilterSet 类,请阅读官方文档django-filter documentation

  • SearchFilter:提供简单的基于单个查询参数的搜索 ,并在browsable API中提供一个搜索框

    Search Filter

    • search_fields:接收文本类型字段的名称列表,比如CharFieldTextField

      from rest_framework import filters
      
      class UserListView(generics.ListAPIView):
          queryset = User.objects.all()
          serializer_class = UserSerializer
          filter_backends = [filters.SearchFilter]
          search_fields = ['username', 'email']
      

      这将允许客户端通过查询筛选列表中的项目,例如:

      http://example.com/api/users?search=russell
      

      也可以设置外键字段

      search_fields = ['username', 'email', 'profile__profession']
      

      默认情况下,搜索将使用不区分大小写的部分匹配 。查询参数可能包含多个搜索项,这些搜索项应该由空格和/或逗号分隔。如果使用多个搜索词,那么只有当提供的所有词都匹配时,才会在列表中返回对象

    • 搜索行为可以通过在search_fields的元素前面加上不同的字符来限制

      • ^:以此为开头
      • =:精确匹配
      • @:全文搜索(目前仅以Mysql作为数据库时才可使用)
      • $:正则表达式匹配
      search_fields = ['=username', '=email']
      
    • 更改搜索参数search可以通过设置SEARCH_PARAM

    • 根据请求内容动态更改查询:子类化SearchFilter 并重写get_search_fields() 方法

      #如果title_only在查询参数中那么就只在title字段中进行查询
      from rest_framework import filters
      
      class CustomSearchFilter(filters.SearchFilter):
          def get_search_fields(self, view, request):
              if request.query_params.get('title_only'):
                  return ['title']
              return super(CustomSearchFilter, self).get_search_fields(view, request)
      
    • 更多信息,请阅读Django documentation.

  • OrderingFilter:支持简单的查询参数控制的结果排序

    Ordering Filter

    • 默认情况下,排序参数名为 'ordering' ,但可以通过ORDERING_PARAM 进行设置

      #排序请求
      http://example.com/api/users?ordering=username
          
      #反向排序请求
      http://example.com/api/users?ordering=-username
          
      #多种排序限制
      http://example.com/api/users?ordering=account,username
      
    • ordering_fields :指定参与排序的字段

      class UserListView(generics.ListAPIView):
          queryset = User.objects.all()
          serializer_class = UserSerializer
          filter_backends = [filters.OrderingFilter]
          ordering_fields = ['username', 'email']
      

      如果您没有在视图上指定ordering_fields属性,那么过滤器类将默认允许用户在serializer_class属性指定的序列化器上筛选任何可读字段,这是极不安全的。 如果你确信视图使用的查询集不包含任何敏感数据,你也可以通过使用特殊值__all__来明确指定视图应该允许对任何模型字段或查询集聚合进行排序

      class BookingsListView(generics.ListAPIView):
          queryset = Booking.objects.all()
          serializer_class = BookingSerializer
          filter_backends = [filters.OrderingFilter]
          ordering_fields = '__all__'
      
    • 指定默认排序:通常,您可以通过在初始的queryset上设置order_by来控制它的初始排序,同时也可以在视图使用ordering参数来更改顺序 ,当请求URL中带'ordering'参数时使用视图中指定的排序方式,但请求URL中不带'ordering'参数时就使用queryset上设置的order_by顺序,这在某些情况下是十分游泳的

自定义通用过滤

  • 重写BaseFilterBackend 并实现filter_queryset(self, request, queryset, view) 方法,该方法应该返回一个新的过滤后的查询集。除了允许客户端执行搜索和排序等动作之外,通用过滤后端还可以用于限制任何给定的请求、限制用户可见的数据

  • 例子:

    #限制用户只能看到他们创建的对象
    class IsOwnerFilterBackend(filters.BaseFilterBackend):
        """
        Filter that only allows users to see their own objects.
        """
        def filter_queryset(self, request, queryset, view):
            return queryset.filter(owner=request.user)
    

    我们可以通过在视图上覆盖get_queryset()来实现相同的行为,但是使用筛选器后端可以更容易地将此限制添加到多个视图,或者在整个API中应用它。

第三方包

  • django-rest-framework-filters package ,可以与DjangoFilterBackend 一同生效,允许您轻松地跨关系创建筛选器,或为给定字段创建多个筛选器查找类型
  • djangorestframework-word-filter ,作为filters.SearchFilter 的替代品
  • django-url-filter ,提供一种安全、友好的、易读的方式来根据URLs进行数据过滤
  • drf-url-filter ,以一种干净、简单和可配置的方式在 ModelViewSetQueryset上应用过滤器。,它还支持对传入的查询参数及其值进行验证

分页Pagination

分页实现

  • 当使用对应的通用viewsviewsets 时分页将自动实现,通过pagination_class属性控制分页开闭; 但使用常规的APIView 时必须手动设置分页API

  • 分页配置

    • 手动配置分页样式

      #继承PageNumberPagination,自定义分页样式
      class LargeResultsSetPagination(PageNumberPagination):
          page_size = 1000
          page_size_query_param = 'page_size'
          max_page_size = 10000
      
      class StandardResultsSetPagination(PageNumberPagination):
          page_size = 100
          page_size_query_param = 'page_size'
          max_page_size = 1000
      
    • 全局分页样式配置

      • 使用默认分页样式

        REST_FRAMEWORK = {
            'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
            'PAGE_SIZE': 100
        }
        
      • 使用自定义分页样式

        REST_FRAMEWORK = {
            'DEFAULT_PAGINATION_CLASS': 'apps.core.pagination.StandardResultsSetPagination'
        }
        
    • 单个视图设置:使用pagination_class属性在单个视图上设置分页样式。但通常会在整个网站或应用中使用相同的分页样式

      class BillingRecordsView(generics.ListAPIView):
          queryset = Billing.objects.all()
          serializer_class = BillingRecordsSerializer
          pagination_class = LargeResultsSetPagination #设置为需要的分页样式
      

常用API

  • PageNumberPagination :在请求查询参数中接受单个页码。

    • 请求:

      GET https://api.example.org/accounts/?page=4
      
    • 响应

      {
          "count": 1023
          "next": "https://api.example.org/accounts/?page=5",
          "previous": "https://api.example.org/accounts/?page=3",
          "results": [
             …
          ]
      }
      
    • 设置

      REST_FRAMEWORK = {
          'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
          'PAGE_SIZE': 100
      }
      
    • 可继承后重写的属性:用在自定义分页样式时

      • django_paginator_class :默认为原生的django.core.paginator.Paginator ,通常不需要更改
      • page_size :整型,每页显示的数据项数量
      • page_query_param :字符串类型,分页控件的查询参数的名称
      • page_size_query_param :指示查询参数的名称,该参数允许客户端通过每个请求设置不同的页面大小
      • max_page_size :配合page_size_query_param使用,设置允许请求的最大的页面大小
      • last_page_strings :字符串值的列表或元组,指示可以与page_query_param一起使用的值,以请求分页中的最终页面 ,默认为('last',)
      • templatebrowsable API 在呈现分页控件时要使用的模板名称,可以被重写来修改呈现样式,或者设置为None来完全禁用HTML分页控件 ,默认为"rest_framework/pagination/numbers.html"
  • LimitOffsetPagination :使用偏移量offset和限制项limit来决定分页样式

    • 请求:

      GET https://api.example.org/accounts/?limit=100&offset=400
      
    • 响应

      HTTP 200 OK
      {
          "count": 1023
          "next": "https://api.example.org/accounts/?limit=100&offset=500",
          "previous": "https://api.example.org/accounts/?limit=100&offset=300",
          "results": [
             …
          ]
      }
      
    • 设置:全局和单个视图都可以

      REST_FRAMEWORK = {
          'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination'
      }
      

      您也可以设置一个PAGE_SIZE键,如果还使用了PAGE_SIZE参数,那么limit查询参数将是可选的,客户端可以省略。

    • 可继承后重写的属性:用在自定义分页样式时

      • default_limit :一个数值,指示在查询参数中客户端未提供limit的情况下使用的限制。默认值与PAGE_SIZE设置键相同
      • limit_query_param :指limit查询参数名称的字符串值 ,默认为'limit'
      • offset_query_param :指offset查询参数名称的字符串值 ,默认为'offset'
      • max_limit :该值是一个数值,如果设置该值,指示客户端可能请求的最大允许限制。默认为None
      • template :默认为"rest_framework/pagination/numbers.html"
  • CursorPagination:显示一个不透明的“游标”指示器,客户端可以使用它来浏览结果集。

    • 注意:

      • 这种分页样式只显示正向和反向控件,不允许客户端导航到任意位置。
      • 基于游标的分页要求结果集中的项有一个惟一的、不变的顺序。这种顺序通常可能是记录上的创建时间戳,因为这样可以根据一致的顺序进行分页;正确使用时,可以确保客户端在分页记录时不会看到相同的项,即使在分页过程中其他客户端插入了新项
      • 支持使用大型数据集。对于非常大的数据集,使用基于偏移量的分页样式进行分页可能会效率低下或无法使用。基于游标的分页方案具有固定时间属性,不会随着数据集大小的增加而变慢
      • 默认是按-created进行排序。该模型实例上必须有一个created时间戳字段,并将呈现一个“timeline”样式的分页视图,首先是最近添加的项。
    • 正确使用游标分页应该有一个满足以下要求的排序字段 :

      • 应该是不变的值,比如时间戳或在创建时只设置一次的其他字段
      • 应该是唯一的,或者几乎是唯一的。毫秒精度时间戳就是一个很好的例子。这种游标分页的实现使用了一种智能的“位置加偏移”样式,允许它正确地支持非严格惟一的值作为排序
      • 应该是非空值,可以强制为字符串
      • 不应该是浮动类型double的值,精度误差容易导致不正确的结果,通常使用小数decimals代替
      • 字段应该有一个数据库索引
    • 设置:全局或单个

      REST_FRAMEWORK = {
          'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.CursorPagination',
          'PAGE_SIZE': 100
      }
      
    • 可继承后重写的属性:用在自定义分页样式时

      • page_size
      • cursor_query_param :指示“游标”查询参数名称的字符串值 ,默认为'cursor'
      • ordering :应该是一个字符串或字符串列表,指示将应用基于游标的分页的字段 ;比如 ordering = 'slug' 或者默认的 ordering = '-created' ,这个值也可以通过在视图上使用OrderingFilter过滤器来覆盖
      • template :默认为"rest_framework/pagination/previous_and_next.html"

自定义分页样式

  • 步骤:子类化pagination.BasePagination 或其子类并重写paginate_queryset(self, queryset, request, view=None)get_paginated_response(self, data) 方法

    • paginate_queryset(self, queryset, request, view=None):传递初始的queryset,并且应该返回一个只包含被请求页面中的数据的可迭代对象。
    • get_paginated_response :传递序列化的页面数据,并应该返回一个响应实例。
  • 举例:假设我们想用一种修改过的格式替换默认的分页输出样式,这种格式在嵌套的links中包含下一个和上一个链接。我们可以像这样指定一个自定义分页类:

    class CustomPagination(pagination.PageNumberPagination):
        def get_paginated_response(self, data):
            return Response({
                'links': {
                    'next': self.get_next_link(),
                    'previous': self.get_previous_link()
                },
                'count': self.page.paginator.count,
                'results': data
            })
    '''
    注意,如果您关心在browsable API中响应中的键的顺序,那么在构造分页响应体时可以选择使用OrderedDict,这是可选的。
    '''
    

    然后需要在settings中设置

    REST_FRAMEWORK = {
        'DEFAULT_PAGINATION_CLASS': 'my_project.apps.core.pagination.CustomPagination',
        'PAGE_SIZE': 100
    }
    

    browsable API输出

    Link Header

HTML分页控件

  • 默认情况下,使用分页类将使用一个HTML分页控件来控制browsable API中的显示 ,框架中内置了
    • rest_framework/pagination/numbers.html :可定义到任意页数
    • rest_framework/pagination/previous_and_next.html :只能通过上一页和下一页进行控制
  • 通过子类化对应的分页类并设置template来自定义HTML分页控件

第三方包

  • DRF-extensions :包内含有一个PaginateaByMaxMixin (mixin类)允许API客户端指定?page_size=max来获得允许的最大页面大小
  • drf-proxy-pagination :包内含有一个ProxyPagination 分页类,允许通过使用查询参数选择不同的分页类
  • django-rest-framework-link-header-pagination :包内含有一个LinkHeaderPagination 分页类,通过HTTPLink头提供分页
posted @ 2020-07-02 18:01  言兴  阅读(281)  评论(0)    收藏  举报