From 4761c9d895bc808ca64109de474b441ddb729469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Fri, 1 Sep 2023 16:14:23 +0200 Subject: [PATCH 01/43] Add 'odoo_project' Module listing all modules belonging to an Odoo project by leveraging the data provided by 'odoo_repository'. --- odoo_project/__init__.py | 2 + odoo_project/__manifest__.py | 21 ++++ odoo_project/models/__init__.py | 2 + odoo_project/models/odoo_module_branch.py | 15 +++ odoo_project/models/odoo_project.py | 117 ++++++++++++++++++ odoo_project/security/ir.model.access.csv | 4 + odoo_project/views/odoo_module_branch.xml | 37 ++++++ odoo_project/views/odoo_project.xml | 110 ++++++++++++++++ odoo_project/wizards/__init__.py | 1 + .../wizards/odoo_project_import_modules.py | 79 ++++++++++++ .../wizards/odoo_project_import_modules.xml | 33 +++++ 11 files changed, 421 insertions(+) create mode 100644 odoo_project/__init__.py create mode 100644 odoo_project/__manifest__.py create mode 100644 odoo_project/models/__init__.py create mode 100644 odoo_project/models/odoo_module_branch.py create mode 100644 odoo_project/models/odoo_project.py create mode 100644 odoo_project/security/ir.model.access.csv create mode 100644 odoo_project/views/odoo_module_branch.xml create mode 100644 odoo_project/views/odoo_project.xml create mode 100644 odoo_project/wizards/__init__.py create mode 100644 odoo_project/wizards/odoo_project_import_modules.py create mode 100644 odoo_project/wizards/odoo_project_import_modules.xml diff --git a/odoo_project/__init__.py b/odoo_project/__init__.py new file mode 100644 index 00000000..aee8895e --- /dev/null +++ b/odoo_project/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/odoo_project/__manifest__.py b/odoo_project/__manifest__.py new file mode 100644 index 00000000..64cd3410 --- /dev/null +++ b/odoo_project/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2023 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) +{ + "name": "Odoo Project", + "summary": "Analyze your Odoo projects code bases.", + "version": "16.0.1.0.0", + "category": "Tools", + "author": "Camptocamp, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/TODO", + "data": [ + "security/ir.model.access.csv", + "views/odoo_module_branch.xml", + "views/odoo_project.xml", + "wizards/odoo_project_import_modules.xml", + ], + "installable": True, + "depends": [ + "odoo_repository", + ], + "license": "AGPL-3", +} diff --git a/odoo_project/models/__init__.py b/odoo_project/models/__init__.py new file mode 100644 index 00000000..1a51d937 --- /dev/null +++ b/odoo_project/models/__init__.py @@ -0,0 +1,2 @@ +from . import odoo_project +from . import odoo_module_branch diff --git a/odoo_project/models/odoo_module_branch.py b/odoo_project/models/odoo_module_branch.py new file mode 100644 index 00000000..d1adb2a4 --- /dev/null +++ b/odoo_project/models/odoo_module_branch.py @@ -0,0 +1,15 @@ +# Copyright 2023 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import fields, models + + +class OdooModuleBranch(models.Model): + _inherit = "odoo.module.branch" + + odoo_project_ids = fields.Many2many( + comodel_name="odoo.project", + relation="odoo_project_module_branch_rel", + column1="module_branch_id", column2="odoo_project_id", + string="Projects", + ) diff --git a/odoo_project/models/odoo_project.py b/odoo_project/models/odoo_project.py new file mode 100644 index 00000000..ebb142a8 --- /dev/null +++ b/odoo_project/models/odoo_project.py @@ -0,0 +1,117 @@ +# Copyright 2023 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +import ast + +from odoo import api, fields, models + + +class OdooProject(models.Model): + _name = "odoo.project" + _inherits = {"odoo.repository": "repository_id"} + _description = "Odoo Project" + _order = "name" + + name = fields.Char(required=True, index=True) + active = fields.Boolean(default=True) + repository_id = fields.Many2one( + comodel_name="odoo.repository", + ondelete="restrict", + string="Repository", + domain=[ + ("clone_branch_id", "!=", False), + ("odoo_version_id", "!=", False), + ], + required=True, + ) + module_branch_ids = fields.Many2many( + comodel_name="odoo.module.branch", + relation="odoo_project_module_branch_rel", + column1="odoo_project_id", column2="module_branch_id", + string="Modules/Branch", + ) + module_ids = fields.Many2many( + comodel_name="odoo.module", + relation="odoo_project_module_rel", + column1="odoo_project_id", column2="module_id", + string="Modules", + compute="_compute_module_ids", + store=True, + ) + modules_count = fields.Integer(compute="_compute_modules_count") + module_not_installed_ids = fields.Many2many( + comodel_name="odoo.module.branch", + string="Modules not installed", + help="Modules available in the project repository but not installed.", + compute="_compute_module_not_installed_ids", + ) + unmerged_module_ids = fields.Many2many( + comodel_name="odoo.module.branch", + string="Modules to merge", + help="Modules installed belonging to an open PR.", + compute="_compute_unmerged_module_ids", + ) + unknown_module_ids = fields.Many2many( + comodel_name="odoo.module.branch", + string="Modules unknown", + help="Modules installed but cannot be found among repositories/branches.", + compute="_compute_unknown_module_ids", + ) + + @api.depends("module_branch_ids.module_id") + def _compute_module_ids(self): + for rec in self: + rec.module_ids = rec.module_branch_ids.module_id.ids + + @api.depends("module_branch_ids") + def _compute_modules_count(self): + for rec in self: + rec.modules_count = len(rec.module_branch_ids) + + @api.depends("repository_id.branch_ids.module_ids", "module_branch_ids") + def _compute_module_not_installed_ids(self): + for rec in self: + all_module_ids = set(rec.repository_id.branch_ids.module_ids.ids) + installed_module_ids = set(rec.module_branch_ids.ids) + rec.module_not_installed_ids = list(all_module_ids - installed_module_ids) + + @api.depends("module_branch_ids.pr_url") + def _compute_unmerged_module_ids(self): + for rec in self: + rec.unmerged_module_ids = rec.module_branch_ids.filtered( + lambda module: module.pr_url + ) + + @api.depends("module_branch_ids.repository_id") + def _compute_unknown_module_ids(self): + for rec in self: + rec.unknown_module_ids = rec.module_branch_ids.filtered( + lambda module: not module.repository_id + ) + + def open_import_modules(self): + """Open a wizard to import the modules of this Odoo project.""" + self.ensure_one() + action = self.env["ir.actions.actions"]._for_xml_id( + "odoo_project.odoo_project_import_modules_action" + ) + ctx = action.get("context", {}) + if isinstance(ctx, str): + ctx = ast.literal_eval(ctx) + ctx["default_odoo_project_id"] = self.id + action["context"] = ctx + return action + + def open_modules(self): + self.ensure_one() + action = self.env["ir.actions.actions"]._for_xml_id( + "odoo_repository.odoo_module_branch_action" + ) + ctx = action.get("context", {}) + if isinstance(ctx, str): + ctx = ast.literal_eval(ctx) + action["domain"] = [("id", "in", self.module_branch_ids.ids)] + ctx["search_default_group_by_org_id"] = 1 + ctx["search_default_group_by_repository_id"] = 2 + action["context"] = ctx + return action diff --git a/odoo_project/security/ir.model.access.csv b/odoo_project/security/ir.model.access.csv new file mode 100644 index 00000000..6cbba4ce --- /dev/null +++ b/odoo_project/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_odoo_project_user,odoo_project_user,model_odoo_project,base.group_user,1,1,1,0 +access_odoo_project_manager_manager,odoo_project_manager,model_odoo_project,base.group_system,1,1,1,1 +access_odoo_project_import_modules_user,odoo_project_import_modules_user,model_odoo_project_import_modules,base.group_user,1,1,1,1 diff --git a/odoo_project/views/odoo_module_branch.xml b/odoo_project/views/odoo_module_branch.xml new file mode 100644 index 00000000..84147403 --- /dev/null +++ b/odoo_project/views/odoo_module_branch.xml @@ -0,0 +1,37 @@ + + + + + + odoo.module.branch.form.inherit + odoo.module.branch + + + + + + + + + + + + odoo.module.branch.search.inherit + odoo.module.branch + + + + + + + + + + + + + diff --git a/odoo_project/views/odoo_project.xml b/odoo_project/views/odoo_project.xml new file mode 100644 index 00000000..f782bc9f --- /dev/null +++ b/odoo_project/views/odoo_project.xml @@ -0,0 +1,110 @@ + + + + + + odoo.project.form + odoo.project + +
+
+
+ + +
+ +
+
+

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + odoo.project.tree + odoo.project + + + + + + + + + + + odoo.project.search + odoo.project + search + + + + + + + + + + + + + + + Projects + ir.actions.act_window + odoo.project + + + + + +
diff --git a/odoo_project/wizards/__init__.py b/odoo_project/wizards/__init__.py new file mode 100644 index 00000000..7b4f991f --- /dev/null +++ b/odoo_project/wizards/__init__.py @@ -0,0 +1 @@ +from . import odoo_project_import_modules diff --git a/odoo_project/wizards/odoo_project_import_modules.py b/odoo_project/wizards/odoo_project_import_modules.py new file mode 100644 index 00000000..b68abbe3 --- /dev/null +++ b/odoo_project/wizards/odoo_project_import_modules.py @@ -0,0 +1,79 @@ +# Copyright 2023 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +import re + +from odoo import fields, models + + +class OdooProjectImportModules(models.TransientModel): + _name = "odoo.project.import.modules" + _description = "Import modules for an Odoo project" + + odoo_project_id = fields.Many2one( + comodel_name="odoo.project", + string="Project", + required=True, + ) + modules_list = fields.Text( + string="Modules List", + help=( + "Copy/paste your list of technical module names here.\n" + "They can be separated by spaces, tabulations, comma or any other " + "special characters." + ), + required=True, + ) + + def action_import(self): + """Import the modules for the given Odoo project.""" + self.ensure_one() + self.odoo_project_id.module_branch_ids = False + module_names = ( + re.split(r"\W+", self.modules_list) if self.modules_list else [] + ) + module_names = list(filter(None, module_names)) + module_branch_ids = [] + for module_name in module_names: + module = self._get_module(module_name) + module_branch = self._get_module_branch(module) + module_branch_ids.append(module_branch.id) + self.odoo_project_id.module_branch_ids = module_branch_ids + return True + + def _get_module(self, module_name): + """Return a `odoo.module` record. + + If it doesn't exist it'll be automatically created. + """ + module_model = self.env["odoo.module"] + module = module_model.search([("name", "=", module_name)]) + if not module: + module = module_model.sudo().create({"name": module_name}) + return module + + def _get_module_branch(self, module): + """Return a `odoo.module.branch` record for the project. + + If it doesn't exist it'll be automatically created. + """ + module_branch_model = self.env["odoo.module.branch"] + args = [ + ("module_id", "=", module.id), + ("branch_id", "=", self.odoo_project_id.odoo_version_id.id), + ] + module_branch = module_branch_model.search(args) + if not module_branch: + # Create the module to make it available for the project + branch = self.odoo_project_id.odoo_version_id + values = { + "module_id": module.id, + "branch_id": branch.id, + } + module_branch = module_branch_model.sudo().create(values) + if not module_branch.repository_branch_id: + # If the module hasn't been found in existing repositories content, + # it could be available somewhere on GitHub as a PR that could help + # to identity its repository + module_branch.with_delay().action_find_pr_url() + return module_branch diff --git a/odoo_project/wizards/odoo_project_import_modules.xml b/odoo_project/wizards/odoo_project_import_modules.xml new file mode 100644 index 00000000..60af61ef --- /dev/null +++ b/odoo_project/wizards/odoo_project_import_modules.xml @@ -0,0 +1,33 @@ + + + + + + odoo.project.import.modules.form + odoo.project.import.modules + +
+ + + + + + +
+
+
+
+
+ + + Import Project Modules + ir.actions.act_window + odoo.project.import.modules + form + new + + +
From 5a30eede4d424cd989e175b076ca30830111eefe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Fri, 3 Nov 2023 11:13:16 +0100 Subject: [PATCH 02/43] New data model 'odoo.project.module' This new data model is here to distinguish available upstream modules and installed modules in a project. It inherits from `odoo.module.branch` so it has access to all its data, but is linked to an `odoo.project` and has its own `installed_version` so it becomes easy to find modules that could be upgraded within a project. --- odoo_project/__manifest__.py | 2 + odoo_project/models/__init__.py | 1 + odoo_project/models/odoo_module_branch.py | 14 +++- odoo_project/models/odoo_project.py | 41 +++++----- odoo_project/models/odoo_project_module.py | 38 ++++++++++ odoo_project/security/ir.model.access.csv | 1 + odoo_project/views/menu.xml | 11 +++ odoo_project/views/odoo_module_branch.xml | 2 +- odoo_project/views/odoo_project.xml | 14 ++-- odoo_project/views/odoo_project_module.xml | 75 +++++++++++++++++++ .../wizards/odoo_project_import_modules.py | 58 ++++++++++---- 11 files changed, 215 insertions(+), 42 deletions(-) create mode 100644 odoo_project/models/odoo_project_module.py create mode 100644 odoo_project/views/menu.xml create mode 100644 odoo_project/views/odoo_project_module.xml diff --git a/odoo_project/__manifest__.py b/odoo_project/__manifest__.py index 64cd3410..89c6da71 100644 --- a/odoo_project/__manifest__.py +++ b/odoo_project/__manifest__.py @@ -9,8 +9,10 @@ "website": "https://github.com/OCA/TODO", "data": [ "security/ir.model.access.csv", + "views/menu.xml", "views/odoo_module_branch.xml", "views/odoo_project.xml", + "views/odoo_project_module.xml", "wizards/odoo_project_import_modules.xml", ], "installable": True, diff --git a/odoo_project/models/__init__.py b/odoo_project/models/__init__.py index 1a51d937..c59b850c 100644 --- a/odoo_project/models/__init__.py +++ b/odoo_project/models/__init__.py @@ -1,2 +1,3 @@ from . import odoo_project +from . import odoo_project_module from . import odoo_module_branch diff --git a/odoo_project/models/odoo_module_branch.py b/odoo_project/models/odoo_module_branch.py index d1adb2a4..d0232ef7 100644 --- a/odoo_project/models/odoo_module_branch.py +++ b/odoo_project/models/odoo_module_branch.py @@ -1,15 +1,27 @@ # Copyright 2023 Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -from odoo import fields, models +from odoo import api, fields, models class OdooModuleBranch(models.Model): _inherit = "odoo.module.branch" + odoo_project_module_ids = fields.One2many( + comodel_name="odoo.project.module", + inverse_name="module_branch_id", + string="Deployed Modules", + ) odoo_project_ids = fields.Many2many( comodel_name="odoo.project", relation="odoo_project_module_branch_rel", column1="module_branch_id", column2="odoo_project_id", string="Projects", + compute="_compute_module_ids", + store=True, ) + + @api.depends("odoo_project_module_ids.odoo_project_id") + def _compute_module_ids(self): + for rec in self: + rec.odoo_project_ids = rec.odoo_project_module_ids.odoo_project_id.ids diff --git a/odoo_project/models/odoo_project.py b/odoo_project/models/odoo_project.py index ebb142a8..596d0d18 100644 --- a/odoo_project/models/odoo_project.py +++ b/odoo_project/models/odoo_project.py @@ -24,11 +24,10 @@ class OdooProject(models.Model): ], required=True, ) - module_branch_ids = fields.Many2many( - comodel_name="odoo.module.branch", - relation="odoo_project_module_branch_rel", - column1="odoo_project_id", column2="module_branch_id", - string="Modules/Branch", + project_module_ids = fields.One2many( + comodel_name="odoo.project.module", + inverse_name="odoo_project_id", + string="Deployed Modules", ) module_ids = fields.Many2many( comodel_name="odoo.module", @@ -58,35 +57,39 @@ class OdooProject(models.Model): compute="_compute_unknown_module_ids", ) - @api.depends("module_branch_ids.module_id") + @api.depends("project_module_ids.module_id") def _compute_module_ids(self): for rec in self: - rec.module_ids = rec.module_branch_ids.module_id.ids + rec.module_ids = rec.project_module_ids.module_id.ids - @api.depends("module_branch_ids") + @api.depends("project_module_ids") def _compute_modules_count(self): for rec in self: - rec.modules_count = len(rec.module_branch_ids) + rec.modules_count = len(rec.project_module_ids) - @api.depends("repository_id.branch_ids.module_ids", "module_branch_ids") + @api.depends("repository_id.branch_ids.module_ids", "project_module_ids") def _compute_module_not_installed_ids(self): for rec in self: all_module_ids = set(rec.repository_id.branch_ids.module_ids.ids) - installed_module_ids = set(rec.module_branch_ids.ids) + installed_module_ids = set(rec.project_module_ids.ids) rec.module_not_installed_ids = list(all_module_ids - installed_module_ids) - @api.depends("module_branch_ids.pr_url") + @api.depends("project_module_ids.pr_url") def _compute_unmerged_module_ids(self): for rec in self: - rec.unmerged_module_ids = rec.module_branch_ids.filtered( - lambda module: module.pr_url + rec.unmerged_module_ids = ( + rec.project_module_ids.module_branch_id.filtered( + lambda module: module.pr_url + ) ) - @api.depends("module_branch_ids.repository_id") + @api.depends("project_module_ids.repository_id") def _compute_unknown_module_ids(self): for rec in self: - rec.unknown_module_ids = rec.module_branch_ids.filtered( - lambda module: not module.repository_id + rec.unknown_module_ids = ( + rec.project_module_ids.module_branch_id.filtered( + lambda module: not module.repository_id + ) ) def open_import_modules(self): @@ -105,12 +108,12 @@ def open_import_modules(self): def open_modules(self): self.ensure_one() action = self.env["ir.actions.actions"]._for_xml_id( - "odoo_repository.odoo_module_branch_action" + "odoo_project.odoo_project_module_action" ) ctx = action.get("context", {}) if isinstance(ctx, str): ctx = ast.literal_eval(ctx) - action["domain"] = [("id", "in", self.module_branch_ids.ids)] + action["domain"] = [("id", "in", self.project_module_ids.ids)] ctx["search_default_group_by_org_id"] = 1 ctx["search_default_group_by_repository_id"] = 2 action["context"] = ctx diff --git a/odoo_project/models/odoo_project_module.py b/odoo_project/models/odoo_project_module.py new file mode 100644 index 00000000..031ceac4 --- /dev/null +++ b/odoo_project/models/odoo_project_module.py @@ -0,0 +1,38 @@ +# Copyright 2023 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import api, fields, models +from odoo.tools.parse_version import parse_version as v + + +class OdooProjectModule(models.Model): + _name = "odoo.project.module" + _inherits = {"odoo.module.branch": "module_branch_id"} + _description = "Odoo Project Module" + _order = "name" + + odoo_project_id = fields.Many2one( + comodel_name="odoo.project", + ondelete="cascade", + string="Project", + ) + module_branch_id = fields.Many2one( + comodel_name="odoo.module.branch", + ondelete="set null", + string="Upstream Module", + required=True, + ) + installed_version = fields.Char() + to_upgrade = fields.Boolean( + string="To Upgrade", + compute="_compute_to_upgrade", + store=True, + ) + + @api.depends("version", "installed_version") + def _compute_to_upgrade(self): + for rec in self: + rec.to_upgrade = False + installed_version = rec.installed_version or rec.version + if installed_version and rec.version: + rec.to_upgrade = v(installed_version) < v(rec.version) diff --git a/odoo_project/security/ir.model.access.csv b/odoo_project/security/ir.model.access.csv index 6cbba4ce..209d57da 100644 --- a/odoo_project/security/ir.model.access.csv +++ b/odoo_project/security/ir.model.access.csv @@ -1,4 +1,5 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_odoo_project_user,odoo_project_user,model_odoo_project,base.group_user,1,1,1,0 access_odoo_project_manager_manager,odoo_project_manager,model_odoo_project,base.group_system,1,1,1,1 +access_odoo_project_module_user,odoo_project_module_user,model_odoo_project_module,base.group_user,1,0,0,0 access_odoo_project_import_modules_user,odoo_project_import_modules_user,model_odoo_project_import_modules,base.group_user,1,1,1,1 diff --git a/odoo_project/views/menu.xml b/odoo_project/views/menu.xml new file mode 100644 index 00000000..07e8d4bb --- /dev/null +++ b/odoo_project/views/menu.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/odoo_project/views/odoo_module_branch.xml b/odoo_project/views/odoo_module_branch.xml index 84147403..0bbf4cf5 100644 --- a/odoo_project/views/odoo_module_branch.xml +++ b/odoo_project/views/odoo_module_branch.xml @@ -23,7 +23,7 @@ + domain="[('odoo_project_ids', '!=', False)]"/> diff --git a/odoo_project/views/odoo_project.xml b/odoo_project/views/odoo_project.xml index f782bc9f..acbbbe8f 100644 --- a/odoo_project/views/odoo_project.xml +++ b/odoo_project/views/odoo_project.xml @@ -13,7 +13,7 @@ string="Import modules" class="btn-primary"/> - +
- + @@ -70,7 +70,7 @@ - +
@@ -83,13 +83,12 @@ + -
@@ -103,8 +102,7 @@ + parent="odoo_project_main_menu" + action="odoo_project_action"/> diff --git a/odoo_project/views/odoo_project_module.xml b/odoo_project/views/odoo_project_module.xml new file mode 100644 index 00000000..1bc5483a --- /dev/null +++ b/odoo_project/views/odoo_project_module.xml @@ -0,0 +1,75 @@ + + + + + + odoo.project.module.form.inherit + odoo.project.module + + primary + + + + + + Last Version + + + + + + odoo.project.module.tree.inherit + odoo.project.module + + primary + + + to_upgrade + pr_url + + + hide + + + + + + + Last Version + + + + show + + + + + + odoo.project.module.search.inherit + odoo.project.module + + primary + + + + + + + + + + + + Installed Modules + ir.actions.act_window + odoo.project.module + + + + + + diff --git a/odoo_project/wizards/odoo_project_import_modules.py b/odoo_project/wizards/odoo_project_import_modules.py index b68abbe3..a3cba9b7 100644 --- a/odoo_project/wizards/odoo_project_import_modules.py +++ b/odoo_project/wizards/odoo_project_import_modules.py @@ -19,8 +19,9 @@ class OdooProjectImportModules(models.TransientModel): string="Modules List", help=( "Copy/paste your list of technical module names here.\n" - "They can be separated by spaces, tabulations, comma or any other " - "special characters." + "One module per line, with an optional version number placed " + "after the module name separated by any special character " + "(space, tabulation, comma...)." ), required=True, ) @@ -28,17 +29,25 @@ class OdooProjectImportModules(models.TransientModel): def action_import(self): """Import the modules for the given Odoo project.""" self.ensure_one() - self.odoo_project_id.module_branch_ids = False - module_names = ( - re.split(r"\W+", self.modules_list) if self.modules_list else [] - ) - module_names = list(filter(None, module_names)) - module_branch_ids = [] - for module_name in module_names: + self.odoo_project_id.sudo().project_module_ids = False + module_lines = list(filter(None, self.modules_list.split("\n"))) + # module_names = ( + # re.split(r"\W+", self.modules_list) if self.modules_list else [] + # ) + # module_names = list(filter(None, module_names)) + project_module_ids = [] + for line in module_lines: + data = re.split(r"\W+", line, maxsplit=1) + if len(data) > 1: + module_name, version = data + else: + module_name, version = data[0], False + # for module_name in module_names: module = self._get_module(module_name) module_branch = self._get_module_branch(module) - module_branch_ids.append(module_branch.id) - self.odoo_project_id.module_branch_ids = module_branch_ids + project_module = self._get_project_module(module_branch, version) + project_module_ids.append(project_module.id) + self.odoo_project_id.sudo().project_module_ids = project_module_ids return True def _get_module(self, module_name): @@ -53,7 +62,7 @@ def _get_module(self, module_name): return module def _get_module_branch(self, module): - """Return a `odoo.module.branch` record for the project. + """Return a `odoo.module.branch` record. If it doesn't exist it'll be automatically created. """ @@ -64,7 +73,7 @@ def _get_module_branch(self, module): ] module_branch = module_branch_model.search(args) if not module_branch: - # Create the module to make it available for the project + # Create the module branch = self.odoo_project_id.odoo_version_id values = { "module_id": module.id, @@ -77,3 +86,26 @@ def _get_module_branch(self, module): # to identity its repository module_branch.with_delay().action_find_pr_url() return module_branch + + def _get_project_module(self, module_branch, version): + """Return a `odoo.project.module` record for the project. + + If it doesn't exist it'll be automatically created. + """ + project_module_model = self.env["odoo.project.module"] + args = [ + ("module_branch_id", "=", module_branch.id), + ("odoo_project_id", "=", self.odoo_project_id.id), + ] + project_module = project_module_model.search(args) + values = { + "module_branch_id": module_branch.id, + "odoo_project_id": self.odoo_project_id.id, + "installed_version": version, + } + if project_module: + project_module.sudo().write(values) + else: + # Create the module to make it available for the project + project_module = project_module_model.sudo().create(values) + return project_module From 80b70df9c89beb4d03ad3fd2f3acf032106813b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Fri, 3 Nov 2023 11:36:16 +0100 Subject: [PATCH 03/43] odoo_project: fix computation of uninstalled modules --- odoo_project/models/odoo_project.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/odoo_project/models/odoo_project.py b/odoo_project/models/odoo_project.py index 596d0d18..fd473789 100644 --- a/odoo_project/models/odoo_project.py +++ b/odoo_project/models/odoo_project.py @@ -67,11 +67,11 @@ def _compute_modules_count(self): for rec in self: rec.modules_count = len(rec.project_module_ids) - @api.depends("repository_id.branch_ids.module_ids", "project_module_ids") + @api.depends("repository_id.branch_ids.module_ids", "project_module_ids.module_branch_id") def _compute_module_not_installed_ids(self): for rec in self: all_module_ids = set(rec.repository_id.branch_ids.module_ids.ids) - installed_module_ids = set(rec.project_module_ids.ids) + installed_module_ids = set(rec.project_module_ids.module_branch_id.ids) rec.module_not_installed_ids = list(all_module_ids - installed_module_ids) @api.depends("project_module_ids.pr_url") From 182d85c39b0e2f1a49c8646c24e08864c65c4e74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Fri, 3 Nov 2023 12:13:31 +0100 Subject: [PATCH 04/43] Apply pre-commit --- odoo_project/README.rst | 61 +++ odoo_project/__manifest__.py | 2 +- odoo_project/models/odoo_module_branch.py | 3 +- odoo_project/models/odoo_project.py | 19 +- odoo_project/models/odoo_project_module.py | 1 - odoo_project/readme/CONTRIBUTORS.rst | 2 + odoo_project/readme/DESCRIPTION.rst | 1 + odoo_project/static/description/index.html | 417 ++++++++++++++++++ odoo_project/views/menu.xml | 12 +- odoo_project/views/odoo_module_branch.xml | 29 +- odoo_project/views/odoo_project.xml | 82 ++-- odoo_project/views/odoo_project_module.xml | 35 +- .../wizards/odoo_project_import_modules.py | 3 +- .../wizards/odoo_project_import_modules.xml | 15 +- 14 files changed, 601 insertions(+), 81 deletions(-) create mode 100644 odoo_project/README.rst create mode 100644 odoo_project/readme/CONTRIBUTORS.rst create mode 100644 odoo_project/readme/DESCRIPTION.rst create mode 100644 odoo_project/static/description/index.html diff --git a/odoo_project/README.rst b/odoo_project/README.rst new file mode 100644 index 00000000..e948e679 --- /dev/null +++ b/odoo_project/README.rst @@ -0,0 +1,61 @@ +============ +Odoo Project +============ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:dc350fed0f23a205eb546f1673d2c5e99b4f6cdb5be6d6ab76a010e2517e203b + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/licence-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-camptocamp%2Fodoo--repository-lightgray.png?logo=github + :target: https://github.com/camptocamp/odoo-repository/tree/16.0/odoo_project + :alt: camptocamp/odoo-repository + +|badge1| |badge2| |badge3| + +This module allows to declare your Odoo projects and analyze their code bases. + +**Table of contents** + +.. contents:: + :local: + +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 +~~~~~~~ + +* Camptocamp + +Contributors +~~~~~~~~~~~~ + +* Camptocamp + * Sébastien Alix + +Maintainers +~~~~~~~~~~~ + +This module is part of the `camptocamp/odoo-repository `_ project on GitHub. + +You are welcome to contribute. diff --git a/odoo_project/__manifest__.py b/odoo_project/__manifest__.py index 89c6da71..4ff208db 100644 --- a/odoo_project/__manifest__.py +++ b/odoo_project/__manifest__.py @@ -6,7 +6,7 @@ "version": "16.0.1.0.0", "category": "Tools", "author": "Camptocamp, Odoo Community Association (OCA)", - "website": "https://github.com/OCA/TODO", + "website": "https://github.com/camptocamp/odoo-repository", "data": [ "security/ir.model.access.csv", "views/menu.xml", diff --git a/odoo_project/models/odoo_module_branch.py b/odoo_project/models/odoo_module_branch.py index d0232ef7..6f190121 100644 --- a/odoo_project/models/odoo_module_branch.py +++ b/odoo_project/models/odoo_module_branch.py @@ -15,7 +15,8 @@ class OdooModuleBranch(models.Model): odoo_project_ids = fields.Many2many( comodel_name="odoo.project", relation="odoo_project_module_branch_rel", - column1="module_branch_id", column2="odoo_project_id", + column1="module_branch_id", + column2="odoo_project_id", string="Projects", compute="_compute_module_ids", store=True, diff --git a/odoo_project/models/odoo_project.py b/odoo_project/models/odoo_project.py index fd473789..7bf51e9b 100644 --- a/odoo_project/models/odoo_project.py +++ b/odoo_project/models/odoo_project.py @@ -32,7 +32,8 @@ class OdooProject(models.Model): module_ids = fields.Many2many( comodel_name="odoo.module", relation="odoo_project_module_rel", - column1="odoo_project_id", column2="module_id", + column1="odoo_project_id", + column2="module_id", string="Modules", compute="_compute_module_ids", store=True, @@ -67,7 +68,9 @@ def _compute_modules_count(self): for rec in self: rec.modules_count = len(rec.project_module_ids) - @api.depends("repository_id.branch_ids.module_ids", "project_module_ids.module_branch_id") + @api.depends( + "repository_id.branch_ids.module_ids", "project_module_ids.module_branch_id" + ) def _compute_module_not_installed_ids(self): for rec in self: all_module_ids = set(rec.repository_id.branch_ids.module_ids.ids) @@ -77,19 +80,15 @@ def _compute_module_not_installed_ids(self): @api.depends("project_module_ids.pr_url") def _compute_unmerged_module_ids(self): for rec in self: - rec.unmerged_module_ids = ( - rec.project_module_ids.module_branch_id.filtered( - lambda module: module.pr_url - ) + rec.unmerged_module_ids = rec.project_module_ids.module_branch_id.filtered( + lambda module: module.pr_url ) @api.depends("project_module_ids.repository_id") def _compute_unknown_module_ids(self): for rec in self: - rec.unknown_module_ids = ( - rec.project_module_ids.module_branch_id.filtered( - lambda module: not module.repository_id - ) + rec.unknown_module_ids = rec.project_module_ids.module_branch_id.filtered( + lambda module: not module.repository_id ) def open_import_modules(self): diff --git a/odoo_project/models/odoo_project_module.py b/odoo_project/models/odoo_project_module.py index 031ceac4..2beb93c3 100644 --- a/odoo_project/models/odoo_project_module.py +++ b/odoo_project/models/odoo_project_module.py @@ -24,7 +24,6 @@ class OdooProjectModule(models.Model): ) installed_version = fields.Char() to_upgrade = fields.Boolean( - string="To Upgrade", compute="_compute_to_upgrade", store=True, ) diff --git a/odoo_project/readme/CONTRIBUTORS.rst b/odoo_project/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..a0c91e35 --- /dev/null +++ b/odoo_project/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Camptocamp + * Sébastien Alix diff --git a/odoo_project/readme/DESCRIPTION.rst b/odoo_project/readme/DESCRIPTION.rst new file mode 100644 index 00000000..a86173ee --- /dev/null +++ b/odoo_project/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module allows to declare your Odoo projects and analyze their code bases. diff --git a/odoo_project/static/description/index.html b/odoo_project/static/description/index.html new file mode 100644 index 00000000..747faf31 --- /dev/null +++ b/odoo_project/static/description/index.html @@ -0,0 +1,417 @@ + + + + + + +Odoo Project + + + +
+

Odoo Project

+ + +

Beta License: AGPL-3 camptocamp/odoo-repository

+

This module allows to declare your Odoo projects and analyze their code bases.

+

Table of contents

+ +
+

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

+
    +
  • Camptocamp
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is part of the camptocamp/odoo-repository project on GitHub.

+

You are welcome to contribute.

+
+
+
+ + diff --git a/odoo_project/views/menu.xml b/odoo_project/views/menu.xml index 07e8d4bb..60def18d 100644 --- a/odoo_project/views/menu.xml +++ b/odoo_project/views/menu.xml @@ -1,11 +1,13 @@ - + - + diff --git a/odoo_project/views/odoo_module_branch.xml b/odoo_project/views/odoo_module_branch.xml index 0bbf4cf5..570b8254 100644 --- a/odoo_project/views/odoo_module_branch.xml +++ b/odoo_project/views/odoo_module_branch.xml @@ -1,4 +1,4 @@ - + @@ -6,11 +6,11 @@ odoo.module.branch.form.inherit odoo.module.branch - + - + @@ -19,17 +19,26 @@ odoo.module.branch.search.inherit odoo.module.branch - + - - + + - + diff --git a/odoo_project/views/odoo_project.xml b/odoo_project/views/odoo_project.xml index acbbbe8f..4c28bf52 100644 --- a/odoo_project/views/odoo_project.xml +++ b/odoo_project/views/odoo_project.xml @@ -1,4 +1,4 @@ - + @@ -9,33 +9,45 @@
-
- +
-
-

+

- + - - - + + + @@ -43,17 +55,17 @@ - - - + + + - - + + @@ -68,9 +80,9 @@ odoo.project - - - + + + @@ -81,14 +93,20 @@ search - - - + + + - - + + @@ -98,11 +116,13 @@ Projects ir.actions.act_window odoo.project - + - + diff --git a/odoo_project/views/odoo_project_module.xml b/odoo_project/views/odoo_project_module.xml index 1bc5483a..6feac27a 100644 --- a/odoo_project/views/odoo_project_module.xml +++ b/odoo_project/views/odoo_project_module.xml @@ -1,4 +1,4 @@ - + @@ -6,11 +6,11 @@ odoo.project.module.form.inherit odoo.project.module - + primary - + Last Version @@ -21,7 +21,7 @@ odoo.project.module.tree.inherit odoo.project.module - + primary @@ -32,12 +32,12 @@ hide - - + + Last Version - + show @@ -48,15 +48,18 @@ odoo.project.module.search.inherit odoo.project.module - + primary - + - + @@ -65,11 +68,13 @@ Installed Modules ir.actions.act_window odoo.project.module - + - + diff --git a/odoo_project/wizards/odoo_project_import_modules.py b/odoo_project/wizards/odoo_project_import_modules.py index a3cba9b7..d7de8ac6 100644 --- a/odoo_project/wizards/odoo_project_import_modules.py +++ b/odoo_project/wizards/odoo_project_import_modules.py @@ -16,7 +16,6 @@ class OdooProjectImportModules(models.TransientModel): required=True, ) modules_list = fields.Text( - string="Modules List", help=( "Copy/paste your list of technical module names here.\n" "One module per line, with an optional version number placed " @@ -42,7 +41,7 @@ def action_import(self): module_name, version = data else: module_name, version = data[0], False - # for module_name in module_names: + # for module_name in module_names: module = self._get_module(module_name) module_branch = self._get_module_branch(module) project_module = self._get_project_module(module_branch, version) diff --git a/odoo_project/wizards/odoo_project_import_modules.xml b/odoo_project/wizards/odoo_project_import_modules.xml index 60af61ef..34a3da32 100644 --- a/odoo_project/wizards/odoo_project_import_modules.xml +++ b/odoo_project/wizards/odoo_project_import_modules.xml @@ -1,4 +1,4 @@ - + @@ -10,13 +10,18 @@ - - + +
-
From e5f1c9fb1108b6f2601e9c86fddba44c48a11e87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Fri, 3 Nov 2023 12:43:00 +0100 Subject: [PATCH 05/43] Remove dead code --- odoo_project/wizards/odoo_project_import_modules.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/odoo_project/wizards/odoo_project_import_modules.py b/odoo_project/wizards/odoo_project_import_modules.py index d7de8ac6..2bf1adb0 100644 --- a/odoo_project/wizards/odoo_project_import_modules.py +++ b/odoo_project/wizards/odoo_project_import_modules.py @@ -30,10 +30,6 @@ def action_import(self): self.ensure_one() self.odoo_project_id.sudo().project_module_ids = False module_lines = list(filter(None, self.modules_list.split("\n"))) - # module_names = ( - # re.split(r"\W+", self.modules_list) if self.modules_list else [] - # ) - # module_names = list(filter(None, module_names)) project_module_ids = [] for line in module_lines: data = re.split(r"\W+", line, maxsplit=1) From d17239768083ec3942746e0148b3861ca151e01c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Sat, 4 Nov 2023 15:22:41 +0100 Subject: [PATCH 06/43] odoo_project: enable chatter on 'odoo.project' --- odoo_project/models/odoo_project.py | 1 + odoo_project/views/odoo_project.xml | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/odoo_project/models/odoo_project.py b/odoo_project/models/odoo_project.py index 7bf51e9b..e7d816e0 100644 --- a/odoo_project/models/odoo_project.py +++ b/odoo_project/models/odoo_project.py @@ -9,6 +9,7 @@ class OdooProject(models.Model): _name = "odoo.project" _inherits = {"odoo.repository": "repository_id"} + _inherit = "mail.thread" _description = "Odoo Project" _order = "name" diff --git a/odoo_project/views/odoo_project.xml b/odoo_project/views/odoo_project.xml index 4c28bf52..e5da2fd4 100644 --- a/odoo_project/views/odoo_project.xml +++ b/odoo_project/views/odoo_project.xml @@ -71,6 +71,10 @@
+
+ + +
From 864ed434b4b9a0240165c596a57e1bae489acc4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Sat, 4 Nov 2023 16:56:06 +0100 Subject: [PATCH 07/43] odoo_project: fix warning regarding '_inherits' --- odoo_project/models/odoo_project_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/odoo_project/models/odoo_project_module.py b/odoo_project/models/odoo_project_module.py index 2beb93c3..5868eb4c 100644 --- a/odoo_project/models/odoo_project_module.py +++ b/odoo_project/models/odoo_project_module.py @@ -18,7 +18,7 @@ class OdooProjectModule(models.Model): ) module_branch_id = fields.Many2one( comodel_name="odoo.module.branch", - ondelete="set null", + ondelete="cascade", string="Upstream Module", required=True, ) From c2904fad027167cb3da57e93333334b063568981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Fri, 24 Nov 2023 12:03:14 +0100 Subject: [PATCH 08/43] odoo_project: global 'Installed modules' menu opening pivot view --- odoo_project/models/odoo_project.py | 2 +- odoo_project/views/odoo_project_module.xml | 36 ++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/odoo_project/models/odoo_project.py b/odoo_project/models/odoo_project.py index e7d816e0..a67625fc 100644 --- a/odoo_project/models/odoo_project.py +++ b/odoo_project/models/odoo_project.py @@ -108,7 +108,7 @@ def open_import_modules(self): def open_modules(self): self.ensure_one() action = self.env["ir.actions.actions"]._for_xml_id( - "odoo_project.odoo_project_module_action" + "odoo_project.odoo_project_module_for_project_action" ) ctx = action.get("context", {}) if isinstance(ctx, str): diff --git a/odoo_project/views/odoo_project_module.xml b/odoo_project/views/odoo_project_module.xml index 6feac27a..70004833 100644 --- a/odoo_project/views/odoo_project_module.xml +++ b/odoo_project/views/odoo_project_module.xml @@ -28,10 +28,14 @@ to_upgrade pr_url + + + hide + @@ -61,6 +65,22 @@ domain="[('to_upgrade', '=', True)]" />
+ + + {'group_by': 'odoo_project_id'} + +
+ + + + odoo.project.module.pivot + odoo.project.module + + + + + + @@ -68,6 +88,22 @@ Installed Modules ir.actions.act_window odoo.project.module + tree,form,pivot + + + + + + pivot + + + + + + Installed Modules + ir.actions.act_window + odoo.project.module + tree,form,pivot From 7b022d72aa999782d3a165c34cba8dadfcc79979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Fri, 1 Dec 2023 09:41:17 +0100 Subject: [PATCH 09/43] odoo_project: add migration scripts indicator New boolean field on installed modules to tell if some migration scripts have to be played between the installed version and the latest version available. --- odoo_project/models/odoo_project_module.py | 43 ++++++++++++++++++++++ odoo_project/views/odoo_project_module.xml | 14 ++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/odoo_project/models/odoo_project_module.py b/odoo_project/models/odoo_project_module.py index 5868eb4c..71b9f3b0 100644 --- a/odoo_project/models/odoo_project_module.py +++ b/odoo_project/models/odoo_project_module.py @@ -27,6 +27,11 @@ class OdooProjectModule(models.Model): compute="_compute_to_upgrade", store=True, ) + migration_scripts = fields.Boolean( + compute="_compute_migration_scripts", + store=True, + help="Available migration scripts between installed and last version.", + ) @api.depends("version", "installed_version") def _compute_to_upgrade(self): @@ -35,3 +40,41 @@ def _compute_to_upgrade(self): installed_version = rec.installed_version or rec.version if installed_version and rec.version: rec.to_upgrade = v(installed_version) < v(rec.version) + + @api.depends( + "to_upgrade", + "installed_version", + "version_ids.name", + "version_ids.has_migration_script", + ) + def _compute_migration_scripts(self): + for rec in self: + rec.migration_scripts = False + if not rec.to_upgrade: + continue + installed_version = rec._get_installed_version() + versions_with_mig_script = rec.version_ids.filtered( + lambda v: ( + v.sequence > installed_version.sequence and v.has_migration_script + ) + ) + rec.migration_scripts = bool(versions_with_mig_script) + + def _get_installed_version(self): + self.ensure_one() + installed_version = self.version_ids.browse() + if not self.installed_version: + return installed_version + # Installed version could not be available in inventoried versions + # if it is coming from a pending-merge. In such case we take the last + # matching version as the installed one. + # - Available versions upstream = "14.0.2.0.0" & "14.0.2.1.0" + # - Installed version = "14.0.2.0.1" (in a pending-merge) + # - Computed installed version = "14.0.2.0.0" + inst_ver = [int(n) for n in self.installed_version.split(".")] + for version in self.version_ids.sorted("sequence"): + ver = [int(n) for n in version.name.split(".")] + if ver > inst_ver: + break + installed_version = version + return installed_version diff --git a/odoo_project/views/odoo_project_module.xml b/odoo_project/views/odoo_project_module.xml index 70004833..1d0da748 100644 --- a/odoo_project/views/odoo_project_module.xml +++ b/odoo_project/views/odoo_project_module.xml @@ -12,6 +12,9 @@ + + + Last Version @@ -29,16 +32,18 @@ pr_url - + hide - + + + Last Version @@ -64,6 +69,11 @@ string="To Upgrade" domain="[('to_upgrade', '=', True)]" /> + From f46450cecacf5bd909e6db6f7ce8a44976c95719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Fri, 19 Apr 2024 18:24:43 +0200 Subject: [PATCH 10/43] odoo_project: compute nearest installed version --- odoo_project/models/odoo_project_module.py | 52 +++++++++++++--------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/odoo_project/models/odoo_project_module.py b/odoo_project/models/odoo_project_module.py index 71b9f3b0..1c5af9cd 100644 --- a/odoo_project/models/odoo_project_module.py +++ b/odoo_project/models/odoo_project_module.py @@ -22,7 +22,17 @@ class OdooProjectModule(models.Model): string="Upstream Module", required=True, ) - installed_version = fields.Char() + installed_version = fields.Char(help="Installed version in project database.") + installed_version_id = fields.Many2one( + comodel_name="odoo.module.branch.version", + string="Nearest installed version", + help=( + "If the real installed version is not available in the history of " + "versions (could come from a pending merge not scanned), this field " + "computes the nearest version available." + ), + compute="_compute_installed_version_id", + ) to_upgrade = fields.Boolean( compute="_compute_to_upgrade", store=True, @@ -33,6 +43,25 @@ class OdooProjectModule(models.Model): help="Available migration scripts between installed and last version.", ) + @api.depends("installed_version") + def _compute_installed_version_id(self): + for rec in self: + rec.installed_version_id = rec.version_ids.browse() + if not rec.installed_version: + continue + # Installed version could not be available in inventoried versions + # if it is coming from a pending-merge. In such case we take the + # nearest version matching the installed one. + # - Available versions upstream = "14.0.2.0.0" & "14.0.2.1.0" + # - Installed version = "14.0.2.0.1" (in a pending-merge) + # - Computed installed version = "14.0.2.0.0" + inst_ver = [int(n) for n in rec.installed_version.split(".")] + for version in rec.version_ids.sorted("sequence"): + ver = [int(n) for n in version.name.split(".")] + if ver > inst_ver: + break + rec.installed_version_id = version + @api.depends("version", "installed_version") def _compute_to_upgrade(self): for rec in self: @@ -52,29 +81,10 @@ def _compute_migration_scripts(self): rec.migration_scripts = False if not rec.to_upgrade: continue - installed_version = rec._get_installed_version() + installed_version = rec.installed_version_id versions_with_mig_script = rec.version_ids.filtered( lambda v: ( v.sequence > installed_version.sequence and v.has_migration_script ) ) rec.migration_scripts = bool(versions_with_mig_script) - - def _get_installed_version(self): - self.ensure_one() - installed_version = self.version_ids.browse() - if not self.installed_version: - return installed_version - # Installed version could not be available in inventoried versions - # if it is coming from a pending-merge. In such case we take the last - # matching version as the installed one. - # - Available versions upstream = "14.0.2.0.0" & "14.0.2.1.0" - # - Installed version = "14.0.2.0.1" (in a pending-merge) - # - Computed installed version = "14.0.2.0.0" - inst_ver = [int(n) for n in self.installed_version.split(".")] - for version in self.version_ids.sorted("sequence"): - ver = [int(n) for n in version.name.split(".")] - if ver > inst_ver: - break - installed_version = version - return installed_version From b164a14e1274bacc20984c8cfab11524e822a201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Fri, 19 Apr 2024 18:49:42 +0200 Subject: [PATCH 11/43] odoo_project: add a button to recheck unknown modules --- odoo_project/models/odoo_project.py | 5 +++++ odoo_project/views/odoo_project.xml | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/odoo_project/models/odoo_project.py b/odoo_project/models/odoo_project.py index a67625fc..5b4a88ad 100644 --- a/odoo_project/models/odoo_project.py +++ b/odoo_project/models/odoo_project.py @@ -118,3 +118,8 @@ def open_modules(self): ctx["search_default_group_by_repository_id"] = 2 action["context"] = ctx return action + + def action_find_unknown_modules(self): + """Try to locate unknown modules.""" + for module in self.unknown_module_ids: + module.action_find_pr_url() diff --git a/odoo_project/views/odoo_project.xml b/odoo_project/views/odoo_project.xml index e5da2fd4..4cd80d9c 100644 --- a/odoo_project/views/odoo_project.xml +++ b/odoo_project/views/odoo_project.xml @@ -68,6 +68,12 @@ + + + + + + From c42f20f1d721cda741e5af26402aa2e66cdbcd73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Fri, 18 Oct 2024 14:35:41 +0200 Subject: [PATCH 22/43] [REF] Allow same module name across different repositories This change allows to get a module `x` present in different repositories (unicity constraint on `odoo.module.branch` has changed accordingly). This is useful in case two projects have `x` installed but the code of these modules (that can be different) are hosted in their own project repository. Therefore, the scan of repositories and project modules import have been adapted to fullfil this new possibility. Features: - allows multiple instances of `` records sharing the same module technical name and branch but belonging to different repositories, - repositories have now a new `specific` flag, propagated to their modules.. By default they host generic modules, but project repositories will host specific modules that cannot be shared to other projects, - a generic module cannot depend on a specific module - when importing modules in a project, specific modules of other projects cannot be mapped, - once a specific module is scanned in a project repository, relevant orphaned modules installed in a project will be re-mapped to this newly created specific module, - detect PR only on generic modules (unmerged/pending modules are generic only, specific modules have to be found in project repositories). --- odoo_project/data/queue_job.xml | 16 ++ odoo_project/models/odoo_module_branch.py | 29 +++ odoo_project/models/odoo_project.py | 1 + odoo_project/models/odoo_project_module.py | 18 ++ odoo_project/tests/__init__.py | 1 + odoo_project/tests/common.py | 29 +++ odoo_project/tests/test_import_modules.py | 165 ++++++++++++++++++ .../wizards/odoo_project_import_modules.py | 26 ++- 8 files changed, 269 insertions(+), 16 deletions(-) create mode 100644 odoo_project/data/queue_job.xml create mode 100644 odoo_project/tests/__init__.py create mode 100644 odoo_project/tests/common.py create mode 100644 odoo_project/tests/test_import_modules.py diff --git a/odoo_project/data/queue_job.xml b/odoo_project/data/queue_job.xml new file mode 100644 index 00000000..641669a7 --- /dev/null +++ b/odoo_project/data/queue_job.xml @@ -0,0 +1,16 @@ + + + + + + + _remap_to_specific_module + + + + + diff --git a/odoo_project/models/odoo_module_branch.py b/odoo_project/models/odoo_module_branch.py index 6f190121..66969759 100644 --- a/odoo_project/models/odoo_module_branch.py +++ b/odoo_project/models/odoo_module_branch.py @@ -26,3 +26,32 @@ class OdooModuleBranch(models.Model): def _compute_module_ids(self): for rec in self: rec.odoo_project_ids = rec.odoo_project_module_ids.odoo_project_id.ids + + def _filter_module_to_update(self, repo_branch, module_branch): + # A module found in a specific repository cannot be linked to an orphaned + # module that is already listed/installed in different projects (chances + # are these projects refer to a generic version of the module). + # For such specific module we want to create a dedicated 'odoo.module.branch'. + if ( + repo_branch.repository_id.specific + # Orphaned module + and module_branch + and not module_branch.repository_id + # Installed in multiple projects + # NOTE: we could have multiple project instances using the same + # repository, so we check the underlying repo instead + and len(module_branch.odoo_project_ids.repository_id) > 1 + ): + repo_projects = repo_branch.repository_id.project_ids + if repo_projects: + # Corner case: if the current project(s) are referring to this + # orphaned module, remove them as they will now have a dedicated + # specific module created. + # Spawn a job to re-map project modules to the new (not yet created) + # specific module. + project_modules = module_branch.odoo_project_module_ids.filtered( + lambda o: o.odoo_project_id in repo_projects + ) + project_modules.with_delay()._remap_to_specific_module() + return False + return module_branch diff --git a/odoo_project/models/odoo_project.py b/odoo_project/models/odoo_project.py index c0adb013..074197f5 100644 --- a/odoo_project/models/odoo_project.py +++ b/odoo_project/models/odoo_project.py @@ -20,6 +20,7 @@ class OdooProject(models.Model): string="Repository", domain=[ ("clone_branch_id", "!=", False), + ("specific", "=", True), ("odoo_version_id", "!=", False), ], help=( diff --git a/odoo_project/models/odoo_project_module.py b/odoo_project/models/odoo_project_module.py index 988f168a..c4015aaf 100644 --- a/odoo_project/models/odoo_project_module.py +++ b/odoo_project/models/odoo_project_module.py @@ -129,3 +129,21 @@ def open_recursive_dependencies(self): ) action["domain"] = [("id", "in", project_dependencies.ids)] return action + + def _remap_to_specific_module(self): + """Re-map orphaned project modules. + + As soon as an orphaned module has been scanned as a specific module + of a project repository, all project modules that were targeting this + orphaned module will inherit from this newly specific module instead. + """ + for rec in self: + if not rec.odoo_project_id.repository_id: + continue + specific_module_branch = self.env["odoo.module.branch"]._get_module_branch( + rec.branch_id, + rec.module_id, + repo=rec.odoo_project_id.repository_id, + ) + if specific_module_branch: + rec.write({"module_branch_id": specific_module_branch.id}) diff --git a/odoo_project/tests/__init__.py b/odoo_project/tests/__init__.py new file mode 100644 index 00000000..9c82a9f7 --- /dev/null +++ b/odoo_project/tests/__init__.py @@ -0,0 +1 @@ +from . import test_import_modules diff --git a/odoo_project/tests/common.py b/odoo_project/tests/common.py new file mode 100644 index 00000000..2506670c --- /dev/null +++ b/odoo_project/tests/common.py @@ -0,0 +1,29 @@ +# Copyright 2024 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo.tests.common import Form + +from odoo.addons.odoo_repository.tests import common + + +class Common(common.Common): + def setUp(self): + super().setUp() + self.project = self.env["odoo.project"].create( + { + "name": "TEST", + "odoo_version_id": self.branch.id, + } + ) + self.wiz_model = self.env["odoo.project.import.modules"] + + @classmethod + def _run_import_modules(cls, project, modules_list_text, **kwargs): + wiz_model = cls.env["odoo.project.import.modules"].with_context( + default_odoo_project_id=project.id + ) + with Form(wiz_model) as form: + form.modules_list = modules_list_text + wiz = form.save() + wiz.action_import() + return wiz diff --git a/odoo_project/tests/test_import_modules.py b/odoo_project/tests/test_import_modules.py new file mode 100644 index 00000000..390fdcd4 --- /dev/null +++ b/odoo_project/tests/test_import_modules.py @@ -0,0 +1,165 @@ +# Copyright 2024 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from .common import Common + + +class TestImportModules(Common): + def test_import_modules_names(self): + mod1 = "test1" + mod2 = "test2" + # Ensure these modules don't exist + existing_mods = self.module_branch_model.search( + [("module_name", "in", [mod1, mod2])] + ) + self.assertFalse(existing_mods) + # Import them through the wizard + modules_list_text = f"{mod1}\n{mod2}" + self._run_import_modules(self.project, modules_list_text) + # They now exists as orphaned modules and are linked to the project + existing_mods = self.module_branch_model.search( + [ + ("module_name", "in", [mod1, mod2]), + ("repository_id", "=", False), + ("odoo_project_ids", "=", self.project.id), + ] + ) + self.assertEqual(len(existing_mods), 2) + # Project modules are also created + self.assertEqual(len(existing_mods.odoo_project_module_ids), 2) + + def test_import_modules_names_versions(self): + mod1 = "test1" + mod2 = "test2" + # Ensure these modules don't exist + existing_mods = self.module_branch_model.search( + [("module_name", "in", [mod1, mod2])] + ) + self.assertFalse(existing_mods) + # Import them through the wizard with their installed versions + modules_list_text = f"{mod1} 1.0.0\n{mod2} {self.branch}.1.0.0" + self._run_import_modules(self.project, modules_list_text) + # They now exists as orphaned modules and are linked to the project + existing_mods = self.module_branch_model.search( + [ + ("module_name", "in", [mod1, mod2]), + ("repository_id", "=", False), + ("odoo_project_ids", "=", self.project.id), + ] + ) + self.assertEqual(len(existing_mods), 2) + # Project modules are also created + self.assertEqual(len(existing_mods.odoo_project_module_ids), 2) + pm1 = existing_mods.odoo_project_module_ids.filtered( + lambda m: m.module_name == mod1 + ) + self.assertEqual(pm1.installed_version, "1.0.0") + pm2 = existing_mods.odoo_project_module_ids.filtered( + lambda m: m.module_name == mod2 + ) + self.assertEqual(pm2.installed_version, f"{self.branch}.1.0.0") + + def test_match_blacklisted_module(self): + mod1 = "test1" + mod2 = "test2" + mod1_blacklisted = self.wiz_model._get_module(mod1) + mod1_blacklisted.blacklisted = True + # Import them through the wizard + modules_list_text = f"{mod1}\n{mod2}" + self._run_import_modules(self.project, modules_list_text) + # Only one module has been imported as an orphaned module + existing_mods = self.module_branch_model.search( + [ + ("module_name", "in", [mod1, mod2]), + ("repository_id", "=", False), + ("odoo_project_ids", "=", self.project.id), + ] + ) + self.assertEqual(len(existing_mods), 1) + # Project modules are also created + self.assertEqual(len(existing_mods.odoo_project_module_ids), 1) + + def test_match_orphaned_module(self): + mod1 = "test1" + mod2 = "test2" + mod1_orphaned = self.wiz_model._get_module(mod1) + mod1_branch_orphaned = self.module_branch_model._create_orphaned_module_branch( + self.branch, mod1_orphaned + ) + # Import them through the wizard + modules_list_text = f"{mod1}\n{mod2}" + self._run_import_modules(self.project, modules_list_text) + # They now exists as orphaned modules and are linked to the project + existing_mods = self.module_branch_model.search( + [ + ("module_name", "in", [mod1, mod2]), + ("repository_id", "=", False), + ("odoo_project_ids", "=", self.project.id), + ] + ) + self.assertEqual(len(existing_mods), 2) + # One of them matched the existing orphaned module + self.assertIn(mod1_branch_orphaned, existing_mods) + # Project modules are also created + self.assertEqual(len(existing_mods.odoo_project_module_ids), 2) + + def test_match_generic_module(self): + mod1 = "test1" + mod2 = "test2" + mod1_generic = self.wiz_model._get_module(mod1) + repo_branch = self._create_odoo_repository_branch( + self.odoo_repository, self.branch + ) + mod1_branch_generic = self._create_odoo_module_branch( + mod1_generic, + self.branch, + specific=False, + repository_branch_id=repo_branch.id, + ) + # Import them through the wizard + modules_list_text = f"{mod1}\n{mod2}" + self._run_import_modules(self.project, modules_list_text) + # They now exists and are linked to the project + existing_mods = self.module_branch_model.search( + [ + ("module_name", "in", [mod1, mod2]), + ("odoo_project_ids", "=", self.project.id), + ] + ) + self.assertEqual(len(existing_mods), 2) + # One of them matched the existing generic module + self.assertIn(mod1_branch_generic, existing_mods) + # Project modules are also created + self.assertEqual(len(existing_mods.odoo_project_module_ids), 2) + + def test_match_project_repo_module(self): + # Assign a repository to the project + self.odoo_repository.odoo_version_id = self.branch + self.project.repository_id = self.odoo_repository + mod1 = "test1" + mod2 = "test2" + mod1_in_repo = self.wiz_model._get_module(mod1) + repo_branch = self._create_odoo_repository_branch( + self.odoo_repository, self.branch + ) + mod1_branch_in_repo = self._create_odoo_module_branch( + mod1_in_repo, + self.branch, + specific=True, + repository_branch_id=repo_branch.id, + ) + # Import them through the wizard + modules_list_text = f"{mod1}\n{mod2}" + self._run_import_modules(self.project, modules_list_text) + # They now exists and are linked to the project + existing_mods = self.module_branch_model.search( + [ + ("module_name", "in", [mod1, mod2]), + ("odoo_project_ids", "=", self.project.id), + ] + ) + self.assertEqual(len(existing_mods), 2) + # One of them matched the existing module hosted in project repository + self.assertIn(mod1_branch_in_repo, existing_mods) + # Project modules are also created + self.assertEqual(len(existing_mods.odoo_project_module_ids), 2) diff --git a/odoo_project/wizards/odoo_project_import_modules.py b/odoo_project/wizards/odoo_project_import_modules.py index f3b43c72..42858cc7 100644 --- a/odoo_project/wizards/odoo_project_import_modules.py +++ b/odoo_project/wizards/odoo_project_import_modules.py @@ -67,6 +67,8 @@ def _action_import_modules_list(self): module_name, version = data[0], False # for module_name in module_names: module = self._get_module(module_name) + if module.blacklisted: + continue module_branch = self._get_module_branch(module) project_module = self._get_project_module(module_branch, version) project_module_ids.append(project_module.id) @@ -108,20 +110,12 @@ def _get_module_branch(self, module): If it doesn't exist it'll be automatically created. """ module_branch_model = self.env["odoo.module.branch"] - args = [ - ("module_id", "=", module.id), - ("branch_id", "=", self.odoo_project_id.odoo_version_id.id), - ] - module_branch = module_branch_model.search(args) - if not module_branch: - # Create the module - branch = self.odoo_project_id.odoo_version_id - values = { - "module_id": module.id, - "branch_id": branch.id, - } - module_branch = module_branch_model.sudo().create(values) - if not module.blacklisted and not module_branch.repository_branch_id: + module_branch = False + branch = self.odoo_project_id.odoo_version_id + module_branch = module_branch_model._find_or_create( + branch, module, self.odoo_project_id.repository_id + ) + if not module_branch.repository_branch_id and not module_branch.specific: # If the module hasn't been found in existing repositories content, # it could be available somewhere on GitHub as a PR that could help # to identity its repository @@ -134,11 +128,11 @@ def _get_project_module(self, module_branch, version): If it doesn't exist it'll be automatically created. """ project_module_model = self.env["odoo.project.module"] - args = [ + domain = [ ("module_branch_id", "=", module_branch.id), ("odoo_project_id", "=", self.odoo_project_id.id), ] - project_module = project_module_model.search(args) + project_module = project_module_model.search(domain) values = { "module_branch_id": module_branch.id, "odoo_project_id": self.odoo_project_id.id, From 9f1828318331495c272d5097a7b2b2ca7225f94a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Thu, 31 Oct 2024 09:05:28 +0100 Subject: [PATCH 23/43] odoo_repository_*: rename some filters --- odoo_project/views/odoo_module_branch.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/odoo_project/views/odoo_module_branch.xml b/odoo_project/views/odoo_module_branch.xml index 570b8254..47fc3c52 100644 --- a/odoo_project/views/odoo_module_branch.xml +++ b/odoo_project/views/odoo_module_branch.xml @@ -21,14 +21,14 @@ odoo.module.branch - + From 8ba0956ca41a2df0ee0045571736e412fd00b84a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Tue, 26 Nov 2024 11:25:17 +0100 Subject: [PATCH 24/43] odoo_project: add notebook in form view --- odoo_project/views/odoo_project.xml | 72 +++++++++++++++-------------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/odoo_project/views/odoo_project.xml b/odoo_project/views/odoo_project.xml index 7b5b4e7d..59502512 100644 --- a/odoo_project/views/odoo_project.xml +++ b/odoo_project/views/odoo_project.xml @@ -54,40 +54,44 @@ options="{'no_open': True}" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

-
- - - + + + + Modules inst. + + + +
+

+ +

+
+ + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - + + + + + diff --git a/odoo_project/wizards/odoo_project_import_modules.xml b/odoo_project/wizards/odoo_project_import_modules.xml index e6f10598..2e8f7ec5 100644 --- a/odoo_project/wizards/odoo_project_import_modules.xml +++ b/odoo_project/wizards/odoo_project_import_modules.xml @@ -2,58 +2,59 @@ - - - odoo.project.import.modules.form - odoo.project.import.modules - -
- - - - - -
+ odoo.project.import.modules.form + odoo.project.import.modules + + + + + + + + - - - -
+ + + - The list can be copied-pasted from columns 'Technical Name' and 'Latest Version' put side by side from a standard Odoo export of modules installed.
+ -
-
- - - -
-
-
- -
- - - - Import Project Modules - ir.actions.act_window - odoo.project.import.modules - form - new - +
@@ -434,7 +434,7 @@

Maintainers

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/module-composition-analysis project on GitHub.

+

This module is part of the OCA/module-composition-analysis project on GitHub.

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

diff --git a/odoo_project/tests/common.py b/odoo_project/tests/common.py index 2506670c..b387a9d5 100644 --- a/odoo_project/tests/common.py +++ b/odoo_project/tests/common.py @@ -1,7 +1,7 @@ # Copyright 2024 Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -from odoo.tests.common import Form +from odoo.tests import Form from odoo.addons.odoo_repository.tests import common @@ -26,4 +26,5 @@ def _run_import_modules(cls, project, modules_list_text, **kwargs): form.modules_list = modules_list_text wiz = form.save() wiz.action_import() + cls.env.flush_all() # Force fields computation return wiz diff --git a/odoo_project/views/odoo_project.xml b/odoo_project/views/odoo_project.xml index f9db14a8..ac0e1556 100644 --- a/odoo_project/views/odoo_project.xml +++ b/odoo_project/views/odoo_project.xml @@ -51,14 +51,14 @@
@@ -69,16 +69,14 @@ nolabel="1" colspan="2" > - + - +
- + - + - +
@@ -101,10 +99,10 @@ nolabel="1" colspan="2" > - + - +
From 28f438f9faca7d88df7f47b76502db8913707e85 Mon Sep 17 00:00:00 2001 From: oca-ci Date: Fri, 3 Apr 2026 12:40:40 +0000 Subject: [PATCH 39/43] [UPD] Update odoo_project.pot --- odoo_project/i18n/odoo_project.pot | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/odoo_project/i18n/odoo_project.pot b/odoo_project/i18n/odoo_project.pot index 170bac0d..0921c123 100644 --- a/odoo_project/i18n/odoo_project.pot +++ b/odoo_project/i18n/odoo_project.pot @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 16.0\n" +"Project-Id-Version: Odoo Server 18.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: \n" "Language-Team: \n" @@ -379,13 +379,6 @@ msgstr "" msgid "JavaScript source lines of code" msgstr "" -#. module: odoo_project -#: model:ir.model.fields,field_description:odoo_project.field_odoo_project____last_update -#: model:ir.model.fields,field_description:odoo_project.field_odoo_project_import_modules____last_update -#: model:ir.model.fields,field_description:odoo_project.field_odoo_project_module____last_update -msgid "Last Modified on" -msgstr "" - #. module: odoo_project #: model:ir.model.fields,field_description:odoo_project.field_odoo_project_module__last_scanned_commit msgid "Last Scanned Commit" @@ -421,11 +414,6 @@ msgstr "" msgid "License" msgstr "" -#. module: odoo_project -#: model:ir.model.fields,field_description:odoo_project.field_odoo_project__message_main_attachment_id -msgid "Main Attachment" -msgstr "" - #. module: odoo_project #: model:ir.model.fields,field_description:odoo_project.field_odoo_project_module__maintainer_ids msgid "Maintainers" From 1c7f5b5322eb8600b80958d4bdc5ca35624919ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Fri, 10 Apr 2026 10:00:35 +0200 Subject: [PATCH 40/43] [IMP] odoo_project: tests, rename common class and consolidate in setUpClass --- odoo_project/tests/common.py | 15 ++++++++------- odoo_project/tests/test_import_modules.py | 12 ++++++------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/odoo_project/tests/common.py b/odoo_project/tests/common.py index b387a9d5..15f00a30 100644 --- a/odoo_project/tests/common.py +++ b/odoo_project/tests/common.py @@ -6,20 +6,21 @@ from odoo.addons.odoo_repository.tests import common -class Common(common.Common): - def setUp(self): - super().setUp() - self.project = self.env["odoo.project"].create( +class ProjectCommon(common.Common): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.project = cls.env["odoo.project"].create( { "name": "TEST", - "odoo_version_id": self.branch.id, + "odoo_version_id": cls.branch.id, } ) - self.wiz_model = self.env["odoo.project.import.modules"] + cls.wiz_import_modules_model = cls.env["odoo.project.import.modules"] @classmethod def _run_import_modules(cls, project, modules_list_text, **kwargs): - wiz_model = cls.env["odoo.project.import.modules"].with_context( + wiz_model = cls.wiz_import_modules_model.with_context( default_odoo_project_id=project.id ) with Form(wiz_model) as form: diff --git a/odoo_project/tests/test_import_modules.py b/odoo_project/tests/test_import_modules.py index 361de268..2f3167bf 100644 --- a/odoo_project/tests/test_import_modules.py +++ b/odoo_project/tests/test_import_modules.py @@ -1,10 +1,10 @@ # Copyright 2024 Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -from .common import Common +from .common import ProjectCommon -class TestImportModules(Common): +class TestImportModules(ProjectCommon): def test_import_modules_names(self): mod1 = "test1" mod2 = "test2" @@ -62,7 +62,7 @@ def test_import_modules_names_versions(self): def test_match_blacklisted_module(self): mod1 = "test1" mod2 = "test2" - mod1_blacklisted = self.wiz_model._get_module(mod1) + mod1_blacklisted = self.wiz_import_modules_model._get_module(mod1) mod1_blacklisted.blacklisted = True # Import them through the wizard modules_list_text = f"{mod1}\n{mod2}" @@ -82,7 +82,7 @@ def test_match_blacklisted_module(self): def test_match_orphaned_module(self): mod1 = "test1" mod2 = "test2" - mod1_orphaned = self.wiz_model._get_module(mod1) + mod1_orphaned = self.wiz_import_modules_model._get_module(mod1) mod1_branch_orphaned = self.module_branch_model._create_orphaned_module_branch( self.branch, mod1_orphaned ) @@ -106,7 +106,7 @@ def test_match_orphaned_module(self): def test_match_generic_module(self): mod1 = "test1" mod2 = "test2" - mod1_generic = self.wiz_model._get_module(mod1) + mod1_generic = self.wiz_import_modules_model._get_module(mod1) repo_branch = self._create_odoo_repository_branch( self.odoo_repository, self.branch ) @@ -138,7 +138,7 @@ def test_match_project_repo_module(self): self.project.odoo_version_id = self.branch mod1 = "test1" mod2 = "test2" - mod1_in_repo = self.wiz_model._get_module(mod1) + mod1_in_repo = self.wiz_import_modules_model._get_module(mod1) repo_branch = self._create_odoo_repository_branch( self.odoo_repository, self.branch ) From 09ce2973046d947eb23432d45fb0fb39f8a9245f Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Fri, 10 Apr 2026 08:17:22 +0000 Subject: [PATCH 41/43] [BOT] post-merge updates --- odoo_project/README.rst | 2 +- odoo_project/__manifest__.py | 2 +- odoo_project/static/description/index.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/odoo_project/README.rst b/odoo_project/README.rst index 9b967099..09c88e4a 100644 --- a/odoo_project/README.rst +++ b/odoo_project/README.rst @@ -11,7 +11,7 @@ Odoo Project !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:3331542182c08aa9be85d1710d7fea6e5eb548ce72a9634c889bee41d66da216 + !! source digest: sha256:6b7ed4e8b5f4bc6826a8930f0ac173efea20e75fd5c8494b13a035b785812f2a !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/odoo_project/__manifest__.py b/odoo_project/__manifest__.py index eee01808..3a6a2f6a 100644 --- a/odoo_project/__manifest__.py +++ b/odoo_project/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Odoo Project", "summary": "Analyze your Odoo projects code bases.", - "version": "18.0.1.0.0", + "version": "18.0.1.0.1", "category": "Tools", "author": "Camptocamp, Odoo Community Association (OCA)", "website": "https://github.com/OCA/module-composition-analysis", diff --git a/odoo_project/static/description/index.html b/odoo_project/static/description/index.html index 848eb740..26c19620 100644 --- a/odoo_project/static/description/index.html +++ b/odoo_project/static/description/index.html @@ -372,7 +372,7 @@

Odoo Project

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:3331542182c08aa9be85d1710d7fea6e5eb548ce72a9634c889bee41d66da216 +!! source digest: sha256:6b7ed4e8b5f4bc6826a8930f0ac173efea20e75fd5c8494b13a035b785812f2a !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: AGPL-3 OCA/module-composition-analysis Translate me on Weblate Try me on Runboat

This module allows you to declare your Odoo projects with the list of From fb2ac6e3250ecd51130cb3c82a17c9b0210ff471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Alan=20Ramos=20Rodr=C3=ADguez?= Date: Wed, 29 Jul 2026 06:41:29 -0600 Subject: [PATCH 42/43] [MIG] odoo_project: Migration to 19.0 --- odoo_project/README.rst | 10 +++++----- odoo_project/__manifest__.py | 3 ++- odoo_project/models/odoo_project.py | 7 ++++--- odoo_project/static/description/index.html | 6 +++--- odoo_project/views/odoo_project.xml | 22 ++++++++++------------ 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/odoo_project/README.rst b/odoo_project/README.rst index 09c88e4a..71ea8f6d 100644 --- a/odoo_project/README.rst +++ b/odoo_project/README.rst @@ -21,13 +21,13 @@ Odoo Project :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fmodule--composition--analysis-lightgray.png?logo=github - :target: https://github.com/OCA/module-composition-analysis/tree/18.0/odoo_project + :target: https://github.com/OCA/module-composition-analysis/tree/19.0/odoo_project :alt: OCA/module-composition-analysis .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/module-composition-analysis-18-0/module-composition-analysis-18-0-odoo_project + :target: https://translation.odoo-community.org/projects/module-composition-analysis-19-0/module-composition-analysis-19-0-odoo_project :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/module-composition-analysis&target_branch=18.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/module-composition-analysis&target_branch=19.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -57,7 +57,7 @@ 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 `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -89,6 +89,6 @@ 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/module-composition-analysis `_ project on GitHub. +This module is part of the `OCA/module-composition-analysis `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/odoo_project/__manifest__.py b/odoo_project/__manifest__.py index 3a6a2f6a..f00616d7 100644 --- a/odoo_project/__manifest__.py +++ b/odoo_project/__manifest__.py @@ -3,11 +3,12 @@ { "name": "Odoo Project", "summary": "Analyze your Odoo projects code bases.", - "version": "18.0.1.0.1", + "version": "19.0.1.0.0", "category": "Tools", "author": "Camptocamp, Odoo Community Association (OCA)", "website": "https://github.com/OCA/module-composition-analysis", "data": [ + "data/queue_job.xml", "security/ir.model.access.csv", "views/menu.xml", "views/odoo_module_branch.xml", diff --git a/odoo_project/models/odoo_project.py b/odoo_project/models/odoo_project.py index 6f613e6e..070a52e3 100644 --- a/odoo_project/models/odoo_project.py +++ b/odoo_project/models/odoo_project.py @@ -79,9 +79,10 @@ class OdooProject(models.Model): compute="_compute_unknown_module_ids", ) - _sql_constraints = [ - ("name_uniq", "UNIQUE (name)", "This project already exists."), - ] + _name_uniq = models.Constraint( + "UNIQUE (name)", + "This project already exists.", + ) @api.depends("repository_id") def _compute_available_odoo_version_ids(self): diff --git a/odoo_project/static/description/index.html b/odoo_project/static/description/index.html index 26c19620..ec1beb1e 100644 --- a/odoo_project/static/description/index.html +++ b/odoo_project/static/description/index.html @@ -374,7 +374,7 @@

Odoo Project

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:6b7ed4e8b5f4bc6826a8930f0ac173efea20e75fd5c8494b13a035b785812f2a !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 OCA/module-composition-analysis Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/module-composition-analysis Translate me on Weblate Try me on Runboat

This module allows you to declare your Odoo projects with the list of installed modules.

Based on the data collected by odoo_repository module, it will:

@@ -405,7 +405,7 @@

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.

+feedback.

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

@@ -434,7 +434,7 @@

Maintainers

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/module-composition-analysis project on GitHub.

+

This module is part of the OCA/module-composition-analysis project on GitHub.

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

diff --git a/odoo_project/views/odoo_project.xml b/odoo_project/views/odoo_project.xml index ac0e1556..9b00387e 100644 --- a/odoo_project/views/odoo_project.xml +++ b/odoo_project/views/odoo_project.xml @@ -141,18 +141,16 @@ - - - - + + From 70e5fdab3f1c41bb53b8cf3474096abc03654cde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Alan=20Ramos=20Rodr=C3=ADguez?= Date: Wed, 29 Jul 2026 07:17:39 -0600 Subject: [PATCH 43/43] [FIX] odoo_project: do not crash on malformed installed versions --- odoo_project/models/odoo_project_module.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/odoo_project/models/odoo_project_module.py b/odoo_project/models/odoo_project_module.py index 767179bc..3bb5d678 100644 --- a/odoo_project/models/odoo_project_module.py +++ b/odoo_project/models/odoo_project_module.py @@ -65,9 +65,16 @@ def _compute_installed_version_id(self): # - Available versions upstream = "14.0.2.0.0" & "14.0.2.1.0" # - Installed version = "14.0.2.0.1" (in a pending-merge) # - Computed installed version = "14.0.2.0.0" - inst_ver = [int(n) for n in rec.installed_version.split(".")] + try: + inst_ver = [int(n) for n in rec.installed_version.split(".")] + except ValueError: + # Malformed user-entered version (e.g. "15.0.1.0.") + continue for version in rec.version_ids.sorted("sequence"): - ver = [int(n) for n in version.name.split(".")] + try: + ver = [int(n) for n in version.name.split(".")] + except ValueError: + continue if ver > inst_ver: break rec.installed_version_id = version