权限与视图裁剪

Odoo 为什么“字段有权限、页面却还会报错”:field groups、视图裁剪与 Access Rights Inconsistency 讲透

很多人把 groups 只当成“控制显示”。但在 Odoo 里,字段 groups、视图节点 groups、模型访问权和表达式依赖会一起工作。本文从 ir_ui_view.py 和 NameManager 源码讲清为什么页面会出现 Access Rights Inconsistency。

Odoo 开发 框架
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 6 阅读

很多人对 groups 的第一反应就错了

一看到 groups="...",很多人会本能地把它理解成:

  • 让某些用户看到
  • 让某些用户看不到

这当然没错,但只说到了“显示层”。

在 Odoo 里,groups 相关逻辑远不只是 UI 开关。

源码里至少有三层东西会同时参与:

  1. 模型访问权限:这个模型能不能读、写、建、删
  2. 字段级 groups:这个字段对哪些组可读
  3. 视图节点 groups:这个 XML 节点对哪些组可见

而真正容易炸的地方,是第四层:

  1. 表达式依赖,比如 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
  • amountinvisible 表达式却依赖 secret_flag
  • 那普通用户加载这个 view 时,表达式上下文是否仍然成立?

如果不成立,这个 view 对某部分用户就是不自洽的。

于是系统就会报权限不一致,提醒你这不是一个对所有组都安全的视图设计。


为什么“字段不显示”不代表“字段不需要可用”

这是很多人最容易忽略的一点。

在 Odoo 视图里,一个字段可能承担三种不同角色:

  1. 展示字段:用户真能看到
  2. 控制字段:决定别的节点显隐、只读、必填
  3. 技术依赖字段:并不打算让用户操作,但 view 表达式必须依赖它

对第 2、3 类字段来说,最大的误区就是:

“反正用户不看见,就不用管权限一致性。”

恰恰相反。

越是技术依赖字段,越要保证它和依赖它的元素拥有一致的组覆盖关系。


field.groupsnode 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

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。