先说结论
Odoo 的权限判断不是一把锁,而是两道门:
- ACL(ir.model.access) 先判断你有没有“碰这个模型”的资格
- 记录规则(ir.rule) 再判断你能碰到哪些具体记录
这两层不是重复,而是分工。 如果你把它们当成同一个概念,很多 AccessError、sudo 误判、以及“为什么搜索得到但写不了”的问题都会看起来很乱。
第一层:ACL 先管“模型级别能不能做事”
IrModelAccess.check() 做的是最基础的模型访问判断。
它会先看当前用户是否已经在允许模型集合里。
源码里的关键点是:
env.su直接放行- 否则读取
_get_allowed_models(mode) - 如果模型不在允许集合里,就直接抛 AccessError
这意味着 ACL 解决的是最粗的一层问题:
你有没有资格去读、写、建、删这个模型。
它不关心你要访问哪条记录,只管“这个门你能不能进”。
第二层:记录规则再管“你能看见哪些记录”
如果 ACL 通过了,Odoo 还会继续算 ir.rule。
IrRule._compute_domain() 会把规则转成一个 domain,然后和模型本身的访问一起合并。
这一步很重要,因为记录规则会同时考虑:
- 当前用户是谁
- 当前公司有哪些
- 当前上下文里有什么 key
- 哪些规则是 global,哪些是 group-based
换句话说,ACL 是“能不能进大门”,记录规则是“进门以后哪些房间能进”。
为什么要分开,而不是只保留一种权限
如果只有 ACL,系统只能做“模型级放行”,没法表达:
- 只能看自己负责的销售单
- 只能看当前公司的单据
- 只能看符合某个业务 domain 的记录
如果只有记录规则,很多基本能力又会变得太慢、太复杂,甚至无法明确阻断“完全没资格访问这个模型”的情况。
所以 Odoo 把两层拆开,是为了同时做到:
- 粗粒度快拦截
- 细粒度按业务场景过滤
这是一种非常典型的平台设计。
缓存为什么会让排查变得更像魔术
IrRule._compute_domain() 有缓存,而且缓存键不只是用户 ID。
它还会把一些上下文值放进 key,尤其是 allowed_company_ids。
这背后的原因很简单:
- 同一个用户在不同公司切换时,domain 可能不同
- 同一个模型在不同上下文里,规则结果也可能不同
所以你在调试时,如果只盯着“这个用户明明有权限”,但没看公司上下文,常常会走偏。
sudo() 为什么经常让人误判
sudo() 很容易让人以为“权限没问题”。
但在 Odoo 里,它只是让当前环境跳过很多访问判断。
这会导致两个常见错觉:
- 你在 sudo 环境里查到记录,不代表普通用户也查得到
- 你在 sudo 环境里看不到报错,不代表规则没问题
所以调试权限问题时,最忌讳只用 sudo 看结果。 你需要回到真实用户、真实公司上下文再验证。
AccessError 是怎么拼出来的
IrRule._make_access_error() 和 IrModelAccess._make_access_error() 都不是只抛一个冷冰冰的错误。
它们会尽量告诉你:
- 是 ACL 拦了,还是记录规则拦了
- 拦的是哪个模型
- 如果是记录规则,还会给出一些更有帮助的线索
这也是 Odoo 排障体验里的一个细节: 系统不是只拒绝你,还尽量告诉你“为什么拒绝”。
新手最容易混淆的 4 件事
1. “有菜单,就一定有模型权限”
不对。菜单只是导航入口,不等于模型 ACL 已经放行。
2. “查得到记录,就一定写得了”
也不对。读和写会分别走自己的权限和规则判断。
3. “sudo 查到了,所以普通用户也行”
不对。sudo 只是特权视角,不是普通视角。
4. “记录规则就是更复杂的 ACL”
不是。它们的职责根本不同。
实战排查顺序
如果你遇到权限问题,我建议这样看:
- 先确认模型 ACL 是否允许当前操作
- 再确认记录规则是否把目标记录过滤掉
- 再检查公司上下文和
allowed_company_ids - 最后再看是不是 sudo 让你误读了现场
按这个顺序,通常比直接翻规则配置快很多。
最后一句话
Odoo 的 ACL 和记录规则不是两份重复清单,而是两道不同层级的门禁:
- ACL 决定你能不能进模型
- 记录规则决定你能碰到哪些记录
把这两层分开理解,权限问题就会清楚很多。
DISCUSSION
评论区