diff --git a/base_module_quick_update/README.rst b/base_module_quick_update/README.rst new file mode 100644 index 00000000000..5cf46737972 --- /dev/null +++ b/base_module_quick_update/README.rst @@ -0,0 +1,130 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +======================== +Base Module Quick Update +======================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:c4b04defc7ec971db19abc414e467dc5c83a01ec7c8091551cc02340206cfe0d + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github + :target: https://github.com/OCA/server-tools/tree/16.0/base_module_quick_update + :alt: OCA/server-tools +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/server-tools-16-0/server-tools-16-0-base_module_quick_update + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/server-tools&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module adds a **Quick upgrade** action on Apps — a fast upgrade of +the selected module without cascading upgrades of the installed modules +that depend on it. + +The regular **Upgrade** action keeps the standard Odoo behavior: the +selected module is upgraded together with the installed modules that +depend on it. + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +In standard Odoo, clicking **Upgrade** on a module also queues all +installed modules that depend on it. On large databases and during +frequent development this slows things down: often you only need to +upgrade a single module, without cascading upgrades of its dependents. + +A way is needed to start an upgrade for the selected module only (and +install any missing dependencies if they are not yet installed), without +adding reverse-dependencies to the upgrade. + +Configuration +============= + +This module works out of the box and requires no configuration. + +The **Quick upgrade** action is restricted to the *Administration / +Settings* (``base.group_system``) group, the same as the standard +**Upgrade** action. + +Usage +===== + +1. Install the ``base_module_quick_update`` module. +2. Open **Apps** and the card of the desired installed module. +3. Click **Quick upgrade** (on the form view and/or on the kanban / + module action menu). |Quick upgrade button| +4. Wait for the upgrade to apply to the selected module only (without + mass-upgrading dependents). + +For comparison: the regular **Upgrade** button still upgrades the module +together with the installed modules that depend on it. + +.. |Quick upgrade button| image:: https://raw.githubusercontent.com/OCA/server-tools/16.0/base_module_quick_update/static/description/img/quick_upgrade_button.png + +Changelog +========= + + + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* XXP + +Contributors +------------ + +- XXP + + - Project Manager Mikhail Lapin + - Developer Anton Balmakov + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/server-tools `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/base_module_quick_update/__init__.py b/base_module_quick_update/__init__.py new file mode 100644 index 00000000000..5ff6bbb9825 --- /dev/null +++ b/base_module_quick_update/__init__.py @@ -0,0 +1,4 @@ +# Copyright (C) 2026 XXP +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import models diff --git a/base_module_quick_update/__manifest__.py b/base_module_quick_update/__manifest__.py new file mode 100644 index 00000000000..49bea469dd8 --- /dev/null +++ b/base_module_quick_update/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright (C) 2026 XXP +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Base Module Quick Update", + "version": "16.0.1.0.0", + "summary": "Adds a quick upgrade for the selected module without " + "cascading upgrades of modules that depend on it.", + "author": "XXP, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/server-tools", + "license": "AGPL-3", + "category": "Technical Settings", + "depends": [ + "base", + ], + "data": [ + "views/ir_module_views.xml", + ], + "installable": True, + "application": False, +} diff --git a/base_module_quick_update/models/__init__.py b/base_module_quick_update/models/__init__.py new file mode 100644 index 00000000000..fd46dc0a83e --- /dev/null +++ b/base_module_quick_update/models/__init__.py @@ -0,0 +1,4 @@ +# Copyright (C) 2026 XXP +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import ir_module diff --git a/base_module_quick_update/models/ir_module.py b/base_module_quick_update/models/ir_module.py new file mode 100644 index 00000000000..7769a7329ff --- /dev/null +++ b/base_module_quick_update/models/ir_module.py @@ -0,0 +1,69 @@ +# Copyright (C) 2026 XXP +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import logging + +from odoo import _, models +from odoo.exceptions import UserError + +from odoo.addons.base.models.ir_module import ACTION_DICT, assert_log_admin_access + +_logger = logging.getLogger(__name__) + + +class IrModuleModule(models.Model): + _inherit = "ir.module.module" + + @assert_log_admin_access + def button_quick_upgrade(self): + """ + Upgrade the selected modules. Unlike the standard method, this mode + does not automatically upgrade other modules that depend on them (downstream). + However, any missing required dependencies (upstream) will still be installed + to ensure the module upgrades correctly. + """ + + if not self: + return + self.update_list() + + for module in self: + if module.state not in ("installed", "to upgrade"): + raise UserError( + _("Can not upgrade module '%s'. It is not installed.") + % (module.name,) + ) + if self.get_module_info(module.name).get("installable", True): + self.check_external_dependencies(module.name, "to upgrade") + + self.write({"state": "to upgrade"}) + + to_install = [] + for module in self: + if not self.get_module_info(module.name).get("installable", True): + continue + for dep in module.dependencies_id: + if dep.state == "unknown": + raise UserError( + _( + "You try to upgrade the module %(module)s that " + "depends on the module: %(dependency)s.\nBut this " + "module is not available in your system." + ) + % {"module": module.name, "dependency": dep.name} + ) + if dep.state == "uninstalled": + to_install += self.search([("name", "=", dep.name)]).ids + + self.browse(to_install).button_install() + return dict(ACTION_DICT, name=_("Apply Schedule Upgrade")) + + @assert_log_admin_access + def button_immediate_quick_upgrade(self): + """Immediately upgrade the selected module(s) without cascading to the + installed dependents; returns the next res.config action to execute. + """ + _logger.info("User #%d triggered quick module upgrade", self.env.uid) + return self._button_immediate_function( + self.env.registry[self._name].button_quick_upgrade + ) diff --git a/base_module_quick_update/readme/CONFIGURE.md b/base_module_quick_update/readme/CONFIGURE.md new file mode 100644 index 00000000000..8e518ecfbb8 --- /dev/null +++ b/base_module_quick_update/readme/CONFIGURE.md @@ -0,0 +1,4 @@ +This module works out of the box and requires no configuration. + +The **Quick upgrade** action is restricted to the *Administration / Settings* +(`base.group_system`) group, the same as the standard **Upgrade** action. diff --git a/base_module_quick_update/readme/CONTEXT.md b/base_module_quick_update/readme/CONTEXT.md new file mode 100644 index 00000000000..a5ccb55a8a9 --- /dev/null +++ b/base_module_quick_update/readme/CONTEXT.md @@ -0,0 +1,8 @@ +In standard Odoo, clicking **Upgrade** on a module also queues all installed +modules that depend on it. On large databases and during frequent development +this slows things down: often you only need to upgrade a single module, without +cascading upgrades of its dependents. + +A way is needed to start an upgrade for the selected module only (and install +any missing dependencies if they are not yet installed), without adding +reverse-dependencies to the upgrade. diff --git a/base_module_quick_update/readme/CONTRIBUTORS.md b/base_module_quick_update/readme/CONTRIBUTORS.md new file mode 100644 index 00000000000..6d3408a89b0 --- /dev/null +++ b/base_module_quick_update/readme/CONTRIBUTORS.md @@ -0,0 +1,4 @@ +- XXP \ + + - Project Manager Mikhail Lapin + - Developer Anton Balmakov diff --git a/base_module_quick_update/readme/DESCRIPTION.md b/base_module_quick_update/readme/DESCRIPTION.md new file mode 100644 index 00000000000..20f84422b5b --- /dev/null +++ b/base_module_quick_update/readme/DESCRIPTION.md @@ -0,0 +1,6 @@ +This module adds a **Quick upgrade** action on Apps — a fast upgrade of the +selected module without cascading upgrades of the installed modules that depend +on it. + +The regular **Upgrade** action keeps the standard Odoo behavior: the selected +module is upgraded together with the installed modules that depend on it. diff --git a/base_module_quick_update/readme/HISTORY.md b/base_module_quick_update/readme/HISTORY.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/base_module_quick_update/readme/USAGE.md b/base_module_quick_update/readme/USAGE.md new file mode 100644 index 00000000000..e55c4e1e22a --- /dev/null +++ b/base_module_quick_update/readme/USAGE.md @@ -0,0 +1,10 @@ +1. Install the `base_module_quick_update` module. +2. Open **Apps** and the card of the desired installed module. +3. Click **Quick upgrade** (on the form view and/or on the kanban / module + action menu). + ![Quick upgrade button](../static/description/img/quick_upgrade_button.png) +4. Wait for the upgrade to apply to the selected module only (without + mass-upgrading dependents). + +For comparison: the regular **Upgrade** button still upgrades the module +together with the installed modules that depend on it. diff --git a/base_module_quick_update/readme/newsfragments/.gitkeep b/base_module_quick_update/readme/newsfragments/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/base_module_quick_update/static/description/img/quick_upgrade_button.png b/base_module_quick_update/static/description/img/quick_upgrade_button.png new file mode 100644 index 00000000000..9bdf3da5205 Binary files /dev/null and b/base_module_quick_update/static/description/img/quick_upgrade_button.png differ diff --git a/base_module_quick_update/static/description/index.html b/base_module_quick_update/static/description/index.html new file mode 100644 index 00000000000..e276cc52156 --- /dev/null +++ b/base_module_quick_update/static/description/index.html @@ -0,0 +1,475 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Base Module Quick Update

+ +

Beta License: AGPL-3 OCA/server-tools Translate me on Weblate Try me on Runboat

+

This module adds a Quick upgrade action on Apps — a fast upgrade of +the selected module without cascading upgrades of the installed modules +that depend on it.

+

The regular Upgrade action keeps the standard Odoo behavior: the +selected module is upgraded together with the installed modules that +depend on it.

+

Table of contents

+ +
+

Use Cases / Context

+

In standard Odoo, clicking Upgrade on a module also queues all +installed modules that depend on it. On large databases and during +frequent development this slows things down: often you only need to +upgrade a single module, without cascading upgrades of its dependents.

+

A way is needed to start an upgrade for the selected module only (and +install any missing dependencies if they are not yet installed), without +adding reverse-dependencies to the upgrade.

+
+
+

Configuration

+

This module works out of the box and requires no configuration.

+

The Quick upgrade action is restricted to the Administration / +Settings (base.group_system) group, the same as the standard +Upgrade action.

+
+
+

Usage

+
    +
  1. Install the base_module_quick_update module.
  2. +
  3. Open Apps and the card of the desired installed module.
  4. +
  5. Click Quick upgrade (on the form view and/or on the kanban / +module action menu). Quick upgrade button
  6. +
  7. Wait for the upgrade to apply to the selected module only (without +mass-upgrading dependents).
  8. +
+

For comparison: the regular Upgrade button still upgrades the module +together with the installed modules that depend on it.

+
+ +
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • XXP
  • +
+
+
+

Contributors

+
    +
  • XXP <xxp-odoo.com>
      +
    • Project Manager Mikhail Lapin
    • +
    • Developer Anton Balmakov
    • +
    +
  • +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/server-tools project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+
+ + diff --git a/base_module_quick_update/tests/__init__.py b/base_module_quick_update/tests/__init__.py new file mode 100644 index 00000000000..14ec25c813b --- /dev/null +++ b/base_module_quick_update/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright (C) 2026 XXP +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import test_quick_upgrade diff --git a/base_module_quick_update/tests/test_quick_upgrade.py b/base_module_quick_update/tests/test_quick_upgrade.py new file mode 100644 index 00000000000..4a5ae3e3ce2 --- /dev/null +++ b/base_module_quick_update/tests/test_quick_upgrade.py @@ -0,0 +1,126 @@ +# Copyright (C) 2026 XXP +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from unittest.mock import patch + +from odoo.exceptions import UserError +from odoo.tests.common import TransactionCase + +MODULE_MODEL = "ir.module.module" +DEP_MODEL = "ir.module.module.dependency" + + +class TestQuickUpgrade(TransactionCase): + """Tests for the "Quick upgrade" action. + + The disk-backed helpers ``update_list`` and ``get_module_info`` are + stubbed so the tests run against controlled in-database fixtures and do + not depend on what is actually present on the addons path. + """ + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.Module = cls.env[MODULE_MODEL] + cls.Dependency = cls.env[DEP_MODEL] + + # Target module and an installed module that depends on it. + cls.module_a = cls.Module.create({"name": "test_qu_a", "state": "installed"}) + cls.module_b = cls.Module.create({"name": "test_qu_b", "state": "installed"}) + # test_qu_b depends on test_qu_a (reverse dependency of A). + cls.Dependency.create({"name": "test_qu_a", "module_id": cls.module_b.id}) + + # An uninstalled upstream dependency of the target module. + cls.module_c = cls.Module.create({"name": "test_qu_c", "state": "uninstalled"}) + cls.Dependency.create({"name": "test_qu_c", "module_id": cls.module_a.id}) + + def setUp(self): + super().setUp() + model_cls = type(self.Module) + patch_update = patch.object(model_cls, "update_list", lambda self: None) + patch_info = patch.object( + model_cls, + "get_module_info", + classmethod(lambda cls, name: {"installable": True}), + ) + patch_update.start() + patch_info.start() + self.addCleanup(patch_update.stop) + self.addCleanup(patch_info.stop) + + def test_quick_upgrade_does_not_cascade_to_dependents(self): + """Quick upgrade marks only the selected module, not its dependents.""" + self.module_a.button_quick_upgrade() + + self.assertEqual(self.module_a.state, "to upgrade") + self.assertEqual( + self.module_b.state, + "installed", + "Quick upgrade must not queue installed modules that depend " + "on the selected one.", + ) + + def test_standard_upgrade_cascades_to_dependents(self): + """Control: the standard upgrade still cascades to dependents.""" + self.module_a.button_upgrade() + + self.assertEqual(self.module_a.state, "to upgrade") + self.assertEqual( + self.module_b.state, + "to upgrade", + "Standard upgrade is expected to keep cascading to dependents.", + ) + + def test_quick_upgrade_installs_missing_upstream_dependency(self): + """A missing upstream dependency is still installed.""" + self.module_a.button_quick_upgrade() + + self.assertEqual(self.module_a.state, "to upgrade") + self.assertEqual( + self.module_c.state, + "to install", + "A missing upstream dependency must be queued for install.", + ) + + def test_quick_upgrade_requires_installed_module(self): + """Quick-upgrading a non-installed module raises the "not installed" error.""" + module_d = self.Module.create({"name": "test_qu_d", "state": "uninstalled"}) + with self.assertRaisesRegex( + UserError, + r"Can not upgrade module 'test_qu_d'\. It is not installed\.", + msg=( + "Quick upgrade of the uninstalled module test_qu_d must fail on " + "the state check with the 'not installed' message." + ), + ): + module_d.button_quick_upgrade() + + self.assertEqual( + module_d.state, + "uninstalled", + "A rejected quick upgrade must leave the module state untouched.", + ) + + def test_quick_upgrade_rejects_unknown_dependency(self): + """A dependency missing from the system raises the "not available" error.""" + module_e = self.Module.create({"name": "test_qu_e", "state": "installed"}) + self.Dependency.create({"name": "test_qu_missing", "module_id": module_e.id}) + + with self.assertRaisesRegex( + UserError, + r"depends on the module: test_qu_missing", + msg=( + "Quick upgrade of test_qu_e must fail on the dependency check " + "naming the unavailable module test_qu_missing, not on any " + "other UserError raised earlier in the flow." + ), + ): + module_e.button_quick_upgrade() + + def test_quick_upgrade_empty_recordset_is_noop(self): + """Calling the action on an empty recordset returns without error.""" + self.assertFalse( + self.Module.browse().button_quick_upgrade(), + "Quick upgrade on an empty recordset must return a falsy value " + "instead of an action or an error.", + ) diff --git a/base_module_quick_update/views/ir_module_views.xml b/base_module_quick_update/views/ir_module_views.xml new file mode 100644 index 00000000000..b6f5c44e8e3 --- /dev/null +++ b/base_module_quick_update/views/ir_module_views.xml @@ -0,0 +1,42 @@ + + + + + + ir.module.module.form.quick.upgrade + ir.module.module + + + + + + + + ir.module.module.kanban.quick.upgrade + ir.module.module + + + + Quick upgrade + + + + + diff --git a/setup/base_module_quick_update/odoo/addons/base_module_quick_update b/setup/base_module_quick_update/odoo/addons/base_module_quick_update new file mode 120000 index 00000000000..b9cb8f8ca1e --- /dev/null +++ b/setup/base_module_quick_update/odoo/addons/base_module_quick_update @@ -0,0 +1 @@ +../../../../base_module_quick_update \ No newline at end of file diff --git a/setup/base_module_quick_update/setup.py b/setup/base_module_quick_update/setup.py new file mode 100644 index 00000000000..28c57bb6403 --- /dev/null +++ b/setup/base_module_quick_update/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)