跳至内容

过滤、排序与分页

DRF 为列表接口提供了完整的过滤、排序与分页支持,通过配置 filter_backendspagination_class 即可启用。本文还介绍异常处理机制,以及如何统一处理 DRF 未覆盖的业务异常。

过滤(Filtering)

django-filter 集成

对字段进行精确或范围过滤,推荐使用 django-filter 扩展:

pip install django-filter

注册应用并配置全局过滤后端:

# settings.py
INSTALLED_APPS = [
    ...
    'django_filters',
]

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': [
        'django_filters.rest_framework.DjangoFilterBackend',
    ]
}

在视图中指定可过滤字段:

from rest_framework.generics import ListAPIView
from django_filters.rest_framework import DjangoFilterBackend

class StudentListView(ListAPIView):
    queryset         = Student.objects.all()
    serializer_class = StudentSerializer
    filter_backends  = [DjangoFilterBackend]
    filterset_fields = ['age', 'sex', 'class_null']

# 客户端请求:GET /students/?age=20&sex=true

精细化过滤条件

通过自定义 FilterSet 类支持范围、包含、模糊匹配等过滤方式:

import django_filters
from .models import Student

class StudentFilter(django_filters.FilterSet):
    age_min = django_filters.NumberFilter(field_name="age", lookup_expr="gte")
    age_max = django_filters.NumberFilter(field_name="age", lookup_expr="lte")
    name    = django_filters.CharFilter(field_name="name", lookup_expr="icontains")

    class Meta:
        model  = Student
        fields = ["age_min", "age_max", "name", "sex"]


class StudentListView(ListAPIView):
    queryset         = Student.objects.all()
    serializer_class = StudentSerializer
    filter_backends  = [DjangoFilterBackend]
    filterset_class  = StudentFilter

# 客户端请求:GET /students/?age_min=18&age_max=30&name=张

搜索过滤

DRF 内置 SearchFilter,支持跨字段模糊搜索:

from rest_framework.filters import SearchFilter

class StudentListView(ListAPIView):
    queryset         = Student.objects.all()
    serializer_class = StudentSerializer
    filter_backends  = [SearchFilter]
    search_fields    = ['name', 'description']

# 客户端请求:GET /students/?search=张三

search_fields 字段前缀含义:

前缀说明示例
模糊(icontains)'name'
^前缀匹配'^name'
=精确匹配'=name'
@全文检索(需数据库支持)'@description'

排序(Ordering)

DRF 内置 OrderingFilter 允许客户端通过 URL 参数控制排序:

from rest_framework.filters import OrderingFilter

class StudentListView(ListAPIView):
    queryset         = Student.objects.all()
    serializer_class = StudentSerializer
    filter_backends  = [OrderingFilter]
    ordering_fields  = ['id', 'age', 'name']   # 允许排序的字段
    ordering         = ['-id']                  # 默认排序(可选)

# 升序:GET /students/?ordering=age
# 降序:GET /students/?ordering=-age
# 多字段:GET /students/?ordering=class_null,-age

过滤 + 排序组合

同时使用多个 filter_backends 时,需要全部列出(局部配置会覆盖全局配置):

from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter, OrderingFilter

class StudentListView(ListAPIView):
    queryset         = Student.objects.all()
    serializer_class = StudentSerializer
    filter_backends  = [DjangoFilterBackend, SearchFilter, OrderingFilter]
    filterset_fields = ['sex', 'class_null']
    search_fields    = ['name']
    ordering_fields  = ['id', 'age']
    ordering         = ['-id']

分页(Pagination)

PageNumberPagination(页码分页)

最常用的分页方式,客户端通过页码 + 每页大小请求数据:

from rest_framework.pagination import PageNumberPagination

class StandardPagination(PageNumberPagination):
    page_size             = 10        # 默认每页条数
    page_size_query_param = 'size'    # 允许客户端指定每页大小的参数名
    max_page_size         = 100       # 每页最大条数
    page_query_param      = 'page'    # 页码参数名(默认已是 page)


class StudentListView(ListAPIView):
    queryset         = Student.objects.all()
    serializer_class = StudentSerializer
    pagination_class = StandardPagination

# 请求示例:GET /students/?page=2&size=20

分页响应格式:

{
    "count": 100,
    "next": "http://127.0.0.1:8000/students/?page=3&size=20",
    "previous": "http://127.0.0.1:8000/students/?page=1&size=20",
    "results": [...]
}

LimitOffsetPagination(偏移分页)

按偏移量取数据,适合无限滚动场景:

from rest_framework.pagination import LimitOffsetPagination

class StandardLimitOffsetPagination(LimitOffsetPagination):
    default_limit      = 10        # 默认取多少条
    max_limit          = 100       # 最多取多少条
    limit_query_param  = 'limit'   # 取数量参数名
    offset_query_param = 'offset'  # 起始偏移参数名


class StudentListView(ListAPIView):
    pagination_class = StandardLimitOffsetPagination

# 请求示例:GET /students/?limit=20&offset=40
# 含义:从第 40 条开始,取 20 条

全局分页配置

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'myapp.pagination.StandardPagination',
    'PAGE_SIZE': 10,
}
全局分页配置会影响所有使用 list() 方法的视图。对于不需要分页的接口(如下拉列表选项),在视图中设置 pagination_class = None 关闭分页。

异常处理

DRF 内置异常

DRF 自动处理以下异常,并返回适当的 HTTP 响应:

异常类触发场景HTTP 状态码
ParseError请求体解析失败400
AuthenticationFailed认证失败401
NotAuthenticated未认证401
PermissionDenied权限拒绝403
NotFound对象不存在404
MethodNotAllowedHTTP 方法不被允许405
Throttled超出限流频率429
ValidationError序列化器校验失败400

自定义异常处理

对于 DRF 未处理的异常(如数据库错误、业务逻辑异常),可注册自定义处理函数:

# myapp/utils/exceptions.py
from rest_framework.views import exception_handler
from rest_framework.response import Response
from rest_framework import status
from django.db import DatabaseError
import logging

logger = logging.getLogger(__name__)


def custom_exception_handler(exc, context):
    # 先交给 DRF 默认处理器
    response = exception_handler(exc, context)

    if response is not None:
        # DRF 处理了的异常,统一添加状态码字段
        response.data['status_code'] = response.status_code
        return response

    # DRF 未处理的异常,按类型分类处理
    view = context.get('view')

    if isinstance(exc, DatabaseError):
        logger.error("[%s] 数据库错误: %s", view.__class__.__name__, exc)
        return Response(
            {'detail': '数据库错误,请稍后重试'},
            status=status.HTTP_503_SERVICE_UNAVAILABLE,
        )

    # 其他未知异常记录日志,返回 500
    logger.exception("[%s] 未预期的异常", view.__class__.__name__)
    return Response(
        {'detail': '服务器内部错误'},
        status=status.HTTP_500_INTERNAL_SERVER_ERROR,
    )

注册到 settings:

REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'myapp.utils.exceptions.custom_exception_handler',
}

在视图中主动抛出 DRF 异常

from rest_framework.exceptions import NotFound, ValidationError, PermissionDenied

class StudentAPIView(APIView):
    def get(self, request, pk):
        try:
            student = Student.objects.get(pk=pk)
        except Student.DoesNotExist:
            raise NotFound(f"学生 id={pk} 不存在")
        return Response(StudentSerializer(student).data)

    def post(self, request):
        if not request.data.get("name"):
            raise ValidationError({"name": "姓名不能为空"})
        ...

接口文档

DRF 支持通过 coreapidrf-spectacular 自动生成接口文档。

推荐使用 drf-spectacular(OpenAPI 3.0)

pip install drf-spectacular
# settings.py
INSTALLED_APPS = [..., 'drf_spectacular']

REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}
# urls.py
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView

urlpatterns = [
    path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
    path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
]

访问 http://127.0.0.1:8000/api/docs/ 即可查看 Swagger UI 风格的接口文档,支持在线测试。

最后更新于