曦远 (DealiAxy)一份私人的文字刊物

StarBlog
返回文章列表折页手记阅读全文
Django

DjangoRestFramework框架三种分页功能的实现 - 在DjangoStarter项目模板中封装

前言 继续Django后端开发系列文章。刚好遇到一个分页的需求,就记录一下。 Django作为一个“全家桶”型的框架,本身啥都有,分页组件也是有的,但默认的分页组件没有对API开发做优化,所以DjangoRestFramework这个专门写API的框架又把Django的分页组件包装了一层,集成在viewsets里的时候会更方便。 不过我们不可能一直用viewsets,有一部分API还是要用自由度更

曦远 (DealiAxy)约 11 分钟
目录7 个章节

前言

继续Django后端开发系列文章。刚好遇到一个分页的需求,就记录一下。

Django作为一个“全家桶”型的框架,本身啥都有,分页组件也是有的,但默认的分页组件没有对API开发做优化,所以DjangoRestFramework这个专门写API的框架又把Django的分页组件包装了一层,集成在viewsets里的时候会更方便。

不过我们不可能一直用viewsets,有一部分API还是要用自由度更高的ApiView的,但ApiView里又没办法直接使用默认的分页组件,这时我们就需要封装一下。

并且DjangoRestFramework默认的分页信息也不够全,比如没有总页数,这点我们也可以在封装的时候魔改一下。

DRF中的分页方式

DRF中为我们封装了三种分页方式,分别是:

  • PageNumberPagination:顾名思义,不解释
  • LimitOffsetPagination:Offset分页
  • CursorPagination:加密分页

本文打算只介绍最常用的第一种,后面两种同时也会做封装,但篇幅关系就不介绍了,有兴趣的同学可以尝试使用一下。

开始代码

首先还是在我们的「DjangoStarter」项目中,在utils目录下新建一个名为paginator的Python Package。

因为代码不多,我们直接写在utils/paginator/__init__.py文件下就好。

from collections import OrderedDict

from django.core.paginator import Paginator
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination
from rest_framework.response import Response


class NumberPaginator(PageNumberPagination):
    """页码分页"""
    def __init__(
            self,
            page_size,
            page_size_query_param='page_size',
            page_query_param='page',
            max_page_size=None
    ):
        """
        初始化分页

        :param page_size: 每页显示多少条
        :param page_size_query_param: URL中每页显示条数的参数
        :param page_query_param: URL中页码的参数
        :param max_page_size: 最大页码数限制
        """
        self.page_size = page_size
        self.page_size_query_param = page_size_query_param
        self.page_query_param = page_query_param
        self.max_page_size = max_page_size

    def get_paginated_response(self, data):
        paginator: Paginator = self.page.paginator

        return Response(OrderedDict([
            ('total_item_count', paginator.count),
            ('page_count', paginator.num_pages),
            ('page_number', self.page.number),
            ('page_size', self.page_size),
            ('next', self.get_next_link()),
            ('previous', self.get_previous_link()),
            ('results', data)
        ]))


class LimitOffsetPaginator(LimitOffsetPagination):
    """Offset分页"""
    default_limit = 1
    limit_query_param = 'limit'
    offset_query_param = 'offset'
    max_limit = 999


class CursorPaginator(CursorPagination):
    """加密分页"""
    cursor_query_param = 'cursor'
    page_size = 1
    ordering = '-id'  # 重写要排序的字段

针对前面说的“DjangoRestFramework默认的分页信息也不够全”问题,我重写了get_paginated_response方法,在返回值中加入这几个参数

  • page_count:总页数
  • page_number:当前页码
  • page_size:每页数量

然后另外两个参数也改了名字,更直观,更符合我们平时的开发习惯。

...

这样就完成了封装,我们接下来在代码里测试一下

测试接口

来写个测试接口看看效果

from utils.paginator import NumberPaginator

@swagger_auto_schema(
    method='get', operation_summary='测试分页功能',
    manual_parameters=[
        openapi.Parameter('page', openapi.IN_QUERY, type=openapi.TYPE_NUMBER),
        openapi.Parameter('page_size', openapi.IN_QUERY, type=openapi.TYPE_NUMBER),
    ])
@api_view()
def test_page(request):
    # 测试数据
    data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    paginator = NumberPaginator(request.query_params.get('page_size', 10))
    return paginator.get_paginated_response({
        'data': paginator.paginate_queryset(queryset=data, request=request)
    })

配置一下路由

urlpatterns = [
    path('test_page', views.test_page),
]

测试效果

写完的接口接受两个参数,page和page_size,我在@swagger_auto_schema装饰器里声明了这两个参数,方便我们在Swagger文档中做测试。

我们设定page_size=5,拿到的JSON数据是这样的:

{
  "message": "请求成功",
  "code": 200,
  "data": {
    "total_item_count": 10,
    "page_count": 2,
    "page_number": 1,
    "page_size": "5",
    "next": "http://127.0.0.1:8005/core/test_page?page=2&page_size=5",
    "previous": null,
    "results": {
      "data": [
        1,
        2,
        3,
        4,
        5
      ]
    }
  }
}

效果不错,很清晰。

收工。

在ViewSet中使用

当然,这个封装好的分页组件在ViewSet里也可以使用

比如说直接配置

from utils.paginator import NumberPaginator

class TestViewSet(viewsets.ReadOnlyModelViewSet):
  # ...
  pagination_class = NumberPaginator

也可以全局配置

编辑settings.py文件,修改RestFramework的配置

DjangoStarter做了settings.py拆分,RestFramework的配置在config/rest_framework.py文件里

REST_FRAMEWORK = {
  # 设置分页(使用DjangoStarter封装的分页器)
  'DEFAULT_PAGINATION_CLASS': 'utils.paginator.NumberPaginator'
}

参考资料

_EOF_

DjangoRestFramework框架三种分页功能的实现 - 在DjangoStarter项目模板中封装

本文作者:
程序设计实验室
版权声明:
本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

相关文章

Django中间件之实现Admin后台IP白名单

前言 Django自带的Admin管理后台很方便,但是实际生产环境真的会有挺多安全问题的,在admin的安全防护这方面,我之前就研究实现了给admin加上登录验证码和限流功能,可以参考这篇文章:给Django Admin添加验证码和多次登录尝试限制 不过就在内部使用的话就好很多,因此我们可以通过Django的中间件来实现IP过滤,所以本文中涉及到两个知识点,一个是Django的中间件,另外一个是P

2022-08-26 · Django · 约 1 分钟

Django统一包装接口返回数据格式

前言 最近实在太忙了,开始了一个新的项目,为了快速形成产品,我选择了Django来实现后端,然后又拿起了之前我封装了项目脚手架「DjangoStarter」。 由于前段时间我写了不少.NetCore的后端代码,对CRUD的接口开发又有了一些新的理解,所以肯定也要把DjangoStarter改造一番,改得更加顺手~ 题外话:话说我在前端终于真正用上react,不得不说 ts + react 真好用,

2022-08-26 · Django · 约 1 分钟

Django项目笔记:sessions处理以及复杂对象序列化

前言:一点题外话 我发现,不更新博客的时候,不是非常忙,就是效率非常低。最近没怎么更新博客,原因是第二种= =。惭愧惭愧。 今天效率出奇的高,一天时间把PassNote后端的接口全部写完了,Django很好用,不过实际项目中还是有些框架不能实现的功能需要解决的,我比较喜欢造轮子,不过为了保证效率,还是尽量用现成的工具,减少不必要的折腾。 关于PassNote 之前应该在博客有说过,这是一个密码管理

2022-04-02 · Django · 约 1 分钟

给Django的Admin添加自定义Action-并移除需要选择对象的限制

前言 不得不说,Django的Admin真的给开发带来很多便利,这不,我又来折腾了,这次是添加自定义的action 这个自定义的Action可以看看官方文档的介绍,很详细,不再赘述。 https://docs.djangoproject.com/en/3.0/ref/contrib/admin/actions/ 界面效果 Django默认的添加完action是要选择下面的对象才能提交的,不过我不需

2022-08-26 · Django · 约 1 分钟

讨论

共 0 条

良言一句三冬暖,恶语伤人六月寒。

加载评论中…