如何在Django REST Framework开发的API中正确实现分页功能
Django Rest Framework 动态分页正确实现方案
你当前的硬编码写法仅能覆盖极个别固定参数场景,无法适配动态传入的页码、单页条数需求,且存在GET请求参数取值位置错误的问题:GET请求的URL参数应从request.query_params获取,而非用于读取请求体数据的request.data。
以下提供两种可直接落地的实现方式,完全匹配你需要的「传入page、rows参数,返回对应范围数据+总条目数」的需求。
方式一:手动实现动态分页逻辑(完全自定义逻辑,适配现有返回结构)
核心逻辑是先做参数校验与类型转换,再根据分页规则计算数据库查询的切片范围,直接通过ORM的分页语法在数据库层面完成数据截取,避免全量查询浪费性能。
完整视图代码如下:
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from .models import DeviceControlPolicy from .serializers import DeviceControlPolicySerializer class DeviceControlPolicyView(APIView): def get(self, request): # 1. 读取分页参数,设置默认值 try: # 页码默认第1页,单页条数默认10条 page = int(request.query_params.get('page', 1)) rows = int(request.query_params.get('rows', 10)) except ValueError: # 参数传非数字时返回错误提示 return Response( {"error": "page和rows参数必须为正整数"}, status=status.HTTP_400_BAD_REQUEST ) # 2. 参数合法性校验 if page < 1 or rows < 1: return Response( {"error": "page和rows参数必须大于0"}, status=status.HTTP_400_BAD_REQUEST ) # 限制单页最大查询条数,防止拖垮数据库 max_rows_per_page = 100 if rows > max_rows_per_page: rows = max_rows_per_page # 3. 计算分页切片位置(ORM切片为左闭右开规则,页码从1开始计数) start_index = (page - 1) * rows end_index = page * rows # 4. 数据库查询:先查总条数,再查当前页数据,自动生成LIMIT/OFFSET SQL total_entries = DeviceControlPolicy.objects.count() page_data = DeviceControlPolicy.objects.all()[start_index:end_index] # 5. 序列化返回 serializer = DeviceControlPolicySerializer(page_data, many=True) return Response({ "details": serializer.data, "entry": total_entries })
方式二:使用DRF内置分页组件(推荐,可复用、少写重复代码)
DRF自带成熟的分页组件,不需要自己手写切片、参数校验逻辑,只需要自定义分页类适配你需要的返回格式即可,后续其他接口可以直接复用这套分页规则。
第一步:自定义分页类
可以在项目的公用utils文件里写,或者直接在当前视图所在文件写:
from rest_framework.pagination import PageNumberPagination class CustomPagePagination(PageNumberPagination): # 指定URL中页码参数的名称,对应你的page参数 page_query_param = "page" # 指定URL中单页条数参数的名称,对应你的rows参数 page_size_query_param = "rows" # 默认单页条数 page_size = 10 # 单页最大允许条数 max_page_size = 100 # 重写返回格式,匹配你需要的details+entry结构 def get_paginated_response(self, data): return Response({ "details": data, "entry": self.page.paginator.count })
第二步:视图中直接引用分页类
代码会比手动实现简洁很多,不需要自己处理分页逻辑:
from rest_framework.views import APIView from rest_framework.response import Response from .models import DeviceControlPolicy from .serializers import DeviceControlPolicySerializer # 导入上面写的自定义分页类 from .pagination import CustomPagePagination class DeviceControlPolicyView(APIView): def get(self, request): queryset = DeviceControlPolicy.objects.all() # 初始化分页类,自动完成参数校验、切片查询 paginator = CustomPagePagination() page_result = paginator.paginate_queryset(queryset, request, view=self) serializer = DeviceControlPolicySerializer(page_result, many=True) return paginator.get_paginated_response(serializer.data)
关键注意点
- 禁止全量查询数据后再在Python内存里做切片,一定要直接对QuerySet做切片,Django ORM会自动把切片逻辑转成数据库层面的
LIMIT、OFFSET语句,性能差距会随数据量增大变得非常明显。 - 必须设置单页最大条数限制,避免恶意请求传入超大rows参数导致数据库查询压力过高。
- 如果后续需要加筛选、排序逻辑,直接在
DeviceControlPolicy.objects.all()后面链式调用筛选方法即可,分页逻辑不需要改动。
内容的提问来源于stack exchange,提问作者Mukul
相关产品推荐
相关产品推荐

