第14章:API开发
14.2 RESTful API设计
API版本控制
实现API版本控制以确保向后兼容性:
python
# urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views
# 版本1的路由
v1_router = DefaultRouter()
v1_router.register(r'articles', views.v1.ArticleViewSet, basename='v1-article')
v1_router.register(r'categories', views.v1.CategoryViewSet, basename='v1-category')
# 版本2的路由
v2_router = DefaultRouter()
v2_router.register(r'articles', views.v2.ArticleViewSet, basename='v2-article')
v2_router.register(r'categories', views.v2.CategoryViewSet, basename='v2-category')
v2_router.register(r'tags', views.v2.TagViewSet, basename='v2-tag')
urlpatterns = [
# 版本1 API
path('api/v1/', include(v1_router.urls)),
path('api/v1/', include('myapp.api.v1.urls')),
# 版本2 API
path('api/v2/', include(v2_router.urls)),
path('api/v2/', include('myapp.api.v2.urls')),
# 默认版本(指向最新版本)
path('api/', include(v2_router.urls)),
]
# 版本控制通过Accept头实现
# settings.py
REST_FRAMEWORK = {
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning',
'DEFAULT_VERSION': 'v2',
'ALLOWED_VERSIONS': ['v1', 'v2'],
}
# 或通过URL路径实现版本控制
# urls.py
from rest_framework.versioning import URLPathVersioning
urlpatterns = [
path('api/<str:version>/', include(router.urls)),
]
# 在视图中处理版本
class ArticleViewSet(viewsets.ModelViewSet):
def get_serializer_class(self):
if self.request.version == 'v1':
return ArticleSerializerV1
return ArticleSerializerV2
def get_queryset(self):
queryset = Article.objects.all()
if self.request.version == 'v1':
# v1版本的特殊处理
queryset = queryset.filter(status='published')
return queryset认证和权限
实现多种认证和权限控制机制:
python
# authentication.py
from rest_framework.authentication import TokenAuthentication, SessionAuthentication
from rest_framework.permissions import BasePermission, IsAuthenticated, IsAdminUser
from rest_framework.authtoken.models import Token
class CustomTokenAuthentication(TokenAuthentication):
"""自定义Token认证"""
def authenticate_credentials(self, key):
try:
token = Token.objects.select_related('user').get(key=key)
except Token.DoesNotExist:
raise exceptions.AuthenticationFailed('无效的Token')
if not token.user.is_active:
raise exceptions.AuthenticationFailed('用户账户已被禁用')
# 检查Token是否过期
from django.utils import timezone
from datetime import timedelta
if token.created < timezone.now() - timedelta(days=30):
raise exceptions.AuthenticationFailed('Token已过期')
return (token.user, token)
# permissions.py
from rest_framework.permissions import BasePermission
class IsOwnerOrReadOnly(BasePermission):
"""只允许所有者编辑对象"""
def has_object_permission(self, request, view, obj):
# 读取权限允许任何请求
if request.method in permissions.SAFE_METHODS:
return True
# 写入权限只允许对象的所有者
return obj.author == request.user
class IsAuthorOrAdmin(BasePermission):
"""只允许作者或管理员"""
def has_permission(self, request, view):
# 匿名用户只能查看
if request.method in permissions.SAFE_METHODS:
return True
# 认证用户可以创建
return request.user and request.user.is_authenticated
def has_object_permission(self, request, view, obj):
# 读取权限允许任何请求
if request.method in permissions.SAFE_METHODS:
return True
# 修改权限只允许作者或管理员
return obj.author == request.user or request.user.is_staff
class IsVerifiedUser(BasePermission):
"""只允许已验证的用户"""
def has_permission(self, request, view):
return request.user and request.user.is_authenticated and request.user.profile.is_verified
# views.py
from rest_framework.decorators import action
from rest_framework.response import Response
class ArticleViewSet(viewsets.ModelViewSet):
authentication_classes = [CustomTokenAuthentication, SessionAuthentication]
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
@action(detail=True, methods=['post'], permission_classes=[IsAuthenticated])
def like(self, request, pk=None):
"""点赞文章"""
article = self.get_object()
# 实现点赞逻辑
article.likes.add(request.user)
return Response({'status': 'liked'})
@action(detail=True, methods=['post'], permission_classes=[IsAuthenticated])
def unlike(self, request, pk=None):
"""取消点赞"""
article = self.get_object()
article.likes.remove(request.user)
return Response({'status': 'unliked'})
@action(detail=False, methods=['get'], permission_classes=[IsAdminUser])
def draft_articles(self, request):
"""获取草稿文章(仅管理员)"""
drafts = Article.objects.filter(status='draft')
serializer = self.get_serializer(drafts, many=True)
return Response(serializer.data)分页和过滤
实现分页和过滤功能:
python
# pagination.py
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination
from rest_framework.response import Response
class CustomPageNumberPagination(PageNumberPagination):
"""自定义页码分页"""
page_size = 20
page_size_query_param = 'page_size'
max_page_size = 100
def get_paginated_response(self, data):
return Response({
'links': {
'next': self.get_next_link(),
'previous': self.get_previous_link()
},
'count': self.page.paginator.count,
'total_pages': self.page.paginator.num_pages,
'current_page': self.page.number,
'results': data
})
class CursorPagination(CursorPagination):
"""游标分页"""
page_size = 20
ordering = '-created_at'
# filters.py
from django_filters import rest_framework as filters
from .models import Article, Category
class ArticleFilter(filters.FilterSet):
"""文章过滤器"""
title = filters.CharFilter(lookup_expr='icontains')
category = filters.ModelChoiceFilter(queryset=Category.objects.all())
tags = filters.ModelMultipleChoiceFilter(
queryset=Tag.objects.all(),
field_name='tags',
lookup_expr='in'
)
author = filters.CharFilter(field_name='author__username', lookup_expr='icontains')
created_after = filters.DateFilter(field_name='created_at', lookup_expr='gte')
created_before = filters.DateFilter(field_name='created_at', lookup_expr='lte')
is_published = filters.BooleanFilter(field_name='status', method='filter_is_published')
class Meta:
model = Article
fields = {
'title': ['icontains'],
'category': ['exact'],
'author': ['exact'],
'created_at': ['gte', 'lte'],
}
def filter_is_published(self, queryset, name, value):
status = 'published' if value else 'draft'
return queryset.filter(status=status)
# views.py
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter, OrderingFilter
class ArticleViewSet(viewsets.ModelViewSet):
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_class = ArticleFilter
search_fields = ['title', 'content', 'excerpt', 'author__username']
ordering_fields = ['created_at', 'updated_at', 'view_count', 'published_at']
ordering = ['-created_at']
pagination_class = CustomPageNumberPagination
@action(detail=False, methods=['get'])
def popular(self, request):
"""获取热门文章"""
queryset = self.filter_queryset(
self.get_queryset().filter(view_count__gt=1000)
)
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)API文档生成
生成API文档:
python
# settings.py
INSTALLED_APPS = [
# ...
'rest_framework',
'rest_framework.authtoken',
'drf_yasg', # Swagger/OpenAPI文档
]
# urls.py
from rest_framework import permissions
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
schema_view = get_schema_view(
openapi.Info(
title="博客API",
default_version='v2',
description="博客系统的RESTful API文档",
terms_of_service="https://www.google.com/policies/terms/",
contact=openapi.Contact(email="contact@blog.local"),
license=openapi.License(name="BSD License"),
),
public=True,
permission_classes=[permissions.AllowAny],
)
urlpatterns = [
# API路由
path('api/v2/', include(router.urls)),
# API文档
path('swagger/', schema_view.with_ui(
'swagger',
cache_timeout=0
), name='schema-swagger-ui'),
path('redoc/', schema_view.with_ui(
'redoc',
cache_timeout=0
), name='schema-redoc'),
path('swagger.json', schema_view.without_ui(
cache_timeout=0
), name='schema-json'),
]
# serializers.py - 添加文档注释
class ArticleSerializer(serializers.ModelSerializer):
"""
文章序列化器
用于序列化和反序列化Article模型实例
"""
class Meta:
model = Article
fields = '__all__'
read_only_fields = ['author', 'view_count', 'created_at', 'updated_at']
def create(self, validated_data):
"""
创建文章
Args:
validated_data (dict): 验证后的数据
Returns:
Article: 创建的文章实例
"""
return Article.objects.create(**validated_data)
# views.py - 添加详细文档
class ArticleViewSet(viewsets.ModelViewSet):
"""
文章视图集
提供文章的完整CRUD操作
"""
queryset = Article.objects.all()
serializer_class = ArticleSerializer
@action(detail=True, methods=['post'])
def like(self, request, pk=None):
"""
点赞文章
为指定的文章添加点赞
Args:
request: HTTP请求对象
pk (int): 文章ID
Returns:
Response: 点赞结果
"""
article = self.get_object()
article.likes.add(request.user)
return Response({'status': 'liked'})通过这些RESTful API设计实践,你可以创建出功能完善、易于使用和维护的API接口。
小结
RESTful API设计的核心要点:
- ✅ 实施API版本控制确保向后兼容性
- ✅ 设计灵活的认证和权限控制机制
- ✅ 实现高效的分页和过滤功能
- ✅ 生成完整的API文档便于开发者使用
- ✅ 遵循REST架构约束和最佳实践
良好的API设计能够提升开发效率和用户体验。
下一篇
我们将学习Django测试系统。