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[self.page_size_query_param],
- 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个产品
- ]
- }
复制代码 核心源码解析
[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 |