很多人对 groups 的第一反应就错了
一看到 groups="...",很多人会本能地把它理解成:
- 让某些用户看到
- 让某些用户看不到
这当然没错,但只说到了“显示层”。
在 Odoo 里,groups 相关逻辑远不只是 UI 开关。
源码里至少有三层东西会同时参与:
- 模型访问权限:这个模型能不能读、写、建、删
- 字段级 groups:这个字段对哪些组可读
- 视图节点 groups:这个 XML 节点对哪些组可见
而真正容易炸的地方,是第四层:
- 表达式依赖,比如
invisible="field_b"、required="field_a"、readonly="not d"
也就是说,一个字段即使“本身不显示”,只要它被别的节点表达式引用,它就仍然是视图正确性的组成部分。
这也是为什么会出现很多人第一次看到就发懵的提示:
Access Rights Inconsistency
源码主线:不是只删节点,而是先建“字段可见性模型”
ir_ui_view.py 里的 postprocess_and_fields() / _postprocess_view() / NameManager 这几段代码,是理解这个问题的关键。
系统不是粗暴地把带 groups 的节点删掉就完事。
它会同时做几件事:
- 收集这个 view 里出现了哪些字段
- 记录这些字段在哪些组下会出现
- 记录哪些表达式依赖了哪些字段
- 计算模型访问组、字段访问组、视图节点组的交集或包含关系
- 最后检查:是否存在“某些用户能看到依赖表达式,但看不到被依赖字段”的情况
所以 Odoo 的真实问题意识是:
一个 view 不只要“能渲染”,还要对所有可能用户组组合都保持自洽。
_postprocess_access_rights() 在做什么
源码里 _postprocess_access_rights() 会处理两件非常核心的事。
第一件:按 groups 裁剪节点
带 __groups_key__ 的节点如果当前用户不匹配,就会直接从树里删除。
这说明很多“按钮为什么完全没了”的现象,不是前端隐藏,而是:
后端在把视图发给前端之前,就已经把节点剪掉了。
第二件:补充 create / edit / delete 等能力标记
对于带 model_access_rights 的节点,系统还会根据模型访问权写入:
create="False"edit="False"delete="False"- field 上的
can_create/can_write
所以 Odoo 的视图后处理不是单纯“删元素”,而是在视图结构里嵌入了一部分权限结论。
真正高级的部分在 NameManager
如果只看 _postprocess_access_rights(),你会以为 groups 逻辑就是“该删就删”。
但真正让 Odoo 能发现“权限不一致”的,是 NameManager。
它会维护几组信息:
available_fields:视图里有哪些字段节点可用field_groups:字段本身允许哪些组访问used_fields:有哪些表达式在依赖哪些字段available_names/used_names:名字、动作、引用等依赖
也就是说,Odoo 不只在看“字段有没有放进 XML”。
它还在看:
这个字段是不是被别的节点偷偷依赖了?而依赖它的那些节点,对哪些用户会出现?
这才是 Access Rights Inconsistency 的根。
一个最典型的坑
假设你有:
<field name="secret_flag" groups="base.group_system"/>
<field name="amount" invisible="not secret_flag"/>
很多人会觉得这没问题:
- 管理员看得到
secret_flag - 普通用户看不到
amount自己照样显示
但 Odoo 会继续追问:
- 普通用户虽然看不到
secret_flag - 可
amount的invisible表达式却依赖secret_flag - 那普通用户加载这个 view 时,表达式上下文是否仍然成立?
如果不成立,这个 view 对某部分用户就是不自洽的。
于是系统就会报权限不一致,提醒你这不是一个对所有组都安全的视图设计。
为什么“字段不显示”不代表“字段不需要可用”
这是很多人最容易忽略的一点。
在 Odoo 视图里,一个字段可能承担三种不同角色:
- 展示字段:用户真能看到
- 控制字段:决定别的节点显隐、只读、必填
- 技术依赖字段:并不打算让用户操作,但 view 表达式必须依赖它
对第 2、3 类字段来说,最大的误区就是:
“反正用户不看见,就不用管权限一致性。”
恰恰相反。
越是技术依赖字段,越要保证它和依赖它的元素拥有一致的组覆盖关系。
field.groups 和 node groups 不是同一回事
字段级 groups
定义在 Python 字段声明上,意思是:
- 这个字段本身的读取权限只开放给某些组
这是数据层的字段访问边界。
节点级 groups
定义在 XML 节点上,意思是:
- 这个节点是否出现在某些用户的视图里
这是视图层的呈现边界。
这两者可以重合,但并不会自动一致。
而 Odoo 正是在检查它们是否一致。
如果一个节点会在 A 组用户面前出现,但它依赖的字段只允许 B 组访问,那就是潜在问题。
为什么有时系统报的是 warning,不是立刻阻断
源码里的 check() / get_missing_fields() / _error_message_group_inconsistency() 会区分情况:
- 字段根本不存在 → 直接视图错误
- 字段存在,但组覆盖不一致 → 记录警告或报权限不一致信息
这反映出官方态度很明确:
- 结构性错误必须阻断
- 潜在权限组合风险至少要明确暴露出来
因为这类问题在单一管理员账号下常常复现不出来,但一到真实项目多组并存就会炸。
base.group_no_one 为什么又是个特殊角色
源码里还有 _postprocess_debug_to_cache() 和 _postprocess_debug(),专门处理 base.group_no_one。
它不是普通安全组,而更像一种“调试显示特性”的历史兼容物。
官方甚至在注释里明确说明:
- 它的行为历史上并不一致
- 更像 display feature,而不是严肃的 security group
这意味着你在设计权限时,不要把 base.group_no_one 当成标准业务权限方案去思考。
它更多是:
- 开发者字段
- 调试入口
- 超级用户可见的技术节点
而不是业务角色分层的核心手段。
实战里怎么避免 Access Rights Inconsistency
1. 控制字段和被控制节点尽量共组
如果 field_a 只给某组人看,而别的节点显隐依赖 field_a,那这些节点最好也限制在同一组范围里。
2. 技术字段可以隐藏,但不要让它在用户组上“缺席”
常见做法是:
- 把控制字段放进 view
- 用
invisible="1"隐藏 - 但保证它在会用到它的用户组组合里依然可访问
3. 不要把“前端看不见”当成安全
字段级 groups、ACL、record rule 才是安全边界;XML groups 只是视图裁剪层。
4. 多角色测试
只用管理员测 view,几乎注定会漏掉这类问题。
你至少要用:
- 普通内部用户
- 受限业务用户
- 多公司/多组组合用户
去验证关键界面。
一句话记住
如果只记一句,就记这句:
在 Odoo 里,groups 不只是“这个节点给谁看”,而是“这个 view 对哪些用户组组合仍然逻辑自洽”。
所以 Access Rights Inconsistency 不是系统在找你麻烦,而是在提醒你:
- 某些用户能看到一个依赖关系
- 但他们却没有访问这个依赖字段的能力
从源码设计上看,这其实是很有价值的一层保护。
DISCUSSION
评论区