导入链路

从导入按钮到 ORM 写入:base_import 如何把表格变成记录

解释 Odoo 数据导入的前后端链路:上传、映射、校验、创建/更新记录,以及为什么导入失败通常不是“模型写不进去”。

Odoo 开发 框架
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

很多导入问题,表面上看像“模型写不进去”,实际上卡在更前面的地方:

  • 文件格式没被正确识别;
  • 列头没匹配到字段;
  • 日期、金额、二进制内容没解析对;
  • 多列映射到同一个字段时被合并了;
  • 预校验就已经失败了。

base_import 的源码很适合拿来拆这种问题,因为它把“把表格变成记录”分成了非常清晰的几段。

第一步不是写数据库,而是读文件

_read_file() 会先根据文件内容、用户传入的类型、文件扩展名,去判断到底该走 CSV、XLS、XLSX 还是 ODS。

这一步的重要性常被低估。

因为很多导入失败,根本不是 ORM 的错,而是:

  • 文件被错误识别成别的格式;
  • 分隔符不对;
  • 语言环境导致日期/浮点解析异常。

所以你看到“导入报错”,第一反应不该是怀疑 model.load(),而要先看读取阶段。

parse_preview() 负责“先看一眼再决定怎么导”

parse_preview() 做的事情很多,但核心目标很简单:

  1. 读出前几行;
  2. 识别表头;
  3. 推断列类型;
  4. 给出字段映射建议;
  5. 展示样例值;
  6. 判断是否需要 advanced mode。

它甚至会利用前几行数据来猜:

  • 这列像日期;
  • 那列像金额;
  • 这个表头是不是和字段名很接近。

也就是说,导入 UI 里那些“自动帮你选字段”的体验,背后不是魔法,而是预览和匹配逻辑。

字段映射不是硬匹配,而是逐层猜测

get_fields_tree() 会先把目标模型能导入的字段树整理出来,然后 _get_mapping_suggestions() 再根据:

  • 之前保存过的 mapping;
  • 字段技术名;
  • 字段显示名;
  • 语言翻译后的标签;
  • 模糊匹配距离;

去推测最可能的对应关系。

这解释了一个很实用的现象:

同样一张表,在不同语言环境下,系统的字段建议可能不一样。

_convert_import_data() 只是先把“要导的列”剪出来

这一步做的事非常朴素:

  • 找到被选中的列;
  • 去掉没选的列;
  • 检查每行长度是否一致;
  • 按需跳过表头。

如果这里报错,常见原因往往是:

  • 分隔符错了;
  • 行长不一致;
  • 你以为的“空列”其实被文件解析器读成了别的东西。

真正的类型处理发生在 _parse_import_data()

这里会递归处理字段树,重点包括:

  • date / datetime:按格式解析;
  • float / monetary:处理千分位、小数点、币种符号、括号负数;
  • binary:支持 URL 或 base64;
  • 关系字段:继续递归进入子模型。

这也是为什么导入时经常会报出很具体的“某列第几行有问题”。

因为 Odoo 不是等到最后才知道不对,而是在预处理阶段就尽量把错误拦下来。

execute_import() 才是进入 ORM 的真正入口

前面的步骤都只是准备数据。

到了 execute_import(),才会:

  1. 开 savepoint;
  2. 转换数据;
  3. 解析日期和浮点;
  4. 处理多列映射;
  5. 处理 fallback values;
  6. model.load()

这里最关键的是 savepoint

它让测试导入和真实导入都能在失败时回滚,不会把半成品脏数据留在库里。

为什么多列映射经常让人困惑

_handle_multi_mapping() 会把多个列合并到同一个字段,例如:

  • char 用空格拼接;
  • text 用换行拼接;
  • many2many 用逗号语义处理。

所以当你看到“我明明导了两列,最后却只进来一列”,很多时候不是丢数据,而是 系统按规则把它们合并了

导入失败时,最值得先查的 5 件事

  1. 文件类型有没有被正确识别;
  2. 列头和字段映射是否合理;
  3. 日期格式是否和当前导入设置一致;
  4. 浮点里有没有千分位或币种符号;
  5. 是否有多列映射被合并,造成你以为的“丢列”。

一句话总结

base_import 不是“把 Excel 一把扔进 ORM”那么简单,而是:

先读文件,再做预览和字段匹配,接着做类型清洗与多列合并,最后才通过 model.load() 把数据写进数据库。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。