很多人把问题看错了
一提到视图继承失效,第一反应通常都是:
- XPath 没命中
- 节点写错了
- position 不对
这些当然常见,但在大型项目里,另一个更隐蔽的来源是:
你的 patch 可能命中了,只是最终组合顺序不是你以为的那个顺序。
而这个顺序,和 priority、mode、继承树结构都有关系。
第一层:inherit_id 不是全部,mode 也决定“你是谁”
ir.ui.view 里 mode 有两个值:
extensionprimary
源码注释写得非常关键:
extension
它还是在已有 primary 视图之上继续叠 patch。
primary
它会先找到最近的 primary 根,再把这个视图自己的继承规格应用上去,并把结果当成“像自己原生 arch 一样”的结果使用。
这意味着 mode=primary 不是“普通 patch 再高级一点”,而是在继承树里改变了定位身份。
所以有时你以为自己只是插了一层扩展,实际上你是在切换组合根的解释方式。
第二层:系统会把所有 inheriting views 按 priority, id 排序
_get_inheriting_views() 的 SQL 很直白:
ORDER BY v.priority, v.id
源码旁边还写了说明:
- 先按
priority - 再按数据库里的
id
其中 priority 就是开发者手动控制“谁先组合、谁后组合”的旋钮。
这点非常重要。
因为在视图世界里:
- 后应用的 patch,往往更有机会覆盖前面的结果
所以当两个模块都在改同一块节点时,问题不只是“谁写得对”,而是“谁最后上场”。
第三层:为什么同一个 patch 在不同环境看起来顺序不一样
因为排序除了 priority,还会看 id。
而 id 受:
- 安装顺序
- 数据装载顺序
- 数据库实际插入顺序
影响。
所以当两个视图 priority 一样时,你在测试库和生产库里看到的“最终谁盖谁”,有时真的会不一样。
这不是玄学,而是排序 tie-breaker 变了。
也正因为如此,真正存在覆盖冲突时,别赌默认顺序,直接明确设 priority 更稳。
第四层:_get_combined_archs() 不是线性拼 patch,而是在走继承树
源码会先:
- 从当前视图一路追到 root
- 收集整棵继承树里的相关视图
- 建立 parent → children 的层级映射
- 再调用
_combine()做组合
而且其中有个关键分支:
if child.id in parented_ids or child.mode != 'primary':
这意味着并不是所有子视图都以同一种方式参与组合。
primary 在这里不是普通 extension 的同类项,它会影响继承层级如何被纳入当前组合。
所以你遇到“这个 patch 单看没问题,但从某个入口打开时又像没应用”的现象,很多时候不是 XPath,而是组合起点不同。
第五层:访问边界也会影响你看到的最终视图
_check_view_access() 里有个容易被忽略的逻辑:
- 若当前视图有
inherit_id且mode != 'primary' - 会递归检查父视图访问权限
同时,视图还可能受 group_ids 影响。
这意味着“最终组合结果”并不只是技术上能不能拼出来,还包括:
- 当前用户是否有资格走到这棵视图树
- 某些组限定视图是否被纳入
于是你就会看到一种经典现象:
- 管理员看起来 patch 正常
- 普通用户像没生效
很多时候不是缓存,也不是浏览器,而是组边界不同。
实战里最该怎么排
当你怀疑 patch 被覆盖时,建议按这个顺序看:
- 这条视图是
extension还是primary - 它挂在谁的
inherit_id下 - 同一块区域还有哪些视图也在改
- 它们的
priority分别是多少 - 是否存在相同 priority、只靠 id 决定先后的情况
- 当前用户组会不会让某些视图参与组合、某些不参与
这个顺序通常比直接盯 XPath 高效得多。
一个实用判断标准
如果你只是想在已有主视图上补一刀,优先还是:
mode='extension'- 明确 priority
- patch 范围尽量小
如果你要做的是“把一套继承后的结果当成新的基底入口”,那才考虑 mode='primary'。
不要把 primary 当成“我想让自己更强势”的开关,它的语义比这重得多。
结论
视图继承最容易让人误判的地方是:
你看到的不是某条 patch 是否命中,而是整棵继承树按 priority、mode、用户组和入口路径组合后的最终结果。
理解这一点之后,很多“明明命中了却像没生效”的问题,就不再神秘了。
DISCUSSION
评论区