Skip to content

[20.0][MIG] openupgrade_framework - #117

Open
dnplkndll wants to merge 5 commits into
20.0from
20.0-mig-openupgrade_framework
Open

dnplkndll wants to merge 5 commits into
20.0from
20.0-mig-openupgrade_framework

Conversation

@dnplkndll

@dnplkndll dnplkndll commented Sep 20, 2026 •

Copy link
Copy Markdown

Port of openupgrade_framework from 19.0 to 20.0. Fork-only review PR — OCA has
no 20.0 branch yet, so the base here is 19.0 and a [DO NOT MERGE] commit
carries the series pin. Drop that commit when re-basing onto the real branch.

This is the gate for everything else: without it a 19→20 run dies on the first
stale inherited view, long before any upgrade script gets a chance to run.

Why the diff is so small

Every patch under odoo_patch/ wraps its target through _original_method and
forwards *args/**kwargs, rather than copying the core body. So the substantial
20.0 rewrites of BaseModel.unlink and load_module_graph need no adaptation,
and load_module_graph dropping its models_to_check parameter is absorbed by
the forwarding. I checked every patch target and helper resolves on 20.0 —
MODULE_UNINSTALL_FLAG, models.MAGIC_COLUMNS, registry._force_upgrade_scripts,
get_resource_from_path, and all eleven patched methods.

The one real removal

The ModuleGraph._update_from_database patch carried an 18→19 fixup:

# v19-specific: ir.model.fields#translate has changed semantics ...
self._cr.execute("UPDATE ir_model_fields SET translate=NULL where translate::varchar='false'")

It normalised translate='false' to NULL while that column was mid-conversion
from boolean to varchar. On a 19.0 source the column is already varchar holding
NULL / standard / html_translate / xml_translate, and the translate selection
is byte-identical in 20.0 — so the statement matches zero rows and no longer
describes a real step. Removed rather than left as a no-op.

Verification

Ran against a 19.0 database (154 core modules, 1100 ACLs, 311 record rules) with
a local arm64 build of odoo/odoo 20.0 at 310a97e0:

--load=base,web,openupgrade_framework -u all

Before this port the run aborts:

ParseError: while parsing base/views/res_partner_views.xml:317
Element '<filter name="type_company">' cannot be located in parent view
  xmlid: hr_calendar.view_res_partner_filter_inherit_calendar

With it, that becomes a logged warning from
openupgrade_framework.odoo_patch.odoo.addons.base.models.ir_ui_view
("Can't render custom view … Assuming you are migrating between major versions")
and the load continues — which is the patch doing exactly its job.

The run then stops further on, at base/security/ir.access.csv, for a reason
that is not a framework problem and is the next piece of work:

For external id base.access_decimal_precision_config when trying to create/update
a record of model ir.access found record of different model ir.model.access (6478)

20.0 reuses the 19.0 ACL external ids for the new ir.access model, so every
ir_model_data row still pointing at ir.model.access (and ir.rule) has to be
converted and re-pointed. That belongs in a base pre-migration script in
openupgrade_scripts, not here.

All of this ran on an internal-only Docker network with egress verified absent.

AI-assisted (Claude Code); every change reviewed, tested, and owned by the author.


CI: the migration job fails on this branch, by construction

Test OpenUpgrade migration is red here and green on
#120. That is the expected
shape, not a defect in this port.

The coverage table on the 20.0 series branch marks base as the one module
CI migrates, so every PR branch is asked to migrate it. This branch carries
only the framework; the base migration script lives in
#118. Without it the run
stops where it always did before that script existed — the
res_country_state insert, because nothing has renamed
base.state_in_or to base.state_in_od yet:

ERROR odoo.sql_db: bad query: b'INSERT INTO "res_country_state" ...

A slice of the work cannot pass an integration test. #120 stacks framework +
base scripts + analysis + the per-module scripts and is the gate that means
something: it migrates 564 modules end to end, Modules loaded, Registry loaded in 1767s.

@dnplkndll
dnplkndll force-pushed the 20.0-mig-openupgrade_framework branch 2 times, most recently from e067d05 to 8065d8c Compare September 20, 2026 22:44
@dnplkndll
dnplkndll changed the base branch from 19.0 to 20.0 September 21, 2026 00:25
@dnplkndll
dnplkndll force-pushed the 20.0-mig-openupgrade_framework branch 11 times, most recently from 3df115b to eb832de Compare September 21, 2026 02:51
Every patch in odoo_patch wraps its target via _original_method and forwards
*args/**kwargs, so the 20.0 core rewrites of unlink() and load_module_graph()
need no adaptation -- load_module_graph dropping its models_to_check parameter
is absorbed by the forwarding. All patch targets still resolve on 20.0:
BaseModel.unlink, ModuleGraph._update_from_database, MigrationManager._get_files,
IrModel._drop_table, IrModelFields._drop_column, IrModelRelation.
_module_data_uninstall, the four IrUiView methods, IrModule.update_list and
check_external_dependencies, plus MODULE_UNINSTALL_FLAG, models.MAGIC_COLUMNS
and registry._force_upgrade_scripts.

Dropped the 18->19 ir_model_fields.translate normalisation from the
ModuleGraph patch. It rewrote translate='false' to NULL while the column was
being converted from boolean to varchar. On a 19.0 source the column is already
varchar holding NULL/standard/html_translate/xml_translate, and the selection is
byte-identical in 20.0, so the statement matches nothing and no longer describes
a real migration step.

Assisted-by: Claude Opus 5
…gistry loads

20.0 refuses _button_immediate_function on an unloaded registry and points at
button_install instead. Core still reaches for it from code that runs during an
install: hr's res_config_settings.set_values installs hr_attendance, and every
installed module's set_values chains into hr's, so any post_init_hook executing
a res.config.settings brings the operation down once hr is present. Seen from
l10n_mx._enable_group_uom_post_init and stock_fleet._enable_dispatch_management
-- excluding one module only moves the crash to the next.

Doing what the error message asks keeps the module operation, which is all the
caller wanted.

Assisted-by: Claude Opus 5
…dels

mail.activity._get_phone_numbers_by_activity resolves self.env[res_model] for
every distinct model among the activities it is given. That holds at runtime,
where every installed module is in the registry, but not during a major
upgrade: activities belonging to a module that has not been loaded yet are
already in the table, so the lookup raises KeyError and stops the upgrade. A
single mail.activity on event.event ends a 19->20 run while mail is being
updated and event has not been reached.

Filters those activities out before delegating. mail is not a dependency of
this module, so the patch is skipped when mail is absent.

Assisted-by: Claude Opus 5
…pgrade

account is inconsistent about which mapping it uses. _load_translations picks
the companies to work on with _get_chart_template_mapping(get_all=True), while
_parse_csv resolves those same codes against the default visible-only mapping
and subscripts the result. A company on a template 20.0 stopped offering passes
the filter and then fails the lookup.

19.0's plain "fr" is one of them: 20.0 keeps it as the parent of the visible
French variants, so an existing FR company ends the load with "'NoneType'
object is not subscriptable" after every module has already migrated -- the
last thing standing between a 585-module database and a finished upgrade.

A database being upgraded has whatever templates it has, and they all need to
resolve. account is not a dependency here, so the patch is skipped when absent.

Assisted-by: Claude Opus 5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant