From 1d70e2c81b23fe9a8ca5ec73cb20f89ef5473ebb Mon Sep 17 00:00:00 2001 From: esam Date: Wed, 1 Oct 2025 01:15:19 -0400 Subject: [PATCH] apps_features --- odex30_base/odex30_apps_features/__init__.py | 3 + .../odex30_apps_features/__manifest__.py | 41 +++++++++ .../odex30_apps_features/models/__init__.py | 4 + .../models/ir_module_addons_path.py | 42 +++++++++ .../models/ir_module_history.py | 38 ++++++++ .../models/ir_module_module.py | 59 +++++++++++++ .../security/ir.model.access.csv | 3 + .../views/ir_module_addons_path_view.xml | 86 +++++++++++++++++++ .../views/ir_module_history_view.xml | 35 ++++++++ .../views/ir_module_module_view.xml | 31 +++++++ .../views/module_multiple_uninstall.xml | 15 ++++ .../views/upgrade_button_views.xml | 30 +++++++ .../odex30_apps_features/wizard/__init__.py | 3 + .../wizard/base_module_uninstall.py | 19 ++++ .../wizard/base_module_uninstall_views.xml | 18 ++++ 15 files changed, 427 insertions(+) create mode 100644 odex30_base/odex30_apps_features/__init__.py create mode 100644 odex30_base/odex30_apps_features/__manifest__.py create mode 100644 odex30_base/odex30_apps_features/models/__init__.py create mode 100644 odex30_base/odex30_apps_features/models/ir_module_addons_path.py create mode 100644 odex30_base/odex30_apps_features/models/ir_module_history.py create mode 100644 odex30_base/odex30_apps_features/models/ir_module_module.py create mode 100644 odex30_base/odex30_apps_features/security/ir.model.access.csv create mode 100644 odex30_base/odex30_apps_features/views/ir_module_addons_path_view.xml create mode 100644 odex30_base/odex30_apps_features/views/ir_module_history_view.xml create mode 100644 odex30_base/odex30_apps_features/views/ir_module_module_view.xml create mode 100644 odex30_base/odex30_apps_features/views/module_multiple_uninstall.xml create mode 100644 odex30_base/odex30_apps_features/views/upgrade_button_views.xml create mode 100644 odex30_base/odex30_apps_features/wizard/__init__.py create mode 100644 odex30_base/odex30_apps_features/wizard/base_module_uninstall.py create mode 100644 odex30_base/odex30_apps_features/wizard/base_module_uninstall_views.xml diff --git a/odex30_base/odex30_apps_features/__init__.py b/odex30_base/odex30_apps_features/__init__.py new file mode 100644 index 0000000..408a600 --- /dev/null +++ b/odex30_base/odex30_apps_features/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +from . import models +from . import wizard diff --git a/odex30_base/odex30_apps_features/__manifest__.py b/odex30_base/odex30_apps_features/__manifest__.py new file mode 100644 index 0000000..1f69064 --- /dev/null +++ b/odex30_base/odex30_apps_features/__manifest__.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +{ + 'name': 'Odex30-Apps Feature', + 'version': '18.0.0.1', + 'summary': 'Enhance Odoo with Addons Path Management and Module History Tracking', + 'summary': 'Enhance Odoo with Addons Path Management and History Tracking, with Uninstall Restrictions', + 'description': """ + This custom module extends Odoo's functionality by introducing features related to Addons Path management and tracking module installation, upgrades, and uninstallations. + + Key Features: + - Addons Path Management: Organize and manage Addons Paths with customizable short names and visually distinctive colors. + - Module History Tracking: Keep a record of module installation, upgrades, and uninstallations, including the author and timestamp. + - Uninstall Restrictions: Admins can set an uninstallation password to add an extra layer of security, preventing unauthorized module removal. + + Note: This module should be used by experienced Odoo administrators to enhance system management and tracking capabilities. The Uninstall Restrictions feature adds an extra layer of security to prevent unauthorized module removal. + """, + 'category': 'Odex30-base', + 'author': "Expert Co. Ltd.", + 'website': "http://www.exp-sa.com", + 'license': 'AGPL-3', + + 'depends': ['base'], + + 'data': [ + 'security/ir.model.access.csv', + 'views/ir_module_module_view.xml', + 'views/ir_module_addons_path_view.xml' + ,'views/upgrade_button_views.xml' + ,'wizard/base_module_uninstall_views.xml' + ,'views/ir_module_history_view.xml', + 'views/module_multiple_uninstall.xml', + ], + 'demo': [ + + ], + 'qweb': [], + + 'installable': True, + 'auto_install': False, + 'application': False, +} diff --git a/odex30_base/odex30_apps_features/models/__init__.py b/odex30_base/odex30_apps_features/models/__init__.py new file mode 100644 index 0000000..48cdeb0 --- /dev/null +++ b/odex30_base/odex30_apps_features/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +from . import ir_module_module +from . import ir_module_addons_path +from . import ir_module_history diff --git a/odex30_base/odex30_apps_features/models/ir_module_addons_path.py b/odex30_base/odex30_apps_features/models/ir_module_addons_path.py new file mode 100644 index 0000000..08a8499 --- /dev/null +++ b/odex30_base/odex30_apps_features/models/ir_module_addons_path.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +import random +from odoo import models, fields + + +class IrModuleAddonsPath(models.Model): + _name = "ir.module.addons.path" + _description = "Addons Path" + + + def _default_bg_color(self): + colors = ['#F06050', '#F4A45F', '#F7CD2E', '#6CC1ED', '#EB7E7F', '#5CC482', + '#2c8297', '#D8485E', '#9365B8', '#804967', '#475576', ] + res = '#FFFFFF' + try: + res = random.choice(colors) + except: + pass + return res + + name = fields.Char(string='Short Name') + path = fields.Char(string='Full Path') + path_temp = fields.Char(string='Shortened Path') + color = fields.Char(default=_default_bg_color) + module_ids = fields.One2many('ir.module.module', 'addons_path_id') + module_count = fields.Integer(compute='_compute_module_count') + + def _compute_module_count(self): + for rec in self: + rec.module_count = len(rec.module_ids) + + def open_apps_view(self): + self.ensure_one() + + return {'type': 'ir.actions.act_window', + 'name': 'Apps', + 'view_mode': 'kanban,list,form', + 'res_model': 'ir.module.module', + 'context': {}, + 'domain': [('addons_path_id', '=', self.id)], + } + diff --git a/odex30_base/odex30_apps_features/models/ir_module_history.py b/odex30_base/odex30_apps_features/models/ir_module_history.py new file mode 100644 index 0000000..90181f9 --- /dev/null +++ b/odex30_base/odex30_apps_features/models/ir_module_history.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +from odoo import models, fields, api + + +class IrModuleHistory(models.Model): + _name = 'ir.module.history' + _description = 'Module History' + + TYPES = [ + ('install', 'Installed'), + ('upgrade', 'Upgraded'), + ('uninstall', 'Uninstalled'), + ] + + module_name = fields.Char(required=True, string='Module') + type = fields.Selection(TYPES, required=True, string='Action') + user_id = fields.Many2one('res.users', string='Author', required=True) + + +class IrModuleModule(models.Model): + _inherit = 'ir.module.module' + + def _button_immediate_function(self, function): + res = super(IrModuleModule, self)._button_immediate_function(function) + for module in self: + action_type = { + 'button_install': 'install', + 'button_upgrade': 'upgrade', + 'button_uninstall': 'uninstall', + }.get(function.__name__) + + if action_type: + self.env['ir.module.history'].sudo().create({ + 'module_name': module.name, + 'type': action_type, + 'user_id': self._uid, + }) + return res diff --git a/odex30_base/odex30_apps_features/models/ir_module_module.py b/odex30_base/odex30_apps_features/models/ir_module_module.py new file mode 100644 index 0000000..5f816b0 --- /dev/null +++ b/odex30_base/odex30_apps_features/models/ir_module_module.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +from odoo import models, fields, api +from odoo.modules.module import get_module_path + + +class IrModule(models.Model): + _inherit = "ir.module.module" + + addons_path_id = fields.Many2one('ir.module.addons.path', string='Addons Path Record', readonly=True) + addons_path = fields.Char(string='Addons Path', related='addons_path_id.path', readonly=True) + + def module_multiple_uninstall(self): + modules_uninstall = self.browse(self.env.context.get('active_ids')).button_immediate_uninstall() + + +class BaseModuleUpdate(models.TransientModel): + _inherit = "base.module.update" + + def update_addons_paths(self): + from odoo import addons + addons_path_obj = self.env['ir.module.addons.path'] + ad_paths = addons.__path__ + + for path in ad_paths: + if not addons_path_obj.search([('path', '=', path)]): + path_temp = path + + if len(path_temp) > 42: + path_temp = '%s......%s' % (path[:12], path[-19:]) + + addons_path_obj.sudo().create({ + 'name': path.split('/')[-1], + 'path': path, + 'path_temp': path_temp, + }) + + for addons_path_id in addons_path_obj.search([]): + if addons_path_id.path not in ad_paths: + addons_path_id.unlink() + + def update_module_addons_paths(self): + addons_path_obj = self.env['ir.module.addons.path'] + + for module_id in self.env['ir.module.module'].search([]): + module_path = get_module_path(module_id.name) + if not module_path: + continue + addons_path = module_path.rstrip(module_id.name).rstrip('/') + addons_path_id = addons_path_obj.search([('path', '=', addons_path)]) + + if addons_path_id: + module_id.addons_path_id = addons_path_id.id + + def update_module(self): + result = super(BaseModuleUpdate, self).update_module() + self.sudo().update_addons_paths() + self.sudo().update_module_addons_paths() + return result + diff --git a/odex30_base/odex30_apps_features/security/ir.model.access.csv b/odex30_base/odex30_apps_features/security/ir.model.access.csv new file mode 100644 index 0000000..2f4f2a7 --- /dev/null +++ b/odex30_base/odex30_apps_features/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_ir_module_addons_path,ir.module.addons.path access,model_ir_module_addons_path,base.group_system,1,1,1,1 +access_ir_module_history,ir.module.history access,model_ir_module_history,base.group_system,1,1,1,1 diff --git a/odex30_base/odex30_apps_features/views/ir_module_addons_path_view.xml b/odex30_base/odex30_apps_features/views/ir_module_addons_path_view.xml new file mode 100644 index 0000000..0899b8a --- /dev/null +++ b/odex30_base/odex30_apps_features/views/ir_module_addons_path_view.xml @@ -0,0 +1,86 @@ + + + + + + ir.module.addons.path.kanban + ir.module.addons.path + + + + + + + + +
+
+ + +
+
+
+
+
+
+
+ + + + ir.module.addons.path.view.tree + ir.module.addons.path + + + + + + + + + + + + + ir.module.addons.path.view.form + ir.module.addons.path + +
+ +
+
+ + + +
+
+
+
+ + + + Addons Paths + ir.module.addons.path + kanban,list,form + {} + [] + +

Please Update the Apps List ...

+
+
+ + + + +
\ No newline at end of file diff --git a/odex30_base/odex30_apps_features/views/ir_module_history_view.xml b/odex30_base/odex30_apps_features/views/ir_module_history_view.xml new file mode 100644 index 0000000..13e10e1 --- /dev/null +++ b/odex30_base/odex30_apps_features/views/ir_module_history_view.xml @@ -0,0 +1,35 @@ + + + + + + ir.module.history.view.tree + ir.module.history + + + + + + + + + + + + + History + ir.module.history + list + {} + [] + +

+ No history +

+
+
+ + + + +
\ No newline at end of file diff --git a/odex30_base/odex30_apps_features/views/ir_module_module_view.xml b/odex30_base/odex30_apps_features/views/ir_module_module_view.xml new file mode 100644 index 0000000..15db0f0 --- /dev/null +++ b/odex30_base/odex30_apps_features/views/ir_module_module_view.xml @@ -0,0 +1,31 @@ + + + + + + ir.module.module.view.search.inherit.app.addons.path + ir.module.module + + + + + + + + + + + ir.module.module.view.form.inherit.app.addons.path + ir.module.module + + + + + + + + + + + + \ No newline at end of file diff --git a/odex30_base/odex30_apps_features/views/module_multiple_uninstall.xml b/odex30_base/odex30_apps_features/views/module_multiple_uninstall.xml new file mode 100644 index 0000000..fe7fecb --- /dev/null +++ b/odex30_base/odex30_apps_features/views/module_multiple_uninstall.xml @@ -0,0 +1,15 @@ + + + + + + Modules Un-Install + ir.actions.server + + + code + records.module_multiple_uninstall() + + + + diff --git a/odex30_base/odex30_apps_features/views/upgrade_button_views.xml b/odex30_base/odex30_apps_features/views/upgrade_button_views.xml new file mode 100644 index 0000000..3122436 --- /dev/null +++ b/odex30_base/odex30_apps_features/views/upgrade_button_views.xml @@ -0,0 +1,30 @@ + + + + Apps Kanban.Inherit.UpgradeButton + ir.module.module + + + + 1 + + +
+ + +
+
+ +
+
+
diff --git a/odex30_base/odex30_apps_features/wizard/__init__.py b/odex30_base/odex30_apps_features/wizard/__init__.py new file mode 100644 index 0000000..dd58436 --- /dev/null +++ b/odex30_base/odex30_apps_features/wizard/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import base_module_uninstall diff --git a/odex30_base/odex30_apps_features/wizard/base_module_uninstall.py b/odex30_base/odex30_apps_features/wizard/base_module_uninstall.py new file mode 100644 index 0000000..8e96435 --- /dev/null +++ b/odex30_base/odex30_apps_features/wizard/base_module_uninstall.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models, tools +from odoo.exceptions import ValidationError + + +class BaseModuleUninstall(models.TransientModel): + _inherit = "base.module.uninstall" + + is_restrict = fields.Boolean() + password = fields.Char(required=True) + + def action_uninstall(self): + if not tools.config.get('uninstall_password'): + raise ValidationError("Uninstall password not yet set!") + if self.password != tools.config.get('uninstall_password').replace('\'', ''): + raise ValidationError("Invalid Password!") + return super(BaseModuleUninstall, self).action_uninstall() diff --git a/odex30_base/odex30_apps_features/wizard/base_module_uninstall_views.xml b/odex30_base/odex30_apps_features/wizard/base_module_uninstall_views.xml new file mode 100644 index 0000000..8cd98e7 --- /dev/null +++ b/odex30_base/odex30_apps_features/wizard/base_module_uninstall_views.xml @@ -0,0 +1,18 @@ + + + + Uninstall module + base.module.uninstall + + + +

+ + +


+ +

+
+
+
+