Article
第一章:DRF 入门与环境搭建
1.1 DRF 简介与核心特性
| 名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| Web API | 无固定语法 | 提供前后端分离的数据接口 | - | 需遵循 RESTful 设计规范 |
| 可浏览 API | 自动生成功能 | 开发调试时可直接在浏览器查看接口 | - | 生产环境建议关闭以提升安全 |
| 序列化器 (Serializer) | class MySerializer(serializers.Serializer): | 将复杂数据(如模型实例)转换为 JSON 可序列化格式 | - | 支持反序列化和数据验证 |
| 视图 (Views) | 继承 APIView 或使用 @api_view | 处理 HTTP 请求并返回响应 | - | 比 Django 原生视图更专注 API 逻辑 |
| 认证与权限 | authentication_classes, permission_classes | 控制谁可以访问哪些接口 | - | 默认允许所有人访问,需显式配置限制 |
| 内容协商 | 自动根据请求头选择响应格式 | 支持 JSON、HTML、XML 等多种格式输出 | - | 可通过配置自定义支持的格式 |
| 可扩展性 | 提供插件机制 | 支持自定义认证、权限、分页等组件 | - | 社区生态丰富,如 drf-spectacular 用于文档 |
1.2 安装与配置 DRF
| 名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 安装 DRF | pip install djangorestframework | 安装 Django REST Framework 包 | - | 确保已安装 Django |
| 添加应用 | INSTALLED_APPS += ['rest_framework'] | 启用 DRF 功能模块 | INSTALLED_APPS = ['django.contrib.admin', 'django.contrib.auth', ..., 'rest_framework'] | 必须添加,否则无法使用 DRF 组件 |
| 配置全局设置 | REST_FRAMEWORK = { ... } | 设置默认认证、分页、渲染器等 | REST_FRAMEWORK = {'DEFAULT_RENDERER_CLASSES': ['rest_framework.renderers.JSONRenderer'], 'DEFAULT_AUTHENTICATION_CLASSES': []} | 可省略,使用默认配置 |
| 配置可浏览 API 页面 | 'rest_framework.urls' | 启用登录/登出和 API 浏览界面 | urlpatterns = [path('api-auth/', include('rest_framework.urls'))] | 仅用于开发,生产环境可移除 |
| 检查安装 | python -c "import rest_framework; print(rest_framework.__version__)" | 验证是否安装成功 | - | 若报错说明未正确安装 |
1.3 创建第一个 API 视图(APIView 与 @api_view)
| 名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| @api_view 装饰器 | @api_view(['GET', 'POST']) | 将函数视图转换为 APIView 支持的函数 | from rest_framework.decorators import api_view; @api_view(['GET']); def hello(request): return Response({'message': 'Hello'}) | 必须指定允许的 HTTP 方法 |
| APIView 类 | class MyView(APIView): | 面向对象方式处理 API 请求 | from rest_framework.views import APIView; class HelloView(APIView):; def get(self, request): return Response({'data': []}) | 更适合复杂逻辑和复用 |
| Response 对象 | Response(data, status=200) | 返回 JSON 响应并支持内容协商 | return Response({'count': 10}, status=201) | 替代 Django 的 JsonResponse |
| Request 对象 | request.data, request.query_params | 统一获取请求数据(无论 POST/PUT)和查询参数 | if request.method == 'POST':; print(request.data) | 比 Django 原生 request 更强大 |
| URL 路由绑定 | path('hello/', hello_view) | 将视图函数或类注册到 URL | from django.urls import path; urlpatterns = [path('hello/', hello)] | 类视图需加 .as_view() |
第二章:序列化器(Serializers)基础
2.1 序列化与反序列化概念
| 概念 | 说明 | 注意事项 |
|---|---|---|
| 序列化 | 将 Python 对象(如模型实例)转换为 JSON 字符串以便传输 | 用于 API 响应输出 |
| 反序列化 | 将 JSON 数据解析为 Python 数据结构,并进行验证 | 用于处理客户端提交的数据 |
| Serializer 类 | DRF 中用于执行序列化和反序列化的核心类 | 是数据转换的桥梁 |
| is_valid() 方法 | 验证反序列化数据是否合法 | 必须调用才能访问 validated_data |
| validated_data | 存储通过验证后的干净数据 | 仅在 is_valid() 返回 True 后可用 |
| 数据流方向 | 序列化:数据库 → JSON;反序列化:JSON → 数据库 | 理解方向有助于设计接口 |
2.2 Serializer 类的基本使用
| 方法/字段 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 定义序列化器 | class UserSerializer(serializers.Serializer): | 显式声明字段用于数据转换 | class UserSerializer(serializers.Serializer): name = serializers.CharField(); age = serializers.IntegerField() | 字段需与数据结构匹配 |
| 序列化单个对象 | serializer = MySerializer(obj) | 将模型实例转为字典 | user = User.objects.get(id=1); serializer = UserSerializer(user); print(serializer.data) | 输出为 OrderedDict |
| 序列化多个对象 | serializer = MySerializer(queryset, many=True) | 批量序列化查询集 | users = User.objects.all(); serializer = UserSerializer(users, many=True) | 必须设置 many=True |
| 反序列化数据 | serializer = MySerializer(data=request.data) | 接收客户端 JSON 并验证 | serializer = UserSerializer(data=request.data); if serializer.is_valid(): print(serializer.validated_data) | 必须传 data= |
| save() 方法 | serializer.save() | 保存验证后的数据(需实现 create/update) | if serializer.is_valid(): user = serializer.save() | 需重写 create() 或 update() |
| create() 方法 | def create(self, validated_data): | 定义如何创建新对象 | def create(self, validated_data): return User.objects.create(**validated_data) | 反序列化时调用 |
| update() 方法 | def update(self, instance, validated_data): | 定义如何更新现有对象 | def update(self, instance, validated_data): instance.name = validated_data.get('name'); instance.save(); return instance | instance 为原对象 |
2.3 ModelSerializer 的定义与自动字段生成
| 特性 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| ModelSerializer 类 | class MyModelSerializer(serializers.ModelSerializer): | 自动生成字段并提供默认 create/update | class UserSerializer(serializers.ModelSerializer):; class Meta: model = User; fields = 'all' | 减少重复代码 |
| Meta 类 | class Meta: | 配置模型和字段映射 | class Meta: model = Book; fields = ['id', 'title', 'author'] | 必须定义 |
| fields 属性 | fields = ['f1', 'f2'] 或 '__all__' | 指定包含的字段 | fields = '__all__'(推荐显式列出字段) | 推荐显式列出字段 |
| exclude 属性 | exclude = ['field_name'] | 排除某些字段 | exclude = ['created_at'] | 与 fields 互斥 |
| read_only_fields | read_only_fields = ['field'] | 设置字段只读(不参与反序列化) | read_only_fields = ['id', 'created_at'] | 常用于自增字段 |
| 自动字段映射 | 根据模型字段类型生成对应 Serializer 字段 | 减少手动定义 | CharField → CharField, DateTimeField → DateTimeField | 大部分类型自动支持 |
| 自动 create/update | 内置保存逻辑 | 直接调用 save() 保存数据 | serializer = UserSerializer(data=data); if serializer.is_valid(): serializer.save() | 无需手动实现 |
2.4 字段级验证方法(validate_)
| 方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| validate_ | def validate_<field_name>(self, value): | 对特定字段进行额外验证 | def validate_age(self, value):; if value < 0:; raise serializers.ValidationError("年龄不能为负数"); return value | 方法名必须匹配字段名 |
| 抛出 ValidationError | raise serializers.ValidationError("错误信息") | 中断验证并返回错误 | 如上例所示 | 必须从 serializers 导入 |
| 返回验证后值 | return value | 允许修改或原样返回字段值 | return value.strip() # 去空格 | 必须返回值 |
| 执行时机 | 在字段通用验证之后执行 | 确保值类型正确后再做业务逻辑验证 | - | 例如:CharField 已确保是字符串 |
| 仅对存在字段调用 | 字段存在于 data 中才执行 | 跳过未提交字段的验证 | - | 可结合 required=False 使用 |
2.5 对象级验证方法(validate)
| 方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| validate 方法 | def validate(self, attrs): | 验证多个字段之间的逻辑关系 | def validate(self, attrs):; if attrs['password'] != attrs['confirm_password']:; raise serializers.ValidationError("两次密码不一致"); return attrs | 接收所有字段的 validated_data |
| 参数 attrs | 字典类型,包含所有字段值 | 用于跨字段比较 | 如上例所示 | 键为字段名,值为输入值 |
| 返回 attrs | return attrs | 必须返回数据供后续使用 | - | 可修改字段值后返回 |
| 与字段验证顺序 | 在所有 validate_ 之后执行 | 确保每个字段已单独验证通过 | - | 避免处理无效数据 |
| 可抛出 ValidationError | raise serializers.ValidationError({...}) | 返回结构化错误信息 | raise serializers.ValidationError({'start_date': '开始时间不能晚于结束时间'}) | 支持字段级错误定位 |
2.6 自定义字段与只读/必填字段控制
| 字段/参数 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| write_only | write_only=True | 字段仅用于输入(不序列化输出) | password = serializers.CharField(write_only=True) | 常用于密码、验证码 |
| read_only | read_only=True | 字段仅用于输出(不参与反序列化) | created_at = serializers.DateTimeField(read_only=True) | 可替代 read_only_fields |
| required | required=False | 设置字段非必填 | email = serializers.EmailField(required=False) | 默认为 True |
| default | default='value' | 设置默认值 | status = serializers.CharField(default='active') | 可为函数或常量 |
| allow_null | allow_null=True | 允许字段值为 None | age = serializers.IntegerField(allow_null=True) | 注意与 required 区分 |
| source | source='field_name' | 指定字段对应的数据源属性 | full_name = serializers.CharField(source='get_full_name') | 支持方法调用 |
| 自定义字段 | 继承 serializers.Field | 实现特殊序列化逻辑 | class CustomField(serializers.Field):; def to_representation(self, value): return str(value); def to_internal_value(self, data): return int(data) | 需实现两个核心方法 |
第三章:视图层(Views)
3.1 APIView 基类详解
| 方法/属性 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 继承 APIView | class MyView(APIView): | 创建基于类的 API 视图 | from rest_framework.views import APIView; class UserView(APIView): def get(self, request): return Response({'users': []}) | 必须从 rest_framework.views 导入 |
| HTTP 方法处理 | def get(self, request):, def post(self, request): | 分别处理不同 HTTP 请求 | def post(self, request): serializer = UserSerializer(data=request.data); if serializer.is_valid(): serializer.save(); return Response(serializer.data, status=201); return Response(serializer.errors, status=400) | 方法名对应 HTTP 动词小写 |
| permission_classes | permission_classes = [IsAuthenticated] | 设置视图级权限控制 | class UserView(APIView): permission_classes = [IsAuthenticated]; def get(self, request): ... | 覆盖全局设置 |
| authentication_classes | authentication_classes = [...] | 指定认证方式 | authentication_classes = [TokenAuthentication] | 可组合多个认证器 |
| throttle_classes | throttle_classes = [UserRateThrottle] | 添加访问频率限制 | throttle_classes = [AnonRateThrottle] | 常用于防止滥用 |
| 自定义 dispatch | def dispatch(self, request, *args, **kwargs): | 拦截请求前后执行逻辑 | def dispatch(self, request, *args, **kwargs):; print("Request received"); return super().dispatch(request, *args, **kwargs) | 可用于日志、性能监控 |
3.2 混合类(Mixins)与通用视图(GenericAPIView)
| 类/方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| GenericAPIView | class MyView(GenericAPIView): | 提供通用属性和方法(queryset, serializer_class) | class UserListView(GenericAPIView): queryset = User.objects.all(); serializer_class = UserSerializer; def get(self, request): serializer = self.get_serializer(many=True); return Response(serializer.data) | 需配合 Mixin 使用 |
| ListModelMixin | ListModelMixin | 实现 list() 方法返回列表 | class UserList(GenericAPIView, ListModelMixin): queryset = User.objects.all(); serializer_class = UserSerializer; def get(self, request): return self.list(request) | 自动生成分页响应 |
| CreateModelMixin | CreateModelMixin | 实现 create() 方法处理创建 | def post(self, request): return self.create(request) | 自动调用 save() |
| RetrieveModelMixin | RetrieveModelMixin | 实现 retrieve() 返回单个对象 | def get(self, request, pk): return self.retrieve(request, pk) | 需提供 pk |
| UpdateModelMixin | UpdateModelMixin | 实现 update() 和 partial_update() | def put(self, request, pk): return self.update(request, pk); def patch(self, request, pk): return self.partial_update(request, pk) | 支持全量/部分更新 |
| DestroyModelMixin | DestroyModelMixin | 实现 destroy() 删除对象 | def delete(self, request, pk): return self.destroy(request, pk) | 默认返回 204 |
| get_serializer() | self.get_serializer() | 获取序列化器实例 | serializer = self.get_serializer(data=request.data) | 自动传入 context |
3.3 通用视图集(GenericViewSet)与路由映射
| 类/方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| GenericViewSet | class MyViewSet(GenericAPIView, ViewSetMixin): | 结合 GenericAPIView 与 ViewSet 行为 | from rest_framework.viewsets import GenericViewSet; class UserViewSet(GenericViewSet, ListModelMixin, RetrieveModelMixin): queryset = User.objects.all(); serializer_class = UserSerializer | 必须混入 Mixin 实现功能 |
| action 装饰器 | @action(methods=['get'], detail=True) | 添加自定义动作 | from rest_framework.decorators import action; @action(methods=['get'], detail=False); def recent(self, request): users = User.objects.order_by('-date_joined')[:5]; serializer = self.get_serializer(users, many=True); return Response(serializer.data) | detail=True 表示操作单个对象 |
| detail 参数 | detail=True/False | 控制 URL 是否包含 pk | @action(detail=False, methods=['get']); def stats(self, request): ... | False 用于集合级操作 |
| methods 参数 | methods=['get', 'post'] | 指定允许的 HTTP 方法 | @action(methods=['post'], detail=True); def activate(self, request, pk): ... | 默认为 [‘get’] |
| url_path | url_path='custom-path' | 自定义路由路径 | @action(url_path='activate', detail=True); def activate_user(self, request, pk): ... | URL 变为 /users/1/activate/ |
| url_name | url_name='activate' | 设置反向查找名 | @action(url_name='activate', ...) | 用于 reverse 解析 |
3.4 ModelViewSet 快速构建 CRUD 接口
| 方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| ModelViewSet | class MyViewSet(viewsets.ModelViewSet): | 快速实现完整的 CRUD 操作 | from rest_framework import viewsets; class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all(); serializer_class = UserSerializer | 自动生成 list, create, retrieve, update, destroy |
| 默认方法 | .list(), .create(), .retrieve(), .update(), .destroy() | 覆盖即可自定义行为 | def create(self, request): response = super().create(request); # 发送欢迎邮件; return response | 可在原有逻辑前后扩展 |
| queryset | queryset = Model.objects.all() | 定义数据源 | queryset = User.objects.filter(is_active=True) | 推荐过滤敏感数据 |
| serializer_class | serializer_class = MySerializer | 指定默认序列化器 | serializer_class = UserDetailSerializer | 可动态切换 |
| lookup_field | lookup_field = 'slug' | 更改查找字段(默认 ‘pk’) | lookup_field = 'username' | 需确保唯一性 |
| http_method_names | http_method_names = ['get', 'post'] | 限制允许的 HTTP 方法 | http_method_names = ['get', 'head'] | 禁用不安全操作 |
3.5 ViewSet 与 URL 路由绑定(SimpleRouter / DefaultRouter)
| 类/方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| SimpleRouter | router = SimpleRouter() | 自动生成标准 REST 路由 | from rest_framework.routers import SimpleRouter; router = SimpleRouter(); router.register('users', UserViewSet); urlpatterns = router.urls | 不包含额外 HTML 页面 |
| DefaultRouter | router = DefaultRouter() | 同 SimpleRouter,但包含根视图 | from rest_framework.routers import DefaultRouter; router = DefaultRouter(); router.register('users', UserViewSet) | 推荐开发使用,提供导航页 |
| register() 方法 | router.register(prefix, viewset) | 注册 ViewSet 到路由 | router.register('books', BookViewSet, basename='book') | basename 用于反向解析 |
| basename 参数 | basename='model-name' | 指定 URL 名称前缀 | basename='user' → url name: ‘user-list’, ‘user-detail’ | 当 queryset 为空时必须指定 |
| include 路由 | urlpatterns += router.urls | 将路由注入主 URL 配置 | from django.urls import include; urlpatterns = [path('api/', include(router.urls))] | 可加前缀组织 API 版本 |
| 自定义路由 | 继承 BaseRouter | 实现非标准路由逻辑 | - | 高级用法,一般无需自定义 |
第四章:请求与响应处理
4.1 Request 对象封装与属性(data, query_params 等)
| 属性 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| request.data | request.data | 获取请求体数据(兼容 POST/PUT/PATCH) | if request.data.get('name'):; print(request.data['name']) | 替代 request.POST 和 request.body 解析 |
| request.query_params | request.query_params | 获取 URL 查询参数(类似 GET) | page = request.query_params.get('page', 1) | 替代 request.GET |
| request.user | request.user | 获取当前认证用户 | if request.user.is_authenticated:; print(request.user.username) | 未登录为 AnonymousUser |
| request.auth | request.auth | 获取认证凭据(如 Token) | print(request.auth.key) | 仅在 TokenAuthentication 下有效 |
| request.method | request.method | 获取 HTTP 方法(大写字符串) | if request.method == 'POST': ... | 值为 ‘GET’, ‘POST’ 等 |
| request.content_type | request.content_type | 获取请求内容类型 | print(request.content_type) | 如 ‘application/json’ |
| request.stream | request.stream | 低级访问原始请求体 | raw_body = request.stream.read() | 一般不推荐直接使用 |
4.2 Response 对象构造与状态码控制
| 参数/方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| Response 类 | Response(data, status=200) | 返回 API 响应 | return Response({'ok': True}, status=201) | 自动进行内容协商 |
| data 参数 | data={...} | 响应数据(字典、列表等) | return Response({'count': 10}) | 必须可 JSON 序列化 |
| status 参数 | status=status.HTTP_201_CREATED | 设置 HTTP 状态码 | from rest_framework import status; return Response(data, status=status.HTTP_400_BAD_REQUEST) | 使用常量更清晰 |
| template_name | template_name='api.html' | 指定可浏览 API 模板 | return Response(data, template_name='custom.html') | 仅用于调试页面 |
| headers 参数 | headers={'Location': '/new-url'} | 添加响应头 | return Response(data, headers={'X-Process-Time': '100ms'}) | 常用于重定向、自定义头 |
| content_type | content_type='application/xml' | 强制指定内容类型 | return Response(xml_data, content_type='application/xml') | 覆盖自动协商结果 |
4.3 内容协商(Content Negotiation)机制
| 概念/类 | 说明 | 代码示例 | 注意事项 |
|---|---|---|---|
| 内容协商 | 根据客户端请求头选择响应格式 | Accept: application/json / Accept: text/html | DRF 自动判断返回 JSON 或 HTML 页面 |
| DefaultContentNegotiation | 默认协商类 | 可在 REST_FRAMEWORK 设置中替换 | - |
| renderers 设置 | DEFAULT_RENDERER_CLASSES 配置支持的渲染格式 | REST_FRAMEWORK = {'DEFAULT_RENDERER_CLASSES': ['rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer']} | - |
| JSONRenderer | 返回纯 JSON | - | 适合生产环境 |
| BrowsableAPIRenderer | 返回可交互 HTML 页面 | - | 仅用于开发调试 |
| format 参数 | ?format=json 强制指定响应格式 | /users/?format=json | - |
| 自定义 Renderer | 继承 BaseRenderer 支持 XML、CSV 等格式 | class CSVRenderer(BaseRenderer): media_type = 'text/csv'; format = 'csv'; def render(self, data, media_type=None, renderer_context=None): ... | - |
第五章:认证(Authentication)
5.1 认证机制概述(SessionAuthentication, TokenAuthentication 等)
| 认证类 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| SessionAuthentication | authentication_classes = [SessionAuthentication] | 基于 Django 会话的认证,适合浏览器客户端 | REST_FRAMEWORK = {'DEFAULT_AUTHENTICATION_CLASSES': ['rest_framework.authentication.SessionAuthentication']} | 需配合 CSRF 保护,不推荐用于纯 API |
| TokenAuthentication | TokenAuthentication | 使用静态 Token 进行认证,适合移动客户端 | from rest_framework.authentication import TokenAuthentication; class MyView(APIView): authentication_classes = [TokenAuthentication] | 每个用户一个 Token,需手动管理 |
| BasicAuthentication | BasicAuthentication | 使用 HTTP Basic Auth(用户名:密码 Base64 编码) | 'rest_framework.authentication.BasicAuthentication' | 不安全,仅用于测试或 HTTPS 环境 |
| JWTAuthentication | JWTAuthentication | 使用 JSON Web Token 实现无状态认证 | 需安装 djangorestframework-simplejwt | 支持刷新 Token,适合前后端分离 |
| RemoteUserAuthentication | RemoteUserAuthentication | 由前端代理(如 Nginx)完成认证 | 适用于 SSO 或企业级认证系统 | 需配置 Web 服务器 |
| 认证执行顺序 | 按列表顺序依次尝试 | 直到某个认证成功或全部失败 | authentication_classes = [TokenAuthentication, SessionAuthentication] | 返回 (user, auth) 表示成功,None 继续下个 |
5.2 配置全局与局部认证类
| 配置方式 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 全局配置 | REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] | 设置所有视图默认认证方式 | REST_FRAMEWORK = {'DEFAULT_AUTHENTICATION_CLASSES': ['rest_framework.authentication.TokenAuthentication']} | 项目级统一控制 |
| 局部配置(视图类) | authentication_classes = [...] | 覆盖全局设置,仅作用于当前视图 | class PublicView(APIView): authentication_classes = []; def get(self, request): return Response({'msg': '无需认证'}) | 优先级高于全局 |
| 局部配置(函数视图) | @authentication_classes([TokenAuthentication]) | 为函数视图设置认证 | @api_view(['GET']); @authentication_classes([TokenAuthentication]); def profile(request): ... | 需导入 authentication_classes 装饰器 |
| 禁用认证 | authentication_classes = [] | 明确表示该视图无需认证 | authentication_classes = [] | 与 AllowAny 权限配合使用 |
| 多认证组合 | [TokenAuth, SessionAuth] | 支持多种客户端接入 | authentication_classes = [TokenAuthentication, SessionAuthentication] | 按顺序尝试,任一成功即通过 |
| settings.py 位置 | 在 Django 配置文件中 | 集中管理 DRF 行为 | # settings.py; REST_FRAMEWORK = { ... } | 修改后需重启服务 |
5.3 自定义认证类实现
| 方法/属性 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 继承 BaseAuthentication | class MyAuth(BaseAuthentication): | 创建自定义认证类 | from rest_framework.authentication import BaseAuthentication; class CustomTokenAuthentication(BaseAuthentication): ... | 必须实现 authenticate() |
| authenticate() 方法 | def authenticate(self, request): | 核心认证逻辑 | def authenticate(self, request):; token = request.META.get('HTTP_X_API_KEY'); if not token: return None; try: user = User.objects.get(api_key=token); return (user, token); except User.DoesNotExist: raise AuthenticationFailed('无效 API Key') | 返回 (user, auth) 成功,None 继续,抛异常拒绝 |
| 抛出 AuthenticationFailed | from rest_framework.exceptions import AuthenticationFailed | 中断认证并返回 401 | raise AuthenticationFailed('登录失效') | 客户端应重新登录 |
| authenticate_header() | def authenticate_header(self, request): | 返回 WWW-Authenticate 头 | def authenticate_header(self, request): return 'API-Key' | 影响 401 响应头,用于提示客户端 |
| request 参数 | request | 获取请求头、数据等 | auth_header = request.META.get('HTTP_AUTHORIZATION') | 注意 META 中的键为大写 |
| 返回值说明 | (user, auth) 或 None | (user, auth) 表示成功,None 表示不处理(继续下一个认证器) | return (user, 'token-str') | 若所有认证器返回 None,用户为 AnonymousUser |
第六章:权限控制(Permissions)
6.1 权限系统工作原理
| 概念 | 说明 | 注意事项 |
|---|---|---|
| 执行时机 | 在认证之后、视图执行之前 | 确保 request.user 已确定 |
| 权限检查流程 | 依次检查每个权限类的 has_permission() 或 has_object_permission() | 所有权限必须通过 |
| has_permission() | 针对整个视图的权限(如能否访问列表) | 用于 GET /items/ 这类集合操作 |
| has_object_permission() | 针对具体对象的权限(如能否编辑某条数据) | 在 get_object() 时调用,用于 PUT /items/1/ |
| 权限组合 | 使用元组或列表配置多个权限 | permission_classes = [IsAuthenticated, IsOwner] |
| 短路机制 | 任一权限返回 False,立即拒绝并返回 403 | 不会继续检查后续权限 |
| 上下文参数 | 传入 request, view, obj(可选) | 可根据请求方法、用户、对象状态判断 |
| 与认证关系 | 认证解决”你是谁”,权限解决”你能做什么” | 通常先认证后授权 |
6.2 常用权限类(IsAuthenticated, IsAdminUser, AllowAny 等)
| 权限类 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| AllowAny | permission_classes = [AllowAny] | 允许所有用户访问 | class PublicView(APIView): permission_classes = [AllowAny]; def get(self, request): ... | 默认权限,无需显式设置 |
| IsAuthenticated | IsAuthenticated | 仅允许已登录用户 | permission_classes = [IsAuthenticated] | 最常用,防止未登录访问 |
| IsAdminUser | IsAdminUser | 仅允许 is_staff=True 的用户 | permission_classes = [IsAdminUser] | 适合后台管理接口 |
| IsAuthenticatedOrReadOnly | IsAuthenticatedOrReadOnly | 已登录可读写,否则仅可读 | permission_classes = [IsAuthenticatedOrReadOnly] | 适合公开内容(如博客) |
| DjangoModelPermissions | DjangoModelPermissions | 基于 Django 权限系统(add, change, delete) | permission_classes = [DjangoModelPermissions] | 需为用户分配具体模型权限 |
| DjangoObjectPermissions | DjangoObjectPermissions | 基于行级权限(django-guardian) | 需配合 ObjectPermission 使用 | 实现细粒度数据访问控制 |
| 全局默认权限 | DEFAULT_PERMISSION_CLASSES | 设置项目默认权限 | REST_FRAMEWORK = {'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated']} | 推荐设为 IsAuthenticated 提升安全 |
6.3 自定义权限类开发
| 方法/类 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 继承 BasePermission | class MyPerm(BasePermission): | 创建自定义权限 | from rest_framework.permissions import BasePermission; class IsOwner(BasePermission): ... | 必须实现 has_permission 或 has_object_permission |
| has_permission() | def has_permission(self, request, view): | 集合级权限判断 | def has_permission(self, request, view): return request.user.is_authenticated | 可用于限制 HTTP 方法 |
| has_object_permission() | def has_object_permission(self, request, view, obj): | 对象级权限判断 | def has_object_permission(self, request, view, obj): return obj.owner == request.user | 在 get_object() 时调用 |
| 结合 request.method | if request.method in SAFE_METHODS: | 区分读写操作 | if request.method in ['GET', 'HEAD', 'OPTIONS']: return True | SAFE_METHODS = (‘GET’, ‘HEAD’, ‘OPTIONS’) |
| 返回布尔值 | return True/False | 允许或拒绝访问 | return obj.team.members.filter(id=request.user.id).exists() | 不可抛异常,否则返回 500 |
| 应用到视图 | permission_classes = [IsOwner] | 将自定义权限加入配置 | class TaskDetailView(APIView): permission_classes = [IsOwner]; ... | 可与其他权限组合使用 |
| 错误信息控制 | self.message = '自定义提示' | 设置拒绝时的错误消息 | class IsOwner(BasePermission): message = '你不是该资源的所有者'; def has_object_permission(...): ... | 在异常响应中返回 |
第七章:限流控制(Throttling)
7.1 限流机制作用与场景
| 概念 | 说明 | 注意事项 |
|---|---|---|
| 限流(Throttling) | 限制客户端在单位时间内可发起的请求数量 | 防止滥用、DDoS 攻击、保护服务器资源 |
| 作用时机 | 在权限检查之后、视图执行之前 | 确保认证和权限通过后再进行频率控制 |
| 典型应用场景 | API 接口防刷、公共接口保护、免费用户配额限制 | 如登录接口、注册接口、公开数据查询 |
| 匿名用户限流 | 防止未登录用户高频请求 | 常用于防止爬虫或暴力破解 |
| 认证用户限流 | 控制登录用户的调用频率 | 可按用户级别设置不同配额(如 VIP 用户更高限额) |
| 返回状态码 | 超过限制时返回 429 Too Many Requests | 客户端应根据 Retry-After 头等待重试 |
| 与缓存依赖 | 限流状态通常存储在缓存中(如 Redis) | 需配置 cache 后端以支持分布式环境 |
7.2 内置限流类(AnonRateThrottle, UserRateThrottle)
| 限流类 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| AnonRateThrottle | throttle_classes = [AnonRateThrottle] | 限制匿名用户(未登录)的请求频率 | REST_FRAMEWORK = {'DEFAULT_THROTTLE_RATES': {'anon': '10/minute'}} | 依据 IP 地址识别用户 |
| UserRateThrottle | UserRateThrottle | 限制认证用户的请求频率 | 'DEFAULT_THROTTLE_RATES': {'user': '100/hour'} | 依据 request.user 识别,更精准 |
| ScopedRateThrottle | class MyThrottle(ScopedRateThrottle): | 按视图作用域(scope)进行限流 | 'DEFAULT_THROTTLE_RATES': {'login': '5/hour'}; class LoginView(APIView): throttle_scope = 'login' | 适用于特定高风险接口 |
| throttle_rates 配置 | 'scope_name': '次数/时间单位' | 定义不同作用域的速率 | 支持 second, minute, hour, day | 格式为 “5/minute”,不可空格 |
| 多限流组合 | [AnonRateThrottle, UserRateThrottle] | 同时应用多种限流策略 | throttle_classes = [AnonRateThrottle, UserRateThrottle] | 所有限流器都必须通过 |
| 响应头字段 | X-Throttle-Count, X-Throttle-Remaining, Retry-After | 提供限流状态信息 | 客户端可据此调整请求行为 | Retry-After 在被限流时返回 |
7.3 自定义限流策略
| 方法/属性 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 继承 BaseThrottle | class MyThrottle(BaseThrottle): | 创建自定义限流类 | from rest_framework.throttling import BaseThrottle; class RateAveragerThrottle(BaseThrottle): ... | 必须实现 allow_request() |
| allow_request() | def allow_request(self, request, view): | 核心判断逻辑:是否允许本次请求 | def allow_request(self, request, view): return random.randint(1, 10) > 3 | 返回 True 允许,False 拒绝 |
| wait() 方法 | def wait(self): | 返回还需等待的秒数 | def wait(self): import time; return 60 - (time.time() % 60) | 用于 Retry-After 头 |
| 缓存存储 | from django.core.cache import cache | 存储请求记录(如 IP + 时间戳) | key = f"throttle_{request.META['REMOTE_ADDR']}"; requests = cache.get(key, []); requests = [r for r in requests if r > time.time() - 60]; if len(requests) >= 5: return False; requests.append(time.time()); cache.set(key, requests, 60) | 推荐使用 Redis |
| 按用户角色限流 | 结合 request.user 判断 | 不同角色不同配额 | if request.user.is_premium: rate = 100; else: rate = 10 | 需权限系统支持 |
| 作用域自定义 | throttle_scope = 'custom' | 与 ScopedRateThrottle 配合 | class UploadView(APIView): throttle_scope = 'upload'; throttle_classes = [ScopedRateThrottle] | 需在 DEFAULT_THROTTLE_RATES 中定义 |
第八章:过滤、搜索与排序
8.1 使用 DjangoFilterBackend 实现字段过滤
| 属性/类 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| DjangoFilterBackend | from django_filters.rest_framework import DjangoFilterBackend | 启用字段级精确过滤 | class ProductListView(ListAPIView): queryset = Product.objects.all(); serializer_class = ProductSerializer; filter_backends = [DjangoFilterBackend]; filterset_fields = ['category', 'in_stock'] | 需安装 django-filter |
| filterset_fields | filterset_fields = ['field1', 'field2'] | 声明可过滤的字段 | filterset_fields = ['status', 'author__id'] | 支持外键关联字段 |
| 查询语法 | ?field=value | 客户端传参进行过滤 | /api/products/?category=electronics&in_stock=True | 多值用 , 分隔(需配置) |
| 精确匹配 | 默认行为 | 等值查询 | ?status=active → status=‘active’ | 不支持模糊匹配 |
| 关联字段过滤 | foreign__field | 跨表过滤 | filterset_fields = ['author__department'] | 路径需存在且可访问 |
| 安装依赖 | pip install django-filter | 启用过滤功能 | INSTALLED_APPS += ['django_filters'] | 必须添加到 INSTALLED_APPS |
8.2 SearchFilter 实现关键词搜索
| 属性/类 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| SearchFilter | from rest_framework.filters import SearchFilter | 启用全文关键词搜索 | filter_backends = [SearchFilter]; search_fields = ['title', 'description'] | 基于数据库 LIKE 查询 |
| search_fields | search_fields = ['field1', ...] | 定义参与搜索的字段 | search_fields = ['^title', '=code', '@description'] | 支持前缀、精确、全文索引 |
| 搜索类型前缀 | ^, =, @, $ | 控制匹配方式 | '^subject' → 以关键词开头;'=name' → 精确匹配;'@description' → 全文搜索(MySQL);'$content' → 正则匹配 | 不同数据库支持不同 |
| 查询参数 | ?search=关键词 | 客户端传入搜索词 | /api/articles/?search=python+web | 多词默认为 OR 关系 |
| 多字段 OR 查询 | 所有 search_fields 字段使用 OR 连接 | - | 搜索效率随字段增多下降 | |
| 数据库性能 | LIKE 查询无索引时较慢 | 大数据量建议使用 Elasticsearch | 可结合数据库全文索引优化 |
8.3 OrderingFilter 实现结果排序
| 属性/类 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| OrderingFilter | from rest_framework.filters import OrderingFilter | 允许客户端对结果排序 | filter_backends = [OrderingFilter]; ordering_fields = ['price', 'created_at'] | 需显式声明可排序字段 |
| ordering_fields | ordering_fields = ['f1', 'f2'] | 定义允许排序的字段 | ordering_fields = ['title', 'price', 'user__name'] | 防止枚举所有字段造成信息泄露 |
| ordering 参数 | ?ordering=field 或 ?-field | 升序或降序 | ?ordering=price → 升序;?ordering=-created_at → 降序 | 支持多字段 ?ordering=price,-date |
| 默认排序 | ordering = ['field'] | 设置默认排序规则 | ordering = ['-created_at'] | 可被客户端覆盖 |
| 关联字段排序 | foreign__field | 按外键字段排序 | ordering_fields = ['author__last_name'] | 需注意性能,避免 N+1 查询 |
| 安全性 | 限制 ordering_fields | 防止敏感字段被排序(如密码、密钥) | 不要包含 is_staff, api_key 等字段 | 显式列出字段更安全 |
第九章:分页(Pagination)
9.1 PageNumberPagination 分页实现
| 属性/方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| PageNumberPagination 类 | class MyPagination(PageNumberPagination): | 基于页码的标准分页器 | from rest_framework.pagination import PageNumberPagination; class StandardResultsSetPagination(PageNumberPagination): page_size = 10; page_size_query_param = 'page_size'; max_page_size = 100 | 最常用的分页方式 |
| page_size | page_size = 20 | 每页默认数据条数 | page_size = 20 | 可被 page_size_query_param 覆盖 |
| page_size_query_param | page_size_query_param = 'size' | 允许客户端通过参数自定义每页数量 | page_size_query_param = 'page_size' | 不设置则客户端无法修改 |
| max_page_size | max_page_size = 100 | 限制客户端请求的最大页大小 | max_page_size = 50 | 防止 page_size=99999 导致性能问题 |
| page_query_param | page_query_param = 'p' | 自定义页码参数名(默认 ‘page’) | page_query_param = 'page' | URL 中使用 ?page=2 |
| get_paginated_response() | def get_paginated_response(self, data): | 自定义分页响应结构 | def get_paginated_response(self, data): return Response({'links': {'next': self.get_next_link(), 'previous': self.get_previous_link()}, 'count': self.page.paginator.count, 'results': data}) | 用于统一响应格式 |
| 应用于视图 | pagination_class = MyPagination | 在视图中指定分页类 | class UserListView(ListAPIView): queryset = User.objects.all(); serializer_class = UserSerializer; pagination_class = StandardResultsSetPagination | 也可在全局设置 |
| 全局配置 | DEFAULT_PAGINATION_CLASS | 设置项目级默认分页 | REST_FRAMEWORK = {'DEFAULT_PAGINATION_CLASS': 'myapp.pagination.StandardResultsSetPagination', 'PAGE_SIZE': 10} | 推荐用于统一风格 |
9.2 LimitOffsetPagination 分页模式
| 属性/方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| LimitOffsetPagination 类 | class MyLimitOffsetPagination(LimitOffsetPagination): | 基于偏移量的分页(类似 SQL LIMIT/OFFSET) | from rest_framework.pagination import LimitOffsetPagination; class CustomLimitOffsetPagination(LimitOffsetPagination): default_limit = 20; limit_query_param = 'limit'; offset_query_param = 'offset'; max_limit = 100 | 适合无限滚动、APP 分页 |
| default_limit | default_limit = 20 | 默认每页数量(即 LIMIT) | default_limit = 10 | 客户端可覆盖 |
| limit_query_param | limit_query_param = 'per_page' | 自定义 limit 参数名 | limit_query_param = 'limit' | 对应 URL 中 ?limit=20 |
| offset_query_param | offset_query_param = 'start' | 自定义 offset 参数名 | offset_query_param = 'offset' | 对应 ?offset=40 表示跳过前40条 |
| max_limit | max_limit = 100 | 限制最大返回数量 | max_limit = 50 | 防止一次性拉取过多数据 |
| get_paginated_response() | 同 PageNumberPagination | 可重写以自定义响应结构 | def get_paginated_response(self, data): return Response({'count': self.count, 'next': self.get_next_link(), 'previous': self.get_previous_link(), 'results': data}) | 保持 API 一致性 |
| 适用场景 | 无页码概念的接口 | 如 APP 列表加载更多 | /api/items/?limit=10&offset=20 | 适合大数据集,但深分页性能差 |
| 性能注意 | OFFSET 越大查询越慢 | 数据库需扫描前 N 行 | OFFSET 100000 时性能显著下降 | 超大数据集建议使用游标分页(CursorPagination) |
9.3 自定义分页样式与响应结构
| 方法/属性 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 继承 BasePagination | class CustomPagination(BasePagination): | 创建完全自定义分页器 | from rest_framework.pagination import BasePagination; class SimplePagination(BasePagination): ... | 需手动实现分页逻辑 |
| paginate_queryset() | def paginate_queryset(self, queryset, request, view=None): | 执行分页并返回当前页数据 | def paginate_queryset(self, queryset, request, view=None): self.page_size = self.get_page_size(request); paginator = Paginator(queryset, self.page_size); page_number = request.query_params.get('p', 1); self.page = paginator.get_page(page_number); return self.page.object_list | 必须实现 |
| get_paginated_response() | def get_paginated_response(self, data): | 定义返回的 JSON 结构 | def get_paginated_response(self, data): return Response({'data': data, 'meta': {'total': self.page.paginator.count, 'current_page': self.page.number, 'last_page': self.page.paginator.num_pages, 'per_page': self.page_size}}) | 推荐统一分页响应格式 |
| get_page_size() | def get_page_size(self, request): | 动态计算页大小 | 可根据用户角色返回不同 page_size | 用于差异化服务 |
| 使用 DRF 内置类继承 | 推荐继承 PageNumberPagination | 在现有功能上扩展 | class MyPagination(PageNumberPagination): def get_paginated_response(self, data): ... | 更稳定,减少出错 |
| 添加额外元数据 | 在响应中加入统计信息 | 如总页数、是否有下一页 | 'meta': {'has_next': self.page.has_next(), 'has_previous': self.page.has_previous()} | 提升客户端体验 |
| 禁用分页 | pagination_class = None | 特定视图关闭分页 | class ExportView(ListAPIView): pagination_class = None; ... | 用于导出全量数据 |
第十章:异常处理与自定义错误响应
10.1 DRF 异常体系(APIException 及子类)
| 异常类 | 说明 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| APIException | 基类异常 | 所有 DRF 自定义异常的父类 | class CustomError(APIException): | 可继承创建自定义错误 |
| status_code | status_code = 400 | HTTP 状态码 | status_code = 429 | 必须为有效 HTTP 状态码 |
| default_detail | default_detail = '错误信息' | 默认错误描述 | default_detail = '请求频率超限' | 客户端可见 |
| default_code | default_code = 'invalid' | 错误码标识符 | default_code = 'rate_limit_exceeded' | 用于程序判断 |
| ParseError | 请求数据解析失败 | 如 JSON 格式错误 | raise ParseError('Invalid JSON') | 自动捕获 request.data 解析异常 |
| AuthenticationFailed | 认证失败 | Token 过期、无效凭证 | raise AuthenticationFailed('Token expired') | 返回 401 |
| NotAuthenticated | 未提供认证信息 | 未登录访问受保护接口 | raise NotAuthenticated() | 通常由认证类抛出 |
| PermissionDenied | 权限不足 | 用户无权访问资源 | raise PermissionDenied('No permission') | 返回 403 |
| NotFound | 资源不存在 | 对象查询失败(get_object) | raise NotFound('User not found') | 返回 404 |
| MethodNotAllowed | 方法不被允许 | 如对只读视图发 PUT 请求 | raise MethodNotAllowed(request.method) | 返回 405 |
| NotAcceptable | 内容协商失败 | 客户端 Accept 头无法满足 | raise NotAcceptable() | 返回 406 |
| Throttled | 请求频率超限 | 超过限流阈值 | raise Throttled(wait=60) | 返回 429,可带 wait 秒数 |
| ValidationError | 数据验证失败 | 序列化器 .is_valid() 失败 | raise ValidationError({'name': 'This field is required.'}) | 可返回字段级错误 |
10.2 全局异常处理器 custom_exception_handler
| 属性/方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| custom_exception_handler | def custom_exception_handler(exc, context): | 全局捕获并格式化异常响应 | from rest_framework.views import exception_handler; def custom_exception_handler(exc, context): response = exception_handler(exc, context); if response is not None: response.data['status_code'] = response.status_code; return response | 必须返回 Response 对象 |
| exc 参数 | exc | 当前抛出的异常实例 | if isinstance(exc, ValidationError): ... | 可判断异常类型进行特殊处理 |
| context 参数 | context | 包含 request、view 等上下文 | view = context['view']; request = context['request'] | 用于日志记录或动态响应 |
| 调用默认处理器 | response = exception_handler(exc, context) | 复用 DRF 默认处理逻辑 | if response: response.data['success'] = False; return response | 推荐先调用默认逻辑 |
| 返回自定义响应 | return Response(data, status=code) | 直接返回新响应(如500错误) | if isinstance(exc, Exception): return Response({'error': 'Internal server error', 'detail': str(exc)}, status=500) | 生产环境避免暴露详细错误 |
| 配置位置 | REST_FRAMEWORK['EXCEPTION_HANDLER'] | 注册全局处理器 | REST_FRAMEWORK = {'EXCEPTION_HANDLER': 'myapp.exceptions.custom_exception_handler'} | 只能设置一个全局处理器 |
| 日志记录 | import logging | 记录异常便于排查 | logger.error(f"API Error: {exc}") | 推荐在处理器中添加日志 |
10.3 返回格式统一化设计
| 设计要素 | 说明 | 推荐格式示例 | 注意事项 |
|---|---|---|---|
| 成功响应结构 | 统一成功返回格式 | { "success": true, "data": { ... }, "message": "OK" } | 避免直接返回裸数据 |
| 错误响应结构 | 统一错误返回格式 | { "success": false, "error": "Invalid input", "code": "validation_error", "details": { "email": "Invalid format" } } | 包含可读错误和机器码 |
| data 字段 | 存放业务数据 | "data": { "id": 1, "name": "John" } | 列表数据也应包裹在 data 中 |
| message 字段 | 用户可读提示 | "message": "用户创建成功" | 可选,用于前端提示 |
| error 字段 | 错误描述 | "error": "User not found" | 仅在失败时存在 |
| code 字段 | 错误码(用于程序判断) | "code": "user_not_found" | 避免使用 HTTP 状态码作为业务码 |
| details 字段 | 详细错误信息(如字段级验证) | "details": { "password": "Too short" } | 适合表单验证场景 |
| 状态标识 | success 或 ok | "success": true/false | 比 status 更清晰 |
| 分页响应整合 | 将分页信息与数据合并 | { "success": true, "data": [...], "pagination": { "total": 100, "page": 1, "pages": 10 } } | 保持一致性 |
| 避免裸数组 | 不直接返回 […] | 应包裹在 data 字段中 | 防止 JSON Hijacking,提升可扩展性 |
| 版本兼容 | 保持字段稳定 | 新增字段可选,避免删除或重命名 | 有利于客户端兼容 |
第十一章:信号与事件驱动
11.1 利用 Django 信号解耦业务逻辑
| 信号类型 | 语法/说明 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| post_save | @receiver(post_save, sender=MyModel) | 模型保存后触发(创建或更新) | from django.db.models.signals import post_save; from django.dispatch import receiver; @receiver(post_save, sender=User); def create_profile(sender, instance, created, **kwargs): if created: Profile.objects.create(user=instance) | 常用于关联对象创建 |
| pre_save | @receiver(pre_save, sender=...) | 模型保存前触发 | def validate_slug(sender, instance, **kwargs): if not instance.slug: instance.slug = slugify(instance.title) | 可修改 instance 属性 |
| post_delete | @receiver(post_delete, sender=...) | 模型删除后触发 | @receiver(post_delete, sender=Article); def cleanup_files(sender, instance, **kwargs): if instance.image: instance.image.delete() | 清理文件、缓存等资源 |
| m2m_changed | @receiver(m2m_changed, sender=Model.relation.through) | 多对多关系变更时触发 | @receiver(m2m_changed, sender=Article.tags.through); def update_tag_count(sender, instance, action, **kwargs): if action == 'post_add': instance.update_tag_stats() | action 可为 pre_add, post_remove 等 |
| pre_delete | @receiver(pre_delete, sender=...) | 删除前执行清理或备份 | @receiver(pre_delete, sender=Post); def backup_post(sender, instance, **kwargs): Backup.objects.create(content=instance.content) | 防止数据丢失 |
| 自定义信号 | my_signal = Signal(providing_args=["user", "action"]) | 实现模块间松耦合 | from django.dispatch import Signal; user_logged_in = Signal(); # 触发:user_logged_in.send(sender=User, user=user) | 避免过度使用导致流程混乱 |
11.2 在序列化或视图中触发信号
| 方法/位置 | 语法/说明 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 视图中手动发送信号 | my_signal.send(sender=Model, **kwargs) | 显式触发业务事件 | class CreateOrderView(APIView): def post(self, request): order = Order.objects.create(...); order_created.send(sender=Order, order=order); return Response(...) | 推荐用于关键业务节点 |
| 序列化器 .save() 后 | serializer.save() → 触发 post_save | 利用模型信号自动响应 | class OrderSerializer(serializers.ModelSerializer): def create(self, validated_data): order = super().create(validated_data); # 自动触发 post_save; return order | 不需在序列化器内写业务逻辑 |
| 信号处理器中调用服务 | 在 receiver 函数中调用 service 模块 | 解耦核心逻辑与副作用 | @receiver(order_created); def send_confirmation_email(**kwargs): from .services import send_order_email; send_order_email(kwargs['order']) | 避免阻塞主流程,可异步处理 |
| 使用 Celery 异步处理 | 结合 @shared_task | 提升响应速度,避免超时 | @receiver(order_created); def async_process(sender, order, **kwargs): process_order.delay(order.id) | 推荐用于耗时操作(如邮件、推送) |
| 避免在信号中修改 instance | 尤其是 post_save | 防止无限递归 | ❌ 错误:在 post_save 中再次 instance.save() | 如需保存,检查条件或使用 update_fields |
| 单元测试信号逻辑 | 使用 @patch 或禁用信号 | 确保测试独立性 | with mock.patch('myapp.signals.update_cache'): response = client.post(url, data) | 测试视图时可临时断开信号 |
第十二章:API 文档生成
12.1 使用 CoreAPI 或 OpenAPI(drf-spectacular)生成文档
| 工具/类 | 语法/配置 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| drf-spectacular | pip install drf-spectacular | 生成现代 OpenAPI 3.0 文档 | INSTALLED_APPS += ['drf_spectacular']; REST_FRAMEWORK = {'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema'}; SPECTACULAR_SETTINGS = {'TITLE': 'My API', 'DESCRIPTION': 'Awesome API docs', 'VERSION': '1.0.0'} | 推荐替代旧版 CoreAPI |
| Schema 配置入口 | path('schema/', SpectacularAPIView.as_view(), name='schema') | 生成 JSON Schema | from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView; urlpatterns = [path('api/schema/', SpectacularAPIView.as_view(), name='schema'), path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui')] | 必须添加路由 |
| Swagger UI | SpectacularSwaggerView | 图形化 API 文档界面 | 访问 /api/schema/swagger-ui/ 查看交互式文档 | 支持在线测试接口 |
| Redoc | SpectacularRedocView | 更美观的文档展示 | path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc') | 适合对外发布 |
| 标记废弃接口 | @extend_schema(deprecated=True) | 标识过期 API | @extend_schema(deprecated=True); def old_endpoint(...): ... | 提醒客户端迁移 |
| 自定义请求体/响应 | @extend_schema(request=..., responses=...) | 精确描述复杂结构 | @extend_schema(request=OrderCreateSerializer, responses={201: OrderDetailSerializer}) | 用于非标准返回格式 |
12.2 视图文档注释与参数描述
| 方法/属性 | 语法/说明 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| docstring | """三引号注释""" | 自动生成接口摘要和描述 | def list(self, request):; """获取用户列表; 支持分页和搜索"""; ... | 被 drf-spectacular 自动提取 |
| @extend_schema 装饰器 | @extend_schema(description="...", summary="...") | 手动覆盖或补充文档 | @extend_schema(description="批量删除文章,需管理员权限", summary="删除多篇文章"); def destroy(self, request): ... | 最灵活的控制方式 |
| 查询参数描述 | @extend_schema(parameters=[...]) | 描述 filter/search 参数 | from drf_spectacular.utils import extend_schema, OpenApiParameter; @extend_schema(parameters=[OpenApiParameter(name='category', type=str, location=OpenApiParameter.QUERY), OpenApiParameter(name='active', type=bool, location=OpenApiParameter.QUERY)]) | 提高文档清晰度 |
| 请求头参数 | OpenApiParameter(location='header') | 描述自定义 Header(如 API Key) | OpenApiParameter(name='X-API-Version', type=str, location=OpenApiParameter.HEADER) | 用于版本控制或认证 |
| 响应状态码说明 | responses={400: ..., 403: ...} | 补充错误响应结构 | @extend_schema(responses={200: UserListSerializer(many=True), 403: OpenApiResponse(description='Permission denied')}) | 增强客户端容错能力 |
| 文件上传文档 | request=OpenApiRequest(...) | 描述 multipart/form-data 接口 | @extend_schema(request=OpenApiTypes.BINARY, responses={201: ImageSerializer}); def upload_image(...): ... | 自动生成文件上传表单 |
第十三章:性能优化与最佳实践
13.1 查询优化(select_related, prefetch_related)
| 方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| select_related() | queryset.select_related('foreign_key') | 一对一或外键关联,生成 SQL JOIN | articles = Article.objects.select_related('author', 'category').all() | 减少 N+1 查询,适用于单值关系 |
| prefetch_related() | queryset.prefetch_related('many_to_many') | 多对多或反向外键,执行额外查询并缓存 | articles = Article.objects.prefetch_related('tags', 'comments__user').all() | 适用于集合关系,支持嵌套 |
| only() | .only('field1', 'field2') | 仅加载指定字段 | User.objects.only('id', 'name') | 减少数据传输量 |
| defer() | .defer('large_field') | 延迟加载大字段(如 text、file) | Post.objects.defer('content') | 适用于详情页拆分 |
| values() / values_list() | .values('name', 'email') | 返回字典或元组,跳过模型实例化 | User.objects.values('name', 'email') | 用于只读场景,性能更高 |
| count() 优化 | 使用 .exists() 替代 .count() > 0 | 判断是否存在 | if Article.objects.filter(status='draft').exists(): ... | EXISTS 比 COUNT 更快 |
| 数据库索引 | db_index=True 或 Meta.indexes | 加速 WHERE、ORDER BY 查询 | class Article(models.Model): status = models.CharField(..., db_index=True) | 对高频查询字段建立索引 |
13.2 缓存策略集成(Redis + CacheKey)
| 方法/工具 | 语法/配置 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| Redis 作为缓存后端 | CACHES 配置 | 分布式缓存存储 | CACHES = {'default': {'BACKEND': 'django_redis.cache.RedisCache', 'LOCATION': 'redis://127.0.0.1:6379/1'}} | 需安装 django-redis |
| cache.set() / get() | cache.set(key, value, timeout) | 手动缓存数据 | from django.core.cache import cache; data = cache.get('homepage_data'); if not data: data = expensive_query(); cache.set('homepage_data', data, 60*15) # 15分钟 | 设置合理过期时间 |
| 缓存键设计 | 使用前缀 + 主键 + 版本 | 避免冲突,支持失效 | key = f"user_profile_{user_id}_v2" | 加版本号便于批量失效 |
| @cache_page 装饰器 | @cache_page(60 * 15) | 缓存整个视图响应 | from django.views.decorators.cache import cache_page; @api_view(['GET']); @cache_page(60*15); def homepage(request): ... | 适用于静态内容 |
| @method_decorator 用于类视图 | @method_decorator(cache_page(...), name='dispatch') | 为 CBV 添加缓存 | class ArticleListView(ListAPIView): @method_decorator(cache_page(60*10)); def dispatch(self, *args, **kwargs): return super().dispatch(*args, **kwargs) | 注意作用范围 |
| 缓存失效策略 | cache.delete(key) 或 cache.clear() | 更新数据后清除缓存 | def update_article(article): article.save(); cache.delete(f'article_{article.id}') | 保持数据一致性 |
| 用户个性化内容缓存 | 结合 request.user 生成键 | 缓存登录用户数据 | key = f"dashboard_{request.user.id}" | 不要缓存跨用户敏感数据 |
13.3 序列化器嵌套优化与懒加载问题
| 问题/方案 | 说明 | 代码示例 | 注意事项 |
|---|---|---|---|
| 嵌套序列化器导致 N+1 查询 | 默认未优化关联查询 | class ArticleSerializer(serializers.ModelSerializer): author = UserSerializer(); tags = TagSerializer(many=True); class Meta: model = Article; fields = '__all__' | 每个 article 都会单独查 author 和 tags |
| 使用 prefetch_related | 在视图层预加载关联数据 | def get_queryset(self): return Article.objects.prefetch_related('author', 'tags') | 必须在 get_queryset() 中设置 |
| 使用 select_related | 优化外键字段 | Article.objects.select_related('author') | 仅适用于 ForeignKey 和 OneToOne |
| 自定义 to_representation() | 控制输出结构,避免无谓查询 | def to_representation(self, instance): rep = super().to_representation(instance); rep['author_name'] = instance.author.name; return rep | 可减少序列化器嵌套 |
| 懒加载(Lazy Loading)风险 | 访问未预加载的关联属性触发查询 | for article in articles: print(article.author.name) # 每次都查数据库 | 循环中极易造成性能瓶颈 |
| 使用 .only() 和 .prefetch_related() 组合 | 最小化查询开销 | Article.objects.select_related('author').only('title', 'author__name').prefetch_related('tags') | 精确控制字段和关系 |
| 缓存序列化结果 | 对频繁访问的静态数据 | serialized = cache.get(key); if not serialized: serialized = ArticleSerializer(articles, many=True).data; cache.set(key, serialized, 300) | 适用于低频更新的数据 |
第十四章:项目实战:构建博客 API
14.1 需求分析与模型设计
| 模型/功能 | 字段设计 | 说明 | 注意事项 |
|---|---|---|---|
| User | Django 内置 auth.User 或 AbstractUser | 用户基础信息 | 推荐扩展自定义字段 |
| Blog | name, description, owner(ForeignKey) | 博客站点(可选) | 支持多博客场景 |
| Category | name, slug | 文章分类 | 可选功能 |
| Tag | name, slug | 标签系统 | 多对多关联文章 |
| Article | title, content, author(User), category, status(choices), published_at, created_at, updated_at | 核心文章模型 | status 可为 draft/published |
| Comment | article, user, content, parent, created_at | 评论及回复 | 支持树形结构 |
| 关系设计 | Article.tags = ManyToManyField(Tag) | 文章与标签多对多 | 使用中间表可扩展 |
| 权限需求 | 作者可管理自己文章,管理员可管理所有,访客只读 | RBAC 基础 | 在权限类中实现 |
| RESTful 设计 | /api/articles/, /api/comments/ | 遵循 REST 规范 | 使用标准 HTTP 方法 |
14.2 用户认证与文章管理接口开发
| 接口 | URL | 方法 | 功能 | 示例代码(关键部分) |
|---|---|---|---|---|
| 用户注册 | /api/auth/register/ | POST | 创建用户 | class RegisterView(APIView): … 使用 UserSerializer |
| 用户登录 | /api/auth/login/ | POST | 返回 Token 或 Session | 使用 TokenObtainPairView(JWT) |
| 获取文章列表 | /api/articles/ | GET | 分页显示文章 | class ArticleListCreateAPIView(ListCreateAPIView): … |
| 创建文章 | /api/articles/ | POST | 作者创建新文章 | 验证 request.user 是否有权 |
| 查看文章详情 | /api/articles/ | GET | 显示单篇文章及作者信息 | 使用 RetrieveAPIView |
| 更新文章 | /api/articles/ | PUT/PATCH | 作者编辑文章 | UpdateAPIView,检查所有权 |
| 删除文章 | /api/articles/ | DELETE | 作者或管理员删除 | DestroyAPIView |
| JWT 认证配置 | settings.py | - | 启用 JWT | 安装 djangorestframework-simplejwt 并配置 DEFAULT_AUTHENTICATION_CLASSES |
14.3 权限划分(作者、管理员、访客)
| 角色 | 可访问接口 | 权限规则 | 实现方式 |
|---|---|---|---|
| 访客(Anonymous) | GET /articles/, GET /articles/ | 仅可读公开文章 | permission_classes = [IsAuthenticatedOrReadOnly] 或自定义 |
| 登录用户(Author) | 上述 + 创建、管理自己的文章 | 可 CRUD 自己的文章 | 自定义权限类 IsOwnerOrReadOnly |
| 管理员(Admin) | 所有接口 | 可管理所有文章,包括删除他人文章 | IsAdminUser 或 IsSuperUser |
| IsOwnerOrReadOnly | 自定义类 | has_object_permission 检查 obj.author == request.user | class IsOwnerOrReadOnly(BasePermission):; def has_object_permission(self, request, view, obj):; if request.method in SAFE_METHODS: return True; return obj.author == request.user |
| 组合权限 | 多个权限类 | 如 [IsAuthenticated, IsOwnerOrReadOnly] | 所有权限必须通过 |
| 状态过滤 | 仅显示 status=‘published’ 的文章给访客 | 在 get_queryset() 中过滤 | def get_queryset(self):; if not request.user.is_authenticated: return Article.objects.filter(status='published') |
14.4 接口测试与 Postman 验证
| 测试项 | 测试方法 | 预期结果 | 注意事项 |
|---|---|---|---|
| 注册功能 | POST /api/auth/register/ 带用户名密码 | 返回 201,用户创建成功 | 检查密码是否加密存储 |
| 登录获取 Token | POST /api/token/ 带凭证 | 返回 access 和 refresh Token | 使用 JWT 时验证 Token 有效性 |
| 创建文章 | POST /api/articles/ 带 Token 和数据 | 201 Created,作者为当前用户 | 无 Token 应返回 401 |
| 获取文章列表 | GET /api/articles/ | 返回分页数据,包含标题、作者 | 匿名用户也应能访问 |
| 编辑他人文章 | PUT /api/articles/2/(非作者) | 403 Forbidden | 验证权限拦截 |
| 删除文章 | DELETE /api/articles/1/(作者) | 204 No Content | 数据库记录应被删除 |
| 搜索文章 | GET /api/articles/?search=python | 返回标题或内容包含”python”的文章 | 确保 SearchFilter 正常工作 |
| Postman 测试集合 | 导出为 Collection | 团队共享测试用例 | 可自动化运行(Newman) |
| 环境变量 | 在 Postman 中设置 base_url, token | 避免硬编码 | 便于切换开发/生产环境 |
| 响应断言 | 添加 Tests 脚本验证状态码、字段 | pm.test("Status 200", () => { pm.response.to.have.status(200); }); | 提高测试可靠性 |