导入向导的目标不是“读文件”,而是“把外部列变成 Odoo 字段”
base_import 最容易被低估的地方,是它并不只是一个 CSV 上传器。
它真正做的事,是把一份外部文件逐步变成 ORM 能理解的数据矩阵。
所以它要解决的问题其实有三个:
- 这份文件有哪些列?
- 每一列最像哪个 Odoo 字段?
- 真的落库前,数据格式和结构有没有问题?
这就是为什么导入向导会先预览,再建议映射,最后才执行导入。
预览阶段先看字段树,再看前几行数据
parse_preview() 的第一步是 get_fields_tree()。
这一步不是随便列出模型字段,而是递归地整理出“可导入字段树”,包括:
- 普通字段
- many2one / many2many 的子字段
- o2m 的递归层级
- 哪些字段实际上不能导入
接着它会读文件、取前几行预览,并根据列里的内容估计类型。 这一步很重要,因为字段匹配不是只看列名,还要看列值像不像日期、数字、关系 ID。
换句话说:
Odoo 不是先猜名字,再看数据;它是名字和数据一起看。
字段建议的顺序非常讲究
_get_mapping_suggestion() 的规则可以记成一个三段式:
-
先看用户以前存过的映射 如果同一个外部系统经常导入,旧映射会直接复用。
-
再做精确匹配 先比技术名,再比字段 label,再比英文 label 或翻译后的 label。
-
最后才做模糊匹配 用字符串距离去找最接近的字段,而且会先根据列值类型过滤掉明显不可能的字段。
这也是导入向导看起来“很聪明”的原因: 它不是靠一个规则,而是靠一层层缩小候选集。
对关系字段,lead_id/description 这种斜杠路径会被拆成层层匹配:
先找 lead_id,再在它的子字段里找 description。
这就是导入多层关联时最实用的语法。
真正落库前,先把稀疏映射压成密集矩阵
execute_import() 干的核心工作,和 UI 没什么关系,和数据结构关系最大。
它会先把“哪些列要导入”整理成一份连续字段列表,再把每一行数据裁成同样的结构。
这么做是为了让 model.load() 能直接消费。
然后它会依次处理:
- 日期、时间、浮点等格式解析
- 二进制图片文件名提取
- 多列映射到同一个字段时的合并
- fallback value 的兜底逻辑
最后才把数据交给 ORM 的 load()。
这意味着,真正的写入不是导入向导自己完成的,而是复用 Odoo 一贯的数据加载通道。
这一点很重要,因为它保证了:
- 默认值、约束、权限、计算字段仍然生效
- 导入不是绕过 ORM 直接插数据库
- 导入和普通创建/写入的语义尽量一致
为什么导入支持 dry-run
execute_import() 会先开 savepoint,再决定是否回滚。
这让导入向导可以做“试跑”:
- 先跑一遍看错误
- 不真正提交
- 用户修正映射后再正式导入
对大批量导入来说,这是很实用的设计。 它既能提前暴露错误,又不会污染数据库。
另一个细节是:导入成功后,系统会把列名和字段映射保存到 base_import.mapping。
这样下次再导入同一类文件时,建议会更准。
实战里最该记住的 4 条
- 列名尽量靠近技术名或字段 label,能少很多模糊匹配误差
- 多层关系字段直接用
a/b/c路径,不要硬拆成独立列 - 重复导入同一外部系统时,保存过的 mapping 很值钱
- 导入远程 URL 图片有额外权限控制,不要默认以为谁都能开
你可以把 base_import 理解成一个“把外部文件翻译成 ORM 输入”的翻译器。
它不是简单搬运,而是先理解,再转换,再提交。
DISCUSSION
评论区