
做 Odoo 开发和运维这些年我最怕的不是需求改来改去而是版本号对不上这种听起来不起眼、咬起人来要命的问题。前阵子处理了一个编号 odoo-089 的工单客户从旧版本迁到 Odoo 18 之后一个自制模块无论如何都装不上后端日志里翻来覆去只有一句话invalid version string in manifest。绕了一圈才发现罪魁祸首就是 manifest 里的 version 字段还停留在 17.0.1.0.0而 Odoo 18 的版本检查已经严到系列号不对就直接拦下。这篇就把我排查过程中掌握的 Odoo 18 严格 version 检查机制完整讲一遍它到底查哪几处、每处怎么判、出问题怎么定位以及平时开发怎么少踩这些坑。做升级、写自定义模块、维护多模块生产环境的朋友这篇应该能帮你省不少时间。1. 为什么 Odoo 18 要收紧版本检查1.1 版本错配不是小概率事件在 Odoo 社区里翻 issue十个报障至少有三四个跟版本有关。最常见的形态有三种第一代码分支拉下来没装对数据库里的模块版本还停留在旧值导致字段、菜单、权限规则对不上第二Python 环境被顺手升级系统库里某些 C 扩展没重新编译Odoo 启动时莫名加载失败第三第三方模块的版本号写得很随意什么 1.0、2.1.3beta、v3.0 都有一旦跨大版本升级依赖关系直接乱套。过去 Odoo 对这些问题多半只是警告日志里刷一行继续跑可 ERP 这种系统最忌讳带病运行——等到业务单据录了一半才发现字段名对不上回滚的成本就高得吓人了。所以从 Odoo 17 开始官方就在收口Odoo 18 更是把严格这两个字贯彻到了整个生命周期。1.2 前置失败比运行期崩溃划算我个人的理解是Odoo 18 的版本检查本质是错误前置工程。你想想一次启动失败的代价充其量是几分钟的停机加上一段日志但一次版本不一致导致的隐性错误可能要在某个深夜、某个对账环节才突然爆出来届时连错误原因都难定位。Odoo 18 现在采取的策略就是启动阶段把所有版本相关的硬条件全部验一遍任何一个不满足要么拒绝启动要么拒绝安装对应模块并且给出明确的错误信息。这种 fail fast 的设计在需要保证数据一致性的业务系统里是绝对正确的方向。理解了这层思路后面看它的每一条检查逻辑就都不会觉得多余。2. 五个关卡Odoo 18 到底在检查哪些版本2.1 第一关Python 解释器版本Odoo 18 对 Python 版本的要求比前代更明确。官方支持 3.10 及以上建议使用 3.11 或 3.123.9 及以下在启动阶段就会被直接拦下。这个检查做在 odoo-bin 脚本很靠前的位置逻辑很简单但位置选得很讲究——它在加载任何业务代码之前就判断让你连数据库都不用连就能发现问题。我拿一台只装了 Python 3.9 的老服务器做实验执行python3.9 odoo-bin --addons-pathcustom,addons -d testdb --stop-after-init输出大概是Odoo 18 requires Python 3.10 or later. Current Python version: 3.9.21看到这种错误别急着改代码先检查服务器上有没有别的 Python 版本或者用 pyenv、conda 固定解释器版本。这里有个实操心得就算你系统里是 3.12某些第三方库比如 ldap、reportlab 相关扩展没适配也会在 import 阶段报错。所以我的习惯是先在干净环境里跑一遍odoo-bin --version再跑--stop-after-init做冒烟别直接上生产。顺带说一句服务端版本号本身定义在odoo/release.py里是一个类似version_info (18, 0, 0, final, 0, )的元组odoo-bin --version显示的就是从这里拼出来的。你自己的代码里如果需要判断版本应该用from odoo.release import version_info而不是去解析字符串字符串解析在社区版本和内部版本之间很容易翻车。2.2 第二关PostgreSQL 数据库版本数据库这层很多人容易忽略。Odoo 18 官方安装文档要求 PostgreSQL 12 及以上生产环境我建议直接用 14 以上PG 16 在 18.0 上实测没有兼容问题。这个检查不是装的时候做一次就完了每次连接数据库时Odoo 的 SQL 连接层都会读取连接的server_version并在初始化连接池时做校验。如果版本过低日志里会出现类似psycopg2.errors.FeatureNotSupported: ... version 10.15, server ...或者直接报数据库版本不被支持的明确提示。处理方式也很直接升级数据库实例导出导入前先做全量备份。注意 PG 大版本升级没有捷径别想着原地pg_upgrade一把梭至少在测试环境先演练一遍。这里再提一个坑如果你用 Docker 容器跑 PG镜像里的 PG 主版本可能跟你宿主机上习惯的不一样容器化部署时务必在 compose 文件里显式指定镜像 tag别用 latest 随缘升级否则数据库大版本一变Odoo 的版本检查会第一时间把你拦在门外。检查命令记一下排查时直接用psql -U odoo -d mydb -t -c show server_version;2.3 第三关模块 manifest 的 version 字段解析这是 odoo-089 工单真正卡住的地方也是 Odoo 18 收紧力度最明显的一环。每个模块的__manifest__.py里都有version字段官方约定的格式是五段数字{系列}.{主版本}.{次版本}.{修订}.{构建}比如18.0.1.0.0其中18.0是 Odoo 系列后面1.0.0才是模块自己的版本。过去这个字段写得不规范还能凑合Odoo 18 会调用parse_version做严格解析任何一段不是纯整数、或者多出额外段、或者带上了-beta、build这类字符都会导致解析失败。更严格的地方在于系列号模块版本必须以当前 Odoo 系列开头也就是必须以18.0开头否则模块会被判定为不兼容安装和升级都会失败。日志里会出现类似Invalid version string 17.0.1.0.0 in manifest of module my_module Module my_module has an invalid version for Odoo 18: 17.0.1.0.0这类错误没有捷径可走就是把 version 改成合规格式、把模块版本号递增再继续。如果你维护的模块还要在 Odoo Apps 商店上架商店同样会校验这个格式提前改好能省掉审核来回。我在开发环境里习惯用一行命令验证 manifest 版本能不能被正确解析python3 -c from odoo.tools import parse_version; print(parse_version(18.0.1.0.0))输出(18, 0, 1, 0, 0)就说明格式没问题输出(0, 0, 0, 0, 0)就是解析失败。2.4 第四关数据库里的 ir_module_module 版本记录文件系统里的 manifest 只是声明Odoo 真正判断要不要升级某个模块靠的是数据库里ir_module_module表的记录。这张表存了模块的latest_version、state、dependencies等关键信息。每次启动时 Odoo 会扫描新增模块目录和数据库里已有的模块列表做 diff然后决定哪些模块要安装、哪些要升级、哪些要卸载。后端应用菜单里看到的模块列表、可升级过滤器读的也是这张表。版本比较用的就是parse_version转出来的元组逐段比较数值大者为新。这里有个 Odoo 18 明确收紧的行为数据库里的版本高于文件系统版本时模块会被标记为降级Odoo 会给出明确警告并且在常规启动流程里不允许降级操作执行。以前有人为了重跑一遍安装逻辑故意把 manifest 版本号改小这在 Odoo 18 里走不通了。正确做法是保持版本号递增真要重新执行安装逻辑就直接用-u 模块名强制升级。排查时直接查数据库最直观SELECT name, latest_version, state FROM ir_module_module WHERE name IN (base, sale, stock);2.5 第五关Web 客户端与服务端的版本对齐最后一道检查在浏览器端。Odoo 18 的 Web 客户端启动时会请求/web/webclient/version_info拿服务端返回的版本信息包括server_version、server_serie、server_version_info等。前端资源打包时bundle 文件里会带上版本相关的哈希服务端升级后这个哈希变化客户端在下次加载时就会强制刷新资源避免用户浏览器里跑的旧 JS 调用已经被删掉的 API。所以升级完 Odoo 之后如果遇到前端白屏、报某个方法 undefined、或者某些 RPC 找不到第一反应应该是强制刷新页面CtrlF5而不是去改代码。这个看起来不起眼的机制其实是 Odoo 18 版本检查体系的最后一道防线专门防新旧代码混着跑。到这里我把整个检查链路总结成一张表排查时可以对着看关卡检查对象判定标准失败后的表现第一关Python 解释器3.10 及以上推荐 3.11/3.12启动即退出提示当前版本第二关PostgreSQL12 及以上连接池初始化时报错第三关manifest version五段整数系列为 18.0报 invalid version模块不加载第四关ir_module_module新版本必须高于已装版本拒绝降级跳过自动升级第五关Web 客户端 bundle哈希与服务端一致强制刷新或白屏3. 实操完整复现一次严格版本检查3.1 环境准备和版本速查命令要排查版本问题先把下面这些命令记熟。它们能帮你在一分钟内把代码版本、数据库版本、运行时版本全部摸清楚# Odoo 服务端版本 odoo-bin --version # PostgreSQL 版本 psql -U odoo -d mydb -t -c show server_version; # Python 版本 python3 --version # 客户端视角看到的服务端版本 curl -s http://localhost:8069/web/webclient/version_info | python3 -m json.toolversion_info返回的数据里server_version_info是一个数组里面包含主版本、次版本、发布阶段等信息这个值会同时被 Web 客户端拿去判断是否需要强制刷新资源。生产环境上建议把这几个命令写成一个巡检脚本每周跑一次成本几乎为零但能在问题刚冒头的时候就发现。3.2 模拟一次 Python 版本被拦截拿一个装了 Python 3.9 的旧服务器做实验直接执行python3.9 odoo-bin -d testdb --stop-after-init启动脚本会在加载任何业务代码之前先做版本断言输出错误并退出。这个检查跑到这么靠前就是为了让你连数据库都不用连就能发现问题。实际部署时如果你的服务器装了多个 Python 版本记得在 systemd service 文件里写死解释器路径别用python3这种软链否则哪天系统升级把软链换了Odoo 可能整个起不来。3.3 模拟模块版本系列不匹配把某个自定义模块的 manifest 改成{ name: My Custom Module, version: 17.0.2.1.0, depends: [base], }然后执行odoo-bin -d testdb --stop-after-init --upgrade my_custom_module在 Odoo 18 下你会看到模块被直接标记为不兼容日志里明确指出 version 系列与当前 Odoo 版本不匹配模块不会进入安装流程。这也解释了 odoo-089 工单里为什么客户死活装不上他们那个第三方模块的仓库还停留在 17 分支代码里可能根本没有为 18 做过适配版本检查恰恰把这种没适配硬装的情况挡在门外。强制把版本号改成18.0.x.x.x也许能绕过检查但代码本身如果用了旧 API后续一样会报错。所以正确的顺序是先确认代码适配再改版本号。4. 常见版本问题排查与避坑4.1 Invalid version string 类错误这类报错在 Odoo 18 里很常见错误信息通常长这样ValueError: Invalid version string 18.0.1.0-beta in manifest of module my_module我用一个规则来快速判断版本必须是五段每段都是整数段落之间只有点不能有字母、连字符、下划线第一段固定是18。为了批量检查整个 addons 目录可以写一段小脚本import ast from pathlib import Path for manifest in Path(addons).glob(*/__manifest__.py): data ast.literal_eval(manifest.read_text(encodingutf-8)) version str(data.get(version, )) ok version.startswith(18.0) and len(version.split(.)) 5 print(f{manifest.parent.name:30} {version:20} {OK if ok else ERROR})把这段加到 CI 里能防止开发把不合规版本号推到主干。我用这个脚本在团队里救过好几次很多同事写完模块根本不看 manifest一 push 就是version: 1.0。4.2 重启后模块没有变化还有一种情况是不报错但行为不对你改了模块代码重启 Odoo发现数据表结构没更新。查一下数据库SELECT name, latest_version, state FROM ir_module_module WHERE name IN (base, sale, stock);对比文件系统里的 manifest 版本如果两边完全一致Odoo 启动时不会自动升级模块。这其实是正常设计升级是重操作不该每次启动都来一遍。问题是很多新手只改了代码忘了升版本号结果手动-u也没用。记住改完模块代码之后如果希望下次启动自动升级就必须把 manifest 里的 version 递增如果想立即生效就直接-u 模块名。另外在 Odoo 后端的应用菜单里把过滤器切到可升级能直接看到哪些模块因为版本变化被标记为待升级这个 UI 信息在排查时比日志更快。4.3 第三方模块系列号与依赖不匹配Odoo 18 对模块depends没有显式的版本约束依赖关系只认模块名不认版本。但这不代表版本检查拿第三方模块没办法——它通过系列号卡住了入口。假设你有一个 17.0 的社区模块依赖里写了[sale, stock]在 Odoo 18 里即便sale和stock都装了模块自己系列号不对依然装不上。这其实是保护机制17.0 时代的第三方模块大量依赖旧版 ORM API硬装到 18 上几乎必然出问题。遇到这种情况我的建议是先去模块作者的仓库看有没有 18.0 分支没有的话再看代码里用了哪些 API评估迁移成本而不是简单改个版本号强行骗过检查。骗过检查一时爽生产环境火葬场。4.4 升级后的前端缓存问题速查升级完经常遇到后端没事、前端花屏的情况。排查顺序我总结成一个口诀先强制刷新再查 bundle 版本最后看浏览器控制台。强制刷新用 CtrlF5 或 CtrlShiftR如果还不行就对比version_info接口返回的版本和页面加载的 JS bundle 文件名里带的哈希再不行就清掉站点缓存或者用无痕窗口打开验证是不是缓存问题。Odoo 18 的 asset bundle 自带版本哈希正常情况下升级后第一次访问就应该自动加载新资源如果你观察到多次访问都没有更新多半是 Nginx 或 CDN 层把静态文件缓存设得太死。这个坑我踩过不止一次每次都是先怀疑代码最后发现是代理层配置的问题。5. 把版本管理做成日常习惯5.1 模块版本号的规范写法结合 Odoo 官方约定和社区实践我自己的模块版本号规则是这样的变更类型版本段递增例子兼容性破坏/重构主版本 118.0.2.0.0新增功能次版本 118.0.1.3.0缺陷修复修订 118.0.1.0.4每次改代码哪怕只是改了一行报表里的 SQL也应该考虑是否递增版本号。因为 Odoo 的升级判断完全依赖这个数字版本号不变你的改动就不会在下次启动时自动落到数据库里。别嫌麻烦这个习惯养成了能省掉大量改了没生效的排查时间。5.2 跨版本升级的检查清单从 Odoo 17 升到 18 之前我会先按下面的清单过一遍确认服务器 Python 版本满足 18 的要求低于 3.10 先升级解释器。确认 PostgreSQL 大版本不低于 12低版本数据库先迁移。用脚本扫描所有 addons 目录统计 version 字段是否为18.0.x.x.x把不合规的模块列出来。单独起一个测试库只装核心模块跑一次--stop-after-init确认基础环境没问题。再逐个引入第三方模块每引入一个就跑一遍升级并检查日志。升级完成后用version_info接口和数据库版本查询做最终确认。这套流程按部就班能挡住九成以上的版本问题。5.3 给 CI/CD 加一道版本校验最后分享一个我觉得性价比很高的做法在 CI 里加一个版本校验脚本把 4.1 里的扫描代码放进去任何模块的 version 不合规、或者系列号不对合并请求直接失败。另外加一个冒烟测试步骤在临时数据库上跑odoo-bin --stop-after-init失败就阻断发布。有了这两道保险版本问题基本不会流到生产环境。我自己团队用这个方案跑了十几个迭代后来因为版本号问题导致的线上事故真的就是零。回看 odoo-089 那个工单其实客户只要在升级前跑一遍版本扫描五分钟就能发现问题。Odoo 18 把版本检查做严本质上是在帮你把风险挡在前面——关键是你要懂得顺着它给的线索快速定位、规范修版本号这套玩法摸熟了它反而是最省心的那道防线。