DRF 路由进阶 #
一、嵌套路由 #
1.1 什么是嵌套路由 #
嵌套路由用于表示资源之间的层级关系,如文章下的评论。
text
/articles/1/comments/
/articles/1/comments/2/
1.2 手动实现嵌套 #
python
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ArticleViewSet, CommentViewSet
router = DefaultRouter()
router.register(r'articles', ArticleViewSet)
urlpatterns = [
path('api/', include(router.urls)),
path('api/articles/<int:article_id>/comments/', CommentViewSet.as_view({
'get': 'list',
'post': 'create'
}), name='article-comments-list'),
path('api/articles/<int:article_id>/comments/<int:pk>/', CommentViewSet.as_view({
'get': 'retrieve',
'put': 'update',
'delete': 'destroy'
}), name='article-comments-detail'),
]
1.3 使用drf-nested-routers #
安装:
bash
pip install drf-nested-routers
配置:
python
from rest_framework_nested import routers
from .views import ArticleViewSet, CommentViewSet
router = routers.DefaultRouter()
router.register(r'articles', ArticleViewSet)
articles_router = routers.NestedSimpleRouter(router, r'articles', lookup='article')
articles_router.register(r'comments', CommentViewSet, basename='article-comments')
urlpatterns = [
path('api/', include(router.urls)),
path('api/', include(articles_router.urls)),
]
1.4 嵌套视图集 #
python
class CommentViewSet(viewsets.ModelViewSet):
serializer_class = CommentSerializer
def get_queryset(self):
return Comment.objects.filter(article_id=self.kwargs.get('article_pk'))
def perform_create(self, serializer):
serializer.save(
article_id=self.kwargs.get('article_pk'),
author=self.request.user
)
1.5 多层嵌套 #
python
from rest_framework_nested import routers
router = routers.DefaultRouter()
router.register(r'articles', ArticleViewSet)
articles_router = routers.NestedSimpleRouter(router, r'articles', lookup='article')
articles_router.register(r'comments', CommentViewSet, basename='article-comments')
comments_router = routers.NestedSimpleRouter(articles_router, r'comments', lookup='comment')
comments_router.register(r'replies', ReplyViewSet, basename='comment-replies')
urlpatterns = [
path('api/', include(router.urls)),
path('api/', include(articles_router.urls)),
path('api/', include(comments_router.urls)),
]
URL结构:
text
/api/articles/ # 文章列表
/api/articles/{article_pk}/comments/ # 文章评论
/api/articles/{article_pk}/comments/{comment_pk}/replies/ # 评论回复
二、自定义Router #
2.1 自定义路由类 #
python
from rest_framework.routers import DefaultRouter
class CustomRouter(DefaultRouter):
routes = [
# 标准路由
{
'mapping': {
'get': 'list',
'post': 'create'
},
'detail': False,
'name': '{basename}-list',
'initkwargs': {'suffix': 'List'}
},
# 详情路由
{
'mapping': {
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
},
'detail': True,
'name': '{basename}-detail',
'initkwargs': {'suffix': 'Instance'}
},
]
2.2 扩展DefaultRouter #
python
class ExtendedRouter(DefaultRouter):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.include_format_suffixes = True
def get_api_root_view(self, api_urls=None):
from rest_framework.response import Response
from rest_framework.views import APIView
class APIRootView(APIView):
def get(self, request):
data = {
'articles': request.build_absolute_uri('articles/'),
'users': request.build_absolute_uri('users/'),
'version': 'v1'
}
return Response(data)
return APIRootView.as_view()
2.3 自定义action路由 #
python
from rest_framework.routers import Route, DynamicRoute, SimpleRouter
class ReadOnlyRouter(SimpleRouter):
routes = [
Route(
url=r'^{prefix}/$',
mapping={'get': 'list'},
name='{basename}-list',
detail=False,
initkwargs={'suffix': 'List'}
),
Route(
url=r'^{prefix}/{lookup}/$',
mapping={'get': 'retrieve'},
name='{basename}-detail',
detail=True,
initkwargs={'suffix': 'Instance'}
),
]
三、路由版本控制 #
3.1 URL路径版本控制 #
python
from rest_framework.routers import DefaultRouter
router_v1 = DefaultRouter()
router_v1.register(r'articles', ArticleV1ViewSet)
router_v2 = DefaultRouter()
router_v2.register(r'articles', ArticleV2ViewSet)
urlpatterns = [
path('api/v1/', include(router_v1.urls)),
path('api/v2/', include(router_v2.urls)),
]
3.2 版本化ViewSet #
python
class ArticleV1ViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializerV1
class ArticleV2ViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializerV2
@action(detail=False, methods=['get'])
def featured(self, request):
articles = self.get_queryset().filter(is_featured=True)
serializer = self.get_serializer(articles, many=True)
return Response(serializer.data)
3.3 单一ViewSet多版本 #
python
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
def get_serializer_class(self):
version = self.request.version
if version == 'v1':
return ArticleSerializerV1
return ArticleSerializerV2
3.4 DRF版本控制配置 #
python
REST_FRAMEWORK = {
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
'DEFAULT_VERSION': 'v1',
'ALLOWED_VERSIONS': ['v1', 'v2'],
}
urlpatterns = [
path('api/<str:version>/', include(router.urls)),
]
四、路由与认证 #
4.1 路由级别认证 #
python
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
4.2 action级别认证 #
python
from rest_framework.decorators import action
from rest_framework.permissions import IsAdminUser
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
@action(detail=False, permission_classes=[IsAdminUser])
def all(self, request):
articles = self.get_queryset()
serializer = self.get_serializer(articles, many=True)
return Response(serializer.data)
五、URL命名 #
5.1 basename命名 #
python
router.register(r'articles', ArticleViewSet, basename='article')
生成的URL名称:
article-listarticle-detail
5.2 action命名 #
python
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
@action(detail=False, url_name='published-articles')
def published(self, request):
pass
URL名称:article-published-articles
5.3 反向解析URL #
python
from rest_framework.reverse import reverse
reverse('article-list', request=request)
reverse('article-detail', kwargs={'pk': 1}, request=request)
reverse('article-publish', kwargs={'pk': 1}, request=request)
六、格式后缀 #
6.1 启用格式后缀 #
python
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.include_format_suffixes = True
6.2 访问格式 #
text
/api/articles.json
/api/articles/1.json
/api/articles.xml
/api/articles/1.xml
6.3 ViewSet支持 #
python
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
def get_serializer_class(self):
if self.format_kwarg == 'xml':
return ArticleXMLSerializer
return ArticleSerializer
七、路由中间件 #
7.1 路由级别中间件 #
python
from django.utils.decorators import decorator_from_middleware
class RateLimitMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
return response
urlpatterns = [
path('api/', decorator_from_middleware(RateLimitMiddleware)(include(router.urls))),
]
7.2 ViewSet中间件 #
python
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
@method_decorator(cache_page(60 * 5))
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
八、完整示例 #
8.1 项目结构 #
text
api/
├── v1/
│ ├── __init__.py
│ ├── views.py
│ └── urls.py
├── v2/
│ ├── __init__.py
│ ├── views.py
│ └── urls.py
└── urls.py
8.2 V1路由 #
python
from rest_framework.routers import DefaultRouter
from .views import ArticleViewSet, UserViewSet
router = DefaultRouter()
router.register(r'articles', ArticleViewSet)
router.register(r'users', UserViewSet)
urlpatterns = router.urls
8.3 V2路由 #
python
from rest_framework.routers import DefaultRouter
from rest_framework_nested import routers
from .views import ArticleViewSet, CommentViewSet
router = DefaultRouter()
router.register(r'articles', ArticleViewSet)
articles_router = routers.NestedSimpleRouter(router, r'articles', lookup='article')
articles_router.register(r'comments', CommentViewSet, basename='article-comments')
urlpatterns = router.urls + articles_router.urls
8.4 主路由 #
python
from django.urls import path, include
urlpatterns = [
path('api/v1/', include('api.v1.urls')),
path('api/v2/', include('api.v2.urls')),
]
九、最佳实践 #
9.1 路由组织 #
python
from rest_framework.routers import DefaultRouter
from apps.articles.views import ArticleViewSet
from apps.users.views import UserViewSet
from apps.comments.views import CommentViewSet
router = DefaultRouter()
router.register(r'articles', ArticleViewSet)
router.register(r'users', UserViewSet)
router.register(r'comments', CommentViewSet)
9.2 basename规范 #
python
router.register(r'articles', ArticleViewSet, basename='article')
router.register(r'my-articles', MyArticleViewSet, basename='my-article')
router.register(r'draft-articles', DraftArticleViewSet, basename='draft-article')
9.3 版本控制策略 #
python
urlpatterns = [
path('api/v1/', include(router_v1.urls)),
path('api/v2/', include(router_v2.urls)),
path('api/', include(router_latest.urls)), # 默认最新版本
]
十、总结 #
本章学习了DRF路由进阶:
- 嵌套路由:处理资源层级关系
- 自定义Router:创建自定义路由类
- 版本控制:实现API版本管理
- URL命名:规范URL命名
- 最佳实践:路由组织策略
掌握路由进阶技巧,可以构建更灵活的API结构!
最后更新:2026-03-28