手摸手,带你用Django REST Framework撸接口系列十一(分页篇)
当你的数据库数据量非常大时,如果一次将这些数据查询出来, 必然加大了服务器内存的负载,降低系统的运行速度。一种更好的方式是将数据分段展示给用户。如果用户在展示的分段数据中没有找到自己的内容,可以通过指定页码或翻页的方式查看更多数据,直到找到自己想要的内容为止。
Django REST Framework系列文章
- 手摸手,带你用Django REST Framework撸接口系列一(基础篇)
- 手摸手,带你用Django REST Framework撸接口系列二(序列化器篇)
- 手摸手,带你用Django REST Framework撸接口系列三(视图篇)
- 手摸手,带你用Django REST Framework撸接口系列四(渲染器篇)
- 手摸手,带你用Django REST Framework撸接口系列五(路由篇)
- 手摸手,带你用Django REST Framework撸接口系列六(认证篇)
- 手摸手,带你用Django REST Framework撸接口系列七(权限篇)
- 手摸手,带你用Django REST Framework撸接口系列八(限流篇)
- 手摸手,带你用Django REST Framework撸接口系列九(过滤篇)
- 手摸手,带你用Django REST Framework撸接口系列十(排序篇)
- 手摸手,带你用Django REST Framework撸接口系列十一(分页篇)
- 手摸手,带你用Django REST Framework撸接口系列十二(异常处理篇)
- 手摸手,带你用Django REST Framework撸接口系列十三(自动生成接口文档篇)
DRF提供的分页类
Django REST Framework提供了3种分页类,接下来我们会分别进行演示。
- PageNumberPagination:普通分页器。支持用户按?page=3&size=10这种更灵活的方式进行查询,这样用户不仅可以选择页码,还可以选择每页展示数据的数量。通常还需要设置max_page_size这个参数限制每页展示数据的最大数量,以防止用户进行恶意查询(比如size=10000), 这样一页展示1万条数据将使分页变得没有意义。
- LimitOffsetPagination:偏移分页器。支持用户按?limit=20&offset=100这种方式进行查询。offset是查询数据的起始点,limit是每页展示数据的最大条数,类似于page_size。当你使用这个类时,你通常还需要设置max_limit这个参数来限制展示给用户数据的最大数量。
- CursorPagination类:加密分页器。这是DRF提供的加密分页查询,仅支持用户按响应提供的上一页和下一页链接进行分页查询,每页的页码都是加密的。使用这种方式进行分页需要你的模型有”created”这个字段,否则你要手动指定ordering排序才能进行使用。
PageNumberPagination
此分页样式接受请求查询参数中的单个数字页码。
设置
全局配置
为了全局启用 PageNumberPagination
样式,请使用以下配置,并根据需要设置 PAGE_SIZE
:
1 | REST_FRAMEWORK = { |
局部配置
在 GenericAPIView
子类中,还可以设置 pagination_class
属性,以在每个视图的基础上选择 PageNumberPagination
。
Configuration (配置)
PageNumberPagination
类包含许多属性,通过重写其中的一些属性可以修改分页属性
为了设置这些属性,应当重写 PageNumberPagination
类,然后如上所述启用自定义分页类。
django_paginator_class
—— 要使用的django paginator类。默认为django.core.paginator.Paginator
,适用于大多数情况。page_size
—— 表示每页条数。如果设置,则覆盖PAGE_SIZE
设置。其默认值与PAGE_SIZE
设置键相同。page_query_param
—— 一个字符串值,指示用于分页控件的查询参数的名称。page_size_query_param
—— 如果设置,这是一个字符串值,指示允许客户端根据每个请求设置页面显示条数的查询参数的名称。默认值为None
,表示客户端可能无法控制请求的页面大小(?size=??)。max_page_size
—— 如果设置,这是一个数字值,指示允许的最大请求页面条数。此属性仅在设置了page_size_query_param
时有效。last_page_strings
—— 一个字符串值的列表或元组,指示可与page_query_param
一起使用的值,以请求集合中的最后一页。默认为('last',)
。template
—— 在可浏览API中呈现分页控件时要使用的模板名称。可以重写以修改呈现样式,或设置为None
以完全禁用HTML分页控件。默认为"rest_framework/pagination/numbers.html"
。
但是如果你希望用户按?page=3&size=10
这种更灵活的方式进行查询,你就要进行个性化定制。在实际开发过程中,定制比使用默认的分页类更常见,具体做法如下。
自定义普通分页器
创建自定义分页器
我们自定义了一个MyPageNumberPagination
类,该类继承了PageNumberPagination
类。我们通过page_size
设置了每页默认展示数据的条数,通过page_size_query_param
设置了每页size的参数名以及通过max_page_size
设置了每个可以展示的最大数据条数。
1 | from rest_framework.pagination import PageNumberPagination |
使用自定义的分页类
1 | class ArticleView(viewsets.ModelViewSet): |
展示效果如下:
当然定制分页类不限于指定page_size
和max_page_size
这些属性,你还可以改变响应数据的输出格式。比如我们这里希望把next和previous放在一个名为links
的key里,我们可以修改MyPageNumberPagination
类,重写get_paginated_response
方法:
1 | from rest_framework.pagination import PageNumberPagination |
再次测试展示效果:
注意:重写get_paginated_response
方法非常有用,你还可以给分页响应数据传递额外的内容,比如code状态码等等。
前面的例子中我们只在单个基于类的视图或视图集中使用到了分页类,你还可以修改settings.py
全局使用你自定义的分页类,如下所示。展示效果是一样的,我们就不详细演示了。
1 | REST_FRAMEWORK = { |
LimitOffsetPagination
这种分页样式反映了查找多个数据库记录时使用的语法。客户端包括“limit”和“offset”查询参数。limit指示要返回的项目的最大数目,这相当于其他样式中的 page_size
。offset指示查询相对于完整的未分页项的起始位置。
Setup (设置)
全局配置
为了全局启用 LimitOffsetPagination
样式,请使用以下配置:
1 | REST_FRAMEWORK = { |
可选地,您也可以设置 PAGE_SIZE
键。如果还使用了 PAGE_SIZE
参数,那么 limit
query参数将是可选的,并且可以被客户端忽略。
局部配置
在 GenericAPIView
子类中,您还可以设置 pagination_class
属性,以便在每个视图的基础上选择 LimitOffsetPagination
。
Configuration (配置)
LimitOffsetPagination
类包含许多可以重写以修改分页样式的属性。
为了设置这些属性,需要重写 LimitOffsetPagination
类,然后如上所述启用自定义分页类。
default_limit
—— 数值,指示在客户端未在查询参数中提供限制时使用的限制。默认为与PAGE_SIZE
设置键相同的值。limit_query_param
—— 字符串值,指示“limit”查询参数的名称。默认为'limit'
。offset_query_param
—— 字符串值,指示“offset”查询参数的名称。默认为'offset'
。max_limit
—— 如果设置,这是一个数值,指示客户端可能请求的最大允许限制。默认为None
。template
—— 在可浏览API中呈现分页控件时要使用的模板名称。可以重写以修改呈现样式,或设置为None
以完全禁用HTML分页控件。默认为"rest_framework/pagination/numbers.html"
。
展示效果如下所示,从第5条数据查起,每页展示2条。
自定义位移分页器
创建自定义分页器
我们自定义了一个MyLimitOffsetPagination
类,该类继承了LimitOffsetPagination
类。我们通过default_limit
设置了每页默认展示数据的条数,通过limit_query_param
设置了每页size的参数名以及通过max_limit
设置了每个可以展示的最大数据条数。
1 | from rest_framework.pagination import LimitOffsetPagination |
使用自定义的分页类
1 | class ArticleView(viewsets.ModelViewSet): |
展示效果如下:
CursorPagination
基于cursor的分页提供了一个不透明的“游标”指示器,客户端可以使用它对结果集进行分页。这种分页样式只显示正向和反向控件,而不允许客户端导航到任意位置。
基于cursor的分页要求结果集中的项具有唯一的、不变的顺序。此顺序通常可能是记录上的创建时间戳,因为它提为分页供了一致的顺序。
基于cursor的分页比其他方案更复杂。它还要求结果集呈现固定的顺序,并且不允许客户端任意索引结果集。但是,它确实提供了以下好处:
- 提供一致的分页视图。当恰当地使用时,
CursorPagination
可以确保客户端在翻阅记录时永远不会看到同一个项目两次,即使在分页过程中其他客户端正在插入新的项目时亦是如此。 - 支持使用非常大的数据集。对于极其庞大的数据集,使用基于offset的分页样式进行分页可能会变得效率低下或无法使用。基于cursor的分页方案具有固定时间的属性,并且不会随着数据集大小的增加而减慢。
Details and limitations
正确使用基于cursor的分页需要注意一些细节。你需要考虑你希望该方案适用于什么样的顺序。默认值是按 "-created"
排序。这假设模型实例上必须有一个“created”时间戳字段,并且将呈现一个“timeline”样式的,最新添加的项在前面的分页视图。
可以通过重写分页类中的 'ordering'
属性,或将 OrderingFilter
筛选器类与 CursorPagination
一起使用来修改排序。当与 OrderingFilter
一起使用时,您应该考虑严格限制用户可以按其排序的字段。
正确使用cursor分页应使用有满足以下条件的排序字段:
应当是一个不变的值,例如时间戳、slug或其他在创建时只设置一次的字段。
应当是独一无二的,或者几乎是独一无二的。毫秒精度的时间戳就是一个很好的例子。cursor分页的这种实现使用了一种智能的”position plus offset” 样式,允许它正确地支持不严格唯一的值作为排序。
应当为可强制转化为字符串的不可为null的值。
不应当是浮点数。精度误差容易导致不正确的结果。提示:改用小数。(如果已经有一个浮点数字段,并且必须根据它进行分页,那么这里提供了一个使用小数限制精度的
CursorPagination
子类示例。)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class FloatingPointCursorPagination(CursorPagination):
__rounding_down = decimal.Context(prec=14, rounding=decimal.ROUND_FLOOR)
__rounding_up = decimal.Context(prec=14, rounding=decimal.ROUND_CEILING)
def _get_position_from_instance(self, instance, ordering):
field_name = ordering[0].lstrip('-')
if isinstance(instance, dict):
attr = instance[field_name]
else:
attr = getattr(instance, field_name)
if isinstance(attr, float):
# repr gives more precision than str
# but we still lost some precision just from the postgresql-to-python translation.
if ordering[0][0] == '-':
attr = self.__rounding_down.create_decimal_from_float(attr)
else:
attr = self.__rounding_up.create_decimal_from_float(attr)
attr_str = force_text(attr)
return attr_str字段应具有数据库索引。
使用不满足这些约束的排序字段通常仍然有效,但是您将失去游标分页的一些好处。
Setup (设置)
全局设置
为了全局启用 CursorPagination
样式,请使用以下配置,根据需要修改 PAGE_SIZE
:
1 | REST_FRAMEWORK = { |
局部配置
在 GenericAPIView
子类中,还可以设置 pagination_class
属性,以在每个视图的基础上选择 CursorPagination
。
查看默认渲染视图
为什么会出错误? 使用CursorPagination
类需要你的模型里有created
这个字段,否则你需要手动指定ordering
字段。这是因为CursorPagination
类只能对排过序的查询集进行分页展示。我们的Article模型只有create_dat
e字段,没有created这个字段,所以会报错。
Configuration (配置)
CursorPagination
类包含许多可以重写以修改分页样式的属性,。
为了设置这些属性,需要重写 CursorPagination
类,然后如上所述启用自定义分页类。
page_size
= 表示PAGE_SIZE
的数值。如果设置,则覆盖页面大小设置。默认为与PAGE_SIZE
设置键相同的值。cursor_query_param
= 字符串值,指示“cursor”查询参数的名称。默认为'cursor'
。ordering
= 这应当是一个字符串或字符串列表,指示将对其应用基于cursor的分页的字段。例如:ordering='slug'
。默认为-created
。也可以通过在视图上使用OrderingFilter
覆盖此值。template
= 在可浏览API中呈现分页控件时要使用的模板的名称。可以重写以修改呈现样式,或设置为“无”以完全禁用HTML分页控件。默认为"rest_framework/pagination/previous_and_next.html"
。
自定义加密(游标)分页器
为了解决这个问题,我们需要自定义一个MyCursorPagination
类,手动指定按create_date
排序, 如下所示:
创建自定义分页器
我们自定义了一个MyCursorPagination
类,该类继承了CursorPagination
类。我们通过page_size
设置了每页默认展示数据的条数,通过page_size_query_param
设置了每页size的参数名。
1 | from rest_framework.pagination import CursorPagination |
使用自定义的分页类
由于这个ordering
字段与模型相关,我们并不推荐全局使用自定义的CursorPagination类,更好的方式是在GenericsAPIView或视图集viewsets中通过pagination_class
属性指定,如下所示:
1 | class ArticleView(viewsets.ModelViewSet): |
展示效果如下:
函数类视图中使用分页类
注意pagination_class
属性仅支持在genericsAPIView
和视图集viewset中配置使用。如果你使用函数或简单的APIView开发API视图,那么你需要对你的数据进行手动分页,一个具体使用例子如下所示:
1 | from rest_framework.pagination import PageNumberPagination |
小结
以上就是关于分页器的详细使用方式,有兴趣的同学可以进入rest_framework.pagination.py
看下我们上面讲的三个分页器官方是如何定义的,我们可以参照官方的定义方式,打造出最适合自己的分页器。
Custom pagination styles (自定义分页样式)
为了创建自定义分页序列化程序类,应将 pagination.BasePagination
子类化,并重写 paginate_queryset(self, queryset, request, view=None)
和 get_paginated_response(self, data)
方法:
paginate_queryset
方法被传递给初始的queryset,它应该返回一个iterable对象,该对象只包含请求页中的数据。- The
get_paginated_response
m方法传递序列化的页数据,并应返回一个Response
。
注意,paginate_queryset
方法可能会在分页实例上设置state,该state (?)稍后可能会由get_paginated_response
方法使用。
Django REST Framework系列文章
- 手摸手,带你用Django REST Framework撸接口系列一(基础篇)
- 手摸手,带你用Django REST Framework撸接口系列二(序列化器篇)
- 手摸手,带你用Django REST Framework撸接口系列三(视图篇)
- 手摸手,带你用Django REST Framework撸接口系列四(渲染器篇)
- 手摸手,带你用Django REST Framework撸接口系列五(路由篇)
- 手摸手,带你用Django REST Framework撸接口系列六(认证篇)
- 手摸手,带你用Django REST Framework撸接口系列七(权限篇)
- 手摸手,带你用Django REST Framework撸接口系列八(限流篇)
- 手摸手,带你用Django REST Framework撸接口系列九(过滤篇)
- 手摸手,带你用Django REST Framework撸接口系列十(排序篇)
- 手摸手,带你用Django REST Framework撸接口系列十一(分页篇)
- 手摸手,带你用Django REST Framework撸接口系列十二(异常处理篇)
- 手摸手,带你用Django REST Framework撸接口系列十三(自动生成接口文档篇)