diff --git a/module_auto_update/models/module.py b/module_auto_update/models/module.py index 86a9c584b41..2c714ca5e33 100644 --- a/module_auto_update/models/module.py +++ b/module_auto_update/models/module.py @@ -31,7 +31,7 @@ def ensure_module_state(env, modules, state): if not modules: return env.cr.execute( - "SELECT name FROM ir_module_module " "WHERE id IN %s AND state != %s", + "SELECT name FROM ir_module_module WHERE id IN %s AND state != %s", (tuple(modules.ids), state), ) names = [r[0] for r in env.cr.fetchall()] @@ -78,22 +78,47 @@ def _save_checksums(self, checksums): Icp.set_param(PARAM_INSTALLED_CHECKSUMS, json.dumps(checksums)) Icp.flush_model() + @api.model + def _not_imported_domain(self): + """Domain excluding modules that only exist in the database. + + Imported modules (Odoo Studio customizations, modules uploaded through + the web interface) have no counterpart on the file system, so no + checksum can be computed for them. + + The ``imported`` field is added by ``base_import_module``, which is not + a dependency of this module. When it is not installed, no module can be + imported in the first place, so no filtering is needed. + """ + if "imported" in self._fields: + return [("imported", "=", False)] + return [] + @api.model def _save_installed_checksums(self): checksums = {} - installed_modules = self.search([("state", "=", "installed")]) + installed_modules = self.search( + [("state", "=", "installed"), *self._not_imported_domain()] + ) for module in installed_modules: checksums[module.name] = module._get_checksum_dir() self._save_checksums(checksums) @api.model def _get_modules_partially_installed(self): - return self.search([("state", "in", ("to install", "to remove", "to upgrade"))]) + return self.search( + [ + ("state", "in", ["to install", "to remove", "to upgrade"]), + *self._not_imported_domain(), + ] + ) @api.model def _get_modules_with_changed_checksum(self): saved_checksums = self._get_saved_checksums() - installed_modules = self.search([("state", "=", "installed")]) + installed_modules = self.search( + [("state", "=", "installed"), *self._not_imported_domain()] + ) return installed_modules.filtered( lambda r: r._get_checksum_dir() != saved_checksums.get(r.name), ) diff --git a/module_auto_update/tests/test_module.py b/module_auto_update/tests/test_module.py index 14e18fb0feb..c18c195c7dd 100644 --- a/module_auto_update/tests/test_module.py +++ b/module_auto_update/tests/test_module.py @@ -80,6 +80,42 @@ def test_get_modules_with_changed_checksum(self): self.assertFalse(Imm._get_modules_with_changed_checksum()) +class TestImportedModules(TransactionCase): + def setUp(self): + super().setUp() + self.Imm = self.env["ir.module.module"] + # Skip test if base_import_module is not installed (OCB) + if "imported" not in self.Imm._fields: + self.skipTest("base_import_module is not installed") + # Modules imported into the database (e.g. Odoo Studio customizations) + # have no counterpart on the file system, so no checksum can be + # computed for them. + self.imported_module = self.Imm.create( + { + "name": "test_imported_module", + "state": "installed", + "imported": True, + } + ) + + def test_save_installed_checksums_skips_imported(self): + self.Imm._save_installed_checksums() + self.assertNotIn("test_imported_module", self.Imm._get_saved_checksums()) + + def test_changed_checksum_skips_imported(self): + # no checksum was ever saved for the imported module + self.Imm._save_checksums({}) + self.assertNotIn( + self.imported_module, self.Imm._get_modules_with_changed_checksum() + ) + + def test_partially_installed_skips_imported(self): + self.imported_module.state = "to upgrade" + self.assertNotIn( + self.imported_module, self.Imm._get_modules_partially_installed() + ) + + @odoo.tests.tagged("post_install", "-at_install") class TestModuleAfterInstall(TransactionCase): def setUp(self):