この記事は現在英語版のみです。翻訳は近日公開予定です。
After an Odoo major version upgrade, a database starts behaving strangely. Fields you do not recognize appear on models. Old fields are missing from the form. Some users see the new layout, others see the old. The server log shows a parade of:
WARNING: Field 'x_studio_old_field' on model 'sale.order' not found, ignored
ERROR: View 'sale.order.form.inherit.foo' has invalid arch (column does not exist)
This is registry-versus-database state drift after a partially-failed upgrade. The data is intact but Odoo's metadata about which models, fields, and views exist has gone out of sync. This guide walks through a controlled recovery for Odoo 17.0/18.0/19.0.
Quick Fix
The fastest sanity-restorer is -u all against the upgraded database with a clean worker pool:
sudo systemctl stop odoo
sudo find /opt/odoo -name "__pycache__" -type d -exec rm -rf {} +
sudo -u odoo /opt/odoo/odoo-bin -c /etc/odoo/odoo.conf \
-d <db> -u all --stop-after-init --no-http
sudo systemctl start odoo
If this clears the warnings, you were lucky — only the bytecode and registry caches were stale. If the warnings persist, you have real metadata drift and need the full recovery below.
Why This Happens
Odoo's metadata lives in three places: Python source code (the actual classes), the ir_model / ir_model_fields / ir_ui_view tables (registered metadata), and the running registry (in-process Python). After an upgrade, all three should agree. Drift happens when:
- The upgrade aborted partway through
-u all— half the modules upgraded, half did not, and the registry is mixed. - A custom module's manifest version was not bumped, so Odoo skipped its migration scripts even though the code changed.
- Studio fields added in the previous version are not in the new version's source code — they linger as ghost fields in
ir_model_fields. - A module was renamed (or a model was renamed) without an
openupgrademigration script. The DB has the old name, the source has the new. - Inherited views (
ir.ui.view) reference removed fields. The view loads, fails arch validation, gets disabled, and the form looks broken.
Step-by-Step Diagnosis
1. Confirm no module is in transitional state.
SELECT name, state FROM ir_module_module
WHERE state NOT IN ('installed', 'uninstalled', 'uninstallable');
Any rows here mean the upgrade did not finish. Re-run with -u all.
2. Find ghost fields. Fields that exist in the database but not in the source:
SELECT m.model, f.name
FROM ir_model_fields f
JOIN ir_model m ON m.id = f.model_id
WHERE f.state = 'manual' -- studio/manual fields
AND f.modules IS NULL; -- not declared by any module
Ghost fields are usually safe to drop, but back up first.
3. Find broken inherited views.
SELECT id, name, model, arch_db
FROM ir_ui_view
WHERE active = false AND inherit_id IS NOT NULL
ORDER BY write_date DESC LIMIT 20;
Recently-disabled inherited views are your top candidates.
4. Compare expected vs actual fields per model. In an Odoo shell:
expected = set(env['sale.order']._fields.keys())
actual = set(r['name'] for r in env['ir.model.fields']
.search_read([('model', '=', 'sale.order')], ['name']))
print('In DB but not source:', actual - expected)
print('In source but not DB:', expected - actual)
Either set being non-empty is a sign of drift.
5. Check ir_model_data for orphans.
SELECT module, name, model
FROM ir_model_data
WHERE module NOT IN (SELECT name FROM ir_module_module WHERE state = 'installed');
Orphan ir.model.data references break dependency resolution.
Permanent Fix
Step 1 — Snapshot the database. Always. pg_dump <db> > pre-fix.sql.
Step 2 — Drop ghost fields:
ghost_fields = env['ir.model.fields'].search([
('state', '=', 'manual'),
('modules', '=', False),
])
for f in ghost_fields:
_logger.warning("Dropping ghost field %s.%s", f.model, f.name)
f.unlink()
unlink() on ir.model.fields properly drops the column and all dependent metadata.
Step 3 — Re-enable broken inherited views with their views fixed. For each disabled inherit:
view = env['ir.ui.view'].browse(view_id)
# Inspect view.arch_db, fix the field reference, then:
view.write({'arch': fixed_arch, 'active': True})
If a view is no longer needed, archive it explicitly rather than letting it sit broken.
Step 4 — Run -u all once more with the cleaned metadata:
sudo -u odoo /opt/odoo/odoo-bin -c odoo.conf -d <db> -u all --stop-after-init
Step 5 — Verify with the OCA module checker:
pip install click-odoo-contrib
click-odoo-update -d <db>
It walks the registry and reports drifts that survived the upgrade.
For complex cases, the OCA openupgrade framework includes scripts that automate field renames, model renames, and module merges across major version bumps. ECOSIRE's Odoo migration team uses openupgrade for every version migration — manually replicating its checks on production is rarely worth it.
How to Prevent It
- Never run
-u allon production without a backup. And never run it when modules are in a transitional state. Snapshot, then upgrade, then verify. - Bump custom module versions on every release. A version bump triggers Odoo's migration runner. Without it, your
migrations/directory is silently ignored. - Inventory Studio fields before upgrading. Studio adds
manualfields that are not in any module's source. List them, decide which to keep as Python code, which to delete. - Archive instead of delete inheritance. Never
unlinkanir.ui.viewdirectly — Odoo may reactivate or reference it during upgrade. Archive (active=False) and review. - Use openupgrade. It exists for exactly this reason. The community has written hundreds of migration helpers; your job is to assemble them, not rewrite them.
- Practice on staging. Restore production into staging, run the upgrade end-to-end, run a smoke test, fix every warning before touching production. The "first attempt cost" goes down to almost zero on the second try.
Related Errors
- Upgrade aborted, database corrupted — the worse cousin of this bug.
- KeyError: environments_cache after upgrade — registry / cache reset.
- Module installation error: version mismatch — a frequent trigger of partial upgrades.
- Relational fields broken after upgrade — symptom that lives in the same family.
Frequently Asked Questions
Is dropping ghost fields safe?
Generally yes for state='manual' fields with no modules declaration — they are leftovers from Studio customizations or older module versions. Always snapshot the database first; once dropped, the column is gone and the data with it. If a Studio customization was important, export it before dropping.
Why does Odoo keep loading the broken view?
Odoo loads inherited views in module-dependency order at every server start. A broken view fails arch validation, gets active=False, and is logged but not deleted. On the next start the same thing happens. The loop only ends when you fix or archive the view.
My drift is in ir_model_data (XML IDs). What do I do?
Use the --xmlid flag with click-odoo-update, or write a one-off cleanup that nulls the orphan rows. Orphan XML IDs do not crash anything but they break the next upgrade — clean them as part of the post-upgrade pass.
Can I just restore from backup and try again?
Yes, and often this is the right call if you are within the maintenance window. Restore, fix the manifest version bumps and ghost-field cleanup before running -u all, then upgrade against the cleaned snapshot. Two passes on staging beats fixing live.
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: QWeb テンプレート、paperformat、アクション バインディングを使用して、Odoo 19 でブランド化された PDF レポートを構築します。印刷ロゴ + フッターのオーバーライド付き。