第六章 交叉引用
篇别:第一篇 基础知识
本章学习目标
- 准确理解 XML ID(外部标识符) 在数据库中的存储与命名规则。
- 能在 XML 与 Python 中正确使用
ref、env.ref,杜绝硬编码数字 ID。 - 能规划 跨模块依赖 与 数据文件加载顺序,避免升级期「找不到外部 ID」类错误。
- 能排查 noupdate、重复 ID、模块卸载 对交叉引用的影响。
导读:为什么需要外部 ID
PostgreSQL 中的主键(数字)在 不同数据库、备份恢复、演示库 之间 不一致。若代码写 browse(7),换库后可能指向完全错误的记录。XML ID 将「逻辑名」module.name 映射到「当前库中的 res_id」,是模块安装与升级时 稳定引用 的唯一推荐方式。
6.1 XML ID 与外部标识符
6.1.1 知识要点
- 存储模型:
ir.model.data(常称「外部标识符」记录)。 - 完整 ID =
模块名.数据文件中 id 属性,如sale.action_orders。 - XML 文件里
<record id="action_orders" ...>在模块sale中即sale.action_orders。 noupdate="1"的数据:升级时可能 不再覆盖 该记录字段,但 XML ID 行仍存在,ref仍可用。
6.1.2 ir.model.data 关键字段(理解用)
| 字段 | 含义 |
|---|---|
module |
定义该 id 的模块名 |
name |
记录在本模块内的短名(与 XML id 对应) |
model |
目标模型,如 ir.actions.act_window |
res_id |
目标表中的整数主键 |
6.1.3 案例
案例 A:XML 中 ref 赋 Many2one
<record id="library_book_access_user" model="ir.model.access">
<field name="name">library.book user</field>
<field name="model_id" ref="model_library_book"/>
<field name="group_id" ref="base.group_user"/>
...
</record>
model_library_book 通常由框架为 library.book 模型自动生成对应 ir.model 数据行的 XML id(具体以模块 base/ir 规则为准);自定义模型安装后会存在可引用 id。
案例 B:Python 中安全解析
def _get_book_action(self):
action = self.env.ref('my_library.action_book', raise_if_not_found=False)
if not action:
return {}
return action.read()[0]
raise_if_not_found=False 用于可选扩展:某模块未安装时不应崩溃。
案例 C:eval 中组合 ref
<field name="binding_model_id" ref="model_library_book"/>
<field name="groups_id" eval="[(4, ref('base.group_user'))]"/>
6.1.4 截图占位


6.1.5 本节练习
- 判断:同一数据库中,不同模块可以使用相同的「短 id」
action_book,只要完整 XML ID 不同。( ) - 实操:在技术菜单中找到任意一条
ir.ui.menu,记下其 完整外部 ID。 - 简答:为何禁止在生产自定义代码中写
self.env['ir.model.data'].browse(123)固定引用?
参考答案提示:1. 对(短名在 module 内唯一,完整 id 为 module.name)。3. 数字 id 不稳定且难读。
6.2 跨模块数据引用
6.2.1 知识要点
depends:__manifest__.py中必须列出 被引用 XML ID 所属模块,保证 加载顺序。- 数据文件顺序:同一模块内
data列表 自上而下 加载;先定义的id可被后续文件ref。 - 不要循环依赖:模块 A depends B 且 B depends A 会导致安装失败。
- 演示数据:放在
demo键;生产库 勿依赖 demo 中的 id 作为业务前提。
6.2.2 案例
案例 A:依赖销售并在其菜单下挂接
# __manifest__.py
'depends': ['sale', 'base'],
<menuitem id="menu_library_from_sale" name="图书"
parent="sale.sale_menu_root"
action="my_library.action_book"
sequence="99"/>
若漏写 'sale',升级时常见报错:External ID not found: sale.sale_menu_root。
案例 B:引用他模块 ir.sequence
模块 my_library 依赖 stock(举例),在 XML 中:
<record id="seq_library_card" model="ir.sequence">
<field name="name">借书证号</field>
<field name="prefix">LC</field>
<field name="padding">6</field>
</record>
另一模块 my_library_card 仅需引用时:
'depends': ['my_library'],
<field name="sequence_id" ref="my_library.seq_library_card"/>
案例 C:Python 动态拼模块前缀(慎用)
仅在多品牌/多套件下偶尔使用;默认应 字面量 env.ref('my_module.id') 以便静态检查。
6.2.3 截图占位

6.2.4 本节练习
- 简答:
depends与import在 Python 文件里from odoo.addons.sale import ...有何不同职责? - 实操:故意移除
depends中的被引用模块,记录完整报错信息到学习笔记。 - 情景题:模块 A 的数据文件
ref了模块 B 的 id,但 A 的depends只写了base,在什么操作时失败(安装 A?安装 B?升级 A?)?
参考答案提示:3. 安装/升级加载 A 的数据时,若 B 未先装则 ref 失败。
6.3 常见错误与调试
6.3.1 知识要点
| 错误信息关键词 | 常见原因 |
|---|---|
| External ID not found | 模块未装、depends 缺失、拼写错误、id 在 demo 中未加载 |
| ParseError / ref | XML 语法或 ref 目标不存在 |
| 重复 key | 两模块定义了相同 module.name(冲突) |
6.3.2 案例:用 Shell 验证
./odoo-bin shell -d mydb
env.ref('my_library.action_book')
env['ir.model.data'].search([('module', '=', 'my_library'), ('name', '=', 'action_book')])
6.3.3 截图占位

6.3.4 本节练习
- 实操:用
ir.model.data搜索name=action_book的所有模块,理解「同名短 id 多模块」现象。 - 简答:卸载模块是否会删除其创建的
ir.model.data?对残留数据有何影响?
参考答案提示:2. 通常随模块卸载清理相关数据(视模型 _depends 与卸载策略);残留会导致重装冲突,需技术清理。
本章综合练习
- 列举 三种 必须使用
env.ref而不能使用固定整数 id 的场景。 - 故障排查:升级后某菜单消失——逐步列出检查项(外部 id 是否存在、菜单
active、用户组、parent是否被删、视图继承是否报错阻断升级)。 - 设计:两个独立模块都要向 同一父菜单 增加子菜单,如何组织
depends与sequence避免顺序随机? - 拓展:阅读官方「Module manifest」文档中
data/demo的说明,用一句话总结生产环境建议。
本章对应白皮书目录:第六章 交叉引用。