先说结论
在 Odoo 里,XMLID 不是一个“好记的别名”,而是一种跨安装、跨升级、跨模块都尽量稳定的外部引用机制。
而 env.ref('module.record_id') 本质上就是:
通过 XMLID 去
ir.model.data里查真实模型和真实记录 id,再返回那条记录。
所以你平时拿到的不是魔法,而是一套“稳定找对象”的基础设施。
为什么 XMLID 比数据库 id 更值得依赖
数据库 id 最大的问题,是它只在当前库里有意义。
比如:
- 你在 A 库里,
res.partner(7)可能是管理员公司 - 到了 B 库里,id=7 可能已经是别的记录
- 重装演示数据、迁移模块、导入初始化数据后,数字 id 也可能完全不同
这就是为什么 Odoo 大量系统内置记录都不用硬编码数字 id,而用 XMLID。
因为 XMLID 的意义是:
- 这个记录属于哪个模块
- 这个记录在模块里的逻辑名字是什么
- 后续系统如何再稳定地找到它
所以它更像“系统级坐标”,而不只是一个数字主键。
env.ref() 到底做了什么
从官方实现看,Environment.ref() 会调用:
ir.model.data._xmlid_to_res_model_res_id()- 底层再走
_xmlid_lookup() - 最后得到
(res_model, res_id)
然后 Odoo 再 browse(res_id),返回真正的 recordset。
也就是说,env.ref() 不是直接查业务表,而是先查 ir.model.data 这张“外部标识映射表”。
你可以把它想成:
先查通讯录,再去找本人。
ir.model.data 在这套机制里扮演什么角色
它本质上是在保存一条映射关系:
- module
- name
- model
- res_id
这样做的好处是,模块数据、视图、菜单、动作、分组、邮件模板这些“系统骨架记录”,都可以被稳定引用。
所以 XMLID 不是只服务开发者,它服务的是整个模块化系统。
为什么很多场景必须优先用 XMLID
典型场景包括:
- 取菜单、动作、视图
- 取安全组
- 在安装数据里引用别的记录
- 在 Python 代码里拿基础配置
- 在测试里稳定获取样例数据
这类对象如果你用数字 id,后面会非常脆弱。
但用 XMLID,就算数据库 id 变了,只要映射还在,你的代码和数据关系就还能成立。
新手最容易混淆的 4 件事
1. XMLID 不是字段值
它通常不在业务表里当普通字段给你看,而是存放在 ir.model.data 的映射里。
2. env.ref() 找不到时,不一定是“代码错了”
也可能是:
- 模块没安装
- 数据没加载
- XMLID 名字写错
- 记录被删了
3. XMLID 稳定,不代表记录永远存在
如果那条业务记录后来被删了,env.ref() 仍可能失败,或者查出来后 exists() 为空。
4. 不是所有业务记录都该手工造 XMLID
XMLID 更适合模块初始化数据、需要被别处稳定引用的记录和框架级对象。
一句话记忆法
数据库 id 解决“这条记录现在是谁”,XMLID 解决“系统将来还能稳定找到谁”。
DISCUSSION
评论区