动态权限控制需确保has_object_permission在get_object()后触发,仅适用于retrieve/update/destroy等单对象视图;listapiview等场景须用has_permission或专用方案;时间、角色等动态依据须在权限类内实时校验,避免绕过。

动态权限控制不是靠“加个判断”就能生效的,关键在于权限类是否在对象加载后才执行检查,以及是否能感知请求上下文变化(比如时间、用户角色字段、URL参数)。
has_object_permission 必须在 get_object() 之后触发
很多开发者误以为只要写了 has_object_permission 就能做动态判断,但实际它只在视图调用 get_object() 后才被调用——也就是说,如果视图没取对象(如 ListAPIView 或 create),这个方法根本不会运行。
- 适用于
RetrieveAPIView、UpdateAPIView、DestroyAPIView等单对象操作视图 - 对
ListAPIView或create场景,必须用has_permission+ 手动查对象(不推荐)或改用drf-access-policy这类支持 action-level 判断的方案 - 若你在
has_object_permission里访问obj.some_related_field,要确保 serializer 或 view 的queryset已预加载关联,否则会 N+1
基于用户模型字段做权限判断(如 user_type、is_staff)
直接读 request.user 的字段是最轻量的动态依据,但要注意字段是否已从数据库加载。Django 默认不会懒加载所有字段,尤其自定义字段(如 user_type)可能为空。
- 确保用户模型字段在认证后已加载:在自定义
Authentication类中,用select_related或only()预取关键字段 - 避免在权限类里再查一次数据库:
if request.user.user_type == 2是 OK 的,但User.objects.get(id=request.user.id).user_type会多一次查询 - 字段值变更后权限立即生效,无需缓存或重启服务
时间敏感型权限(如“仅工作日 9–18 点可编辑”)
这类逻辑不能只靠前端传参或 URL 参数,必须在权限类内部用 datetime.now() 判断,否则可被绕过。
- 使用
datetime.now().time()和datetime.now().weekday()组合判断,注意时区:DRF 默认用settings.TIME_ZONE,不是 UTC - 写法示例:
if request.method not in SAFE_METHODS and (datetime.now().weekday() >= 5 or datetime.now().hour = 18) - 测试时别依赖本地机器时间——单元测试中应 monkey patch
datetime.now,否则 CI 可能随机失败
为什么 perform_create 中设置 author 不影响权限校验?
因为 perform_create 在权限检查之后才执行。权限类看到的是“即将创建的对象”,而此时 obj 还不存在,所以 has_object_permission 根本不触发;has_permission 只能判断“能否创建”,无法知道未来谁是作者。
- 想让创建者自动获得后续编辑权,得在
has_permission里允许创建,再靠has_object_permission控制后续修改 - 不要在
serializer.save()前手动塞validated_data['author'] = request.user,这会跳过字段验证和to_internal_value钩子 - 真正安全的做法是:权限类放行
POST,然后在perform_create中用serializer.save(author=request.user),确保 author 字段由序列化器处理
最易被忽略的一点:权限类返回 False 时,DRF 默认返回 403,但不会告诉你具体哪条规则失败。调试时建议在开发环境临时加 print(f"deny: {reason}") 或用 logging 记录拒绝原因,否则线上排查对象级权限问题会非常慢。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











