先说结论
很多人看 Odoo 模块安装,脑子里的模型是:
- 读 manifest
- 建模型
- 导数据
- 完事
这太粗了。
从 /home/ubuntu/odoo-temp/odoo/modules/loading.py 的 load_module_graph() 来看,真正顺序要细得多,而且 hooks 也不是“随便某个时机插一脚”。
更准确的理解是:
- 判断当前是 install、upgrade、reinit 还是普通加载
- 必要时跑 pre-migration
- 导入 Python 模块
- 安装场景才跑
pre_init_hook - 加载 registry 里的模型
- 初始化 / 更新模型与数据库结构
- 加载 data / demo
- 跑 post-migration
- 安装场景才跑
post_init_hook - 卸载时另有
uninstall_hook
所以一句话概括:
hooks 不是模块生命周期里的装饰品,而是被严格嵌在安装 / 升级 / 卸载管线中的固定节点。
load_module_graph() 到底在做什么
这个函数名字就很关键。
它不是在“加载一个模块”,而是在按依赖图加载一批模块。
这意味着 Odoo 在安装 / 升级时,关注的不是孤立模块,而是:
- 当前模块依赖谁
- 谁依赖当前模块
- 哪些模型要重新 setup
- 哪些扩展关系
_inherit / _inherits要一起重建
所以模块加载的真正单位,不是单文件,也不是单 manifest,而是:
依赖图 + registry + schema + data 的联动更新。
这也是为什么很多“我只是改了个小字段,为什么一堆模型都被重新 setup”其实是正常现象。
安装场景里 pre_init_hook 在哪一步
源码里非常清楚:
- 先
load_openerp_module(package.name),也就是先把 Python 模块导进来 - 然后如果当前操作是 install,才去读 manifest 里的
pre_init_hook - 接着执行
registry._setup_models__(env.cr, []) - 再
getattr(py_module, pre_init)(env)
这说明两件事:
1. pre_init_hook 不是“导入 Python 之前”
很多人听名字会误解成“最早最早的前置钩子”。
其实不是。
它发生在:
- Python 包已经能 import
- 但模块正式 model init 和 data load 还没完成
2. 它主要服务“安装前的准备动作”
典型用途是:
- 提前校验数据库状态
- 做一些安装前兼容修正
- 为接下来的模型初始化铺路
它不是给你拿来随便塞业务逻辑的万能入口。
模型加载和 schema 初始化发生在什么时候
在 pre_init_hook 之后,源码会做:
registry.load(package)- 计算受影响模型及其
_inherit / _inherits后代 registry._setup_models__(env.cr, [])registry.init_models(...)
这几步合起来,才是很多开发心里所谓“模块模型建起来了”的真正来源。
所以如果你问:
“manifest 读完以后,模型是不是就已经完全可用了?”
答案通常是否定的。
因为真正的可用状态,取决于:
- 模型类是否导入
- registry 是否装载
- schema 是否更新
- 扩展模型是否一起 setup
这是一整段链,而不是一个瞬间。
data / demo 是在模型初始化之后才加载的
这一点对排错特别重要。
在 install / upgrade / reinit 这些 update_operation 场景里,源码先完成模型和 schema 初始化,然后才:
load_data(...)- 必要时
load_demo(...)
这说明:
XML / CSV 数据加载不是“顺手一起读”,而是建立在模型已经完成 registry / schema 准备之后。
所以如果你在 data 文件里引用了某个字段、视图、模型定义,前提是这些结构已经被正确 setup。
这也解释了为什么某些错误看起来像“数据文件坏了”,根源却可能是模型层没先准备好。
post_init_hook 为什么只在 install 时跑
源码里写得很明确:
if update_operation == 'install':- 才读取 manifest 里的
post_init_hook - 然后执行它
这意味着 post_init_hook 不是升级通用钩子。
升级时,Odoo 走的是另一套思路:
- pre-migration
- data update
- post-migration
- view validation 等升级收尾动作
所以很多升级逻辑如果你写进 post_init_hook,就会出现经典误判:
“为什么我升级模块时这个钩子没跑?”
因为它本来就不是给 upgrade 设计的。
升级场景真正该看哪条链
如果当前是 upgrade,load_module_graph() 里会:
- 必要时先跑
migrations.migrate_module(package, 'pre') - 更新模块信息
- 用
mode = 'update'加载 data / demo - 再跑
migrations.migrate_module(package, 'post') - 最后做视图校验等收尾
这告诉你一件很关键的事:
install hooks 和 upgrade migrations 是两套不同的生命周期节点。
别把它们混成一个“反正都是升级时会跑的自定义逻辑入口”。
不是。
- 安装后收尾,用
post_init_hook - 升级前后修正,用 migration scripts
分工非常明确。
uninstall_hook 又处在哪个层级
在 loading.py 里卸载分支会读取 manifest 的 uninstall_hook,然后 getattr(py_module, uninstall_hook)(env)。
这意味着:
- 它不是普通业务逻辑回调
- 它属于模块卸载流程的正式节点
- 它服务的是“拆模块时的善后清理”
典型用途包括:
- 还原系统配置
- 清理某些安装时建立的外部状态
- 解除某些卸载后会留下脏引用的配置
但也正因为它在卸载链路里,写得不好会非常危险。
因为卸载本身已经是高破坏性动作,你再在 hook 里乱删东西,就更容易把环境搞乱。
新手最容易犯的 4 个误解
1. 以为 pre_init_hook 是“最早的一切之前”
其实它发生在 Python 模块已导入之后。
2. 以为 post_init_hook 升级时也会跑
不会。它是 install 语义,不是 upgrade 通用语义。
3. 以为 data 文件是在模型还没准备好时就读进去
不是。模型与 schema 先准备,data 后加载。
4. 以为 hooks 可以替代 migrations
不能。尤其升级修正逻辑,优先看 migration,而不是把所有事都塞进 hook。
什么时候适合用 hooks,什么时候不适合
适合
- 安装后必须补一次初始化动作
- 卸载时必须还原某些系统级配置
- 安装前需要明确校验环境前提
不适合
- 日常业务逻辑
- 会频繁触发的定时性动作
- 升级迁移的主逻辑
- 可以通过普通模型初始化 / data 文件解决的事情
一句话:
hook 应该是生命周期补刀点,不该是常规业务主战场。
和现有文章怎么区分
这篇不是在讲 ir.cron、Server Action 那类“系统运行后的自动动作”。
本文关注的是更靠底层的:
- 模块安装 / 升级 / 卸载到底怎么走
- hook 在 loading 管线里的精确位置
- 为什么 install hook 不能当 upgrade hook 用
也就是从模块生命周期编排切入,而不是从运行时自动化切入。
最后一句话
很多 Odoo 开发一遇到模块安装问题,就去翻 manifest、翻 data、翻模型,但脑子里没有一条清晰顺序线。
而 load_module_graph() 告诉你的核心其实是:
模块不是“读进去就生效”,而是按依赖图、模型、schema、data、migration、hook 一层层被组装起来的。
顺序想清楚,很多安装升级问题就不再像黑箱了。
DISCUSSION
评论区