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