先记住一句大白话
在 Odoo 里,一次“我能不能看 / 改这条记录”,通常不是只看用户组,也不是只看一条 ir.rule。
真正的顺序更像这样:
- 先过模型级权限(ACL /
ir.model.access) - 再过记录级权限(
ir.rule) - 如果用了
sudo(),前两层大部分都直接绕开 - 如果没 sudo,多公司上下文还会参与记录规则的计算与缓存
所以很多权限问题,不是“规则写错了”,而是你根本在错误的层面排查。
第一层:ACL 先决定“你有没有资格碰这个模型”
在 /home/ubuntu/odoo-temp/odoo/orm/models.py 里,check_access() 最终会走到 _check_access()。
这个方法一上来先看的是:
Access = self.env['ir.model.access']
if not Access.check(self._name, operation, raise_exception=False):
return self, ...
这一步只回答一个问题:
你对这个模型,原则上有没有 create/read/write/unlink 的资格?
比如:
- 你对
sale.order没有 write ACL - 那你连“写销售订单”这件事都不成立
- 根本还轮不到记录规则出场
这就是为什么有些同学一直盯着 ir.rule,结果怎么改都没用:
因为门卫还在第一层,第二层根本没开始查。
第二层:记录规则决定“这个模型里的哪些记录你能碰”
如果 ACL 通过,_check_access() 才会继续看 ir.rule:
Rule = self.env['ir.rule']
domain = Rule._compute_domain(self._name, operation)
if domain and (forbidden := self - self.sudo().with_context(active_test=False).filtered_domain(domain)):
return forbidden, ...
这里的关键心智模型是:
- ACL 管的是 模型级入口
ir.rule管的是 记录级过滤
也就是说:
- ACL 说“你能不能进这个仓库”
- Rule 说“进来以后,你能碰哪几排货架”
所以你会看到一种很常见的现象:
- 用户能打开列表页
- 但只能看到部分记录
- 或者能看到记录却不能修改某几条
这类问题,大概率就是规则层,而不是 ACL 层。
第三层:_compute_domain() 不是简单拼接,而是“全局 AND,本地组 OR”
在 ir_rule.py 里,_compute_domain() 会把规则拆成两类:
- 全局规则:不绑 groups 的规则
- 组规则:绑了 groups 的规则
源码最后的组合方式很重要:
- 全局规则之间:AND
- 组规则之间:OR
- 然后整体再并回全局域里
这意味着:
1)全局规则越多,通常越严
因为是 AND。
例如:
- 规则 A:只能看自己公司
- 规则 B:只能看自己负责的客户
最后不是“满足其一就行”,而是两个都要满足。
2)组规则更像“给不同角色开不同门”
例如:
- 销售组规则:看自己销售单
- 经理组规则:看全团队销售单
如果同一用户命中了多个组规则,Odoo 会把它们做 OR。
所以调权限时,不能只看某一条 rule,要看:
- 它是 global 还是 group rule
- 用户到底命中了哪些组
- 最终是被 AND 严格收紧,还是被 OR 放宽
第四层:sudo() 不是“方便调试”,而是“直接切到管理员视角”
ir_rule.py 里的 _get_rules() 有个特别关键的分支:
if self.env.su:
return self.browse(())
也就是说,一旦你在 superuser 环境里:
- 不是“把全部规则都拿出来继续算”
- 而是规则集合直接变空
而 models.py 里的 check_access() / has_access() 也都把 env.su 当成快速放行条件。
所以 sudo() 的真实含义是:
我现在不是在观察普通用户的权限边界,我是在绕过它。
这就是最常见的调试误区:
- 你
sudo().search(...) - 发现所有记录都能看到
- 然后断言“规则没生效”
其实不是规则没生效,而是你已经不在规则适用的世界里了。
第五层:多公司不是附加条件,而是规则上下文的一部分
ir_rule.py 的 _eval_context() 会给 rule 提供:
usercompany_idscompany_id
其中 company_ids 就来自当前激活公司的集合,也就是用户切公司后那组 allowed_company_ids。
同时,_compute_domain_keys() 默认返回:
['allowed_company_ids']
而 _compute_domain() 又挂了 ormcache。
这表示:
- 规则不仅按用户和模型缓存
- 还按 当前公司上下文 缓存
所以你会遇到这些现象:
- 同一个用户,切一下公司,搜索结果就变了
- 同一段代码,在不同 cron / request / shell 上下文里表现不同
- 某次你觉得“缓存有问题”,其实是
allowed_company_ids变了
这不是 Odoo 在抽风,而是它明确把多公司当成权限求值的一部分。
为什么有时“能读不能写”
很多人第一次碰到这个现象会困惑:
- 列表里能搜到记录
- 表单也能打开
- 一点保存就报 AccessError
原因通常有两类:
1)ACL 的 read / write 分开
模型访问本来就是按操作拆的。
你可能有 read ACL,但没有 write ACL。
2)记录规则也是按操作拆的
ir.rule 本身有:
perm_readperm_writeperm_createperm_unlink
所以完全可能出现:
- 读规则放得宽
- 写规则收得紧
从业务角度也很合理:
- 允许客服看全部工单
- 但只允许负责人修改自己的工单
所以“能看到但不能改”不是矛盾,而是 Odoo 权限模型本来就支持的设计。
一个最实用的排查顺序
以后再遇到权限问题,建议按这个顺序排:
第 1 步:先确认有没有 sudo() / with_user()
先搞清楚你看到的是:
- 普通用户上下文
- 还是管理员上下文
这个判断错了,后面全白看。
第 2 步:查 ACL
确认这个用户对该模型的 create/read/write/unlink 是否本来就有权限。
第 3 步:查 rule 是 global 还是 group
重点看:
- rule 绑定了哪些 group
- 用户实际命中了哪些 group
- 是读失败还是写失败
第 4 步:查 allowed_company_ids
尤其是多公司场景:
- shell context
- HTTP request context
- cron context
- 测试用例 context
很可能并不一致。
第 5 步:最后再看 domain 具体内容
很多时候问题根本不在 domain_force 语法,而在:
- 规则没被命中
- 缓存上下文不一致
- 你在 sudo 环境下调试
开发时最容易踩的 4 个坑
坑 1:用 sudo() 写业务逻辑,结果把本该受控的写操作全放飞了
sudo() 很适合:
- 系统级维护
- 后台修复
- 框架层内部动作
但如果你把它随手塞进普通业务逻辑,常见后果就是:
- 用户本来不该改的数据也改了
- 记录规则完全失效
- 多公司隔离被无意穿透
坑 2:只测 read,不测 write
很多权限 bug 其实只在 write / unlink 才暴露。
坑 3:切了公司但没意识到缓存键变了
你以为在复现“同一个问题”,实际上环境已经不是同一个了。
坑 4:把“看不到记录”和“字段只读”混为一谈
记录规则管的是记录集合;字段 readonly、groups、view attrs 又是另一层。
不要把 UI 层限制和 ORM 安全边界混成一锅。
一句话总结
Odoo 的权限不是“用户组打勾”这么简单,而是 ACL 先过模型门槛,Record Rule 再筛记录集合,sudo 负责整体绕过,而多公司上下文又会进入规则求值与缓存键 的分层系统。
把这四层分清,你排权限问题会快很多,也不容易被 sudo 和多公司假象带偏。
DISCUSSION
评论区