DjangoRestFramework框架三種分頁功能的實現 - 在DjangoStarter專案模板中封裝

程式設計實驗室發表於2022-04-11

前言

繼續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),
]

測試效果

寫完的介面接受兩個引數,pagepage_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
      ]
    }
  }
}

效果不錯,很清晰。

收工。

參考資料

相關文章