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的GET和HEAD请求
限流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,hour和day作为单位时间
-
单独配置
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 keyclass 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' } }用户请求
ContactListView或ContactDetailView将被限制到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=denvercoder9class 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 API 和 admin API中支持以HTML控件的形式显示过滤器。

-
设置过滤引擎
-
全局默认配置
DEFAULT_FILTER_BACKENDSREST_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
-
DjangoFilterBackend:django-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_fields:接收文本类型字段的名称列表,比如CharField或TextField。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',但可以通过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 ,以一种干净、简单和可配置的方式在
ModelViewSet的Queryset上应用过滤器。,它还支持对传入的查询参数及其值进行验证
分页Pagination
分页实现
-
当使用对应的通用
views或viewsets时分页将自动实现,通过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',)template:browsable 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:该值是一个数值,如果设置该值,指示客户端可能请求的最大允许限制。默认为Nonetemplate:默认为"rest_framework/pagination/numbers.html"
-
-
CursorPagination:显示一个不透明的“游标”指示器,客户端可以使用它来浏览结果集。-
注意:
- 这种分页样式只显示正向和反向控件,不允许客户端导航到任意位置。
- 基于游标的分页要求结果集中的项有一个惟一的、不变的顺序。这种顺序通常可能是记录上的创建时间戳,因为这样可以根据一致的顺序进行分页;正确使用时,可以确保客户端在分页记录时不会看到相同的项,即使在分页过程中其他客户端插入了新项
- 支持使用大型数据集。对于非常大的数据集,使用基于偏移量的分页样式进行分页可能会效率低下或无法使用。基于游标的分页方案具有固定时间属性,不会随着数据集大小的增加而变慢
- 默认是按
-created进行排序。该模型实例上必须有一个created时间戳字段,并将呈现一个“timeline”样式的分页视图,首先是最近添加的项。
-
正确使用游标分页应该有一个满足以下要求的排序字段 :
- 应该是不变的值,比如时间戳或在创建时只设置一次的其他字段
- 应该是唯一的,或者几乎是唯一的。毫秒精度时间戳就是一个很好的例子。这种游标分页的实现使用了一种智能的“位置加偏移”样式,允许它正确地支持非严格惟一的值作为排序
- 应该是非空值,可以强制为字符串
- 不应该是浮动类型
double的值,精度误差容易导致不正确的结果,通常使用小数decimals代替 - 字段应该有一个数据库索引
-
设置:全局或单个
REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.CursorPagination', 'PAGE_SIZE': 100 } -
可继承后重写的属性:用在自定义分页样式时
page_sizecursor_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输出
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头提供分页

浙公网安备 33010602011771号