Django-drf架构 分页的详解
一、为什么要使用分页?
比如我们要获取数据库中一张表内所有的数据,但是这张表的数据可能会达到千万条的级别,我们不太可能需要一次把所有的数据都展示出来,因为数据量很大,对服务端的内存压力比较大,而且在网络传输过程中耗时也会比较大
,使用分页就可以优化这些问题。
二、DRF 中提供了三种分页模式:
1.PageNumberPagination
2.LimitOffsetPagination
3.CursorPagination
from rest_framework.pagination import PageNumberPagination,
LimitOffsetPagination,
CursorPagination
下面详解三种分页的使用。
三、PageNumberPagination:
基本使用:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^test/$', views.ChapterView.as_view())
]
import json
from rest_framework.pagination import PageNumberPagination
from rest_framework import serializers
from django.shortcuts import HttpResponse
from rest_framework.views import APIView
class CourseChapterSerializers(serializers.ModelSerializer):
"""课程章节的序列化,采用的是ModelSerializer"""
# 获取CourseChapter表所有的数据
class Meta:
model = models.Coursechapter
fields = '__all__'
class ChapterView(APIView):
def get(self, request, *args, **kwargs):
# 获取所有课程章节的信息
chapter_list = models.Coursechapter.objects.all()
# 创建分页对象
page = PageNumberPagination()
# 获取分页的数据
page_chapter = page.paginate_queryset(queryset=chapter_list,
request=request, view=self)
# 将分页后返回的数据, 进行序列化
ser = CourseChapterSerializers(instance=page_chapter, many=True)
# 转换成json格式, ensure_ascii=False 表示显示中文, 默认为True
ret = json.dumps(ser.data, ensure_ascii=False)
# 进行数据的返回
return HttpResponse(ret)
3.settings.py 配置:
# 全局配置
REST_FRAMEWORK = {
# 每页显示多少条数据
'PAGE_SIZE': 5
}
4.在url输入:
http://127.0.0.1:8000/test/?page=1 # ?page=1 代表的是获取第一页的数据
# 输出结果如下:共返回5条数据
[
{
"id":1,
"category":0,
"name":"第一章",
"description":"这一章节,很有趣味",
"course":1
},
{
"id":2,
"category":0,
"name":"第二章",
"description":"这二章节,很不错",
"course":1
},
{
"id":3,
"category":1,
"name":"第三章",
"description":"第三章,也很不错啊",
"course":3
},
{
"id":4,
"category":2,
"name":"第四章",
"description":"第四章,内容很不错哦",
"course":2
},
{
"id":5,
"category":1,
"name":"第五章",
"description":"哎呦,第五章内容不错哦~",
"course":1
}
]
自定义分页:
import json
from rest_framework.pagination import PageNumberPagination
from rest_framework import serializers
from django.shortcuts import HttpResponse
from rest_framework.views import APIView
class MyPagination(PageNumberPagination):
"""自定义分页"""
# 每页显示多少条数据
page_size = 2
# url/?page=1&size=5, 改变默认每页显示的个数
page_size_query_param = 'size'
# 最大页数不超过10条
max_page_size = 10
# 获取页码数
page_query_param = 'page'
class CourseChapterSerializers(serializers.ModelSerializer):
"""课程章节的序列化,采用的是ModelSerializer"""
class Meta:
model = models.Coursechapter
fields = '__all__'
class ChapterView(APIView):
def get(self, request, *args, **kwargs):
# 获取所有课程章节的信息
chapter_list = models.Coursechapter.objects.all()
# 创建分页对象
page = MyPagination()
# 获取分页的数据
page_chapter = page.paginate_queryset(queryset=chapter_list,
request=request, view=self)
# 将分页后返回的数据, 进行序列化
ser = CourseChapterSerializers(instance=page_chapter, many=True)
# 转换成json格式, ensure_ascii=False 表示显示中文, 默认为True
ret = json.dumps(ser.data, ensure_ascii=False)
# 进行数据的返回
return HttpResponse(ret)
在url地址输入:
http://127.0.0.1:8000/test/?page=1&size=11
# 表示第一页数据,每页显示11条,但是自定义分页的时候进行了设置,max_page_size = 10 属性,所以最多只能返回10条,不足10条,返回相应数量
注意:这时可以将settings.py中配置注释掉
# 全局配置
#REST_FRAMEWORK = {
# 每页显示多少条数据
# 'PAGE_SIZE': 5
#}
详解配置:
PageNumberPagination
类包含一些可以被覆盖以修改分页样式的属性。
要设置这些属性,你应该继承 PageNumberPagination
类,然后像上面那样启用你的自定义分页类。
django_paginator_class
- 要使用的 Django Paginator 类。默认是django.core.paginator.Paginator
,对于大多数用例来说应该没问题。page_size
- 指定页面大小的数字值。如果设置,则会覆盖PAGE_SIZE
setting。默认值与PAGE_SIZE
setting key 相同。page_query_param
- 一个字符串值,指定用于分页控件的查询参数的名称。page_size_query_param
- 一个字符串值,指定查询参数的名称,允许客户端根据每个请求设置页面大小。默认为None
,表示客户端可能无法控制所请求的页面大小。max_page_size
- 一个数字值,表示允许的最大页面大小。该属性仅在page_size_query_param
也被设置时有效。last_page_strings
- 字符串列表或元组,用于指定可能与page_query_param
一起使用的值,用以请求集合中的最终页面。默认为('last',)
template
- 在可浏览 API 中渲染分页控件时使用的模板的名称。可能会被覆盖以修改渲染样式,或设置为None
以完全禁用 HTML 分页控件。默认为"rest_framework/pagination/numbers.html"
。
四、LimitOffsetPagination:
这种分页样式反应了查找多个数据库记录时使用的方法
。
客户端包含limit、offset查询参数:
limit表示要返回的item的最大数量,并且等同于其他样式中的page_size。
offset指定查询的起始位置与完整的未分类item的关系
。
自定义分页:
import json
from rest_framework.pagination import LimitOffsetPagination
from rest_framework import serializers
from django.shortcuts import HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response
class MyPagination(LimitOffsetPagination):
"""自定义分页"""
# 默认显示的个数
default_limit = 2
# 每页最多显示的条数
max_limit = 10
# 当前位置
offset_query_param = "offset"
# 通过limit 改变默认显示的个数
limit_query_param = "limit"
class CourseChapterSerializers(serializers.ModelSerializer):
"""课程章节的序列化,采用的是ModelSerializer"""
class Meta:
model = models.Coursechapter
fields = '__all__'
class ChapterView(APIView):
def get(self, request, *args, **kwargs):
# 获取所有课程章节的信息
chapter_list = models.Coursechapter.objects.all()
# 创建分页对象
page = MyPagination()
# 获取分页的数据
page_chapter = page.paginate_queryset(queryset=chapter_list,
request=request, view=self)
# 将分页后返回的数据, 进行序列化
ser = CourseChapterSerializers(instance=page_chapter, many=True)
# 可以使用get_paginated_response方法,返回自带上一页、下一页
return page.get_paginated_response(ser.data)
url请求地址:
http://127.0.0.1:8000/test/?limit=2
# 每页显示的个数2条
返回的页面信息:
返回的时候使用get_paginated_response方法,自带上一页、下一页
GET /test/?limit=2
HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
"count": 11,
"next": "http://127.0.0.1:8000/test/?limit=2&offset=2", # 表示下一页
"previous": null, # 表示上一页
"results": [
{
"id": 1,
"category": 0,
"name": "第一章",
"description": "这一章节,很有趣味",
"course": 1
},
{
"id": 2,
"category": 0,
"name": "第二章",
"description": "这二章节,很不错",
"course": 1
}
]
}
详解配置:
LimitOffsetPagination
类包含一些可以被覆盖以修改分页样式的属性。
要设置这些属性,应该继承 LimitOffsetPagination
类,然后像上面那样启用你的自定义分页类。
default_limit
- 一个数字值,指定客户端在查询参数中未提供的 limit 。默认值与PAGE_SIZE
setting key 相同。limit_query_param
- 一个字符串值,指示 “limit” 查询参数的名称。默认为'limit'
。offset_query_param
- 一个字符串值,指示 “offset” 查询参数的名称。默认为'offset'
。max_limit
- 一个数字值,表示客户端可以要求的最大允许 limit。默认为None
。template
- 在可浏览 API 中渲染分页控件时使用的模板的名称。可能会被覆盖以修改渲染样式,或设置为None
以完全禁用 HTML 分页控件。默认为"rest_framework/pagination/numbers.html"
。
五、CursorPagination:
加密分页方式,只能通过点击"上一页"、"下一页"访问数据
基于游标的分页提供了一个不透明的 “游标” 指示器,客户端可以使用该指示器来翻阅结果集。此分页样式仅提供前向和反向控件,并且不允许客户端导航到任意位置。
它还要求结果集渲染固定顺序,并且不允许客户端任意索引结果集。但它确实提供了以下好处:
- 提供一致的分页视图。正确使用时
CursorPagination
确保客户端在分页时不会看到同一个 item,即使在分页过程中其他客户端正在插入新 item。 - 支持使用非常大的数据集。使用极大数据集分页时,使用基于偏移量的分页样式可能会变得效率低下或无法使用。
基于游标的分页方案具有固定时间属性,并且不会随着数据集大小的增加而减慢
。
细节和限制:
正确使用基于游标的分页需要稍微注意细节。你需要考虑希望将该方案应用于何种顺序。默认是按 "-created"
排序。这假设在模型实例上必须有一个 “created” 时间戳字段,并且会渲染一个 “时间轴” 样式分页视图,其中最近添加的 item 是第一个。
你可以通过重写分页类上的 'ordering'
属性或者将 OrderingFilter
过滤器类与 CursorPagination
一起使用来修改排序。与 OrderingFilter
一起使用时,你应该考虑限制用户可以排序的字段。
正确使用游标分页应该有一个满足以下条件的排序字段:
- 在创建时应该是一个不变的值,例如时间戳,slug,或其他只设置一次的字段。
- 应该是独特的,或几乎独一无二的。毫秒精度时间戳就是一个很好的例子。这种游标分页的实现使用了一种智能的 “位置 + 偏移” 风格,允许它正确地支持非严格唯一的值作为排序。
- 应该是可以强制为字符串的非空值。
- 不应该是一个 float。精度错误很容易导致错误的结果。提示:改用小数。(如果你已经有一个 float 字段并且必须对其进行分页,则可以在此处找到使用小数来限定精度的示例。)
- 该字段应该有一个数据库索引。
使用不满足这些约束条件的排序字段通常仍然有效,但是你将失去游标分页的一些好处。
自定义分页:
from rest_framework.pagination import CursorPagination
from rest_framework import serializers
from django.shortcuts import HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response
class MyPagination(CursorPagination):
"""自定义加密分页"""
# URL 传入游标的参数
cursor_query_param = "cursor"
# 每页显示几条数据
page_size = 2
# 排序(最好对应数据的唯一字段、该字段最好创建索引)
ordering = '-created_at'
# 改变默认每页显示的个数
page_size_query_param = 'page'
# 每页显示的最大条数
max_page_size = 5
class CourseSerializers(serializers.ModelSerializer):
"""课程的序列化,采用的是ModelSerializer"""
class Meta:
model = models.Course
fields = '__all__'
class CourseView(APIView):
def get(self, request, *args, **kwargs):
# 获取所有课程信息
courses_list = models.Course.objects.all()
# 创建分页对象
page = MyPagination()
# 获取分页的数据
page_chapter = page.paginate_queryset(queryset=courses_list,
request=request, view=self)
# 将分页后返回的数据, 进行序列化
ser = CourseSerializers(instance=page_chapter, many=True)
return page.get_paginated_response(ser.data)
在url中输入:
http://127.0.0.1:8000/test/
返回以下信息:
当前页码已经进行加密,只能点击上一页、下一页进行访问数据
详解配置:
CursorPagination
类包含一些可以被覆盖以修改分页样式的属性。
要设置这些属性,你应该继承 CursorPagination
类,然后像上面那样启用你的自定义分页类。
page_size
= 指定页面大小的数字值。如果设置,则会覆盖PAGE_SIZE
设置。默认值与PAGE_SIZE
setting key 相同。cursor_query_param
= 一个字符串值,指定 “游标” 查询参数的名称。默认为'cursor'
.ordering
= 这应该是一个字符串或字符串列表,指定将应用基于游标的分页的字段。例如:ordering = 'slug'
。默认为-created
。该值也可以通过在视图上使用OrderingFilter
来覆盖。template
= 在可浏览 API 中渲染分页控件时使用的模板的名称。可能会被覆盖以修改渲染样式,或设置为None
以完全禁用 HTML 分页控件。默认为"rest_framework/pagination/previous_and_next.html"
。
六、自定义分页样式:
要创建自定义分页序列化类,你应该继承 pagination.BasePagination
并覆盖 paginate_queryset(self, queryset, request, view=None)
和 get_paginated_response(self, data)
方法:
paginate_queryset
方法被传递给初始查询集,并且应该返回一个只包含请求页面中的数据的可迭代对象。get_paginated_response
方法传递序列化的页面数据,并返回一个Response
实例。
请注意,paginate_queryset
方法可以在分页实例上设置状态,而后 get_paginated_response
方法可以使用它。
举个栗子:
from rest_framework import pagination
from rest_framework.response import Response
class MyPagination(pagination.PageNumberPagination):
# http://api.example.org/accounts/?page=4&pageSize=100
# 指定客户端query_param参数:每页数据大小 和 页码
page_size_query_param = 'pageSize'
page_query_param = 'page'
# 定制每页显示多少条数据(默认为None, 最终取决于请求中的查询参数) 以及最大值
page_size = 10
max_page_size = 20
def get_paginated_response(self, data):
"""重写get_paginated_response方法"""
tpl = {
'count': self.page.paginator.count, # 总条数
'links': {
'next': self.get_next_link(), # 下一页
'previous': self.get_previous_link() # 上一页
}
}
tpl.update(data) # 重新定义模板
res = {
'data': tpl,
'retCode': 0,
'retMsg': u"成功 | Success"
}
# 通过渲染器进行返回
return Response(res)
class CourseSerializers(serializers.ModelSerializer):
"""课程的序列化,采用的是ModelSerializer"""
class Meta:
model = models.Course
fields = '__all__'
class CourseView(APIView):
def get(self, request, *args, **kwargs):
# 获取所有课程信息
courses_list = models.Course.objects.all()
# 创建分页对象
page = MyPagination()
# 获取分页的数据
page_chapter = page.paginate_queryset(courses_list, request)
# 将分页后返回的数据, 进行序列化
ser = CourseSerializers(instance=page_chapter, many=True)
data = {'list': ser.data}
return page.get_paginated_response(data)
# 输出结果:以下的数据结构类型
HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
"data": {
"count": 6, # 返回的总共数量
"list": [ # 数据库返回的字段信息
{
"id": 1,
"name": "上海财经大学",
"description": "课程很棒",
"price": "1999.00",
"deleted": false,
"created_at": "2019-04-29T03:36:22+08:00",
"edited_at": "2019-04-29T03:36:24+08:00"
},
{
"id": 2,
"name": "上海财经大学",
"description": "课程真的很棒",
"price": "18888.00",
"deleted": false,
"created_at": "2019-04-29T03:36:53+08:00",
"edited_at": "2019-04-29T03:36:55+08:00"
}
],
"links": {
"previous": null, # 上一页
"next": "http://127.0.0.1:8000/test/?page=2&pageSize=2" # 下一页
}
},
"retCode": 0, # 返回码
"retMsg": "成功 | Success" # 返回的描述信息
}