周冰心 发表于 前天 16:49

DRF分页

Django REST Framework 提供了强大的分页功能,可以帮助你处理大量数据的展示问题。
1、基础概念

分页是将大量数据分割成多个小块(页面)的过程,每个页面包含有限数量的数据项。这样做的好处:

[*]减少单次请求的数据量,提高响应速度
[*]降低客户端内存占用
[*]提供更好的用户体验
分页基类BasePagination:定义分页接口规范
class BasePagination:
    display_page_controls = False

    def paginate_queryset(self, queryset, request, view=None):
      """
      核心方法1:切割查询集,返回当前页数据
      - 参数:待分页的queryset、请求对象request、视图view
      - 返回:当前页数据(列表/queryset切片),若不分页则返回None
      """
      raise NotImplementedError('paginate_queryset() must be implemented.')

    def get_paginated_response(self, data):
      """
      核心方法2:生成带分页元信息的响应
      - 参数:当前页数据的序列化结果data
      - 返回:Response对象(包含总条数、上下页链接等)
      """
      raise NotImplementedError('get_paginated_response() must be implemented.')2、页码分页(PageNumberPagination)

客户端请求时携带page参数(默认第 1 页),服务器返回对应页的数据及分页元信息(总页数、总条数等)。
底SQL:SELECT * FROM 表 LIMIT 10 OFFSET 10(第 2 页,每页 10 条, OFFSET是根据页数计算得出)。
特点:

[*]基于页码的分页
[*]客户端可以指定页码和每页数量
[*]提供总页数和总记录数
请求示例:
GET /api/products/?page=2&page_size=15(第 2 页,每页 15 条)响应示例:
{
    "count": 1023,
    "next": "http://api.example.com/products/?page=3&page_size=15",
    "previous": "http://api.example.com/products/?page=1&page_size=15",
    "results": [
      {"id": 16, "name": "Product 16", ...},
      {"id": 17, "name": "Product 17", ...},
      // ... 共15个产品
    ]
}核心源码解析
class PageNumberPagination(BasePagination):

    def paginate_queryset(self, queryset, request, view=None):
      self.request = request
      page_size = self.get_page_size(request)
      if not page_size:
            return None

      # 初始化Django的Paginator(负责切割逻辑)
      paginator = self.django_paginator_class(queryset, page_size)
      # 获取客户端请求的页码(默认第1页)
      page_number = self.get_page_number(request, paginator)

      # 切割查询集,返回当前页数据
      self.page = paginator.page(page_number)

      return list(self.page)

    def get_page_number(self, request, paginator):
      # 获取当前页码, 处理特殊值如'last'
      page_number = request.query_params.get(self.page_query_param) or 1
      if page_number in self.last_page_strings:
            page_number = paginator.num_pages
      return page_number

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

    def get_page_size(self, request):
      """
      获取每页显示数量,优先使用客户端指定的值
      """
      if self.page_size_query_param:
            with contextlib.suppress(KeyError, ValueError):
                #尝试从查询参数获取page_size
                return _positive_int(
                  request.query_params,
                  strict=True,
                  cutoff=self.max_page_size
                )
      return self.page_size

    def get_next_link(self):
      """
      获取下一页的URL
      """
      if not self.page.has_next():
            return None
      url = self.request.build_absolute_uri()
      page_number = self.page.next_page_number()
      return replace_query_param(url, self.page_query_param, page_number)

    def get_previous_link(self):
      """
      上一页的URL
      """
      if not self.page.has_previous():
            return None
      url = self.request.build_absolute_uri()
      page_number = self.page.previous_page_number()
      if page_number == 1:
            return remove_query_param(url, self.page_query_param)
      return replace_query_param(url, self.page_query_param, page_number)基础使用
# pagination.py
from rest_framework.pagination import PageNumberPagination

class StandardResultsSetPagination(PageNumberPagination):
    page_size = 10# 默认每页10条
    page_size_query_param = 'page_size'# 允许客户端通过?page_size=20自定义每页条数
    max_page_size = 100# 客户端最大可自定义的每页条数(防止恶意请求)
    page_query_param = 'p'# 自定义页码参数名(默认是page,这里改为p,即?p=2)# views.py
from rest_framework.viewsets import ModelViewSet
from .models import Book
from .serializers import BookSerializer
from .pagination import StandardResultsSetPagination

class BookViewSet(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    pagination_class = StandardResultsSetPagination# 指定分页类3、偏移量分页(LimitOffsetPagination)

offset=10:跳过前 10 条数据,limit=20:取接下来的 20 条数据,等价于 SQL 中的LIMIT 20 OFFSET 10
底层 SQL:SELECT * FROM 表 LIMIT 10 OFFSET 20(从第 20 条开始,取 10 条)
特点:

[*]基于限制和偏移量的分页
[*]客户端可以指定从哪条记录开始和获取多少条记录
[*]适用于需要跳过大量记录的场景
请求示例:
GET /api/products/?limit=15&offset=30响应示例:
{
    "count": 1023,
    "next": "http://api.example.com/products/?limit=15&offset=45",
    "previous": "http://api.example.com/products/?limit=15&offset=15",
    "results": [
      {"id": 31, "name": "Product 31", ...},
      {"id": 32, "name": "Product 32", ...},
      // ... 共15个产品
    ]
}核心源码解析
class LimitOffsetPagination(BasePagination):    # 默认限制数量    default_limit = api_settings.PAGE_SIZE    # 查询参数名称    limit_query_param = 'limit'    offset_query_param = 'offset'    # 最大限制数量    max_limit = None    def paginate_queryset(self, queryset, request, view=None):      """      对查询集进行分页处理      """      self.request = request      # 获取客户端指定的limit(每页条数)      self.limit = self.get_limit(request)      if self.limit is None:            return None      # 计算总条数      self.count = self.get_count(queryset)      # 2. 获取客户端指定offset(偏移量)      self.offset = self.get_offset(request)      if self.count > self.limit and self.template is not None:            self.display_page_controls = True      if self.count == 0 or self.offset > self.count:            return []      # 应用限制和偏移      return list(queryset)    def get_paginated_response(self, data):      """      返回包含分页信息的响应      """      return Response({            'count': self.count,            'next': self.get_next_link(),            'previous': self.get_previous_link(),            'results': data      })    def get_limit(self, request):      """      获取限制数量      """      if self.limit_query_param:            with contextlib.suppress(KeyError, ValueError):                return _positive_int(                  request.query_params,                  strict=True,                  cutoff=self.max_limit                )      return self.default_limit    def get_offset(self, request):      """      获取偏移量      """      try:            return _positive_int(                request.query_params,            )      except (KeyError, ValueError):            return 0    def get_next_link(self):      """      获取下一页的URL      """      if self.offset + self.limit >= self.count:            return None      url = self.request.build_absolute_uri()      url = replace_query_param(url, self.limit_query_param, self.limit)      offset = self.offset + self.limit      return replace_query_param(url, self.offset_query_param, offset)    def get_previous_link(self):      """      获取上一页的URL      """      if self.offset
页: [1]
查看完整版本: DRF分页