本文目前仅提供英文版本。翻译即将推出。
After upgrading Odoo, certain forms render with empty fields where data clearly used to exist. The log shows:
WARNING: Many2one field 'sale.order.partner_invoice_id' references unknown comodel 'res.partner.invoice'
Or:
ERROR: relation "stock_picking_carrier_rel" does not exist
Or simply: data that used to be linked is now sitting in null fields. This is post-upgrade relational drift on Odoo 17.0/18.0/19.0 — usually triggered by comodel renames, field type changes, or missing OpenUpgrade scripts.
Quick Fix
For a missing Many2many join table (the second variant above), recreate it from a pre-upgrade backup:
-- Restore the join table data from a snapshot of the pre-upgrade database
-- (assuming you restored to a parallel DB called <db>_premigration)
INSERT INTO stock_picking_carrier_rel (picking_id, carrier_id)
SELECT picking_id, carrier_id
FROM dblink('dbname=<db>_premigration',
'SELECT picking_id, carrier_id FROM stock_picking_carrier_rel')
AS t(picking_id integer, carrier_id integer)
ON CONFLICT DO NOTHING;
For a comodel rename (the first variant), patch the model reference in your code or wait for/install the OpenUpgrade script that handles the rename.
Why This Happens
Odoo's relational fields rely on three pieces of metadata: the comodel name, the column or join table, and the inverse relationship. Major version upgrades occasionally:
- Rename a comodel.
account.tax.templatewas removed;account.taxis the only model now. Custom modules referencing the old name break. - Change
Many2onetoOne2manyor vice versa. Rare but happens —mail.followers.partner_idshape changes have bitten upgrades. - Drop a Many2many join table because the field was reorganized. The
_reltable either gets renamed or replaced with a new one2many model. - Change
ondeletesemantics, leaving cascade orphans where there used to be NULLs. - Drop columns from inheritance — a parent field that was inherited and overridden by a child class can lose its column when the inheritance graph changes.
OpenUpgrade scripts handle most of these for official Odoo modules. Custom modules that reference renamed comodels need their own migration.
Step-by-Step Diagnosis
1. Find the broken field. The log usually names it. If not, walk the chatter:
SELECT model, name, ttype, relation
FROM ir_model_fields
WHERE ttype IN ('many2one', 'one2many', 'many2many')
AND relation NOT IN (SELECT model FROM ir_model);
This finds every field whose comodel does not exist. Each row is a broken field.
2. Compare to source. For each row, check whether the field exists in the source code:
grep -r "partner_invoice_id" /opt/odoo/addons/sale/models/
If the field is in source with a different comodel, you have a comodel rename. If it is missing entirely, the field was removed and your custom code or DB still references it.
3. Check Many2many join tables.
SELECT table_name
FROM information_schema.tables
WHERE table_name LIKE '%_rel'
AND table_schema = 'public';
Cross-reference against ir_model_fields rows of type many2many. Any field whose relation_table does not appear in this list is broken.
4. Check FK constraints.
SELECT conname, conrelid::regclass, confrelid::regclass
FROM pg_constraint
WHERE contype = 'f' AND NOT convalidated;
Invalidated FKs after upgrade indicate data the upgrade could not link.
5. Check OpenUpgrade analysis log. OpenUpgrade emits an openupgrade-analysis.txt listing every field rename, model rename, and removal. The fields you are seeing broken are usually in there — the question is whether your migration script applied the suggested fix.
Permanent Fix
For comodel renames in your custom code, update field declarations:
# Old (Odoo 17.0)
tax_id = fields.Many2one('account.tax.template')
# New (Odoo 18.0+)
tax_id = fields.Many2one('account.tax')
Then ship a migration script that renames the column data if the underlying data model changed:
# migrations/18.0.1.0.0/pre-migration.py
def migrate(cr, version):
cr.execute("""
UPDATE ir_model_fields
SET relation = 'account.tax'
WHERE relation = 'account.tax.template'
""")
For missing join tables, recreate them from the pre-upgrade snapshot:
CREATE TABLE stock_picking_carrier_rel (
picking_id integer NOT NULL REFERENCES stock_picking(id) ON DELETE CASCADE,
carrier_id integer NOT NULL REFERENCES delivery_carrier(id) ON DELETE CASCADE,
PRIMARY KEY (picking_id, carrier_id)
);
INSERT INTO stock_picking_carrier_rel
SELECT picking_id, carrier_id FROM <restored_premigration_table>;
Always verify counts before and after — a missing row is silent until reports notice the drop.
For Many2one to One2many shape changes, this is a serious migration. Read the OpenUpgrade analysis carefully, write the data transformation, and test on staging until counts match production exactly. ECOSIRE's Odoo migration team handles this kind of cross-shape migration on most major upgrades.
For dropped inherited columns, restore the column manually from backup if the data is critical, or accept the loss if it was unused. Always backup first.
How to Prevent It
- Use OpenUpgrade. It exists for exactly this. Skipping it means writing migration scripts you do not need to write.
- Restore production into staging before every major upgrade. Run the upgrade end-to-end. Compare row counts of every business table before and after. Any drop is investigated before production runs.
- Inventory your custom modules. List every Many2one and Many2many. Map each to the source's current state. Rewrite or write migrations for anything renamed or moved.
- Lock OCA dependencies to the version branch. A repo cloned from main on the day of the upgrade is a roll of the dice. Pin to the release tag or version branch.
- Constraint snapshot.
pg_dump --schema-onlybefore the upgrade. Diff againstpg_dump --schema-onlyafter. Every dropped constraint is investigated. - Use
db.backupautomation. Production should have automatic point-in-time backups so you can compare data shape across the upgrade boundary easily, not just restore.
Related Errors
- Upgrade aborted, database corrupted — worse outcome of the same migration class.
- Model state corruption after upgrade — sibling failure mode.
- Recordset has no attribute — runtime symptom of a renamed field.
- Module installation error: version mismatch — pre-upgrade variant.
Frequently Asked Questions
Should I run the upgrade on a copy of production or in place?
Always on a copy first. The first run is your dry run — read the log carefully, fix every warning. The second run on production should be uneventful. Skipping the dry run is the most common cause of "broken relational fields" surfacing at the worst possible moment.
Can OpenUpgrade fix my custom modules?
OpenUpgrade ships scripts for every official Odoo and OCA module. For your custom modules, you write the migration scripts yourself, but you can use OpenUpgrade's helpers (openupgrade.rename_columns, openupgrade.rename_models) inside your scripts. They are correct, tested, and handle the FK reshuffling for you.
Why does Odoo not warn me about renamed comodels at install time?
It would, if the manifest version reflected the change. The warning happens during --update if the upgrade scripts ran. If you skip the upgrade scripts (because the manifest version was not bumped, or you ran -i instead of -u), Odoo silently keeps the old metadata. Always -u on existing databases.
My data looks correct in PostgreSQL but blank in the UI. What's happening?
Almost certainly a related field whose chain is broken — the data is there but the related lookup hits a NULL somewhere on the path. Use the SQL probe in step 1 of the diagnosis to find broken fields, fix the chain (or convert to a stored compute), and the UI lights up again.
Need help with a tricky Odoo error? ECOSIRE's Odoo experts have shipped 215+ modules — get expert help.
作者
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
相关文章
如何将自定义按钮添加到 Odoo 表单视图 (2026)
将自定义操作按钮添加到 Odoo 19 表单视图:Python 操作方法、视图继承、条件可见性、确认对话框。经过生产测试。
如何在没有 Studio 的情况下在 Odoo 中添加自定义字段 (2026)
通过 Odoo 19 中的自定义模块添加自定义字段:模型继承、视图扩展、计算字段、存储/非存储决策。代码优先,版本控制。
如何使用外部布局在 Odoo 中添加自定义报告
使用 web.external_layout 在 Odoo 19 中构建品牌 PDF 报告:QWeb 模板、paperformat、操作绑定。带有印刷徽标+页脚覆盖。