Django REST Framework 中 APIView、GenericAPIView 和 ViewSet 有什么区别?
简化版
DRF 的 APIView 最底层,适合手写请求方法;GenericAPIView 加入 queryset、serializer、pagination 等通用能力;ViewSet 把 list、retrieve、create、update、destroy 等动作组织在一起,常配合 router 自动生成路由。
详细版
APIView 类似 Django CBV 的 API 版,需要自己写 get/post。GenericAPIView 提供 get_queryset()、get_serializer() 等基础设施,再配合 mixins 快速实现 CRUD。ViewSet 不直接写 HTTP 方法,而是写动作方法。
class UserViewSet(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
面试回答要强调:抽象越高,开发越快,但定制和理解成本也越高。简单特殊接口用 APIView,标准资源 CRUD 用 ViewSet/ModelViewSet。
完整版教学
一、DRF 为什么有多层视图抽象
API 开发既有标准 CRUD,也有很多特殊接口。如果只有最底层 APIView,重复代码很多;如果只有最高层 ViewSet,特殊逻辑又显得绕。DRF 提供多层抽象,让你按复杂度选择。
APIView -> GenericAPIView + Mixins -> ViewSet -> ModelViewSet
越往右,默认能力越多,约定也越多。
二、APIView 适合什么
APIView 直接写 HTTP 方法,控制最清晰。适合登录、回调、聚合接口、非标准资源动作。
class PingView(APIView):
def get(self, request):
return Response({"ok": True})
它不会自动知道 queryset 或 serializer,你要自己组织逻辑。优点是直观,缺点是重复。
三、GenericAPIView 和 Mixins
GenericAPIView 提供获取 queryset、serializer、object、pagination 的通用方法。Mixins 提供 list/create/retrieve/update/destroy 等动作。
class UserListView(ListModelMixin, GenericAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
def get(self, request):
return self.list(request)
这种组合比 APIView 省代码,但比 ModelViewSet 更显式。
四、ViewSet 和 Router
ViewSet 把动作和 HTTP 方法映射交给 router。你写 list、retrieve 等动作,router 自动生成 URL。
GET /users/ -> list
POST /users/ -> create
GET /users/1/ -> retrieve
这适合 REST 风格资源接口。团队规范统一时,ViewSet 能让 API 结构很整齐。
ViewSet 的关键不是少写几行路由,而是把资源动作按 REST 约定组织起来。
五、ModelViewSet 的边界
ModelViewSet 一次性提供 CRUD。它很快,但也容易让新手把所有字段、所有操作都暴露出去。生产中要认真配置权限、过滤、字段、queryset。
def get_queryset(self):
return User.objects.filter(tenant=self.request.user.tenant)
如果忘记按租户或用户过滤,可能出现越权数据泄露。
六、怎么选择
| 场景 | 推荐 |
|---|---|
| 特殊回调接口 | APIView |
| 标准列表/详情但需显式控制 | GenericAPIView + Mixins |
| 标准资源 CRUD | ViewSet / ModelViewSet |
| 很复杂的业务动作 | APIView 或自定义 action |
不要为了高级而高级。接口越特殊,越需要显式;接口越标准,越适合约定。
七、权限和序列化要放在哪里
DRF 的视图层负责组织请求流程,但权限、序列化、过滤和业务服务要分清。权限类判断能不能访问,serializer 负责输入输出转换,queryset 控制数据范围,复杂业务最好放 service 或 model 方法中。
permission -> queryset filter -> serializer -> service/domain logic
如果把所有逻辑都塞进 ViewSet,短期很快,长期会变成难测、难复用的大类。
八、面试里的选择表达
回答时可以先给原则,再给例子。比如:“标准资源 CRUD 用 ModelViewSet;需要精细控制但仍是模型资源,用 GenericAPIView + Mixins;登录回调、报表聚合这类非资源接口,用 APIView。”
标准资源: ViewSet
半标准资源: GenericAPIView
特殊接口: APIView
这样比只背类名区别更像工程决策。
九、常见误区与追问
- 误区:ModelViewSet 是最佳实践,所有接口都用它。 标准 CRUD 适合,特殊业务可能 APIView 更清晰。
- 误区:ViewSet 自动保证权限安全。 权限和 queryset 过滤仍要自己配置。
- 误区:APIView 不能用 serializer。 当然可以,只是要自己调用。
- 追问:router 做什么? 根据 ViewSet action 生成 URL 映射。
- 追问:
@action有什么用? 给 ViewSet 增加自定义动作。 - 追问:get_queryset 为什么常重写? 按当前用户、租户、权限动态过滤数据。
- 追问:GenericAPIView 比 APIView 多什么? queryset、serializer、object、pagination 等通用钩子。
十、加强记忆
DRF 视图抽象记成“手写、半自动、全资源”。APIView 最清楚,GenericAPIView 加通用能力,ViewSet 按资源动作组织,ModelViewSet 快速 CRUD。选择时看接口是否标准,别让高层抽象掩盖权限和数据过滤。