Appearance
章节4:DRF 与前后端分离
一、章节概述
本章聚焦 Django REST Framework(DRF),深入学习如何构建符合 RESTful 规范的 API 接口。内容涵盖 API 设计原则、序列化器、视图集、认证权限、限流、过滤分页以及接口文档生成等。前后端分离架构已成为现代 Web 开发的主流模式,掌握 DRF 是成为全栈工程师的关键技能。
二、学习目标
| 目标分类 | 具体目标 |
|---|---|
| 知识目标 | 理解 RESTful API 设计规范与成熟度模型;掌握 DRF 的核心组件及其作用 |
| 技能目标 | 能使用 ModelSerializer 快速序列化数据;熟练使用 ModelViewSet 构建 CRUD API;能配置认证、权限、限流及接口文档 |
| 素养目标 | 培养 API 设计的前瞻性思维;理解前后端分离架构的优势与挑战 |
三、核心知识点
3.1 RESTful API 设计规范
定义
REST(Representational State Transfer,表现层状态转换) 是一种软件架构风格,用于设计网络应用程序的 API。RESTful API 使用 HTTP 协议的原生语义(GET/POST/PUT/PATCH/DELETE)对资源进行操作,以 JSON/XML 格式传输数据。
设计原则
▶ 资源命名规范
# ✅ 好的设计:使用名词复数表示资源集合
GET /api/articles/ # 文章列表
GET /api/articles/1/ # 单篇文章
POST /api/articles/ # 创建文章
PUT /api/articles/1/ # 全量更新文章
PATCH /api/articles/1/ # 部分更新文章
DELETE /api/articles/1/ # 删除文章
# 子资源
GET /api/articles/1/comments/ # 文章的评论列表
GET /api/articles/1/comments/5/ # 文章的某条评论
# 过滤、排序、分页
GET /api/articles/?category=python&page=2&page_size=10
GET /api/articles/?search=django&ordering=-pub_date
# ❌ 不好的设计:使用动词
GET /api/getArticles
POST /api/createArticle
POST /api/deleteArticle▶ HTTP 方法对应 CRUD
| HTTP 方法 | 行为 | 请求体 | 响应 | 幂等 |
|---|---|---|---|---|
GET | 获取资源 | 无 | 200 OK | ✅ |
POST | 创建资源 | 有 | 201 Created | ❌ |
PUT | 全量更新 | 有 | 200 OK | ✅ |
PATCH | 部分更新 | 有 | 200 OK | ✅ |
DELETE | 删除资源 | 可选 | 204 No Content | ✅ |
OPTIONS | 查询可用操作 | 无 | 200 OK | ✅ |
▶ 状态码规范
| 状态码 | 含义 | 示例场景 |
|---|---|---|
200 OK | 请求成功 | GET、PUT、PATCH 成功 |
201 Created | 资源创建成功 | POST 创建成功 |
204 No Content | 成功但无返回内容 | DELETE 成功 |
400 Bad Request | 请求参数错误 | 表单验证失败 |
401 Unauthorized | 未认证 | 未提供 Token |
403 Forbidden | 无权限 | 权限不足 |
404 Not Found | 资源不存在 | 查询不存在的 ID |
405 Method Not Allowed | HTTP 方法不允许 | 使用 GET 调用仅 POST 的接口 |
429 Too Many Requests | 请求频率超限 | 超出限流阈值 |
500 Internal Server Error | 服务器内部错误 | 代码异常 |
▶ API 版本管理
# URL 路径版本(推荐)
/api/v1/articles/
/api/v2/articles/
# 请求头版本
Accept: application/vnd.myapp.v1+json
Accept: application/vnd.myapp.v2+json
# 查询参数版本(不推荐)
/api/articles/?version=1安装 DRF
bash
pip install djangorestframework
pip install django-filter # 过滤支持
pip install drf-spectacular # API 文档生成(替代 swagger)
pip install markdown # Markdown 渲染(浏览 API 用)python
# settings.py
INSTALLED_APPS = [
# ...
'rest_framework',
'django_filters',
'drf_spectacular',
]
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}3.2 DRF 序列化器
定义
序列化器(Serializer) 是 DRF 的核心组件,负责将 Python 对象(模型实例、查询集)转换为 JSON/XML 格式(序列化),以及将接收到的 JSON 数据验证后转为 Python 对象(反序列化)。ModelSerializer 是基于模型自动生成序列化字段的便捷类。
语法
▶ Serializer(基础序列化器)
python
from rest_framework import serializers
class ArticleSerializer(serializers.Serializer):
"""基础序列化器:需要手动定义字段"""
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(max_length=200)
content = serializers.CharField()
pub_date = serializers.DateTimeField(read_only=True)
views = serializers.IntegerField(read_only=True)
category_id = serializers.IntegerField()
def validate_title(self, value):
"""单个字段验证"""
if len(value) < 5:
raise serializers.ValidationError('标题至少需要 5 个字符')
return value
def validate(self, attrs):
"""全局字段验证"""
if 'category_id' not in attrs:
raise serializers.ValidationError('必须指定分类')
return attrs
def create(self, validated_data):
"""自定义创建逻辑"""
return Article.objects.create(**validated_data)
def update(self, instance, validated_data):
"""自定义更新逻辑"""
instance.title = validated_data.get('title', instance.title)
instance.content = validated_data.get('content', instance.content)
instance.save()
return instance▶ ModelSerializer(模型序列化器) ⭐ 推荐
python
from rest_framework import serializers
from blog.models import Article, Category, Tag, Comment
class CategorySerializer(serializers.ModelSerializer):
"""分类序列化器"""
class Meta:
model = Category
fields = '__all__' # 包含所有字段
# fields = ['id', 'name', 'slug', 'description'] # 指定字段
# exclude = ['created_at'] # 排除字段
read_only_fields = ['slug'] # 只读字段
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ['id', 'name']
class CommentSerializer(serializers.ModelSerializer):
"""评论序列化器(包含嵌套和自定义字段)"""
# 嵌套序列化器(外键关联)
author_name = serializers.CharField(source='author.username', read_only=True)
# 自定义方法字段
created_time = serializers.SerializerMethodField()
class Meta:
model = Comment
fields = [
'id', 'article', 'author', 'author_name', 'content',
'created_at', 'created_time', 'is_approved',
]
read_only_fields = ['author', 'is_approved']
def get_created_time(self, obj):
"""SerializerMethodField 对应的方法"""
return obj.created_at.strftime('%Y-%m-%d %H:%M')
class ArticleListSerializer(serializers.ModelSerializer):
"""文章列表序列化器(精简字段)"""
category_name = serializers.CharField(source='category.name', read_only=True)
author_name = serializers.CharField(source='author.username', read_only=True)
tags = serializers.StringRelatedField(many=True, read_only=True)
comment_count = serializers.IntegerField(read_only=True)
class Meta:
model = Article
fields = [
'id', 'title', 'category_name', 'author_name',
'tags', 'pub_date', 'views', 'comment_count',
]
class ArticleDetailSerializer(serializers.ModelSerializer):
"""文章详情序列化器(完整字段 + 嵌套)"""
category = CategorySerializer(read_only=True)
tags = TagSerializer(many=True, read_only=True)
author = serializers.CharField(source='author.username', read_only=True)
comments = CommentSerializer(many=True, read_only=True)
class Meta:
model = Article
fields = '__all__'
read_only_fields = ['views', 'pub_date', 'update_date']▶ 验证器与自定义验证
python
# 内置验证器
from rest_framework.validators import UniqueValidator, UniqueTogetherValidator
class UserSerializer(serializers.ModelSerializer):
email = serializers.EmailField(
validators=[UniqueValidator(queryset=User.objects.all())]
)
class Meta:
model = User
fields = ['id', 'username', 'email', 'password']
extra_kwargs = {
'password': {'write_only': True}, # 密码只写不读
}
validators = [
UniqueTogetherValidator(
queryset=User.objects.all(),
fields=['username', 'email'],
message='用户名和邮箱组合必须唯一'
),
]
# 自定义验证器
def validate_even_number(value):
"""验证数字是否为偶数"""
if value % 2 != 0:
raise serializers.ValidationError('必须是偶数')
return value
# 在 serializer 中应用
class ExampleSerializer(serializers.Serializer):
number = serializers.IntegerField(validators=[validate_even_number])序列化器使用
python
# ---- 序列化(对象 → JSON) ----
article = Article.objects.get(id=1)
serializer = ArticleDetailSerializer(article)
data = serializer.data # Python 字典
# data → {'id': 1, 'title': '...', 'content': '...', ...}
# 序列化查询集
articles = Article.objects.all()
serializer = ArticleListSerializer(articles, many=True)
data = serializer.data # 有序字典列表
# ---- 反序列化(JSON → 对象) ----
# 创建
serializer = ArticleDetailSerializer(data=request.data)
if serializer.is_valid():
serializer.save(author=request.user) # 调用 create()
return Response(serializer.data, status=201)
else:
return Response(serializer.errors, status=400)
# 更新
serializer = ArticleDetailSerializer(article, data=request.data)
if serializer.is_valid():
serializer.save() # 调用 update()
return Response(serializer.data)
# 部分更新
serializer = ArticleDetailSerializer(article, data=request.data, partial=True)3.3 视图集、过滤器与分页
定义
视图集(ViewSet) 是 DRF 提供的将多个相关视图(list、create、retrieve、update、partial_update、destroy)组合到一个类中的方式。配合 Router 可以自动生成 URL 路由。过滤器 用于对 API 查询结果进行筛选,分页 控制返回数据的数量与翻页方式。
语法
▶ ModelViewSet(通用视图集)
python
from rest_framework.viewsets import ModelViewSet
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from rest_framework import status
from django_filters.rest_framework import DjangoFilterBackend
from .models import Article, Category, Comment
from .serializers import (
ArticleListSerializer, ArticleDetailSerializer,
CategorySerializer, CommentSerializer,
)
class CategoryViewSet(ModelViewSet):
"""分类 CRUD API"""
queryset = Category.objects.all()
serializer_class = CategorySerializer
class ArticleViewSet(ModelViewSet):
"""文章 CRUD API"""
queryset = Article.objects.select_related('author', 'category').prefetch_related('tags')
# 根据动作使用不同的序列化器
def get_serializer_class(self):
if self.action == 'list':
return ArticleListSerializer
return ArticleDetailSerializer
# 自定义查询集(如过滤当前用户文章)
def get_queryset(self):
queryset = super().get_queryset()
if self.action == 'list':
queryset = queryset.filter(is_published=True)
return queryset
# 自动设置作者
def perform_create(self, serializer):
serializer.save(author=self.request.user)
# 自定义动作(添加额外端点)
@action(detail=True, methods=['post'])
def publish(self, request, pk=None):
"""发布文章(自定义动作)"""
article = self.get_object()
article.is_published = True
article.pub_date = timezone.now()
article.save()
return Response({'status': 'published'})
@action(detail=True, methods=['get'])
def comments(self, request, pk=None):
"""获取文章的评论(子资源)"""
article = self.get_object()
comments = article.comments.all()
serializer = CommentSerializer(comments, many=True)
return Response(serializer.data)
@action(detail=False, methods=['get'])
def stats(self, request):
"""统计信息"""
data = Article.objects.aggregate(
total=Count('id'),
published=Count('id', filter=Q(is_published=True)),
total_views=Sum('views'),
)
return Response(data)
class CommentViewSet(ModelViewSet):
"""评论 CRUD API"""
queryset = Comment.objects.select_related('author', 'article').all()
serializer_class = CommentSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user)▶ 路由注册
python
# blog/urls.py
from rest_framework.routers import DefaultRouter
from .views import ArticleViewSet, CategoryViewSet, CommentViewSet
router = DefaultRouter()
router.register(r'articles', ArticleViewSet, basename='article')
router.register(r'categories', CategoryViewSet, basename='category')
router.register(r'comments', CommentViewSet, basename='comment')
urlpatterns = router.urls
# 自动生成的 URL:
# GET /articles/ → list
# POST /articles/ → create
# GET /articles/{id}/ → retrieve
# PUT /articles/{id}/ → update
# PATCH /articles/{id}/ → partial_update
# DELETE /articles/{id}/ → destroy
# POST /articles/{id}/publish/ → 自定义动作
# GET /articles/{id}/comments/ → 自定义动作
# GET /articles/stats/ → 自定义动作(无 pk)▶ APIView 与通用视图(适用于更精细的控制)
python
from rest_framework.views import APIView
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView
from rest_framework.response import Response
from rest_framework import status
# APIView:完全自定义
class ArticleListAPIView(APIView):
def get(self, request):
articles = Article.objects.all()
serializer = ArticleListSerializer(articles, many=True)
return Response(serializer.data)
def post(self, request):
serializer = ArticleDetailSerializer(data=request.data)
if serializer.is_valid():
serializer.save(author=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# Generic 视图:更少的代码
class ArticleListCreateView(ListCreateAPIView):
queryset = Article.objects.all()
serializer_class = ArticleListSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user)
class ArticleDetailView(RetrieveUpdateDestroyAPIView):
queryset = Article.objects.all()
serializer_class = ArticleDetailSerializer▶ 过滤器(Filter)
python
# 方式一:django-filter 集成
from django_filters.rest_framework import DjangoFilterBackend
from django_filters import rest_framework as filters
class ArticleFilter(filters.FilterSet):
"""自定义过滤器"""
title = filters.CharFilter(lookup_expr='icontains') # 标题模糊搜索
content = filters.CharFilter(lookup_expr='icontains')
category = filters.NumberFilter(field_name='category_id')
pub_date_after = filters.DateFilter(field_name='pub_date', lookup_expr='gte')
pub_date_before = filters.DateFilter(field_name='pub_date', lookup_expr='lte')
min_views = filters.NumberFilter(field_name='views', lookup_expr='gte')
max_views = filters.NumberFilter(field_name='views', lookup_expr='lte')
author = filters.NumberFilter(field_name='author_id')
# 多字段搜索
search = filters.CharFilter(method='filter_search')
def filter_search(self, queryset, name, value):
return queryset.filter(
Q(title__icontains=value) | Q(content__icontains=value)
)
class Meta:
model = Article
fields = ['title', 'category', 'author', 'is_published']
class ArticleViewSet(ModelViewSet):
queryset = Article.objects.select_related('author', 'category').all()
# 应用过滤器
filter_backends = [DjangoFilterBackend, filters.OrderingFilter, filters.SearchFilter]
filterset_class = ArticleFilter # 自定义过滤器
search_fields = ['title', 'content'] # 搜索字段
ordering_fields = ['pub_date', 'views', 'title'] # 可排序字段
ordering = ['-pub_date'] # 默认排序python
# settings.py
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend',
'rest_framework.filters.SearchFilter',
'rest_framework.filters.OrderingFilter',
],
}▶ 分页
python
# 方式一:全局配置(settings.py)
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
# 或使用 LimitOffsetPagination
# 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
}
# 方式二:自定义分页器(推荐)
from rest_framework.pagination import (
PageNumberPagination,
LimitOffsetPagination,
CursorPagination,
)
class StandardResultsSetPagination(PageNumberPagination):
"""标准分页:页码 + 每页数量"""
page_size = 10 # 默认每页数量
page_size_query_param = 'page_size' # URL 参数名:?page_size=20
max_page_size = 100 # 最大每页数量
page_query_param = 'page' # 页码参数名:?page=2
class LargeResultsSetPagination(PageNumberPagination):
page_size = 50
page_size_query_param = 'page_size'
max_page_size = 1000
class CustomLimitOffsetPagination(LimitOffsetPagination):
"""偏移分页:?limit=10&offset=20"""
default_limit = 10
limit_query_param = 'limit'
offset_query_param = 'offset'
max_limit = 100
class CursorPaginationWithoutOrdering(CursorPagination):
"""游标分页:适合实时数据(如无限滚动)"""
page_size = 10
ordering = '-pub_date'
cursor_query_param = 'cursor'
# 在视图集中使用
class ArticleViewSet(ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleListSerializer
pagination_class = StandardResultsSetPagination # 使用自定义分页分页响应格式:
json
{
"count": 100,
"next": "http://api.example.com/articles/?page=3",
"previous": "http://api.example.com/articles/?page=1",
"results": [
{ "id": 1, "title": "文章1" },
{ "id": 2, "title": "文章2" }
]
}3.4 认证、权限与限流
定义
认证(Authentication) 确认"你是谁"——验证用户身份;权限(Permission) 确认"你能做什么"——控制对资源的访问级别;限流(Throttling) 控制请求频率,防止滥用。
语法
▶ 认证方式配置
python
# settings.py —— 全局配置
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication', # Basic Auth
'rest_framework.authentication.SessionAuthentication', # Session 认证
'rest_framework.authentication.TokenAuthentication', # Token 认证
# 'rest_framework_simplejwt.authentication.JWTAuthentication', # JWT(下章详述)
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny', # 允许任何人访问
# 'rest_framework.permissions.IsAuthenticated', # 仅登录用户
# 'rest_framework.permissions.IsAdminUser', # 仅管理员
],
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'anon': '100/hour', # 未认证用户每小时 100 次
'user': '1000/hour', # 认证用户每小时 1000 次
'burst': '10/minute', # 突发限流
'sustained': '500/day', # 持续限流
},
}▶ Token 认证配置
bash
pip install djangorestframework
# Token 认证是 DRF 内置的,不需要额外包python
# settings.py
INSTALLED_APPS = [
'rest_framework.authtoken', # 添加 Token 应用(需 migrate)
]
# 执行迁移:python manage.py migrate
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
],
}
# 获取 Token 的 URL 配置
# urls.py
from rest_framework.authtoken.views import obtain_auth_token
urlpatterns = [
path('api-token-auth/', obtain_auth_token, name='api_token_auth'),
]python
# views.py —— 在视图中使用 Token
class ArticleViewSet(ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleListSerializer
# 视图级别认证与权限
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
# 手动生成/管理 Token
from rest_framework.authtoken.models import Token
from django.contrib.auth.models import User
# 为用户创建 Token
user = User.objects.get(username='admin')
token, created = Token.objects.get_or_create(user=user)
print(token.key) # 类似:9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
# 在请求中使用 Token
# Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b▶ 权限控制
python
from rest_framework.permissions import (
BasePermission, # 基础权限类(自定义用)
IsAuthenticated, # 仅认证用户
IsAdminUser, # 仅管理员
IsAuthenticatedOrReadOnly, # 未认证用户只能读
AllowAny, # 允许任何人
DjangoModelPermissions, # Django 模型权限
DjangoObjectPermissions, # Django 对象权限
)
# 自定义权限
class IsAuthorOrReadOnly(BasePermission):
"""仅作者可编辑,其他人只读"""
def has_permission(self, request, view):
# 基本权限检查(所有请求)
return request.user and request.user.is_authenticated
def has_object_permission(self, request, view, obj):
# 对象级别权限检查
if request.method in ('GET', 'HEAD', 'OPTIONS'):
return True # 安全方法允许所有人
return obj.author == request.user # 只有作者可修改
class IsAdminOrReadOnly(BasePermission):
"""管理员可编辑,其他人只读"""
def has_permission(self, request, view):
if request.method in ('GET', 'HEAD', 'OPTIONS'):
return True
return request.user and request.user.is_staff
# 在视图中使用
class ArticleViewSet(ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleListSerializer
permission_classes = [IsAuthenticatedOrReadOnly | IsAuthorOrReadOnly]
# 注意:| 表示 OR 组合
def perform_create(self, serializer):
serializer.save(author=self.request.user)
# 不同动作使用不同权限
class ArticleViewSet(ModelViewSet):
queryset = Article.objects.all()
def get_permissions(self):
if self.action in ('list', 'retrieve'):
permission_classes = [AllowAny]
elif self.action == 'create':
permission_classes = [IsAuthenticated]
elif self.action in ('update', 'partial_update', 'destroy'):
permission_classes = [IsAuthenticated, IsAuthorOrReadOnly]
else:
permission_classes = [IsAdminUser]
return [p() for p in permission_classes]▶ 限流(Throttling)
python
from rest_framework.throttling import (
AnonRateThrottle, # 未认证用户限流
UserRateThrottle, # 认证用户限流
ScopedRateThrottle, # 范围限流
)
# 自定义限流
class BurstRateThrottle(UserRateThrottle):
"""突发请求限流"""
scope = 'burst'
class SustainedRateThrottle(UserRateThrottle):
"""持续请求限流"""
scope = 'sustained'
# 在视图中使用
class ArticleViewSet(ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleListSerializer
throttle_classes = [AnonRateThrottle, UserRateThrottle]
# 或自定义
# throttle_classes = [BurstRateThrottle, SustainedRateThrottle]
# 不同视图使用不同限流
class SensitiveAPIView(APIView):
"""敏感接口(如登录)使用更严格的限流"""
throttle_classes = [AnonRateThrottle]
throttle_scope = 'login'
def post(self, request):
# ... 登录逻辑
pass
# 或者使用装饰器
from rest_framework.decorators import throttle_classes
@throttle_classes([AnonRateThrottle])
def special_view(request):
pass▶ 完整示例:文章 API 权限与限流
python
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly
from rest_framework.throttling import AnonRateThrottle, UserRateThrottle
from .permissions import IsAuthorOrReadOnly
from .models import Article
from .serializers import ArticleListSerializer, ArticleDetailSerializer
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.select_related('author', 'category').all()
def get_serializer_class(self):
if self.action == 'list':
return ArticleListSerializer
return ArticleDetailSerializer
def get_permissions(self):
"""不同动作不同权限"""
if self.action in ('list', 'retrieve'):
return [IsAuthenticatedOrReadOnly()]
elif self.action == 'create':
return [IsAuthenticated()]
elif self.action in ('update', 'partial_update', 'destroy'):
return [IsAuthenticated(), IsAuthorOrReadOnly()]
return [IsAuthenticated()]
def get_throttles(self):
"""不同动作不同限流"""
if self.action == 'create':
self.throttle_scope = 'create'
return [UserRateThrottle()]
return [AnonRateThrottle(), UserRateThrottle()]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
@action(detail=True, methods=['post'], permission_classes=[IsAuthenticated])
def like(self, request, pk=None):
"""点赞(仅认证用户)"""
article = self.get_object()
article.likes += 1
article.save()
return Response({'likes': article.likes})3.5 接口文档与版本管理
定义
API 文档 是 API 的使用说明书,描述每个接口的 URL、方法、参数、请求体和响应格式。drf-spectacular 是目前最推荐的 DRF API 文档工具,它根据 DRF 代码自动生成符合 OpenAPI 3.0 规范的文档,并提供 Swagger UI 和 ReDoc 两种可视化界面。
语法
▶ drf-spectacular 安装与配置
bash
pip install drf-spectacularpython
# settings.py
INSTALLED_APPS = [
# ...
'drf_spectacular',
'drf_spectacular_sidecar', # 可选:本地加载 Swagger UI
]
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}
SPECTACULAR_SETTINGS = {
'TITLE': '博客系统 API',
'DESCRIPTION': '博客系统后端 RESTful API 接口文档',
'VERSION': '1.0.0',
'SERVE_INCLUDE_SCHEMA': False,
# 认证
'COMPONENT_SPLIT_REQUEST': True,
'SECURITY': [
{'Bearer': []},
],
# Swagger UI 设置
'SWAGGER_UI_SETTINGS': {
'deepLinking': True,
'persistAuthorization': True,
'displayOperationId': True,
},
# 语言
'CONTACT': {'email': 'admin@example.com'},
'LICENSE': {'name': 'MIT License'},
# 标签分组
'TAGS': [
{'name': '文章', 'description': '文章 CRUD 操作'},
{'name': '分类', 'description': '分类管理'},
{'name': '评论', 'description': '评论管理'},
{'name': '用户', 'description': '用户认证与注册'},
],
}python
# urls.py —— 添加文档路由
from django.urls import path, include
from drf_spectacular.views import (
SpectacularAPIView,
SpectacularSwaggerView,
SpectacularRedocView,
)
urlpatterns = [
# API Schema(JSON/YAML 格式)
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
# Swagger UI(交互式测试界面)
path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
# ReDoc(更整洁的文档阅读界面)
path('api/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
# API 路由
path('api/', include('blog.urls')),
]▶ 在代码中添加文档描述
python
from drf_spectacular.utils import (
extend_schema, # 扩展整个视图的 schema
extend_schema_view, # 视图类装饰器
OpenApiParameter, # 参数描述
OpenApiExample, # 示例
OpenApiResponse, # 响应描述
inline_serializer, # 内联序列化器
)
# 方式一:装饰视图方法
class ArticleListCreateView(APIView):
@extend_schema(
summary='获取文章列表',
description='返回所有已发布文章的列表,支持分页、过滤和排序',
parameters=[
OpenApiParameter(
name='category',
type=int,
description='按分类 ID 过滤',
required=False,
),
OpenApiParameter(
name='search',
type=str,
description='全文搜索(标题+内容)',
required=False,
),
OpenApiParameter(
name='ordering',
type=str,
description='排序字段(pub_date/-pub_date/views/-views)',
required=False,
),
OpenApiParameter(
name='page',
type=int,
description='页码(从 1 开始)',
default=1,
),
],
responses={
200: ArticleListSerializer(many=True),
401: OpenApiResponse(description='未认证'),
},
tags=['文章'],
)
def get(self, request):
"""获取文章列表"""
# ...
pass
@extend_schema(
summary='创建文章',
request=ArticleDetailSerializer,
responses={
201: ArticleDetailSerializer,
400: OpenApiResponse(description='请求参数错误'),
},
)
def post(self, request):
"""创建文章"""
# ...
pass
# 方式二:使用装饰器批量修饰视图集
@extend_schema_view(
list=extend_schema(
summary='获取分类列表',
description='返回所有文章分类',
responses={200: CategorySerializer(many=True)},
),
create=extend_schema(
summary='创建分类',
request=CategorySerializer,
responses={201: CategorySerializer},
),
retrieve=extend_schema(
summary='获取分类详情',
responses={200: CategorySerializer},
),
)
class CategoryViewSet(ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer▶ API 版本管理
python
# 方式一:URL 路径版本(推荐)
# myproject/urls.py
from django.urls import path, include
urlpatterns = [
path('api/v1/', include('blog.urls_v1')),
path('api/v2/', include('blog.urls_v2')),
]
# blog/urls_v1.py(旧版 API)
from rest_framework.routers import DefaultRouter
from .views_v1 import ArticleViewSetV1
router = DefaultRouter()
router.register(r'articles', ArticleViewSetV1, basename='article')
urlpatterns = router.urls
# blog/urls_v2.py(新版 API,添加了新功能)
from rest_framework.routers import DefaultRouter
from .views_v2 import ArticleViewSetV2
router = DefaultRouter()
router.register(r'articles', ArticleViewSetV2, basename='article')
urlpatterns = router.urls
# 方式二:同一视图集内判断版本
class ArticleViewSet(ModelViewSet):
queryset = Article.objects.all()
def get_serializer_class(self):
version = self.request.version
if version == 'v1':
return ArticleSerializerV1
elif version == 'v2':
return ArticleSerializerV2
return ArticleListSerializer
def list(self, request, *args, **kwargs):
version = request.version
if version == 'v2':
# v2 版本返回更多字段
return super().list(request, *args, **kwargs)
else:
# v1 版本行为
return super().list(request, *args, **kwargs)
# settings.py
REST_FRAMEWORK = {
'ALLOWED_VERSIONS': ['v1', 'v2'],
'VERSION_PARAM': 'version',
'DEFAULT_VERSION': 'v1',
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
# 其他选项:
# 'rest_framework.versioning.QueryParameterVersioning' # ?version=v1
# 'rest_framework.versioning.AcceptHeaderVersioning' # Accept: vnd.myapp.v1+json
# 'rest_framework.versioning.NamespaceVersioning' # URL 命名空间
}四、小结
| 知识点 | 核心要点 |
|---|---|
| RESTful API 设计 | 资源用名词复数表示;HTTP 方法对应 CRUD;正确使用状态码;推荐 URL 路径版本管理 |
| 序列化器 | ModelSerializer 自动生成字段;is_valid() 验证 + save() 持久化;支持嵌套序列化、自定义验证 |
| 视图集 | ModelViewSet 一键生成 CRUD;Router 自动注册路由;@action 添加自定义端点 |
| 过滤器与分页 | django-filter 集成实现过滤;SearchFilter/OrderingFilter 搜索排序;PageNumberPagination 分页 |
| 认证权限限流 | TokenAuthentication 令牌认证;自定义 BasePermission 控制权限;Throttling 限制请求频率 |
| API 文档 | drf-spectacular 自动生成 OpenAPI 文档;Swagger UI 交互测试;ReDoc 阅读文档 |
五、练习
基础练习
- ModelSerializer 练习:为
Category和Tag模型创建 ModelSerializer,包含所有字段和必要的验证 - ViewSet 实践:为
Category模型创建ModelViewSet,注册到 Router,测试所有 CRUD 接口
进阶练习
- 过滤与分页:为文章 API 添加以下功能:
- 按分类、标题关键词、发布日期的范围过滤
- 按阅读量、发布时间排序
- 每页 10 条,支持
?page=2&page_size=20参数
- 权限控制:实现以下权限策略:
- 未登录用户可以查看文章列表和详情
- 登录用户可以创建文章
- 只有文章作者可以更新和删除自己的文章
- 管理员可以操作所有文章
挑战练习
- 构建完整博客 API:
- 设计文章、分类、标签、评论四个模型的 API
- 使用
ModelViewSet实现完整 CRUD - 配置 Token 认证 + 角色权限
- 添加限流策略(创建文章每分钟 5 次)
- 集成 drf-spectacular 生成文档
- 实现 API 版本 v1 和 v2
参考代码(练习 5 路由配置)
python
router = DefaultRouter()
router.register(r'articles', ArticleViewSet)
router.register(r'categories', CategoryViewSet)
router.register(r'tags', TagViewSet)
router.register(r'comments', CommentViewSet)
urlpatterns = [
path('api/v1/', include(router.urls)),
path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema')),
]