DC's blog DC's blog
首页
  • 计算机基础
  • linux基础
  • mysql
  • git
  • 数据结构与算法
  • axure
  • english
  • docker
  • opp
  • oop
  • 网络并发编程
  • 不基础的py基础
  • 设计模式
  • html
  • css
  • javascript
  • jquery
  • UI
  • 第一次学vue
  • 第二次学vue
  • Django
  • drf
  • drf_re
  • 温故知新
  • flask
  • 前后端不分离

    • BBS
    • 订单系统
    • CRM
  • 前后端部分分离

    • pear-admin-flask
    • pear-admin-django
  • 前后端分离

    • 供应链系统
  • 理论基础
  • py数据分析包
  • 机器学习
  • 深度学习
  • 华中科大的网课
  • cursor
  • deepseek
  • 杂文
  • 罗老师语录
  • 关于我

    • me
  • 分类
  • 归档
GitHub (opens new window)

DC

愿我一生欢喜,不为世俗所及.
首页
  • 计算机基础
  • linux基础
  • mysql
  • git
  • 数据结构与算法
  • axure
  • english
  • docker
  • opp
  • oop
  • 网络并发编程
  • 不基础的py基础
  • 设计模式
  • html
  • css
  • javascript
  • jquery
  • UI
  • 第一次学vue
  • 第二次学vue
  • Django
  • drf
  • drf_re
  • 温故知新
  • flask
  • 前后端不分离

    • BBS
    • 订单系统
    • CRM
  • 前后端部分分离

    • pear-admin-flask
    • pear-admin-django
  • 前后端分离

    • 供应链系统
  • 理论基础
  • py数据分析包
  • 机器学习
  • 深度学习
  • 华中科大的网课
  • cursor
  • deepseek
  • 杂文
  • 罗老师语录
  • 关于我

    • me
  • 分类
  • 归档
GitHub (opens new window)
  • Django

  • 第一次学drf

  • 第二次学drf

    • drfCBV
    • 认证
    • 权限
    • 限流
    • 版本
    • 解析器
    • 元类
    • 序列化使用
    • 序列化源码
    • 验证&源码
    • 序列化+验证
    • Serializer案例
    • Serializer总结
    • 分页
      • 准备工作
      • 基本分页
        • 快速使用
        • 动态显示每页条数
      • 偏移分页
        • 简单使用
        • 复现bug
        • 生活常用
    • 视图+路由+过滤
    • 练习+跨域问题
    • 博客练习
    • 源码分析汇总
  • 温故知新

  • flask

  • 后端
  • 第二次学drf
DC
2023-11-09
目录

分页

在Django中,Django自带的分页器不好用,于是我们自定义了分页器;
在DRF中, DRF自带的分页器还可以, 该篇博文将阐述其中两种最常用的分页器 PageNumberPagination、LimitOffsetPagination.


# 准备工作

创建实验数据, 为后面的分页做准备!! 可以看到,我们查看book列表,一共有10条数据!

数据库表

from django.db import models


class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.IntegerField()
1
2
3
4
5
6

路由配置

from django.urls import path
from api import views

urlpatterns = [
    path("api/<str:version>/book/",views.BookView.as_view())
]
1
2
3
4
5
6

视图相关逻辑

from rest_framework.views import APIView
from rest_framework import serializers
from rest_framework.response import Response
from api import models


class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Book
        fields = "__all__"


class BookView(APIView):
    def get(self, request, *args, **kwargs):
        queryset = models.Book.objects.all()
        ser = BookSerializer(instance=queryset, many=True)
        return Response({"code": 200, "data": ser.data})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

GET请求 http://127.0.0.1:8000/api/2/book/

{
  "code":200,
  "data":[
    {"id":1,"name":"第1本书.","price":10},
    {"id":2,"name":"第2本书.","price":20},
    {"id":3,"name":"第3本书.","price":30},
    {"id":4,"name":"第4本书.","price":40},
    {"id":5,"name":"第5本书.","price":50},
    {"id":6,"name":"第6本书.","price":60},
    {"id":7,"name":"第7本书.","price":70},
    {"id":8,"name":"第8本书.","price":80},
    {"id":9,"name":"第9本书.","price":90},
    {"id":10,"name":"第10本书.","price":100}
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 基本分页

PageNumberPagination 我们一般就使用默认的.

查看PageNumberPagination的源码, 得理清楚它的几个类变量:

class PageNumberPagination(BasePagination):
    page_size = api_settings.PAGE_SIZE  # -- 默认每页显示的条数

    django_paginator_class = DjangoPaginator

    page_query_param = 'page'  # -- 第几页
    page_query_description = _('A page number within the paginated result set.')

    """若想动态的设置每页展示的条数!! (注意,是每页不是当前页.)
    page_size_query_param和max_page_size都得一块设置!!
    比如:
       page_size_query_param=ps 
       max_page_size = 5
       那么 
          /books/?page=3&ps=2    第3页,每页显示2条数据
          /books/?page=3&ps=100  第3页,每页显示5条数据
          Ps: 若一共10条数据,那么访问/books/?page=3&ps=100时会报错.
              因为ps>max_page_size,So,使用max_page_size 第3页 3*5=15>10 所以报错!!
    """
    page_size_query_param = None
    page_size_query_description = _('Number of results to return per page.')

    max_page_size = None   # -- 限制的动态的显示每页的条数的最大值,不会限制默认每页展示的条数.

    last_page_strings = ('last',)

    template = 'rest_framework/pagination/numbers.html'

    invalid_page_message = _('Invalid page.')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 快速使用

利用 PageNumberPagination 里自带的 方法快速实现分页功能!!

配置文件

REST_FRAMEWORK = {
    "UNAUTHENTICATED_USER": None,
    "PAGE_SIZE": 2,  # - 默认每页显示多少条
}
1
2
3
4

视图相关逻辑

from rest_framework import serializers
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.pagination import PageNumberPagination

from api import models


class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Book
        fields = "__all__"


class BookView(APIView):
    def get(self, request, *args, **kwargs):
        queryset = models.Book.objects.all()

        # PageNumberPagination、以及它的父类BasePagination都没有__init__,没有初始化处理.
        # 所以就是单纯的创建了一个实例.
        pager = PageNumberPagination()
        # 参数: 从db中取到的数据、request、当前视图类的实例对象.
        #      传入request就是为了取url中的那些get参数!
        # 返回: 包含对象的列表
        #      eg: [<Book: Book object (4)>, <Book: Book object (5)>, <Book: Book object (6)>]
        p_list = pager.paginate_queryset(queryset, request, self)
        # 注意,此处传入的是p_queryset
        ser = BookSerializer(instance=p_list, many=True)
        """
        - pager.get_paginated_response(ser.data) 该方法返回的也是一个Response的对象/实例
        - Response("Hello")
        - Response(queryset)
        打印他们,结果都是<Response status_code=200, "text/html; charset=utf-8">
        只有在视图函数中,将其返回前端时,才会校验传给Response的对象是否是可序列化的数据.
        所以仅管queryset对象不可被序列化,print(Response(queryset))时也不会报错!
        '看了下源码,Response类实例化过程中只有init,里面也没有判断传入的数据是否可序列化.'
        <我又从后面的学习回来了> 这里解释下,回顾下drf CBV的执行流程,视图函数的返回值在disptch里还要做处理的!
        
        若通过这种方式返回前端数据,不会有总页数,上一页下一页的链接.
        return Response({"code": 200, "data": ser.data})
        """
        return pager.get_paginated_response(ser.data)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

利用postman模拟前端发送GET请求, http://127.0.0.1:8000/api/2/book/?page=2

{
    "count": 10,
    "next": "http://127.0.0.1:8000/api/2/book/?page=3",
    "previous": "http://127.0.0.1:8000/api/2/book/",
    "results": [
        {
            "id": 3,
            "name": "第3本书.",
            "price": 30
        },
        {
            "id": 4,
            "name": "第4本书.",
            "price": 40
        }
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 动态显示每页条数

可以写个类继承PageNumberPagination, 里面的类变量就可以自定义啦!!

from rest_framework import serializers
from rest_framework.pagination import PageNumberPagination
from rest_framework.views import APIView

from api import models


class MyPageNumberPagination(PageNumberPagination):
    # - 默认每页显示多少条
    page_size = 2
    # - url传递ps参数,动态的显示每页多少条
    page_size_query_param = 'ps'
    max_page_size = 6
    
    """ 在PageNumberPagination的源码中,get_paginated_response方法是这样写的
        若对该封装的数据结构不太满意,可以在我们自己写的子类MyPageNumberPagination中重写get_paginated_response方法!!
    def get_paginated_response(self, data):
        return Response(OrderedDict([
            ('count', self.page.paginator.count),
            ('next', self.get_next_link()),
            ('previous', self.get_previous_link()),
            ('results', data)
        ]))
    
    该源码其实跟下述的写法效果一样的!!(已实验过了,放心食用.)
    
    def get_paginated_response(self, data):
        return Response({
            'count': self.page.paginator.count,
            'next': self.get_next_link(),
            'previous': self.get_previous_link(),
            'results': data,
        })
    """


class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Book
        fields = "__all__"


class BookView(APIView):
    def get(self, request, *args, **kwargs):
        queryset = models.Book.objects.all()
        pager = MyPageNumberPagination()  # 改成我们自定义的分页组件
        p_list = pager.paginate_queryset(queryset, request, self)
        ser = BookSerializer(instance=p_list, many=True)
        return pager.get_paginated_response(ser.data)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

Get请求 http://127.0.0.1:8000/api/2/book/?page=2&ps=100
分析: db中一共10条数据, ps值超过了max_page_size, 所以每页展示6条, 第一页展示了6条, 第二页只能展示4条数据了..
按照该逻辑, 若get请求访问, http://127.0.0.1:8000/api/2/book/?page=3&ps=100 就会报错.

注意一点:
仅管many=True,但是ps=1,每页展示1条数据也是没问题的!
突然提醒我了,灵活点,many=True时,传入的是queryset对象就行! 不一定非是多条数据.
models.Book.objects.filter(id=1)的结果也是queryset对象哒.虽然只有一条数据.
Ps: many=True时, instance传入包含对象的列表也都可以!!!

{
    "count": 10,
    "next": null,
    "previous": "http://127.0.0.1:8000/api/2/book/?ps=100",
    "results": [
        {
            "id": 7,
            "name": "第7本书.",
            "price": 70
        },
        {
            "id": 8,
            "name": "第8本书.",
            "price": 80
        },
        {
            "id": 9,
            "name": "第9本书.",
            "price": 90
        },
        {
            "id": 10,
            "name": "第10本书.",
            "price": 100
        }
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 偏移分页

LimitOffsetPagination 适用于滚动页面的翻页.

# 简单使用

取出来的数据, 不管正序还是倒序取, 都会依次放到一个 容器里.
offset其实指的是 容器里的某个下标位置..(位置是从0开始数的) limit是从该位置开始从容器中取几条数据.

分页是拿容器里的记录分页.

配置文件

REST_FRAMEWORK = {
    "UNAUTHENTICATED_USER": None,
    "PAGE_SIZE": 3,
}
1
2
3
4

视图函数

from rest_framework import serializers
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination
from rest_framework.response import Response
from rest_framework.views import APIView

from api import models


class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Book
        fields = "__all__"


class BookView(APIView):
    def get(self, request, *args, **kwargs):
        queryset = models.Book.objects.all().order_by("-id")
        pager = LimitOffsetPagination()
        p_list = pager.paginate_queryset(queryset, request, self)
        ser = BookSerializer(instance=p_list, many=True)
        return pager.get_paginated_response(ser.data)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

实验结果

取出来的数据, 不管正序还是倒序取, 都会依次放到一个 容器里.  
offset其实指的是 容器里的某个下标位置(从0开始)..  limit是从该位置开始从容器中取几条数据.

现在,一共从表中倒序取了10条数据,放到了一个容器中 默认每次取3条数据.
就像这样 [ID10,ID9,ID8,ID7,ID6,ID5,ID4,ID3,ID2,ID1]
          △   △   △    △ ..
        位置0 位置1 位置2 位置3  ==> 将容器看成一个列表,位置就是下标


So, /api/2/book/?limit=3&offset=0 取容器中第0个位置到第2个位置的数据!即ID10、ID9、ID8 -- /api/2/book/
    /api/2/book/?limit=3&offset=3 取容器中第3个位置到第5个位置的数据!即ID7、ID6、ID5
    /api/2/book/?limit=3&offset=6 取容器中第6个位置到第8个位置的数据!即ID4、ID3、ID2
1
2
3
4
5
6
7
8
9
10
11
12

# 复现bug

使用原生态的偏移分页的话,正序取的时候还好,倒序取的话,会出现BUG.

什么BUG呢?

queryset = models.Book.objects.all().order_by("-id")

现在,一共从表中倒序取了10条数据,放到了一个容器中 就像这样 [ID10,ID9,ID8,ID7,ID6,ID5,ID4,ID3,ID2,ID1]
默认每次取3条数据.

进行以下步骤:  
step1: 访问/api/2/book/ - 依次取到了ID为10、9、8的记录
step2: 往表中新增一条记录 
step3: 访问/api/2/book/?limit=3&offset=3
       注意: 
          表中此时有11条数据,倒序取出. 容器变成了这样 [ID11,ID10,ID9,ID8,ID7,ID6,ID5,ID4,ID3,ID2,ID1]
          (每次访问都会重新执行视图函数!)
          offset=3 表明是从容器的第3个位置开始.limit=3,取3条数据, 即 依次取到ID为8、7、6的记录.
神奇吧,ID为8的记录重复出现了!!
1
2
3
4
5
6
7
8
9
10
11
12
13
14

step1:

image-20230831174602175

step2: models.Book.objects.create(name="11_11",price=11)

step3:

image-20230831174727135

# 生活常用

在原生态的偏移分页上, 添加一个参数 lastid !! 即 最后取到的那个位置/最后取到的那条记录的ID!!

怎么个逻辑呢?

表中一共有11条记录,倒序取出. 放到某个容器中,就像这样 [ID11,ID10,ID9,ID8,ID7,ID6,ID5,ID4,ID3,ID2,ID1]
默认每页展示3条数据

前端访问接口 /api/2/book/?limit=3&offset=0 -- 等同于 /api/2/book/
"后端返回前端 ID为11、10、9的记录"
前端得到数据后,记录得到的最后一条记录的ID值存储起来.即 9. 该值会在下一次访问时作为URL的lastid参数的值.
接着,
前端访问接口 /api/2/book/?limit=3&offset=0&lastid=8 -- 等同于 /api/2/book/?lastid=9
若该访问之前新增了一条数据,因为再次访问了该API,会重新执行视图函数.虽然表中多了一条ID12.
[ID12,ID11,ID10,ID9,ID8,ID7,ID6,ID5,ID4,ID3,ID2,ID1]
但后端会先筛选,先将表中ID小于9的那些记录找到. 容器变成了这样,[ID8,ID7,ID6,ID5,ID4,ID3,ID2,ID1] 
调用offset=0,limit=3 "后端返回前端 ID为8、7、6的记录"
前端得到数据后,记录得到的最后一条记录的ID值存储起来.即 6. 该值会在下一次访问时作为URL的lastid参数的值.
接着..
以此类推.  ★分页是以容器里的记录进行的分页!!

看实验过程,中途增加了数据也没关系啊,前端已经把得到的最后一条记录的ID保存下来了.新增数据的ID肯定是大于该ID的.

那又出现了新的问题.最新的数据获取不到啊?!
场景: 生活中有这样的场景,你往下滑,不停的进行偏移分页,然后在页面的顶端提示你 有1条新数据,点击查看. -- “抽屉新热榜”网站就是这样的
解决思路: 这就需要,在一开始,得记录下最先展示的那条数据的ID,该示例中是 ID11.
         往下滑的过程,不停偏移分页. 中途表中新增了ID12的记录.
         点击想查看最新的,这时只需筛选出 容器中 大于11的ID的数据集成到页面最上方展示.
      
So.针对偏移分页,前端要记录两个ID.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

具体实现

from rest_framework import serializers
from rest_framework.pagination import LimitOffsetPagination
from rest_framework.response import Response
from rest_framework.views import APIView

from api import models


class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Book
        fields = "__all__"


class BookView(APIView):
    def get(self, request, *args, **kwargs):
        queryset = models.Book.objects.all().order_by("-id")
        last_id = request.query_params.get("last_id")
        if last_id:
            queryset = queryset.filter(id__lt=last_id)
        pager = LimitOffsetPagination()
        p_list = pager.paginate_queryset(queryset, request, self)
        ser = BookSerializer(instance=p_list, many=True)
        # return pager.get_paginated_response(ser.data)  # - 感觉滚动翻页没必要上一页下一页
        return Response(ser.data)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

实验结果

==> limit=3&offset=0默认的 <==

http://127.0.0.1:8000/api/2/book/
倒序取所有数据,一共11条放到容器中 [ID11,ID10,ID9,ID8,ID7,ID6,ID5,ID4,ID3,ID2,ID1]   -- ID11,ID10,ID9

http://127.0.0.1:8000/api/2/book/?last_id=9
倒序取所有数据,一共11条,筛选出id小于9的,放到容器中 [ID8,ID7,ID6,ID5,ID4,ID3,ID2,ID1]   -- ID8,ID7,ID6
                                              △   △   △ 
  
http://127.0.0.1:8000/api/2/book/?last_id=6
倒序取所有数据,一共11条,筛选出id小于6的,放到容器中 [ID5,ID4,ID3,ID2,ID1]   -- ID5,ID4,ID3

http://127.0.0.1:8000/api/2/book/?last_id=3
倒序取所有数据,一共11条,筛选出id小于3的,放到容器中 [ID2,ID1]   -- ID2,ID1
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Serializer总结
视图+路由+过滤

← Serializer总结 视图+路由+过滤→

最近更新
01
deepseek本地部署+知识库
02-17
02
实操-微信小程序
02-14
03
教学-cursor深度探讨
02-13
更多文章>
Theme by Vdoing | Copyright © 2023-2025 DC | One Piece
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式