什么是 ViewSet?
我们来看一下传统的 View 和 ViewSet 的区别。

传统 View (基于函数或类)
在标准的 Django 或 DRF 中,一个资源(User)通常需要多个端点来处理不同的 HTTP 方法。
# urls.py
from django.urls import path
from . import views
urlpatterns = [
# 获取所有用户
path('users/', views.UserList.as_view(), name='user-list'),
# 创建新用户
path('users/', views.UserList.as_view(), name='user-create'), # 通常和 list 是同一个 view
# 获取单个用户
path('users/<int:pk>/', views.UserDetail.as_view(), name='user-detail'),
# 更新单个用户
path('users/<int:pk>/', views.UserDetail.as_view(), name='user-update'), # 通常和 detail 是同一个 view
# 删除单个用户
path('users/<int:pk>/', views.UserDetail.as_view(), name='user-delete'), # 通常和 detail 是同一个 view
]
# views.py
from rest_framework import generics
from .models import User
from .serializers import UserSerializer
class UserList(generics.ListCreateAPIView):
"""处理 GET (list) 和 POST (create) 请求"""
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
"""处理 GET (retrieve), PUT/PATCH (update), DELETE (destroy) 请求"""
queryset = User.objects.all()
serializer_class = UserSerializer
可以看到,我们为 User 资源创建了两个视图类,urls.py 中的路径配置有些重复。
ViewSet 的思想
ViewSet 的核心思想是:将处理同一资源(如 User)的多个逻辑操作(如 list, create, retrieve, update, destroy)封装到一个单独的类中。
这个类不再直接处理 HTTP 请求(如 GET 或 POST),而是处理动作,list, create, retrieve 等,通过一个 Router(路由器)来自动生成 URL 配置。

这带来了两个主要好处:
- 代码更整洁:所有与
User资源相关的逻辑都集中在一个地方。 - URL 配置更简洁:你不再需要手动为每个操作编写 URL 路径。
ViewSet 的类型
DRF 提供了两种主要的 ViewSet:
a) ViewSet
这是最基础的 ViewSet,它不提供任何默认的动作(如 list, create 等),你需要手动实现 .action() 方法。
适用场景:当你需要完全自定义 API 的行为,或者你的 API 操作不符合 RESTful 规范时(一个自定义的 custom_action)。
# views.py
from rest_framework import viewsets
from rest_framework.response import Response
from .models import User
from .serializers import UserSerializer
class CustomUserViewSet(viewsets.ViewSet):
"""
一个自定义的 ViewSet,没有默认的 list/create/retrieve 动作
"""
def list(self, request):
"""自定义的 list 动作"""
users = User.objects.all()
serializer = UserSerializer(users, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
"""自定义的 retrieve 动作"""
user = User.objects.get(pk=pk)
serializer = UserSerializer(user)
return Response(serializer.data)
def custom_action(self, request):
"""一个完全自定义的动作,不对应标准的 CRUD"""
# ... 你的自定义逻辑 ...
return Response({"message": "This is a custom action!"})
b) GenericViewSet
这是最常用的 ViewSet,它继承了 GenericAPIView,所以它提供了 get_queryset() 和 get_serializer_class() 等核心方法,但不包含任何默认的动作(如 .list() 或 .create())。
适用场景:当你想使用 DRF 的通用类(如 mixins)来组合出你想要的动作,但又想利用 ViewSet 的代码组织和 URL 自动生成功能。
# views.py
from rest_framework import viewsets, mixins
from .models import User
from .serializers import UserSerializer
# 组合 mixins 来创建只读和可写的 ViewSet
class ReadOnlyUserViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
"""
一个只读的 User ViewSet
"""
queryset = User.objects.all()
serializer_class = UserSerializer
class UserViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
"""
一个支持 list, create, retrieve 的 User ViewSet
"""
queryset = User.objects.all()
serializer_class = UserSerializer
c) ModelViewSet
这是最方便的 ViewSet,它已经为你内置了所有标准的 CRUD 动作:list, create, retrieve, update, partial_update, destroy。
适用场景:当你需要快速为一个 Django 模型创建一个完整的 RESTful API 时,这是首选。
# views.py
from rest_framework import viewsets
from .models import User
from .serializers import UserSerializer
class UserModelViewSet(viewsets.ModelViewSet):
"""
一个完整的 User ViewSet,提供了所有 CRUD 操作
"""
queryset = User.objects.all()
serializer_class = UserSerializer
如何使用 ViewSet 和 Router
ViewSet 不能像普通 View 那样直接绑定到 URL,它需要配合 Router 来使用。
步骤 1: 创建 ViewSet
我们以 ModelViewSet 为例。
# views.py
from rest_framework import viewsets
from .models import User
from .serializers import UserSerializer
class UserViewSet(viewsets.ModelViewSet):
"""
为 User 模型提供完整的 API 接口。
"""
queryset = User.objects.all().order_by('-date_joined')
serializer_class = UserSerializer
步骤 2: 配置 Router
在 urls.py 中,我们不再使用 path,而是使用 Router。
# urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views
# 创建一个路由器并注册我们的 ViewSet
router = DefaultRouter()
router.register(r'users', views.UserViewSet, basename='user')
# basename 是可选的,但推荐提供,它用于生成 URL 名称。
# 如果不提供,DRF 会尝试从 queryset 或 serializer 的 model Meta 中推断。
# API URL 现在由路由器自动决定
urlpatterns = [
path('', include(router.urls)),
]
步骤 3: 查看生成的 URL
运行你的项目并访问 python manage.py show_urls(如果你安装了 django-extensions),或者直接在浏览器中访问 /users/ 和 /users/1/,你会看到类似以下的 URL 结构:
| HTTP 方法 | URL | 动作 | ViewSet 方法 | 用途 |
|---|---|---|---|---|
GET |
/users/ |
list |
.list() |
获取用户列表 |
POST |
/users/ |
create |
.create() |
创建新用户 |
GET |
/users/1/ |
retrieve |
.retrieve() |
获取单个用户详情 |
PUT |
/users/1/ |
update |
.update() |
完整更新单个用户 |
PATCH |
/users/1/ |
partial_update |
.partial_update() |
部分更新单个用户 |
DELETE |
/users/1/ |
destroy |
.destroy() |
删除单个用户 |
DefaultRouter 还会自动为你生成一个 API 根视图,访问 会列出所有已注册的 API 端点。
高级特性:自定义 Actions
除了标准的 CRUD 动作,你还可以在 ViewSet 中添加自定义的动作,这通过 @action 装饰器实现。
示例:添加一个 activate 动作
假设我们想添加一个端点来激活用户。
# views.py
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import User
from .serializers import UserSerializer
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
@action(detail=True, methods=['post'])
def activate(self, request, pk=None):
"""
激活指定用户的自定义动作。
`detail=True` 表示这个动作作用于单个实例 (e.g., /users/1/activate/)
"""
user = self.get_object()
user.is_active = True
user.save()
return Response({'status': 'user activated'}, status=status.HTTP_200_OK)
@action(detail=False, methods=['get'])
def recent_users(self, request):
"""
获取最近注册的用户。
`detail=False` 表示这个动作作用于集合 (e.g., /users/recent_users/)
"""
recent_users = self.queryset.order_by('-date_joined')[:5]
serializer = self.get_serializer(recent_users, many=True)
return Response(serializer.data)
你的 Router 会自动为这些自定义动作生成新的 URL:
| HTTP 方法 | URL | 动作 | ViewSet 方法 |
|---|---|---|---|
POST |
/users/1/activate/ |
activate |
.activate() |
GET |
/users/recent_users/ |
recent_users |
.recent_users() |
@action 装饰器参数
methods: 一个列表,指定该动作支持的 HTTP 方法,['get', 'post']。detail: 布尔值。True(默认): 动作作用于单个资源实例,URL 格式为/<model_name>/<pk>/action_name/。False: 动作作用于资源集合,URL 格式为/<model_name>/action_name/。
url_path: 自定义 URL 路径的一部分,默认为动作函数名。url_name: 自定义 URL 名称的一部分,默认为动作函数名。
总结与最佳实践
| 特性 | View / APIView |
ViewSet / GenericViewSet |
ModelViewSet |
|---|---|---|---|
| 代码组织 | 按功能分散 | 按资源集中 | 按资源集中 |
| URL 配置 | 手动编写 path |
自动生成 | 自动生成 |
| 默认动作 | 无 | 无 | 所有 CRUD 动作 |
| 灵活性 | 最高 | 高 | 相对较低(但足够用) |
| 主要用途 | 完全自定义 API | 自定义 API 或组合通用操作 | 快速为模型生成 API |
最佳实践建议:
- 优先使用
ModelViewSet:对于绝大多数标准的 CRUD API,ModelViewSet是最快、最简洁的选择。 - 使用
GenericViewSet+mixins:当你的 API 需要非标准的组合(只读 API,或者只有list和update但没有create的 API)时,使用GenericViewSet并组合相应的mixins。 - 使用
ViewSet:当你的 API 行为非常特殊,完全不符合 RESTful 规范时,才考虑使用基础的ViewSet。 - 善用
@action:当需要添加超出标准 CRUD 范围的功能(如触发特定业务逻辑、生成报表等)时,使用@action装饰器来扩展你的 API,保持代码的整洁和可维护性。 - 总是使用 Router:一旦你开始使用
ViewSet,就坚持使用Router来管理 URL,这会让你受益匪浅。
