分页Pagination
当我们在PC 或者 App 有大量数据需要展示时,可以对数据进行分页展示。这时就用到了分页功能,分页使得数据更好的展示给用户
比如我们有1W+数据要返回给前端,数据量大一次性返回可能会比较慢,前端一次性展示1W+数据也会比较慢,用分页返回数据效果较好
前端分页和后端分页的区别
前端分页
前端分页是一次性把数据全部拿出来进行分页,比如500条数据,一次展示50条,点击下一页再展示下50条,依次类推
优缺点:
前端分页一次性把数据加载出来,翻页过程中不会再对后端发起请求,对服务器压力较小
但是当数据量过大的时候,比较耗费性能,加载速度会比较慢
后端分页
后端分页是根据前端的需求返回对应的数据给客户端,例如一共有500条数据,一次展示50条,前端传参请求一次只返回50条数据
传下一页的page再请求下一页的数据再返回50条
优缺点:
后端分页比较灵活,每次都单独请求数据,对前端性能要求不高, 更易保证数据准确性
因为每次翻页都需要请求后端拿到对应的数据,多用户同时请求会增加后端压力
如何选择分页
数据量比较小的时候建议一次性返回数据在前端进行分页,数据量庞大的话建议服务器分页单次请求单次返回
DRF中的分页
DRF中的分页介绍
在drf框架中允许自定制分页样式,可以设置每页显示的数量,并且支持以下操作
- 将分页链接作为响应内容的一部分
- 响应头中包含分页链接,比如Content-Range或Link
drf中使用通用视图或者视图集的时候会自动执行分页,如果使用的常规的APIView等,需要自己调用分页API
- 可以通过将分页类设置None来选择是否关闭分页功能
自有分页VIew源码示例
我们拿ListAPIView举例,ListAPIView继承mixins.ListModelMixin和GenericAPIView,下述代码可以看出源码本身包含了分页功能,如果使用常规VIew则需要自己实现对应逻辑
# ListModelMixin源码 如果是常规view要实现分页,在视图中实现下述代码即可
class ListModelMixin:
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
# self.paginate_queryset对原有的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)
分页的settings设置
DEFAULT_PAGINATION_CLASS和PAGE_SIZE必须同时设置,如果不设置或者只设置一个,就相当于是None不分页
REST_FRAMEWORK = {
# drf的分页类位于rest_framework.pagination中
"DEFAULT_PAGINATION_CLASS":"",# 全局默认的指定分页类,如果视图想单独指定,与权限一样在view中单独设置
"PAGE_SIZE": #每一页显示多少数据
}
DRF中的分页类使用详解
BasePagination:
分页的基类,与权限、限流等原理一样,写好模版待后续具体逻辑继承,略过
PageNumberPagination:
继承BasePagination,这种分页接收前端请求的页码参数,参数是第几页则请求第几页的数据,例如前端请求page=10,则返回第10页数据
# settings.py
REST_FRAMEWORK = {
# 指定分页类为PageNumberPagination
"DEFAULT_PAGINATION_CLASS":"rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE":3 #每一页显示3条数据
}
如果是基于通用视图或者视图集,当前已经实现了分页功能
- 图1是没有设置分页时候的请求,返回全部6条数据
- 图2设置了分页后,默认不传page参数的请默认返回了第一页的数据
- 图3请求地址后加了page参数,page=2,则返回的是第二页的数据
- 也可以看出设置分页后response多了三个参数count是接口一共的数量,next是下一页的地址,previous是上一页地址
PageNumberPagination自定义分页类
上述方式每页请求的数据量是根据settings设置PAGE_SIZE写死的数量进行返回,如果我们想要根据传参定义第几页返回多少条数据,可以自定义分页类
from rest_framework import pagination
# 继承分页类
class PublicPagination(pagination.PageNumberPagination):
page_size = 2 # 每页显示的默认数据个数
page_query_param = 'page' # 页号,第几页的参数 ,比如定义为pages,那么请求分页的参数就应该是pages
page_size_query_param = 'page_size' # 自己指定每页显示多少个数
max_page_size = 100 # 最大允许设置的每页显示的数量
# last_page_strings用于指定表示请求最后一页的参数
# page=last的时候会直接到最后一页
# 如果不改参数的话,可以不用设置,不设置的话默认参数就是last
last_page_strings = 'last'
'''
自定义分页类
通过page_size指定默认的每页数据量,
page_size_query_param指定每页自定义的数据量的参数,如果请求page_size=4,则每页显示4个,否则走默认的2
max_page_size是允许设置的每页最大的数据量
'''
# 导入自定义的分页类
from .pagination import PublicPagination
class CategoryViewSet(ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
# 指定自定义的分页类,与权限、限量等不同,每个视图只允许指定一个分页类
# 对指定视图设置分页类,会覆盖settings中默认的全局配置
pagination_class = PublicPagination
上述代码实现对应功能后,继续请求接口
- 图1page=2,请求第二页的数据,返回2条,因为自定义分页类中的page_size覆盖了全局的page_size=3
- 图2没有指定page参数,默认返回第一页的两条数据
- 图3指定page=2,page_size=1,返回第二页的数据,用page_size指定1覆盖代码中设置的2,所以只显示1条数据
LimitOwsetPagination:
这个分页类就类似于查找数据库中查找的语法
比如数据库中 Select * from table limit 100,300,从第101条开始,取300的数据
在分页中limit用于指定取多少条数据,offset用于指定从多少条开始,与sql一样,offse+1开始
#view
class CategoryViewSet(ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
# 给该指定LimitOffsetPagination分页类,也可以在settings指定全局
pagination_class = LimitOffsetPagination
下面使用LimitOwsetPagination分页类请求接口
- 图1,没有传参返回了3条数据是因为在settings中page_size是3,默认从第一条开始取3条数据,下一页依次向后取3条
- 图2是将settings中的page_size删除掉,没有传递limit和offset参数就默认全部返回
- 图3是传入limit=2,offset=2,从第三条开始取2条数据
LimitOwsetPagination自定义分页类
# 继承LimitOffsetPagination分页类
class PublicLimitOffsetPagination(pagination.LimitOffsetPagination):
default_limit = 2 # 用于指定默认的limit数量
limit_query_param = 'lm' # 指定请求时候对应的limit参数名,如果是lm那么传参就是lm=
offset_query_param = 'of' ## 指定请求时候对应的offset参数名,如果是lm那么传参就是of=
max_limit = 4 # 最大的limit可设置数量
CursorPagination:
基于光标的分页
CursorPagination分页类说明
- 显示一个正向和反向的控件,不允许我们任意导航到任意位置
- 要求结果集中有应该唯一的不变的排序方式
- 可以确保客户端在翻页时不会看到同一对象两次,即使在分页的同时有数据插入
- 对于超级大的数据量,使用前两个分页可能会效率低下,基于光标的分页具有固定的时间属性,不会因为数据变大而减慢
- 基于光标的分页的排序方式默认是使用created排序,如果使用默认排序则模型必须有created时间戳字段,首先显示最近添加的数据
- 可以覆盖pagination类的ordering属性,或者使用OrderingFilter过滤器类和CursorPagination来修改排序
- 使用时要注意应该有一个唯一不变的值,例如默认的created
#settings设置
REST_FRAMEWORK = {
# 指定CursorPagination分页类
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.CursorPagination",
"PAGE_SIZE": 2 # 每一页显示3条数据
}
通过光标进行的分页,可以看出next后面的参数进行了加密,无法直接通过传递参数进行跳转,只能一页一页点击
自定义CursorPagination
#继承CursorPagination分页类
class PublicCursorPagination(pagination.CursorPagination):
ordering = '-created' #通过什么进行排序,默认created
page_size = 3 # 每页数据量
cursor_query_param = 'cs' #请求的参数字段,默认cursor