DRF 视图集 #

一、视图集概述 #

1.1 什么是视图集 #

视图集(ViewSet)是DRF中最强大的视图抽象,它将一组相关的视图逻辑封装在一个类中,配合路由器自动生成URL。

text
视图演进
┌─────────────────────────────────────┐
│           APIView                   │
│  - 手动实现所有方法                  │
│  - 手动配置URL                       │
└─────────────────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────┐
│        GenericAPIView               │
│  - 提供queryset和serializer         │
│  - 仍需手动实现方法                  │
└─────────────────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────┐
│         通用视图                     │
│  - 预实现常用方法                    │
│  - 需要多个视图类                    │
└─────────────────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────┐
│          ViewSet                    │
│  - 一个类包含所有操作                │
│  - Router自动生成URL                 │
│  - 最简洁的API开发方式               │
└─────────────────────────────────────┘

1.2 视图集类型 #

类型 操作 说明
ViewSet 自定义 基础视图集
GenericViewSet 自定义 带GenericAPIView功能
ModelViewSet 完整CRUD 完整模型操作
ReadOnlyModelViewSet 只读 list + retrieve

二、ModelViewSet #

2.1 基本用法 #

python
from rest_framework import viewsets
from .models import Article
from .serializers import ArticleSerializer

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

这就是全部代码!ModelViewSet自动提供:

方法 URL 操作
list GET /articles/ 列表
create POST /articles/ 创建
retrieve GET /articles/{id}/ 详情
update PUT /articles/{id}/ 更新
partial_update PATCH /articles/{id}/ 部分更新
destroy DELETE /articles/{id}/ 删除

2.2 配合Router #

python
from rest_framework.routers import DefaultRouter
from .views import ArticleViewSet

router = DefaultRouter()
router.register(r'articles', ArticleViewSet)

urlpatterns = router.urls

2.3 自定义行为 #

python
class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    
    def perform_create(self, serializer):
        serializer.save(author=self.request.user)
    
    def perform_update(self, serializer):
        serializer.save(updated_by=self.request.user)
    
    def perform_destroy(self, instance):
        instance.is_deleted = True
        instance.save()

三、ReadOnlyModelViewSet #

3.1 基本用法 #

python
from rest_framework import viewsets

class ArticleViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

只提供只读操作:

方法 URL 操作
list GET /articles/ 列表
retrieve GET /articles/{id}/ 详情

3.2 应用场景 #

python
class CategoryViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer

class TagViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Tag.objects.all()
    serializer_class = TagSerializer

四、GenericViewSet #

4.1 基本用法 #

python
from rest_framework import viewsets, mixins

class ArticleViewSet(mixins.ListModelMixin,
                     mixins.RetrieveModelMixin,
                     viewsets.GenericViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

4.2 Mixin组合 #

Mixin 提供方法
ListModelMixin list
CreateModelMixin create
RetrieveModelMixin retrieve
UpdateModelMixin update, partial_update
DestroyModelMixin destroy

4.3 自定义组合 #

python
class ArticleViewSet(mixins.ListModelMixin,
                     mixins.CreateModelMixin,
                     mixins.RetrieveModelMixin,
                     viewsets.GenericViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

五、自定义动作 #

5.1 @action装饰器 #

python
from rest_framework.decorators import action
from rest_framework.response import Response

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    
    @action(detail=True, methods=['post'])
    def publish(self, request, pk=None):
        article = self.get_object()
        article.is_published = True
        article.published_at = timezone.now()
        article.save()
        return Response({'status': 'published'})
    
    @action(detail=True, methods=['post'])
    def unpublish(self, request, pk=None):
        article = self.get_object()
        article.is_published = False
        article.save()
        return Response({'status': 'unpublished'})

5.2 action参数 #

python
class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    
    @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 published(self, request):
        articles = self.get_queryset().filter(is_published=True)
        serializer = self.get_serializer(articles, many=True)
        return Response(serializer.data)
    
    @action(detail=False, methods=['get'], url_path='my-articles')
    def my_articles(self, request):
        articles = self.get_queryset().filter(author=request.user)
        serializer = self.get_serializer(articles, many=True)
        return Response(serializer.data)

5.3 action参数说明 #

参数 说明
detail True=单个对象,False=列表
methods 支持的HTTP方法
url_path 自定义URL路径
url_name 自定义URL名称
permission_classes 权限类
serializer_class 序列化器类

5.4 带权限的action #

python
class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    
    @action(detail=True, methods=['post'], permission_classes=[IsAdminUser])
    def feature(self, request, pk=None):
        article = self.get_object()
        article.is_featured = True
        article.save()
        return Response({'status': 'featured'})

六、Router配置 #

6.1 DefaultRouter #

python
from rest_framework.routers import DefaultRouter
from .views import ArticleViewSet

router = DefaultRouter()
router.register(r'articles', ArticleViewSet)

urlpatterns = [
    path('api/', include(router.urls)),
]

6.2 SimpleRouter #

python
from rest_framework.routers import SimpleRouter

router = SimpleRouter()
router.register(r'articles', ArticleViewSet)

urlpatterns = router.urls

6.3 Router区别 #

特性 DefaultRouter SimpleRouter
API根视图
格式后缀 支持 支持
URL样式 包含尾部斜杠 包含尾部斜杠

6.4 多个ViewSet注册 #

python
router = DefaultRouter()
router.register(r'articles', ArticleViewSet)
router.register(r'categories', CategoryViewSet)
router.register(r'tags', TagViewSet)
router.register(r'users', UserViewSet)

urlpatterns = [
    path('api/', include(router.urls)),
]

6.5 自定义basename #

python
router.register(r'articles', ArticleViewSet, basename='article')
router.register(r'my-articles', MyArticleViewSet, basename='my-article')

七、高级用法 #

7.1 动态序列化器 #

python
class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    
    def get_serializer_class(self):
        if self.action == 'list':
            return ArticleListSerializer
        if self.action == 'create':
            return ArticleCreateSerializer
        if self.action in ['update', 'partial_update']:
            return ArticleUpdateSerializer
        return ArticleSerializer

7.2 动态查询集 #

python
class ArticleViewSet(viewsets.ModelViewSet):
    serializer_class = ArticleSerializer
    
    def get_queryset(self):
        queryset = Article.objects.all()
        
        if self.action == 'list':
            queryset = queryset.filter(is_published=True)
        
        if self.request.user.is_staff:
            return queryset
        
        return queryset.filter(author=self.request.user)

7.3 动态权限 #

python
class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    
    def get_permissions(self):
        if self.action == 'create':
            return [IsAuthenticated()]
        if self.action in ['update', 'partial_update', 'destroy']:
            return [IsAuthenticated(), IsOwner()]
        return [AllowAny()]

7.4 分页配置 #

python
class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    pagination_class = PageNumberPagination
    
    @action(detail=False, pagination_class=LargePagination)
    def all(self, request):
        articles = self.get_queryset()
        page = self.paginate_queryset(articles)
        serializer = self.get_serializer(page, many=True)
        return self.get_paginated_response(serializer.data)

八、嵌套资源 #

8.1 嵌套路由 #

python
from rest_framework.routers import DefaultRouter
from .views import ArticleViewSet, CommentViewSet

router = DefaultRouter()
router.register(r'articles', ArticleViewSet)
router.register(r'articles/(?P<article_id>\d+)/comments', CommentViewSet, basename='article-comments')

urlpatterns = router.urls

8.2 嵌套视图集 #

python
class CommentViewSet(viewsets.ModelViewSet):
    serializer_class = CommentSerializer
    
    def get_queryset(self):
        article_id = self.kwargs.get('article_id')
        return Comment.objects.filter(article_id=article_id)
    
    def perform_create(self, serializer):
        article_id = self.kwargs.get('article_id')
        serializer.save(article_id=article_id, author=self.request.user)

8.3 使用drf-nested-routers #

bash
pip install drf-nested-routers
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')

urlpatterns = [
    path('api/', include(router.urls)),
    path('api/', include(articles_router.urls)),
]

九、完整示例 #

9.1 文章API #

python
class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.select_related('author', 'category').prefetch_related('tags')
    permission_classes = [IsAuthenticatedOrReadOnly]
    filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
    filterset_fields = ['category', 'is_published']
    search_fields = ['title', 'content']
    ordering_fields = ['created_at', 'views']
    ordering = ['-created_at']
    
    def get_serializer_class(self):
        if self.action == 'list':
            return ArticleListSerializer
        if self.action == 'create':
            return ArticleCreateSerializer
        return ArticleDetailSerializer
    
    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()
        if article.author != request.user:
            return Response({'error': '无权操作'}, status=403)
        article.is_published = True
        article.save()
        return Response({'status': 'published'})
    
    @action(detail=True, methods=['post'])
    def like(self, request, pk=None):
        article = self.get_object()
        article.likes.add(request.user)
        return Response({'status': 'liked'})
    
    @action(detail=True, methods=['post'])
    def unlike(self, request, pk=None):
        article = self.get_object()
        article.likes.remove(request.user)
        return Response({'status': 'unliked'})
    
    @action(detail=False, methods=['get'])
    def my(self, request):
        articles = self.get_queryset().filter(author=request.user)
        page = self.paginate_queryset(articles)
        serializer = self.get_serializer(page, many=True)
        return self.get_paginated_response(serializer.data)
    
    @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)

9.2 用户API #

python
class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    permission_classes = [IsAuthenticated]
    
    def get_serializer_class(self):
        if self.action == 'create':
            return UserCreateSerializer
        if self.action in ['update', 'partial_update']:
            return UserUpdateSerializer
        return UserSerializer
    
    def get_permissions(self):
        if self.action == 'create':
            return [AllowAny()]
        if self.action == 'destroy':
            return [IsAdminUser()]
        return [IsAuthenticated()]
    
    @action(detail=False, methods=['get', 'put', 'patch'])
    def me(self, request):
        if request.method == 'GET':
            serializer = self.get_serializer(request.user)
            return Response(serializer.data)
        
        serializer = self.get_serializer(request.user, data=request.data, partial=True)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data)
    
    @action(detail=False, methods=['post'])
    def change_password(self, request):
        user = request.user
        old_password = request.data.get('old_password')
        new_password = request.data.get('new_password')
        
        if not user.check_password(old_password):
            return Response({'error': '旧密码错误'}, status=400)
        
        user.set_password(new_password)
        user.save()
        return Response({'status': 'password changed'})

十、URL配置 #

10.1 完整配置 #

python
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ArticleViewSet, UserViewSet, CategoryViewSet

router = DefaultRouter()
router.register(r'articles', ArticleViewSet)
router.register(r'users', UserViewSet)
router.register(r'categories', CategoryViewSet)

urlpatterns = [
    path('api/v1/', include(router.urls)),
    path('api-auth/', include('rest_framework.urls')),
]

10.2 版本化API #

python
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)),
]

十一、最佳实践 #

11.1 查询优化 #

python
class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.select_related(
        'author', 'category'
    ).prefetch_related(
        'tags', 'comments'
    )

11.2 分离关注点 #

python
class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    
    def get_queryset(self):
        qs = super().get_queryset()
        if self.action == 'list':
            return qs.filter(is_published=True)
        return qs
    
    def get_serializer_class(self):
        if self.action == 'list':
            return ArticleListSerializer
        return ArticleDetailSerializer
    
    def get_permissions(self):
        if self.action in ['create', 'update', 'destroy']:
            return [IsAuthenticated(), IsOwner()]
        return [AllowAny()]

11.3 使用action组织代码 #

python
class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    
    @action(detail=False)
    def published(self, request):
        pass
    
    @action(detail=False)
    def draft(self, request):
        pass
    
    @action(detail=False)
    def archived(self, request):
        pass

十二、总结 #

本章学习了DRF视图集:

  • ModelViewSet:完整CRUD操作
  • ReadOnlyModelViewSet:只读操作
  • GenericViewSet:自定义组合
  • 自定义动作:@action装饰器
  • Router配置:自动生成URL

视图集是DRF中最强大的视图抽象,让我们继续学习路由的更多细节!

最后更新:2026-03-28