Django-drf架构 分页的详解

一、为什么要使用分页?

比如我们要获取数据库中一张表内所有的数据,但是这张表的数据可能会达到千万条的级别,我们不太可能需要一次把所有的数据都展示出来,因为数据量很大,对服务端的内存压力比较大,而且在网络传输过程中耗时也会比较大,使用分页就可以优化这些问题。

二、DRF 中提供了三种分页模式:

1.PageNumberPagination

2.LimitOffsetPagination

3.CursorPagination

from rest_framework.pagination import PageNumberPagination,
								   LimitOffsetPagination,
								   CursorPagination

下面详解三种分页的使用。

三、PageNumberPagination:

基本使用:

1.urls.py

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^test/$', views.ChapterView.as_view())
]

2.view.py

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:

这种分页样式反应了查找多个数据库记录时使用的方法

客户端包含limitoffset查询参数:

  • 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"  # 返回的描述信息
}
07-13 03:27