关于为何要分页 以及如何在Django+Template架构中如何使用分页,可以参考之前的文章
django 自定义分页类和使用总结[1]
Django RestFramework中分页限制
今天开篇我们先不讲如何使用,我们先说 Django + restframework 实现前后端分离项目开发时, 分页功能使用的
限制?
缘由是之前在开发运维平台的时候,没有正确使用分页功能,导致 自定义的分页在不同情况下,有的不能用,有的能用。最终原因是看实际使用的时候是 继承的那个类
这里根据实例使用类视图
时的继承关系梳理了如下图
Django RestFramework 列表相关类视图继承关系
这里需要重点关注的是:
1、generics.ListAPIView
、generics.ListCreateAPIView
继承自对应的 XXXModelMixin 和 GenericAPIView
2、ModelViewSet
继承自 对应的 XXXModelMixin 和 GenericViewSet
, 而 GenericViewSet又继承自 ViewSetMixin 和 GenericAPIView
我们查阅 RestFramework 的源码知道,关于分页的逻辑实现,是在 GenericAPIView
中实现的,
class GenericAPIView(views.APIView):
... ...
# The style to use for queryset pagination.
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
... ...
@property
def paginator(self):
"""
The paginator instance associated with the view, or `None`.
"""
if not hasattr(self, '_paginator'):
if self.pagination_class is None:
self._paginator = None
else:
self._paginator = self.pagination_class()
return self._paginator
def paginate_queryset(self, queryset):
"""
Return a single page of results, or `None` if pagination is disabled.
"""
if self.paginator is None:
return None
return self.paginator.paginate_queryset(queryset, self.request, view=self)
def get_paginated_response(self, data):
"""
Return a paginated style `Response` object for the given output data.
"""
assert self.paginator is not None
return self.paginator.get_paginated_response(data)
而使用分页的情况下,都会实现list()
方法,而在 ListModelMixin中 就调用了 self.paginate_queryset
和 self.get_paginated_response
class ListModelMixin:
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
所以综上所述:
Django Restframework 中只有继承以下类的情况才可以使用分页
- • viewsets.ModelViewSet
- • generics.ListAPIView
- • generics.ListCreateAPIView
继承 ModelViewSet 实现分页
1、先定义 serializer
为了方便,我们直接使用 ModelSerializer
# appdemo.serializers.py
from rest_framework.serializers import ModelSerializer
from appdemo.models import Post
class PostModelSerializer(ModelSerializer):
class Meta:
model = Post
fields = '__all__'
2、定义viewset
from rest_framework.viewsets import ModelViewSet
# 这里导入
from rest_framework.pagination import PageNumberPagination
from appdemo.models import Post
from appdemo.serializers import PostModelSerializer
class PostModelViewSet(ModelViewSet):
serializer_class = PostModelSerializer
queryset = Post.objects.all()
# 这是视图级别配置分页
pagination_class = PageNumberPagination
使用 RestFramework 分页时,也可以全局配置
# settings.py
REST_FRAMEWORK ={
# 全局配置
# 'DEFAULT_PAGINATION_CLASS':'rest_framework.pagination.PageNumberPagination',
# 但是至少要配置 PAGE_SIZE, 因为系统默认 PAGE_SIZE = NONE 相当于没有分页
'PAGE_SIZE': 3
}
3、配置viewset对应的路由
和 一般类视图不一样的是, ModelViewSet 定义路由使用如下配置
# appdemo.urls.py
from appdemo.views import PostModelViewSet
router = DefaultRouter()
router.register('post', PostModelViewSet)
urlpatterns = [... ...]
urlpatterns += router.urls
然后就可以通过 http://127.0.0.1:8092/demo/post/ 来访问了
继续 ListAPIView 实现分页
# 这里导入
from rest_framework.pagination import PageNumberPagination
from rest_framework.generics import ListAPIView
from appdemo.models import Post
from appdemo.serializers import PostModelSerializer
class PostListAPIView(ListAPIView):
serializer_class = PostModelSerializer
queryset = Post.objects.all()
pagination_class = PageNumberPagination
和上面 ModelViewSet 不同的是, ListAPIView 只是实现了list()
方法,而 ModelViewSet 实现 create()
、list()
、retrieve()
、update()
和 destory()
方法
自定义分页
其实RestFramework 默认提供了三种分页方式
- • PageNumberPagination 普通分页器
支持用户按?page=3&size=10这种更灵活的方式进行查询,这样用户不仅可以选择页码,还可以选择每页展示数据的数量。
但是一般强烈建议要配置 max_page_size, 防止超大数据量的”恶意“查询
- • LimitOffsetPagination 偏移分页器
支持用户按?limit=20&offset=100这种方式进行查询。offset是查询数据的起始点,limit是每页展示数据的最大条数,类似于page_size。
偏移分页器建议设置 max_limit 限制单页查询的数据量
- • CursorPagination 游标分页器
这是DRF提供的加密
分页查询, 不返回具体的页码、大小或者是起始位置,仅支持用户按响应提供的上一页和下一页链接进行分页查询,每页的页码都是加密的。
使用这种方式进行分页需要你的模型有”created”这个字段,否则你要手动指定ordering排序才能进行使用。
了解这三种分页器之后,下面我们开始自定义
# appdemo.pagination.py
from rest_framework.pagination import PageNumberPagination
class CwsPageNumberPagination(PageNumberPagination):
# 覆盖默认的PAGE_SIZE = None
page_size = 5
# 自定义页面大小参数
page_size_query_param = 'size'
# 设置每页最大数据量, 默认为None,就是不限制
max_page_size = 100
# 自定义页码参数
page_query_param = 'page'
一般如上代码基本就满足我们定制化的需求,但是在前后端分离架构中,有时候会 自定义响应
。
如果存在分页的话,从
mixins.ListModelMixin
类中我们知道它是直接使用rest_framework.response.Response
进行返回的。 (参考文章上面提到的源码)
那么在ModelViewSet中就存在 list()
是 按照 rest_framework.response.Response
返回,而其他方法按照 自定义响应
返回,就存在返回格式不统一的问题
所以这个时候,一般需要在自定义分页器中实现get_paginated_response()
方法,该方法中使用自定义响应类返回就行。
具体如何自定义响应,我们后续文章在介绍
使用自定义分页很简单,在全局配置文件settings.py 中配置 REST_FRAMEWORK 的 DEFAULT_PAGINATION_CLASS
值为 自定义分页器 或者 在视图类中配置 pagination_class
。
具体实现之后的效果, 建议大家自己动手尝试下, 效果如下
django restframework 自定义PageNumber分页器
关于 LimitOffsetPagination
偏移分页器 和 CursorPagination
游标分页器
这里直接给出相关代码,感兴趣的可以自己挨个敲代码实现看看效果,更容易加深理解
# appdemo.pagination.py
class CwsLimitOffsetPagination(LimitOffsetPagination):
# 默认页面大小
default_limit = 5
# 最大页面限制
max_limit = 10
# 页面 大小参数和 起始参数
limit_query_param = 'limit'
offset_query_param = 'offset'
class CwsCursorPagination(CursorPagination):
# page_size 和 page_size_query_param 含义和 PageNumberPagination 一样
page_size = 3
page_size_query_param = 'page_size'
# 默认的游标参数
cursor_query_param = 'cursor'
# 如果模型中没有 created 字段,那么就需要 明确配置 oridering
ordering = '-create_date'
今天的知识就介绍到这里。 如果觉得文章对你有用,请不吝点赞 和 关注个人公众号(搜索 全栈运维 或者 DailyJobOps)
引用链接
[1]
django 自定义分页类和使用总结: http://www.colinspace.com/blog/post/53/