Odoo 的权限系统最容易让人误会的一点,就是把它当成“有权限 / 没权限”的单层开关。
实际上它更像三层门禁:
- 模型访问控制(ACL):这个人能不能碰这个模型;
- 记录规则(record rule):这个人能碰这个模型里的哪几条记录;
- 组和视图可见性:界面上能不能看到、代码里是不是被当成有效成员。
只看其中一层,很容易把问题判断错。
一、组不是简单标签,而是一套“集合”
在 res.groups 里,implied_ids 和 all_implied_ids 说明了一件事:
一个组不仅包含自己,还会继承它隐含的组。
这也是为什么 Odoo 里有 all_group_ids 这个概念。它不是“手工选中的组”,而是用户的有效组集合。
res.users.has_group() 和 _get_group_ids() 也反映了这个设计:
has_group()只适合判断当前用户的有效成员关系;_get_group_ids()被缓存起来,避免每次都重新算;- 非内部用户去 RPC 读别人的组信息会被拦住,避免泄漏。
这说明“属于哪个组”本身就是一项有边界的敏感判断。
二、记录规则由 ir.rule 统一编译
ir.rule 的核心字段很直接:
model_id:作用在哪个模型;domain_force:规则域;perm_read/write/create/unlink:在哪些动作上生效;groups:哪些组可见。
_get_rules() 会根据模型名、操作类型、当前用户组,找出可用规则。
这里有两个重要点:
env.su会让规则查询直接绕开;- 规则不是“每条都 AND”,而是要分 global 和 group 两种语义来合并。
也就是说,权限不是一张平铺的表,而是先筛选、再组合、再计算。
三、_compute_domain() 才是最终的“有效规则”
_compute_domain() 会把规则编译成最终域:
- 全局规则用 AND 叠加;
- 组规则先按组做 OR,再合并进总域;
- 还会把继承模型的限制一起带进去。
它还把 allowed_company_ids 放进缓存键里。这个细节很关键,因为多公司场景下,同一个规则域在不同公司上下文里可能完全不同。
所以如果你遇到“切一下公司,权限表现就变了”,通常不是随机 bug,而是缓存键和上下文正在按设计工作。
四、sudo() 不是“更安全”,只是“换成超级用户视角”
很多开发者会把 sudo() 当成一个万能补丁:
- 读不到就 sudo;
- 写不进去就 sudo;
- 规则太复杂也 sudo。
这会让调试变得很痛苦。
因为 sudo() 改变的不是“业务事实”,而是校验视角。你在 sudo 视角里看到的结果,可能并不是普通用户真正会看到的结果。
所以 sudo() 应该是手术刀,不是胶带:
- 在框架代码里,它有明确用途;
- 在业务代码里,它应该少而准;
- 如果只是为了绕过错误的 domain,那通常是在掩盖设计问题。
五、排查权限问题的顺序
一个实战上比较稳的顺序是:
- 先看 ACL:模型层面有没有
ir.model.access; - 再看 record rule:当前用户的规则域是什么;
- 再看 group inheritance:用户是不是通过 implied groups 得到了更大的权限;
- 最后看 company context:是不是多公司把缓存和规则分支都切换了。
这么排,比一上来就改 sudo() 靠谱得多。
结论
Odoo 的权限校验不是一层,而是分层:
- ACL 负责“能不能碰模型”;
- record rule 负责“能碰哪条记录”;
- group 继承和上下文负责“以什么身份看世界”。
只要把这三层分开想,很多“权限不对”的问题其实会立刻清楚。
DISCUSSION
评论区