找回密码
 立即注册
首页 业界区 安全 DRF分页

DRF分页

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

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

  • 减少单次请求的数据量,提高响应速度
  • 降低客户端内存占用
  • 提供更好的用户体验
分页基类BasePagination:定义分页接口规范
  1. class BasePagination:
  2.     display_page_controls = False
  3.     def paginate_queryset(self, queryset, request, view=None):
  4.         """
  5.         核心方法1:切割查询集,返回当前页数据
  6.         - 参数:待分页的queryset、请求对象request、视图view
  7.         - 返回:当前页数据(列表/queryset切片),若不分页则返回None
  8.         """
  9.         raise NotImplementedError('paginate_queryset() must be implemented.')
  10.     def get_paginated_response(self, data):
  11.         """
  12.         核心方法2:生成带分页元信息的响应
  13.         - 参数:当前页数据的序列化结果data
  14.         - 返回:Response对象(包含总条数、上下页链接等)
  15.         """
  16.         raise NotImplementedError('get_paginated_response() must be implemented.')
复制代码
2、页码分页(PageNumberPagination)

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

  • 基于页码的分页
  • 客户端可以指定页码和每页数量
  • 提供总页数和总记录数
请求示例:
  1. GET /api/products/?page=2&page_size=15(第 2 页,每页 15 条)
复制代码
响应示例:
  1. {
  2.     "count": 1023,
  3.     "next": "http://api.example.com/products/?page=3&page_size=15",
  4.     "previous": "http://api.example.com/products/?page=1&page_size=15",
  5.     "results": [
  6.         {"id": 16, "name": "Product 16", ...},
  7.         {"id": 17, "name": "Product 17", ...},
  8.         // ... 共15个产品
  9.     ]
  10. }
复制代码
核心源码解析
  1. class PageNumberPagination(BasePagination):
  2.     def paginate_queryset(self, queryset, request, view=None):
  3.         self.request = request
  4.         page_size = self.get_page_size(request)
  5.         if not page_size:
  6.             return None
  7.         # 初始化Django的Paginator(负责切割逻辑)
  8.         paginator = self.django_paginator_class(queryset, page_size)
  9.         # 获取客户端请求的页码(默认第1页)
  10.         page_number = self.get_page_number(request, paginator)
  11.         # 切割查询集,返回当前页数据
  12.         self.page = paginator.page(page_number)
  13.         return list(self.page)
  14.     def get_page_number(self, request, paginator):
  15.         # 获取当前页码, 处理特殊值如'last'
  16.         page_number = request.query_params.get(self.page_query_param) or 1
  17.         if page_number in self.last_page_strings:
  18.             page_number = paginator.num_pages
  19.         return page_number
  20.     def get_paginated_response(self, data):
  21.         # 构造包含分页元信息的响应
  22.         return Response({
  23.             'count': self.page.paginator.count,
  24.             'next': self.get_next_link(),
  25.             'previous': self.get_previous_link(),
  26.             'results': data,
  27.         })
  28.     def get_page_size(self, request):
  29.         """
  30.         获取每页显示数量,优先使用客户端指定的值
  31.         """
  32.         if self.page_size_query_param:
  33.             with contextlib.suppress(KeyError, ValueError):
  34.                 #  尝试从查询参数获取page_size
  35.                 return _positive_int(
  36.                     request.query_params[self.page_size_query_param],
  37.                     strict=True,
  38.                     cutoff=self.max_page_size
  39.                 )
  40.         return self.page_size
  41.     def get_next_link(self):
  42.         """
  43.         获取下一页的URL
  44.         """
  45.         if not self.page.has_next():
  46.             return None
  47.         url = self.request.build_absolute_uri()
  48.         page_number = self.page.next_page_number()
  49.         return replace_query_param(url, self.page_query_param, page_number)
  50.     def get_previous_link(self):
  51.         """
  52.         上一页的URL
  53.         """
  54.         if not self.page.has_previous():
  55.             return None
  56.         url = self.request.build_absolute_uri()
  57.         page_number = self.page.previous_page_number()
  58.         if page_number == 1:
  59.             return remove_query_param(url, self.page_query_param)
  60.         return replace_query_param(url, self.page_query_param, page_number)
复制代码
基础使用
  1. # pagination.py
  2. from rest_framework.pagination import PageNumberPagination
  3. class StandardResultsSetPagination(PageNumberPagination):
  4.     page_size = 10  # 默认每页10条
  5.     page_size_query_param = 'page_size'  # 允许客户端通过?page_size=20自定义每页条数
  6.     max_page_size = 100  # 客户端最大可自定义的每页条数(防止恶意请求)
  7.     page_query_param = 'p'  # 自定义页码参数名(默认是page,这里改为p,即?p=2)
复制代码
  1. # views.py
  2. from rest_framework.viewsets import ModelViewSet
  3. from .models import Book
  4. from .serializers import BookSerializer
  5. from .pagination import StandardResultsSetPagination
  6. class BookViewSet(ModelViewSet):
  7.     queryset = Book.objects.all()
  8.     serializer_class = BookSerializer
  9.     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 条)
特点:

  • 基于限制和偏移量的分页
  • 客户端可以指定从哪条记录开始和获取多少条记录
  • 适用于需要跳过大量记录的场景
请求示例:
  1. GET /api/products/?limit=15&offset=30
复制代码
响应示例:
  1. {
  2.     "count": 1023,
  3.     "next": "http://api.example.com/products/?limit=15&offset=45",
  4.     "previous": "http://api.example.com/products/?limit=15&offset=15",
  5.     "results": [
  6.         {"id": 31, "name": "Product 31", ...},
  7.         {"id": 32, "name": "Product 32", ...},
  8.         // ... 共15个产品
  9.     ]
  10. }
复制代码
核心源码解析
[code]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[self.offset:self.offset + self.limit])    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[self.limit_query_param],                    strict=True,                    cutoff=self.max_limit                )        return self.default_limit    def get_offset(self, request):        """        获取偏移量        """        try:            return _positive_int(                request.query_params[self.offset_query_param],            )        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

相关推荐

您需要登录后才可以回帖 登录 | 立即注册