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-list
  • article-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