From f0409505a83eb057fc229f7224c4baf9607ffa0c Mon Sep 17 00:00:00 2001 From: Jinane Maksoud Date: Sun, 7 Dec 2025 23:14:35 +0100 Subject: [PATCH] [IMP] modules: skip autoinstall of unintalled modules In the case where an autoninstall module is uninstalled by users prior to the upgrade start, this module could be reinstalled during the upgrade if there were dependency changes and those dependencies get force installed. This improvement keeps track of any module that was elligible for autoinstall before the upgrade starts and skips its force installation when dependencies change. --- src/base/0.0.0/pre-00-upgrade-start.py | 4 +- src/util/const.py | 1 + src/util/modules.py | 76 ++++++++++++++++++++------ 3 files changed, 62 insertions(+), 19 deletions(-) diff --git a/src/base/0.0.0/pre-00-upgrade-start.py b/src/base/0.0.0/pre-00-upgrade-start.py index eef329ab7..c6aba4da0 100644 --- a/src/base/0.0.0/pre-00-upgrade-start.py +++ b/src/base/0.0.0/pre-00-upgrade-start.py @@ -1,4 +1,5 @@ -# -*- coding: utf-8 -*- +from odoo.addons.base.maintenance.migrations import util +from odoo.addons.base.maintenance.migrations.util.modules import _get_autoinstallable_modules def migrate(cr, version): @@ -13,3 +14,4 @@ def migrate(cr, version): WHERE EXCLUDED.value::timestamp - ir_config_parameter.value::timestamp > interval '72 hours' """ ) + util.ENVIRON["__modules_to_skip_autoinstall"] = _get_autoinstallable_modules(cr) diff --git a/src/util/const.py b/src/util/const.py index 2d1ba26c1..3dcf6a97d 100644 --- a/src/util/const.py +++ b/src/util/const.py @@ -13,6 +13,7 @@ "__renamed_fields": collections.defaultdict(dict), "__modules_auto_discovery_force_installs": {}, "__modules_auto_discovery_force_upgrades": {}, + "__modules_to_skip_autoinstall": set(), "__fix_fk_allowed_cascade": [], "__no_model_data_delete": {}, } diff --git a/src/util/modules.py b/src/util/modules.py index e3802d1dc..7bc2aae11 100644 --- a/src/util/modules.py +++ b/src/util/modules.py @@ -342,6 +342,7 @@ def remove_module(cr, module): ENVIRON["__modules_auto_discovery_force_installs"].pop(module, None) ENVIRON["__modules_auto_discovery_force_upgrades"].pop(module, None) + ENVIRON["__modules_to_skip_autoinstall"].discard(module) def remove_theme(cr, theme, base_theme=None): @@ -362,6 +363,7 @@ def remove_theme(cr, theme, base_theme=None): ENVIRON["__modules_auto_discovery_force_installs"].pop(theme, None) ENVIRON["__modules_auto_discovery_force_upgrades"].pop(theme, None) + ENVIRON["__modules_to_skip_autoinstall"].discard(theme) def _update_view_key(cr, old, new): @@ -415,6 +417,10 @@ def rename_module(cr, old, new): fu = ENVIRON["__modules_auto_discovery_force_upgrades"] if old in fu: fu[new] = fu.pop(old) + si = ENVIRON["__modules_to_skip_autoinstall"] + if old in si: + si.discard(old) + si.add(new) @_warn_usage_outside_base @@ -450,6 +456,10 @@ def merge_module(cr, old, into, update_dependers=True): version = fu[old][1] if parse_version(fu[old][1]) < parse_version(fu[into][1]) else fu[into][1] del fu[old] fu[into] = (init, version) + si = ENVIRON["__modules_to_skip_autoinstall"] + if old not in si and into in si: + si.discard(into) + si.discard(old) if old not in mod_ids: # this can happen in case of temp modules added after a release if the database does not @@ -721,14 +731,24 @@ def _force_install_module(cr, module, if_installed=None, reason="it has been exp [toinstall, list(INSTALLED_MODULE_STATES)], ) for (mod,) in cr.fetchall(): - _force_install_module( - cr, - mod, - reason=( - "it is an auto install module that got all its auto install dependencies installed " - "by the force install of {!r}" - ).format(module), - ) + if mod in ENVIRON["__modules_to_skip_autoinstall"]: + _logger.info( + ( + "Module %r won't be auto installed because its original dependencies were already installed. " + "Yet all its auto install dependencies became installed by the force install of %r." + ), + mod, + module, + ) + else: + _force_install_module( + cr, + mod, + reason=( + "it is an auto install module that got all its auto install dependencies installed " + "by the force install of {!r}" + ).format(module), + ) # TODO handle module exclusions @@ -828,8 +848,19 @@ def module_auto_install(cr, module, auto_install): @_warn_usage_outside_base def trigger_auto_install(cr, module): _assert_modules_exists(cr, module) + to_autoinstall = _get_autoinstallable_modules(cr, module) + if to_autoinstall: + if module in ENVIRON["__modules_to_skip_autoinstall"]: + _logger.info("Skipping the auto installation of module %r because it was previously uninstalled.", module) + return False + force_install_module(cr, module, reason="it's an auto install module and all its dependencies are installed") + return True + return False + + +def _get_autoinstallable_modules(cr, module=None): if AUTO_INSTALL == "none": - return False + return set() dep_match = "true" if column_exists(cr, "ir_module_module_dependency", "auto_install_required"): @@ -863,13 +894,19 @@ def trigger_auto_install(cr, module): hidden = ref(cr, "base.module_category_hidden") cat_match = cr.mogrify("m.category_id = %s", [hidden]).decode() - query = """ - SELECT m.id + name_match = "true" + if module: + name_match = cr.mogrify("m.name = %s", [module]).decode() + + query = format_query( + cr, + """ + SELECT m.name FROM ir_module_module_dependency d JOIN ir_module_module m ON m.id = d.module_id JOIN ir_module_module md ON md.name = d.name {} - WHERE m.name = %s + WHERE {} AND m.state = 'uninstalled' AND m.auto_install = true AND {} @@ -877,13 +914,16 @@ def trigger_auto_install(cr, module): GROUP BY m.id HAVING bool_and(md.state IN %s) {} - """.format(country_join, dep_match, cat_match, country_match) + """, + SQLStr(country_join), + SQLStr(name_match), + SQLStr(dep_match), + SQLStr(cat_match), + SQLStr(country_match), + ) - cr.execute(query, [module, INSTALLED_MODULE_STATES]) - if cr.rowcount: - force_install_module(cr, module, reason="it's an auto install module and all its dependencies are installed") - return True - return False + cr.execute(query, [INSTALLED_MODULE_STATES]) + return {m[0] for m in cr.fetchall()} def _set_module_category(cr, module, category):