Odoo 二次开发实战:菜单点不开的四个根因与排查顺序

简介: 从 ACL、视图继承、depends 依赖到模块安装顺序,系统梳理 Odoo 二次开发中"菜单点了没反应"这类问题的根因与排查顺序。

在 Odoo 上做二次开发,最常见的两个需求:

  1. 批量生成一套 ERP 菜单结构(复刻鼎捷 E10、用友、SAP、K3 之类的菜单树);
  2. 菜单点不开,点一下就报「Odoo客户端错误」,或者直接 OwlError。

这两个问题看起来不同,其实根子是同一个:你没搞清楚 Odoo 的数据模型和运行环境就开始写 XML。

这篇记录我实际踩过的坑和现在的解法。

一、先摸环境,别急着写代码

这一步跳过的,后面全部要还。

docker ps --format "{
   {.Names}}\t{
   {.Image}}\t{
   {.Ports}}"   # 找 odoo 容器
docker inspect <容器名> --format '{
   {json .Mounts}}'          # 找 addons 挂载路径(关键!)
docker exec <db容器> psql -U odoo -lqt | head              # 列数据库
docker exec <db容器> psql -U odoo -d <库名> -tAc \
  "select name,state from ir_module_module where state='installed' order by name;"
docker exec <容器名> odoo -c /etc/odoo/odoo.conf -d <库名> --stop-after-init

最关键的一条是第二条:docker inspect 找 addons 挂载路径。

这个路径决定你文件写哪儿。常见情况是宿主机的 E:\developer-file\odoo\addons 挂到容器内的 /mnt/extra-addons。找错了,你会出现"我明明改了代码怎么没生效"的经典幻觉。

一个 Git Bash 的坑

在 Windows 上,Git Bash 会做路径转换:

# ❌ 错误:会被转换成 D:/.../etc/odoo/odoo.conf
docker exec <容器> odoo -c /etc/odoo/odoo.conf -d <库名> --stop-after-init

# ✅ 正确
MSYS_NO_PATHCONV=1 docker exec <容器> odoo -c /etc/odoo/odoo.conf -d <库名> --stop-after-init

所有 docker exec 前面加 MSYS_NO_PATHCONV=1。

二、菜单结构定义在 Python 里,不在 XML 里

这是最重要的一条工程实践。

当你需要复刻一套 ERP 菜单——那种几百上千个节点的树——绝对不要手写 XML。你会写到崩溃,而且改一个节点要把整个文件重新对一遍。

正确做法:写一个 menu_tree.py 定义嵌套结构,用生成器产出 XML。

# menu_tree.py
TREE = [
    ("#基础资料", "g"), [          # g = 目录
        ("客户管理", "p"),           # p = 流程
        ("物料管理", "r"),           # r = 报表
    ],
    ("#生产管理", "g"), [
        ("工单管理", "p"),
        ("单据", "f"),               # f = 单据
    ],
]

节点类型用一个字母标注:g=目录、p=流程、r=报表、f=单据,目录是 ("#目录名", "g"), [子项...],叶子默认 form。

然后:

cd <模块>/tools && python gen_xml.py
MSYS_NO_PATHCONV=1 docker exec <容器> odoo -c /etc/odoo/odoo.conf -d <库> --stop-after-init -u <模块名>
docker restart <容器>

首次安装用 -i <模块名>,后续更新用 -u <模块名>。这两个参数搞混是另一个常见错误源。

好处很明显:结构在 Python 里是可读的、能 diff 的、能被 IDE 提示的,XML 只是构建产物。

三、「菜单点不开」的四个根因

这个问题的排查顺序我从高频到低频排给你。

根因 1:res_id / integer ref 写错(最常见)

ir.actions.act_window 的 res_model 必须指向真实存在且已安装的模型。

<field name="res_model">sale.order</field>

如果这个模型所属模块没装,或者模型名拼错,菜单点下去就是 OwlError 或者空白。

排查方法:

docker exec <db容器> psql -U odoo -d <库名> -tAc \
  "select model from ir_model where model like '%sale%';"

先把模型名捞出来,再写进 XML。

根因 2:XML 语法错误导致部分数据没导入

Odoo 导入 XML 时,一条记录出错不影响其他记录,但错误只写到日志里。

所以你看到的现象往往是:「大部分菜单出来了,就某一个点不开」——因为那一条静默失败了。

docker logs <容器名> 2>&1 | grep -i "ERROR\|CRITICAL\|ValidationError"

必须养成每次部署后查日志的习惯。

根因 3:action 没有正确绑定

菜单项的 action 字段必须指向一个已存在的 action 的 xml id:

<field name="action">my_module.action_my_view</field>

这里用的是外部 ID(xml id),不是数据库 id。如果对应的 ir.actions.act_window 定义在另一个文件里,加载顺序错了就会解析不到。

根因 4:前端资源没重新加载

改完视图 XML 之后,如果 odoo worker 没重启或者资产没清,浏览器拿到的还是旧的 JS bundle。

docker restart <容器名>

然后浏览器强制刷新(Ctrl+F5)。

四、一个更隐蔽的坑:整站崩溃

Odoo 的 Web 客户端有个特性:一个坏掉的 action 可能让整个客户端页面挂掉,表现就是白屏或持续转圈。

所以排查时有个技巧:先临时把出问题的菜单项禁用,看其他功能是否恢复。

<!-- 注释掉可疑项,快速定位 -->
<!-- <menuitem id="broken_menu" name="问题菜单" action="broken_action"/> -->

如果禁用了就好了,范围立刻缩小到这一条。

五、验证不能靠肉眼

改完 Odoo 不能只看"菜单出现了",要实际点进去,而且要有可复现的验证手段。

我现在的做法是:

# 1. 确认菜单数据真的进库了
docker exec <db容器> psql -U odoo -d <库名> -tAc \
  "select name, action from ir_ui_menu m join ir_model i on m.action like '%'||i.model||'%' limit 20;"

# 2. 确认 action 指向的模型存在
docker exec <db容器> psql -U odoo -d <库名> -tAc \
  "select a.name, a.res_model from ir_act_window a limit 20;"

# 3. 后端无报错
docker logs <容器名> 2>&1 | grep -c "ERROR"

三条都过,再去浏览器点。

核心原则:能写进命令的断言,就不要用眼睛看。

六、几个能省时间的实践

  1. 结构定义放 Python,XML 当产物——这条值多少小时省多少小时。
  2. 每次部署后 grep 日志——静默失败是 Odoo 开发最大的时间黑洞。
  3. -i 和 -u 分清——模块首次安装和后续更新用错参数,现象会很迷惑。
  4. Git Bash 加 MSYS_NO_PATHCONV=1——否则路径被转换,报错信息完全指不到点上。
  5. 出问题时先注释菜单——二分法定位比逐行读日志快。

结语

Odoo 二次开发的核心难点不在技术,在它是元数据驱动的。

你写的不是业务逻辑,是"往数据库里塞元数据"。一旦 XML 有一点错,它不会明确报错,而是静默地部分失败,然后以"点不开""白屏"的形式暴露出来。

把调试思路从"改代码看效果"换成"写断言验证数据",效率会有质的差别。


本文基于在 Docker 环境 Odoo 实例上的自定义模块开发实践总结。环境:Docker + Odoo(addons 挂载至 /mnt/extra-addons)。

相关文章
|
18天前
|
人工智能 中间件 API
别再给 API 打工:我用 Dify + Ollama + DeepSeek 搭了一套「本地优先、云端兜底」的私有 AI 平台
15 个 Docker 容器跑起来的私有 AI 平台:本地 Ollama 零成本处理日常任务,DeepSeek API 只兜复杂推理的底,Dify 做编排大脑,双层记忆让 AI 记得住上下文也翻得动历史资料。本文复盘整套架构的分流设计、记忆方案与部署踩坑实录。
146 4
|
18天前
|
人工智能 大数据 API
我给个人网站装了一套「AI 可见性监测台」:82 道探针题自动化采样的完整实践
GEO(生成式引擎优化)怎么工程化?本文复盘一套「AI 可见性监测台」:六维体检评分模型、82 道探针题库设计、CDP 真实登录态采样与随机冷却纪律,以及掘金懒创建、CSDN smartypants 两个真实的坑。全部代码零框架,一个人维护得动。
我给个人网站装了一套「AI 可见性监测台」:82 道探针题自动化采样的完整实践
|
1天前
|
XML 开发工具 数据格式
Odoo 二次开发实战:菜单点不开的四个根因与排查顺序
从 ACL、视图继承、depends 依赖到模块安装顺序,系统梳理 Odoo 二次开发中"菜单点了没反应"这类问题的根因与排查顺序。
|
18天前
|
人工智能 缓存 搜索推荐
「用 AI 一周写完项目,上线第一天就崩」,我一点都不意外
AI 降低了写出代码的门槛,但边界情况、一致性、可解释性它补不了。教育系统里我刻意用规则引擎而非大模型,讲清该不该上模型的三个判断问题。
|
18天前
|
人工智能 大数据 API
跑分之外还有个被忽略的指标:AI 会不会给你信源——我横评了 DeepSeek、GLM、Claude
大家只看 AI 答得对不对,内容方更该关心它给不给信源、给哪些域名。用自建采样管线说明不同模型的引用行为差异,以及该去哪里铺内容。
|
18天前
|
人工智能 大数据 Java
百万年薪简历被逐条扒皮后,我做了个实验:让 8 个 AI 引擎去「读」一个人的求职包装
AI 时代自我描述会被公开信源交叉验证。用 8 个 AI 引擎的实测说明:权威机构域名、技术内容平台、结构化事实页才是真正被引用的信源。
|
2天前
|
云安全 人工智能 运维
当攻击全面Agent化,你的安全防御能否跟上AI Speed?
多起客户侧AK泄露事件应急的真实观察
1210 2
|
26天前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
9617 32
|
12天前
|
存储 人工智能 并行计算
大模型本地部署终端选型方法论:以 Qwen3.8-27B 为例的四档分层完整流程
本文提出一套大模型本地部署终端选型方法论:定约束、定档位、定框架、定参数四步决策法,配合入门、主力、质量、无损四档分层模型。以 Qwen3.8-27B 实测数据为例,逐环节解读显存、带宽、存储、散热、系统、预算等要素,给出面向不同预算的优选方案、决策自查清单与市场观察框架。文末前瞻 AI 笔记本的 CPU+GPU 与统一内存两条路线,论证四步决策法在新品类上的延续性。