From f3673d324683677462c8bc24e27cde72cfdb66ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Tue, 26 Nov 2024 10:33:47 +0100 Subject: [PATCH 1/2] odoo_project: fix access right typo --- odoo_project/security/ir.model.access.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/odoo_project/security/ir.model.access.csv b/odoo_project/security/ir.model.access.csv index 2321cdcb..48748488 100644 --- a/odoo_project/security/ir.model.access.csv +++ b/odoo_project/security/ir.model.access.csv @@ -1,5 +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,odoo_repository.group_odoo_repository_user,1,1,1,0 -access_odoo_project_manager_manager,odoo_project_manager,model_odoo_project,odoo_repository.group_odoo_repository_manager,1,1,1,1 +access_odoo_project_manager,odoo_project_manager,model_odoo_project,odoo_repository.group_odoo_repository_manager,1,1,1,1 access_odoo_project_module_user,odoo_project_module_user,model_odoo_project_module,odoo_repository.group_odoo_repository_user,1,0,0,0 access_odoo_project_import_modules_user,odoo_project_import_modules_user,model_odoo_project_import_modules,odoo_repository.group_odoo_repository_user,1,1,1,1 From dc2d7965afd331744a032458f3484e1a8066c25c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Tue, 26 Nov 2024 10:36:38 +0100 Subject: [PATCH 2/2] [ADD] odoo_project_stat: get some stats regarding projects --- odoo_project_stat/README.rst | 61 +++ odoo_project_stat/__init__.py | 2 + odoo_project_stat/__manifest__.py | 23 + .../data/odoo_project_stat_config.xml | 77 ++++ odoo_project_stat/models/__init__.py | 3 + odoo_project_stat/models/odoo_project.py | 109 +++++ odoo_project_stat/models/odoo_project_stat.py | 114 +++++ .../models/odoo_project_stat_config.py | 29 ++ odoo_project_stat/readme/CONTRIBUTORS.rst | 2 + odoo_project_stat/readme/DESCRIPTION.rst | 1 + .../security/ir.model.access.csv | 4 + .../static/description/index.html | 416 ++++++++++++++++++ odoo_project_stat/views/odoo_project.xml | 38 ++ .../views/odoo_project_stat_config.xml | 38 ++ odoo_project_stat/wizards/__init__.py | 1 + .../wizards/odoo_project_import_modules.py | 14 + .../odoo/addons/odoo_project_stat | 1 + setup/odoo_project_stat/setup.py | 6 + 18 files changed, 939 insertions(+) create mode 100644 odoo_project_stat/README.rst create mode 100644 odoo_project_stat/__init__.py create mode 100644 odoo_project_stat/__manifest__.py create mode 100644 odoo_project_stat/data/odoo_project_stat_config.xml create mode 100644 odoo_project_stat/models/__init__.py create mode 100644 odoo_project_stat/models/odoo_project.py create mode 100644 odoo_project_stat/models/odoo_project_stat.py create mode 100644 odoo_project_stat/models/odoo_project_stat_config.py create mode 100644 odoo_project_stat/readme/CONTRIBUTORS.rst create mode 100644 odoo_project_stat/readme/DESCRIPTION.rst create mode 100644 odoo_project_stat/security/ir.model.access.csv create mode 100644 odoo_project_stat/static/description/index.html create mode 100644 odoo_project_stat/views/odoo_project.xml create mode 100644 odoo_project_stat/views/odoo_project_stat_config.xml create mode 100644 odoo_project_stat/wizards/__init__.py create mode 100644 odoo_project_stat/wizards/odoo_project_import_modules.py create mode 120000 setup/odoo_project_stat/odoo/addons/odoo_project_stat create mode 100644 setup/odoo_project_stat/setup.py diff --git a/odoo_project_stat/README.rst b/odoo_project_stat/README.rst new file mode 100644 index 00000000..4f9d6ef7 --- /dev/null +++ b/odoo_project_stat/README.rst @@ -0,0 +1,61 @@ +================== +Odoo Project Stats +================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:c28b3566984384c7340cad175a80cb773920714b6f61650fc7cfa5f4c9bbc5be + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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_stat + :alt: camptocamp/odoo-repository + +|badge1| |badge2| |badge3| + +This module allows to generate and render some stats about your Odoo projects. + +**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_stat/__init__.py b/odoo_project_stat/__init__.py new file mode 100644 index 00000000..aee8895e --- /dev/null +++ b/odoo_project_stat/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/odoo_project_stat/__manifest__.py b/odoo_project_stat/__manifest__.py new file mode 100644 index 00000000..b25c7461 --- /dev/null +++ b/odoo_project_stat/__manifest__.py @@ -0,0 +1,23 @@ +# Copyright 2023 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) +{ + "name": "Odoo Project Stats", + "summary": "Get some stats about your Odoo Projects.", + "version": "16.0.1.0.0", + "category": "Tools", + "author": "Camptocamp, Odoo Community Association (OCA)", + "website": "https://github.com/camptocamp/odoo-repository", + "data": [ + "security/ir.model.access.csv", + "data/odoo_project_stat_config.xml", + "views/odoo_project.xml", + "views/odoo_project_stat_config.xml", + ], + "installable": True, + "depends": [ + "odoo_project", + # OCA/web + "web_widget_plotly_chart", + ], + "license": "AGPL-3", +} diff --git a/odoo_project_stat/data/odoo_project_stat_config.xml b/odoo_project_stat/data/odoo_project_stat_config.xml new file mode 100644 index 00000000..857d242a --- /dev/null +++ b/odoo_project_stat/data/odoo_project_stat_config.xml @@ -0,0 +1,77 @@ + + + + + + + Odoo CE + #985184 + + + + + + Odoo Enterprise + #1ad3bb + + + + + + OCA + #a4b0f1 + + + + + + Community + #c3ccfc + + + + + + Specific + #ff680a + + + + + + Misc. + #f3f4f6 + + + + diff --git a/odoo_project_stat/models/__init__.py b/odoo_project_stat/models/__init__.py new file mode 100644 index 00000000..3a61e47d --- /dev/null +++ b/odoo_project_stat/models/__init__.py @@ -0,0 +1,3 @@ +from . import odoo_project_stat_config +from . import odoo_project_stat +from . import odoo_project diff --git a/odoo_project_stat/models/odoo_project.py b/odoo_project_stat/models/odoo_project.py new file mode 100644 index 00000000..eabc762c --- /dev/null +++ b/odoo_project_stat/models/odoo_project.py @@ -0,0 +1,109 @@ +# Copyright 2024 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +import plotly.graph_objects as go +from plotly.offline import plot + +from odoo import fields, models + + +class OdooProject(models.Model): + _inherit = "odoo.project" + + stats_ids = fields.One2many( + comodel_name="odoo.project.stat", + inverse_name="odoo_project_id", + ) + chart_modules_count = fields.Text( + string="Modules Chart", + compute="_compute_charts", + ) + chart_sloc = fields.Text( + string="Lines of Code Chart", + compute="_compute_charts", + ) + + def _get_last_stats(self): + self.ensure_one() + last_stat = self.env["odoo.project.stat"].search( + [("odoo_project_id", "=", self.id)], + order="date DESC", + limit=1, + ) + if not last_stat: + return self.env["odoo.project.stat"].browse() + return self.env["odoo.project.stat"].search( + [("odoo_project_id", "=", self.id), ("date", "=", last_stat.date)], + order="sequence", + ) + + def _compute_charts(self): + for rec in self: + rec.chart_modules_count = rec.chart_sloc = False + stats = rec._get_last_stats() + labels = stats.mapped("name") + colors = stats.mapped("color") + count_values = stats.mapped("modules_count") + sloc_values = stats.mapped("sloc") + # Modules Count Pie Chart + if count_values: + count_chart = go.Figure( + data=[ + go.Pie( + labels=labels, + values=count_values, + ) + ] + ) + self._chart_update_layout(count_chart, colors=colors, title="Modules") + rec.chart_modules_count = plot( + count_chart, + include_plotlyjs=False, + output_type="div", + config={"displaylogo": False}, + ) + # SLOC Pie Chart + if sloc_values: + sloc_chart = go.Figure( + data=[ + go.Pie( + labels=labels, + values=sloc_values, + ) + ] + ) + self._chart_update_layout( + sloc_chart, colors=colors, title="Lines of Code" + ) + rec.chart_sloc = plot( + sloc_chart, + include_plotlyjs=False, + output_type="div", + config={"displaylogo": False}, + ) + + def _chart_update_layout(self, chart, colors=None, title=None): + chart.update_traces( + textinfo="percent+label", + textposition="inside", + hole=0.35, + sort=False, + direction="clockwise", + rotation=180, + ) + if colors: + chart.update_traces(marker=dict(colors=colors)) + if title: + chart.update_layout(title_text=title, title_x=0.5, title_y=0.5) + chart.update_layout( + margin=dict(l=0, r=0, t=20, b=20), + showlegend=False, + autosize=False, + width=450, + height=450, + ) + + def action_generate_stats(self): + for rec in self: + self.env["odoo.project.stat"].sudo()._generate_stats(rec.id) + return True diff --git a/odoo_project_stat/models/odoo_project_stat.py b/odoo_project_stat/models/odoo_project_stat.py new file mode 100644 index 00000000..0935f71d --- /dev/null +++ b/odoo_project_stat/models/odoo_project_stat.py @@ -0,0 +1,114 @@ +# Copyright 2024 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import fields, models +from odoo.tools.safe_eval import safe_eval + + +class OdooProjectStat(models.Model): + _name = "odoo.project.stat" + _description = "Odoo Project Stats" + _rec_name = "name" + _order = "odoo_project_id, date, sequence, name" + + odoo_project_id = fields.Many2one( + comodel_name="odoo.project", + ondelete="cascade", + string="Project", + index=True, + required=True, + readonly=True, + ) + config_id = fields.Many2one( + comodel_name="odoo.project.stat.config", + ondelete="restrict", + string="Project Stat Configuration", + required=True, + readonly=True, + ) + date = fields.Date(required=True, index=True) + sequence = fields.Integer(related="config_id.sequence", store=True) + name = fields.Char(related="config_id.name", store=True) + color = fields.Char(related="config_id.color") + modules_count = fields.Integer(readonly=True) + sloc = fields.Integer(string="Lines of code", readonly=True) + + _sql_constraints = [ + ( + "odoo_project_config_date_uniq", + "UNIQUE (odoo_project_id, config_id, date)", + "This project stats record already exists.", + ), + ] + + def _get_stats(self, odoo_project, date=None, config=None, limit=None): + domain = [("odoo_project_id", "=", odoo_project.id)] + if date: + domain.append(("date", "=", date)) + if config: + domain.append(("config_id", "=", config.id)) + return self.search(domain, limit=limit) + + def _generate_stats(self, odoo_project_id): + """Generate the stats for a given `odoo_project_id`.""" + odoo_project = self.env["odoo.project"].browse(odoo_project_id).exists() + odoo_project.ensure_one() + modules = odoo_project.project_module_ids + total_count = len(modules) + total_sloc = ( + sum(modules.mapped("sloc_python")) + + sum(modules.mapped("sloc_xml")) + + sum(modules.mapped("sloc_js")) + + sum(modules.mapped("sloc_css")) + ) + # Clean up stats of today if any + today = fields.Date.today() + existing_stats = self._get_stats(odoo_project, date=today) + existing_stats.sudo().unlink() + # Create or update existing stat record + configs = self.env["odoo.project.stat.config"].search([]) + for config in configs: + stat = self._get_stats(odoo_project, date=today, config=config, limit=1) + values = self._generate_stat_values(odoo_project, config, total_count) + if stat: + stat.sudo().write(values) + else: + self.sudo().create(values) + stats = self._get_stats(odoo_project, date=today) + stat_residual = stats.filtered(lambda o: o.config_id.residual) + if stat_residual: + other_stats = stats - stat_residual + stat_residual.sudo().write( + { + "modules_count": ( + total_count - sum(other_stats.mapped("modules_count")) + ), + "sloc": (total_sloc - sum(other_stats.mapped("sloc"))), + } + ) + return True + + def _generate_stat_values(self, odoo_project, config, total_count): + # Counter + if config.residual: + modules_count = 0 + sloc = 0 + else: + domain = safe_eval(config.domain) + # FIXME: use odoo.osv.expression.AND + domain.append(("odoo_project_id", "=", odoo_project.id)) + modules = self.env["odoo.project.module"].search(domain) + modules_count = len(modules) + sloc = ( + sum(modules.mapped("sloc_python")) + + sum(modules.mapped("sloc_xml")) + + sum(modules.mapped("sloc_js")) + + sum(modules.mapped("sloc_css")) + ) + return { + "odoo_project_id": odoo_project.id, + "config_id": config.id, + "date": fields.Date.today(), + "modules_count": modules_count, + "sloc": sloc, + } diff --git a/odoo_project_stat/models/odoo_project_stat_config.py b/odoo_project_stat/models/odoo_project_stat_config.py new file mode 100644 index 00000000..cb0407f9 --- /dev/null +++ b/odoo_project_stat/models/odoo_project_stat_config.py @@ -0,0 +1,29 @@ +# Copyright 2024 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import fields, models + + +class OdooProjectStatConfig(models.Model): + _name = "odoo.project.stat.config" + _description = "Odoo Project Stat Config" + _order = "sequence, name" + + sequence = fields.Integer(default=10) + name = fields.Char(string="Title", required=True) + color = fields.Char(required=True) + residual = fields.Boolean( + help=( + "Will catch all modules that doesn't match other rules " + "(domain field will be ignored if enabled)." + ) + ) + domain = fields.Char() + + _sql_constraints = [ + ( + "residual_uniq", + "UNIQUE (residual)", + "Only one configuration should exist for residual modules.", + ), + ] diff --git a/odoo_project_stat/readme/CONTRIBUTORS.rst b/odoo_project_stat/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..a0c91e35 --- /dev/null +++ b/odoo_project_stat/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Camptocamp + * Sébastien Alix diff --git a/odoo_project_stat/readme/DESCRIPTION.rst b/odoo_project_stat/readme/DESCRIPTION.rst new file mode 100644 index 00000000..8155191d --- /dev/null +++ b/odoo_project_stat/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module allows to generate and render some stats about your Odoo projects. diff --git a/odoo_project_stat/security/ir.model.access.csv b/odoo_project_stat/security/ir.model.access.csv new file mode 100644 index 00000000..da109388 --- /dev/null +++ b/odoo_project_stat/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_stat_config_user,odoo_project_stat_config_user,model_odoo_project_stat_config,odoo_repository.group_odoo_repository_user,1,0,0,0 +access_odoo_project_stat_config_manager,odoo_project_stat_config_manager,model_odoo_project_stat_config,odoo_repository.group_odoo_repository_manager,1,1,1,1 +access_odoo_project_stat_user,odoo_project_stat_user,model_odoo_project_stat,odoo_repository.group_odoo_repository_user,1,0,0,0 diff --git a/odoo_project_stat/static/description/index.html b/odoo_project_stat/static/description/index.html new file mode 100644 index 00000000..b16110ad --- /dev/null +++ b/odoo_project_stat/static/description/index.html @@ -0,0 +1,416 @@ + + + + + +Odoo Project Stats + + + +
+

Odoo Project Stats

+ + +

Beta License: AGPL-3 camptocamp/odoo-repository

+

This module allows to generate and render some stats about your Odoo projects.

+

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_stat/views/odoo_project.xml b/odoo_project_stat/views/odoo_project.xml new file mode 100644 index 00000000..c611cfca --- /dev/null +++ b/odoo_project_stat/views/odoo_project.xml @@ -0,0 +1,38 @@ + + + + + + odoo.project.form.inherit + odoo.project + + + + + + + + + + + + +