Coverage for ingadhoc-odoo-saas / saas_client / models / ir_module_module.py: 19%
93 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-09 18:37 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-09 18:37 +0000
1import logging
2from unittest.mock import patch
4from odoo import api, models
5from odoo.fields import Domain
6from odoo.modules import Manifest
7from odoo.tools import config, parse_version
9_logger = logging.getLogger(__name__)
12class IrModuleModule(models.Model):
13 _inherit = "ir.module.module"
15 def button_upgrade(self):
16 """
17 Override to prevent schema conflicts during fixdb operations.
19 When fixdb_mode context is active, temporarily patches the translation function
20 to return untranslated strings, preventing database access to models with fields
21 that don't exist yet in the schema during module upgrades.
22 """
23 if not self.env.context.get("fixdb_mode"):
24 return super().button_upgrade()
26 with patch("odoo.addons.base.models.ir_module._", side_effect=lambda x, **kw: x):
27 return super().button_upgrade()
29 def button_install(self):
30 """
31 Override to prevent schema conflicts during fixdb operations.
33 When fixdb_mode context is active, temporarily patches the translation function
34 to return untranslated strings, preventing database access to models with fields
35 that don't exist yet in the schema during module installations.
36 """
37 if not self.env.context.get("fixdb_mode"):
38 return super().button_install()
40 with patch("odoo.addons.base.models.ir_module._", side_effect=lambda x, **kw: x):
41 return super().button_install()
43 @api.model
44 def _get_not_installable_modules(self):
45 states = ["installed", "to upgrade", "to remove", "to install"]
46 domain = Domain([("state", "in", states)])
47 if self.search_count([("name", "=", "base_import_module"), ("state", "=", "installed")]):
48 domain &= Domain([("imported", "=", False)])
49 modules = self.search(domain)
51 not_installable_modules = self.browse()
52 for module in modules:
53 manifest = Manifest.for_addon(module.name)
54 modules_install_disabled = config.get("module_change_install.modules_disabled") or ""
55 modules_install_disabled = [x.strip() for x in modules_install_disabled.split(",")]
56 # Non-installability due to modules_install_disabled manifests slightly differently:
57 # the system doesn't fail because we allow Odoo to load, but it warns that something is wrong
58 if not manifest or not manifest["installable"] or module.name in modules_install_disabled:
59 not_installable_modules += module
61 return not_installable_modules
63 @api.model
64 def _get_to_upgrade_modules(self):
65 to_upgrade_modules = self.search([("state", "=", "installed")])
66 to_upgrade_modules = to_upgrade_modules.filtered(
67 lambda x: parse_version(x.installed_version) != parse_version(x.latest_version),
68 )
69 return to_upgrade_modules
71 @api.model
72 def _get_not_installed_autoinstall_modules(self):
73 uninstalled_modules = self.search(
74 [
75 ("state", "=", "uninstalled"),
76 ("auto_install", "=", True),
77 ("country_ids", "=", False),
78 ]
79 )
80 satisfied_states = frozenset(("installed", "to install", "to upgrade"))
82 def all_dependencies_satisfied(m):
83 states = set(d.state for d in m.dependencies_id)
84 return states.issubset(satisfied_states)
86 return uninstalled_modules.filtered(all_dependencies_satisfied)
88 @api.model
89 def get_update_status_details(self):
90 not_installable = self._get_not_installable_modules()
91 update_required = self._get_to_upgrade_modules()
92 to_upgrade_modules = self.search([("state", "=", "to upgrade")])
93 to_install_modules = self.search([("state", "=", "to install")])
94 to_remove_modules = self.search([("state", "=", "to remove")])
95 not_installed_autoinstall_modules = self._get_not_installed_autoinstall_modules()
97 if not_installable:
98 update_state = "not_installable"
99 elif update_required:
100 update_state = "update_required"
101 elif to_upgrade_modules:
102 update_state = "on_to_upgrade"
103 elif to_install_modules:
104 update_state = "on_to_install"
105 elif to_remove_modules:
106 update_state = "on_to_remove"
107 elif not_installed_autoinstall_modules:
108 update_state = "uninstalled_auto_install"
109 else:
110 update_state = "ok"
112 details = {
113 "not_installable": not_installable.mapped("name") if not_installable else [],
114 "update_required": update_required.mapped("name") if update_required else [],
115 "to_upgrade_modules": to_upgrade_modules.mapped("name") if to_upgrade_modules else [],
116 "to_install_modules": to_install_modules.mapped("name") if to_install_modules else [],
117 "to_remove_modules": to_remove_modules.mapped("name") if to_remove_modules else [],
118 "not_installed_autoinstall_modules": not_installed_autoinstall_modules.mapped("name")
119 if not_installed_autoinstall_modules
120 else [],
121 }
122 return {"state": update_state, "details": details}
124 @api.model
125 def update_list(self):
126 """
127 Override to mark modules as uninstallable when they are no longer available.
129 After updating the module list, compares modules in the database with available manifests
130 and marks as uninstallable those that are no longer present in the addons paths.
131 Only affects uninstalled modules to avoid breaking active installations.
132 """
133 res = super().update_list()
135 known_uninstalled_mods = self.with_context(lang=None).search([("state", "=", "uninstalled")])
136 available_manifests = {manifest.name for manifest in Manifest.all_addon_manifests()}
137 modules_to_mark = known_uninstalled_mods.filtered(lambda mod: mod.name not in available_manifests)
139 if modules_to_mark:
140 _logger.info(
141 "Marking modules no longer available as uninstallable: %s",
142 modules_to_mark.mapped("name"),
143 )
144 modules_to_mark.write({"state": "uninstallable"})
146 return res
148 @api.model
149 def fix_modules(self):
150 """
151 Execute fixdb operations: update list, upgrade modules, install auto_install modules.
152 Can be called from CLI (fixdb command) or remotely via odooly.
153 """
154 # Update module list
155 self.update_list()
157 # Upgrade modules that need upgrading
158 to_upgrade_modules = self._get_to_upgrade_modules()
159 if to_upgrade_modules:
160 _logger.info("Upgrading modules: %s", to_upgrade_modules.mapped("name"))
161 to_upgrade_modules.with_context(fixdb_mode=True).button_upgrade()
163 # Install auto_install modules with all dependencies satisfied
164 to_install_modules = self._get_not_installed_autoinstall_modules()
165 if to_install_modules:
166 _logger.info(
167 "Installing auto_install modules with dependencies satisfied: %s", to_install_modules.mapped("name")
168 )
169 to_install_modules.with_context(fixdb_mode=True).button_install()
171 # Run upgrade wizard to apply changes
172 self.env["base.module.upgrade"].sudo().upgrade_module()
174 return True