先说结论
在 Odoo 里,用户点一个菜单,并不是“页面直接打开某个模型”。
更准确地说,中间通常隔着一层动作对象,最常见的就是:
- 菜单
menuitem - Window Action
ir.actions.act_window - 视图模式
view_mode - domain / context
- 最终打开具体列表、表单、看板或搜索结果
所以菜单更像入口,真正决定“打开什么、怎么打开”的核心,往往是 action。
为什么很多菜单问题其实不是菜单问题
因为用户表面看到的是“我点了左边菜单”。
但真正影响结果的常常是:
- action 绑的是哪个模型
- 默认打开列表还是表单
- domain 有没有过滤
- context 有没有默认值
- 指定视图有没有优先级冲突
所以很多“菜单打开不对”的问题,本质上应该去看 ir.actions.act_window,而不只是盯着 XML 里的 menuitem。
ir.actions.act_window 本质上是什么
它最适合理解成:
告诉客户端:请用什么方式打开这类记录。
它常常决定这些事情:
- 打开哪个模型
- 可用哪些视图模式
- 默认先落在哪个视图
- 是否带默认 domain
- 是否带 context
- 是否限制某个 search view
所以它并不是“一个小跳转配置”,而是用户进入一个业务对象页面时的主要入口协议。
菜单为什么通常只是 action 的外壳
菜单本身更多是导航结构。
它负责:
- 把入口放到左侧导航里
- 组织层级
- 控制可见性和归属位置
但菜单通常不应该承担太多打开逻辑。
真正的“业务入口定义”更应该放在 action 里。
这也是为什么很多时候一个 action 可以被:
- 菜单复用
- 按钮复用
- Python 返回值复用
而菜单只是其中一个触发点。
view_mode 为什么这么关键
很多人只记得写个 tree,form,但没真正意识到它在表达什么。
它本质上是在告诉客户端:
- 这个入口允许用户用哪些视图看数据
- 默认先从哪类视图开始
所以像:
tree,formkanban,formcalendar,tree,form
不只是语法差异,而是在设计用户进来之后的第一体验。
如果入口目标是“先浏览一批记录”,通常列表/看板优先; 如果入口目标是“直接处理单条对象”,有时表单就更合适。
domain 和 context 为什么经常一起出现
因为它们解决的是两类不同问题:
- domain:这次主要看哪些记录
- context:这次打开页面时带什么默认意图
你可以把它们理解成:
- domain = 看什么
- context = 怎么带着前提去看
比如同样打开销售订单:
- 一个 action 只看报价单
- 一个 action 只看销售订单
- 一个 action 默认新建时带某种单据类型
看起来都像“打开 sale.order”,但入口体验其实完全不同。
为什么一个模型会有很多不同的 action
因为同一个模型可以承载很多业务入口。
例如同一个对象可能同时需要:
- 全量管理入口
- 我的待处理入口
- 某状态筛选入口
- 从某模块跳转过来的专用入口
如果你把所有入口都挤进一个 action,再靠用户自己切筛选,体验通常不会太好。
所以多个 action 并不是重复劳动,而往往是在表达不同业务入口语义。
实战里最容易踩的坑
1. 菜单绑对了,但 action 模型错了
结果页面能开,但内容完全不对。
2. view_mode 配了,但首屏体验不合适
用户一进来就觉得别扭。
3. domain 过重,导致入口看起来“丢数据”
其实是被过滤了。
4. context 里塞太多默认值
后面 create / 搜索 / 切换入口时容易混乱。
5. 同一个入口目标,却复制很多相似 action
维护会越来越散。
一套很实用的设计顺序
设计菜单入口时,我更建议按这个顺序思考:
1. 用户点这个入口,第一眼应该看到什么
先定列表、看板、表单还是日历。
2. 这批用户应该默认看哪类记录
再决定 domain。
3. 新建时是否需要携带明确上下文
再决定 context。
4. 这是全局入口还是某业务场景专用入口
再决定要不要拆成多个 action。
一句话记忆法
把这条链记成一句话:
菜单负责把入口摆出来,
ir.actions.act_window负责定义“打开哪个模型、用什么视图、带什么过滤和上下文”。
理解这一句,Odoo 菜单与页面打开链路会清楚很多。
DISCUSSION
评论区