Thursday, July 9, 2026
2 changes · 19.0
Resolved issues and error corrections
Peppol now correctly switches to demo mode when installed on a database that was already neutralized, such as staging or test copies. This prevents test environments from accidentally registering or sending documents through the live Peppol network.
Original PR description
When Peppol is installed on a database that was already neutralized (ex: a staging database where the feature is enabled after the neutralization happened), the account_peppol.edi.mode parameter is not set: data/neutralize.sql only runs at neutralization time, not when the module is installed afterwards. The demo/ data that also sets this parameter is not loaded on databases without demo data (real production/staging databases). As a result, _get_peppol_edi_mode() falls back to 'prod' and the neutralized database registers and sends documents against the live Peppol network. Steps to reproduce: - Neutralize a database on which Peppol is not installed yet - Install the account_peppol module - Open the Peppol settings / registration wizard: the mode is Production instead of Demo Force the demo mode in the pre_init_hook when the database is neutralized, mirroring data/neutralize.sql opw-6307710 Forward-Port-Of: odoo/odoo#273458 Forward-Port-Of: odoo/odoo#273019
This fix prevents Odoo from failing to start when custom models exist and a related internal model field is no longer stored in the database. It makes registry loading more resilient, reducing the risk of system-wide downtime after module combinations or cleanup operations.
Original PR description
`_add_manual_models` builds Python model classes for custom ("manual") `ir.model` records with a raw `SELECT *` on the `ir_model` table, then hands each row to `_instanciate_attrs`, which reads a…
`_add_manual_models` builds Python model classes for custom ("manual") `ir.model` records with a raw `SELECT *` on the `ir_model` table, then hands each row to `_instanciate_attrs`, which reads a fixed list of expected columns (`model`, `name`, `abstract`, `transient`, `order`, `fold_name`, `info`) via dict subscription (`model_data['xxx']`).
This assumes every one of these fields is always backed by a real database column. That assumption breaks whenever a field originally declared as stored on `ir.model` gets redeclared as non-stored (`store=False`) by another module inheriting the same model: Odoo correctly drops the now-unneeded column during that module's `_auto_init()`, but any code still doing `SELECT *` on the table no longer gets that key in the result row, and the subscript access raises a bare `KeyError`. This aborts the *entire* registry load, for every model in the database, not just the one whose field changed -- because `_add_manual_models` runs once per registry load and any database with at least one manual/custom model exercises it.
This is exactly the scenario introduced by 267bec440d56 ("[IMP] base: add IrModel.abstract field"): it added a new, plain stored `abstract` field on `ir.model`. `web_studio` has independently defined its own `abstract` field on `ir.model` since 2016 (ac1129f6577), as a computed, `store=False` field. Once both are loaded together, Odoo's field- merging logic settles on the `store=False` definition, so `_auto_init()` drops the `abstract` column -- and any database that also has at least one manual model (built through Studio, or via a module that dynamically generates SQL-view-backed models) then fails to boot its registry at all:
File ".../odoo/orm/model_classes.py", line 554, in _add_manual_models
attrs = env['ir.model']._instanciate_attrs(model_data)
File ".../odoo/addons/base/models/ir_model.py", line 487, in _instanciate_attrs
'_abstract': bool(model_data['abstract']),
KeyError: 'abstract'
Fix `_instanciate_attrs` to fall back to a sane default instead of assuming the column exists. This doesn't just patch the `base`/ `web_studio` collision -- it hardens `_add_manual_models` against any future field made non-stored by an inheriting module, which is otherwise a silent, whole-database-breaking failure with no visible relation to whichever module actually changed the field.
A companion fix removing web_studio's now-redundant override (the actual root cause of the collision) is proposed separately.
Steps to reproduce:
1. Install `base` and `web_studio` together (or in either order) --
Odoo's field-merging settles on web_studio's `store=False`
definition, but leaves the `abstract` column physically in place;
it is not dropped automatically.
2. Have at least one manual model in the database (`state='manual'`
on `ir.model`, e.g. created through Settings > Technical > Models).
3. Actually remove the now-orphaned `abstract` column -- e.g. via the
OCA `database_cleanup` module's column-purge wizard
(`cleanup.purge.wizard.column`), which explicitly targets exactly
this kind of column (computed + non-stored fields are excluded
from its "keep" list, so `abstract` gets flagged and dropped).
4. Restart Odoo / reload the registry: it fails to boot entirely with
a KeyError on that field name, for every model, not just the one
whose owning module changed.
Related: odoo/enterprise#123572
---
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr