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

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

Implementing Three Pagination Features of Django REST Framework - Encapsulated in the DjangoStarter Project Template

Preface Continuing the Django backend development series. I happened to run into a pagination requirement, so I'll document it here. Django, as an "all-in-one" framework, has everything built in, including a pagination component. However, the default pagination component isn't optimized for API development, so Django REST Framework, which is specifically for writing APIs, wraps Django's pagination component another layer, making it more convenient when integrated into viewsets. However, we can't always use viewsets; some APIs still need the more flexible

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

Preface

Continuing the Django backend development series. I happened to run into a pagination requirement, so I'll document it here.

Django, as an "all-in-one" framework, has everything built in, including a pagination component. However, the default pagination component isn't optimized for API development, so Django REST Framework, which is specifically for writing APIs, wraps Django's pagination component another layer, making it more convenient when integrated into viewsets.

However, we can't always use viewsets; some APIs still need the more flexible ApiView, but ApiView can't directly use the default pagination component, so we need to wrap it ourselves.

Moreover, Django REST Framework's default pagination info isn't complete enough—for example, it doesn't include the total number of pages. We can also hack this a bit when wrapping it.

Pagination Methods in DRF

DRF provides three pagination methods for us:

  • PageNumberPagination: As the name suggests, no explanation needed
  • LimitOffsetPagination: Offset pagination
  • CursorPagination: Encrypted pagination

This article will only introduce the first, most commonly used one. The other two will also be wrapped, but due to space constraints I won't cover them. Interested readers can give them a try.

Let's Start Coding

First, in our DjangoStarter project, create a Python package named paginator under the utils directory.

Since there isn't much code, we can just write it in the utils/paginator/__init__.py file.

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'  # 重写要排序的字段

To address the earlier issue that "Django REST Framework's default pagination info isn't complete enough", I overrode the get_paginated_response method and added these fields to the response:

  • page_count: total number of pages
  • page_number: current page number
  • page_size: items per page

I also renamed the other two fields to make them more intuitive and more consistent with our usual development habits.

...

That completes the wrapping. Next, let's test it in code.

Test Endpoint

Let's write a test endpoint to see how it works

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)
    })

Configure the route

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

Test Results

The finished endpoint accepts two parameters, page and page_size. I declared these two parameters in the @swagger_auto_schema decorator so we can test them conveniently in the Swagger docs.

We set page_size=5, and the JSON data we get looks like this:

{
  "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
      ]
    }
  }
}

Looks good, very clear.

That's a wrap.

Using It in a ViewSet

Of course, this wrapped pagination component can also be used in a ViewSet

For example, configure it directly

from utils.paginator import NumberPaginator

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

You can also configure it globally

Edit the settings.py file and modify the REST Framework configuration

DjangoStarter splits settings.py; the REST Framework configuration is in the config/rest_framework.py file

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

References

_EOF_

Implementing Three Pagination Features of Django REST Framework - Encapsulated in the DjangoStarter Project Template

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

相关文章

讨论

共 0 条

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

加载评论中…