够用但需谨慎配置,ModelViewSet默认提供list、retrieve、create、update、destroy五个动作及标准路由,但实际需手动处理过滤、权限、字段控制、软删除等;必须显式定义get_queryset()和serializer_class,并在perform_create()中注入用户等后端字段。

ModelViewSet 默认路由和动作是否真够用?
够用,但容易误以为“写个类就完事”。ModelViewSet 自带 list、retrieve、create、update、destroy 五个动作,对应标准 REST 路由(如 /api/users/ 和 /api/users/{id}/),但实际项目里常要:加搜索、过滤字段、控制返回字段、做权限校验、处理软删除、支持批量操作——这些都不是开箱即用的。
常见错误是直接继承 ModelViewSet 后啥也不重写,结果前端调 GET /api/orders/ 拿到全部订单(含敏感字段),或 POST /api/orders/ 不校验用户只能下自己的单。
- 必须显式配置
queryset和serializer_class,漏写会报AssertionError: 'View' should either include a `queryset` attribute... -
get_queryset()比直接写queryset = Xxx.objects.all()更安全,方便按请求动态过滤(比如只返回当前用户数据) - 别在
serializer_class里硬编码read_only_fields = ['created_at'],应改用read_only_fields或extra_kwargs控制字段级行为
怎么限制 list 返回的数据范围?
不能只靠前端传参数,后端必须拦截并约束。DRF 的 get_queryset() 是唯一可靠入口点。
例如用户只能看自己的订单:
立即学习“Python免费学习笔记(深入)”;
def get_queryset(self):
return Order.objects.filter(user=self.request.user)
如果还要支持管理员查看全部,就得加权限判断:
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
- 用
self.request.user.is_staff判断角色,但更推荐用 DRF 的has_permission或自定义权限类 - 避免在
get_queryset()里写复杂条件链(如.filter(a=...).filter(b=...).exclude(...)),先用变量存中间 queryset,提高可读性 - 注意
select_related和prefetch_related必须放get_queryset()里,否则序列化时 N+1 查询照常发生
如何让 create/update 避开字段篡改?
ModelViewSet 默认把所有字段都交给序列化器处理,而 ModelSerializer 默认允许所有模型字段写入——这是最大安全隐患。
正确做法是明确声明哪些字段可写、哪些由后端控制:
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = ['id', 'items', 'status']
read_only_fields = ['id', 'status'] # id 和 status 不允许客户端提交
extra_kwargs = {
'items': {'required': True},
}
-
read_only_fields只影响序列化输出,不影响反序列化输入;真正阻止字段被写入的是extra_kwargs中的'write_only': True或直接不列在fields里 - 用户 ID、时间戳这类字段绝不能从 request.data 取,必须在
perform_create()里注入:def perform_create(self, serializer): serializer.save(user=self.request.user, created_at=timezone.now()) - 如果需要校验字段间逻辑(如 “结束时间不能早于开始时间”),必须在
Serializer的validate()方法里做,而不是视图层
为什么 PATCH 请求有时 400 却没提示?
因为 DRF 默认对 partial update 做宽松校验,但某些字段(比如外键关联对象不存在)仍会静默失败,最终返回空响应或 400 + 无意义错误信息。
关键检查点:
- 确认 URL 路径是否带尾部斜杠 —— Django 2.2+ 默认
APPEND_SLASH=True,但 DRF 路由若用router.register()生成,必须保证路径末尾有/,否则PATCH /api/orders/1(缺斜杠)会被重定向成 GET,导致方法错乱 - 检查
serializer.is_valid(raise_exception=True)是否启用;没启用时错误会吞掉,需手动 printserializer.errors - 外键字段提交字符串而非 ID(如
{"category": "electronics"})会直接 400,必须传整数 ID 或确保CategorySerializer支持 slug lookup(通过lookup_field和to_internal_value)
真实项目里,ModelViewSet 看似省事,但每个动作背后都有隐式行为链。最易忽略的是:它不自动处理软删除、不区分「列表筛选」和「详情加载」的查询优化、也不隔离租户数据——这些都得手动补全,而且得在对的地方补。

















