先把两个概念分开
很多人第一次看 Odoo 权限时,会把 访问权限 和 记录规则 混在一起。 其实它们不是一回事:
- 访问权限(access rights) 解决的是“这个用户能不能碰这个模型”
- 记录规则(record rules) 解决的是“这个用户能碰这个模型里的哪些记录”
前者像大门钥匙,后者像门禁卡。 你有大门钥匙,不代表所有房间都能进去。
ir.rule 里最重要的三个输入
在 odoo/addons/base/models/ir_rule.py 里,规则计算的核心输入有三个:
domain_force:规则本身的过滤条件groups:规则是否只对某些用户组生效allowed_company_ids:当前激活公司的上下文
相关代码先从 _get_rules() 开始,按模型、操作类型和用户组把规则挑出来;然后 _compute_domain() 再把它们拼成最终域。
_get_rules():先筛选出“和你有关”的规则
_get_rules() 的作用是按当前用户和操作类型取出规则:
- 不是当前模型的规则不要
- 没启用的规则不要
- 不支持当前操作模式的规则不要
- 和用户组不匹配的组规则不要
你可以把它理解成“先做候选集,再谈合并”。
这一步非常关键,因为它决定了后面的域到底来自哪些规则。
_compute_domain():全局规则要 AND,组规则要 OR
这是最容易看错的一段。
源码的思路是:
- 全局规则(global rules):全部都要满足,所以是 AND
- 组规则(group rules):同一组里的候选规则会被 OR 起来
也就是说,Odoo 不是简单把所有 domain 拼一起,而是在做有层次的组合。
这会直接影响你的调试结论:
- 如果记录“明明应该可见却看不到”,可能是某条全局规则太严
- 如果某个组的用户能看到的记录太多,可能是组规则被 OR 后放宽了
多公司为什么要特别小心
ir.rule 的 _eval_context() 里会把下面这些值放进安全评估上下文:
usercompany_idscompany_id
同时,_compute_domain_keys() 又把 allowed_company_ids 放进缓存键。
这意味着:
- 你切换公司,规则域可能变
- 规则结果也会跟着缓存重新计算
所以在多公司环境里,很多“权限问题”其实不是 ACL 出错,而是规则域在当前公司上下文下把记录过滤掉了。
_make_access_error() 为什么这么“贴心”
Odoo 抛出拒绝访问时,不只是说一句“没权限”。
_make_access_error() 会尽量帮助你定位问题:
- 告诉你是 read / write / create / unlink 哪种操作失败
- 提示是哪一个模型
- 如果是内部用户,还会列出可能导致失败的规则
- 如果涉及公司,还会提示可能需要切换公司
这不是“文案好看”这么简单,而是 Odoo 试图把权限调试成本降下来。
新手最容易踩的三个坑
-
只看
ir.model.access.csv,不看ir.rule模型权限允许,不代表记录一定可见。 -
把 global rule 当成“默认规则” 它不是默认值,而是硬约束;所有全局规则都要同时满足。
-
忽略公司上下文 很多规则里都隐含了
company_id或可用公司集合。
开发和排错时的实用建议
- 先确认是 ACL 失败,还是 record rule 失败
- 再看具体操作是 read、write 还是 create
- 如果是多公司场景,先切换上下文再复现
- 调试自定义规则时,优先检查
domain_force是否能在当前用户上下文下通过safe_eval
一句话总结
Odoo 记录规则的本质,是把“谁能看什么”转换成一个最终访问域。 这个域由规则类型、用户组和多公司上下文共同决定,而不是靠单条规则简单生效。
DISCUSSION
评论区