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]