做 Odoo 界面调试时,按钮问题特别容易让人烦躁。
常见现象包括:
- A 用户看得到按钮,B 用户看不到;
- 同一个用户在列表页看得到,在表单里不见了;
- 按钮明明显示出来了,却是灰的、点不动;
- 改了一段 XML,以为是 XPath 没命中,结果根本不是继承问题。
这些现象之所以容易混乱,是因为很多人把“按钮能不能用”想成一个开关。
但源码视角下,它根本不是一个开关,而是几层机制叠在一起:
- 服务端按 groups 裁剪视图节点
- 前端按 invisible / readonly 表达式决定是否显示、是否可编辑
- 视图层 activeActions 再决定 create/edit/delete 等动作是否开放
所以更准确的说法是:
按钮不是“显示 or 不显示”二选一,而是先过服务端可见性,再过前端状态表达式,最后再过视图动作能力。
一、第一层:groups 在服务端就可能把节点删掉
在 ir_ui_view.py 里,_postprocess_access_rights() 做了一件特别关键的事:
- 带
groups的节点,会先根据当前用户组匹配结果处理; - 如果用户不在允许组里,节点会直接从视图树里移除。
这一步的含义很重。
如果一个按钮是被 groups 挡掉的,那么对前端来说:
- 它不是“有但隐藏”;
- 而是压根就不存在。
这也解释了为什么你有时打开浏览器调试,怎么都看不到那颗按钮:
不是 CSS 把它藏起来了,是后端返回的 arch 里就没这节点。
二、第二层:create / edit / delete 这类能力会写进视图动作能力里
同样在 _postprocess_access_rights() 里,Odoo 还会基于模型访问权限,给节点补充像下面这样的能力信息:
can_createcan_write
而在 Web 端,getActiveActions() 会把根视图节点上的:
createeditdeleteduplicate
整理成 activeActions。
这意味着就算按钮节点本身还在,用户是否能进行对应动作,仍然可能受视图整体动作开关影响。
例如列表页里:
- 视图声明
edit="false" - 或者模型没有 write 权限
- 或 action 本身限制了某些动作
都会让前端把这类操作当成不可用。
三、第三层:invisible / readonly 不是服务端删节点,而是前端表达式求值
在 Web 源码里,processButton() 会把按钮节点上的属性拆成:
clickParamsattrsinvisiblereadonly
然后列表控制器、渲染器和 widget 会用 evaluateBooleanExpr() 去求这些表达式。
这表示:
对 groups
是后端结构裁剪。
对 invisible
通常是前端基于当前 record / context 计算:
- 条件成立 → 不显示
- 条件不成立 → 显示
对 readonly
则更像“显示了,但不能编辑 / 不能交互”。
这三者不是一回事。
四、为什么“明明按钮在 XML 里,却页面上没了”
最常见有三类原因。
1)被 groups 在服务端删掉
这种情况最彻底。
前端收不到节点,所以你会以为:
- 继承没生效;
- XPath 没打中;
- 模块没升级;
其实只是当前用户组不匹配。
2)invisible 在前端求值为真
这时按钮通常还在 arch 语义里,但渲染层不显示。
列表页里就能看到类似:
t-if="!evalViewModifier(button.invisible)"- 或针对 record 的
evalInvisible(...)
也就是说,它不是永久没了,而是当前上下文下不显示。
3)视图动作能力把它变成不可操作
有些按钮虽然看得到,但最终不能点,常见原因是:
- 缺少 name/type/special 等必要 click 参数;
activeActions不允许相应动作;- 按钮被标记为 disabled;
- 对应记录处于只读态。
这时问题就不是“可见性”,而是“可执行性”。
五、缓存为什么会让你误判
ir_ui_view.py 里还有 _get_view_cache_key()。
默认 cache key 会考虑:
view_idview_typemobilelang- 某些
*_view_ref上下文
这说明视图结果本来就会缓存。
而 skill 里也强调过一个重要边界:
_get_view_cache缓存的是针对所有组的后处理视图;- 之后仍要再做一次 group restricted block 的移除。
所以调按钮问题时,你不能只问“缓存有没有刷新”,还得问:
我看到的是缓存的通用视图,还是按当前用户组进一步裁剪后的最终视图?
六、现代 Odoo 里别再把 attrs / states 当万能解释
当前源码里甚至会校验旧式 attrs / states 的使用问题。
这意味着在较新的 Odoo 版本里,很多开发者脑中的老经验已经不完全适用。
更稳的理解方式是:
- groups:服务端节点可见性
- invisible / readonly 等表达式:前端基于上下文和记录值求值
- activeActions:视图级动作能力边界
如果你还把所有按钮异常都归因于旧式 attrs/states,很容易看错方向。
七、实战排错时最有效的顺序
如果用户说:
为什么这个按钮有人有、有人没有、有人能看不能点?
建议按这个顺序查:
- 按钮节点有没有
groups - 当前用户是否命中这些组
- 返回的最终 arch 里,这个节点还在不在
- 按钮的
invisible/readonly表达式是什么 - 当前 record / context 求值后结果是什么
- 当前视图
activeActions里对应动作是否允许 - 按钮本身是否缺少
name/type/special,导致ViewButton.disabled为真
这个顺序特别重要,因为它对应的就是源码真正运行的层次。
八、最容易产生的 4 个误判
误判 1:看不到按钮 = XPath 失败
不一定。
也可能是 groups 已经把节点删掉了。
误判 2:按钮显示了 = 用户一定能操作
不一定。
显示和可执行是两套判断。
误判 3:只要有权限,按钮就一定出现
也不一定。
还有 invisible 表达式、上下文条件、视图动作能力等因素。
误判 4:这一定是前端 CSS 问题
大多数时候不是。
Odoo 的按钮状态更多是数据驱动的结构与表达式结果,不是样式层的小毛病。
九、一句话记住这件事
Odoo 按钮的真实判定顺序,更接近于:
- 先看后端是否允许这个节点存在(
groups) - 再看前端当前上下文下是否应该显示(
invisible) - 再看是否处于只读或禁用态(
readonly/ disabled) - 再看视图动作能力是否允许执行(
activeActions)
所以“按钮不见了 / 按钮灰了 / 按钮点不动”虽然看起来像一类问题,源码里其实属于三种不同层面的故障。
把这三层分开想,按钮类问题会一下子好排很多。
DISCUSSION
评论区