From 7783b04e73790fb48b06e40db659fb4c4672c4e1 Mon Sep 17 00:00:00 2001 From: xtremxpert Date: Mon, 2 Dec 2024 15:12:08 -0500 Subject: [PATCH] wip --- chatgpt_assistant/__init__.py | 2 + chatgpt_assistant/__manifest__.py | 20 ++++ chatgpt_assistant/controllers/__init__.py | 1 + chatgpt_assistant/controllers/main.py | 23 +++++ .../static/src/js/discuss_extension.js | 28 ++++++ chatgpt_assistant/views/assets.xml | 8 ++ odoo_unifi_manager/__init__.py | 2 + odoo_unifi_manager/__manifest__.py | 19 ++++ odoo_unifi_manager/models/__init__.py | 2 + odoo_unifi_manager/models/firewall_rule.py | 43 +++++++++ .../models/firewall_rule_template.py | 42 +++++++++ .../security/ir.model.access.csv | 4 + .../views/firewall_rule_template_views.xml | 23 +++++ .../views/firewall_rule_views.xml | 16 ++++ .../views/firewall_rule_wizard_action.xml | 32 +++++++ odoo_unifi_manager/views/unify_menu.xml | 5 + odoo_unifi_manager/wizard/__init__.py | 1 + .../wizard/firewall_rule_wizard.py | 85 +++++++++++++++++ openai_partner_purchase_analysis/__init__.py | 1 + .../__manifest__.py | 1 + .../controlers/__init__.py | 1 + .../controlers/sale_analysis_controller.py | 14 +++ .../partner_purchase_analysis_wizard.py | 93 ------------------- .../models/res_partner.py | 17 ++++ .../views/res_partner_view.xml | 4 +- .../views/sale_analysis_templates.xml | 5 + repos_module_linker/__manifest__.py | 17 ++++ repos_module_linker/models/__init__.py | 1 + .../models/ir_module_extension.py | 75 +++++++++++++++ .../security/ir.model.access.csv | 2 + .../views/module_filter_view.xml | 15 +++ repos_module_linker/views/module_menu.xml | 17 ++++ repos_module_linker/views/module_view.xml | 26 ++++++ 33 files changed, 550 insertions(+), 95 deletions(-) create mode 100644 chatgpt_assistant/__init__.py create mode 100644 chatgpt_assistant/__manifest__.py create mode 100644 chatgpt_assistant/controllers/__init__.py create mode 100644 chatgpt_assistant/controllers/main.py create mode 100644 chatgpt_assistant/static/src/js/discuss_extension.js create mode 100644 chatgpt_assistant/views/assets.xml create mode 100644 odoo_unifi_manager/__init__.py create mode 100644 odoo_unifi_manager/__manifest__.py create mode 100644 odoo_unifi_manager/models/__init__.py create mode 100644 odoo_unifi_manager/models/firewall_rule.py create mode 100644 odoo_unifi_manager/models/firewall_rule_template.py create mode 100644 odoo_unifi_manager/security/ir.model.access.csv create mode 100644 odoo_unifi_manager/views/firewall_rule_template_views.xml create mode 100644 odoo_unifi_manager/views/firewall_rule_views.xml create mode 100644 odoo_unifi_manager/views/firewall_rule_wizard_action.xml create mode 100644 odoo_unifi_manager/views/unify_menu.xml create mode 100644 odoo_unifi_manager/wizard/__init__.py create mode 100644 odoo_unifi_manager/wizard/firewall_rule_wizard.py create mode 100644 openai_partner_purchase_analysis/controlers/__init__.py create mode 100644 openai_partner_purchase_analysis/controlers/sale_analysis_controller.py delete mode 100644 openai_partner_purchase_analysis/models/partner_purchase_analysis_wizard.py create mode 100644 openai_partner_purchase_analysis/views/sale_analysis_templates.xml create mode 100644 repos_module_linker/__manifest__.py create mode 100644 repos_module_linker/models/__init__.py create mode 100644 repos_module_linker/models/ir_module_extension.py create mode 100644 repos_module_linker/security/ir.model.access.csv create mode 100644 repos_module_linker/views/module_filter_view.xml create mode 100644 repos_module_linker/views/module_menu.xml create mode 100644 repos_module_linker/views/module_view.xml diff --git a/chatgpt_assistant/__init__.py b/chatgpt_assistant/__init__.py new file mode 100644 index 0000000..f705942 --- /dev/null +++ b/chatgpt_assistant/__init__.py @@ -0,0 +1,2 @@ + +from . import controllers diff --git a/chatgpt_assistant/__manifest__.py b/chatgpt_assistant/__manifest__.py new file mode 100644 index 0000000..c177379 --- /dev/null +++ b/chatgpt_assistant/__manifest__.py @@ -0,0 +1,20 @@ + +{ + 'name': 'ChatGPT Assistant for Discuss', + 'version': '1.0', + 'author': 'Bemade Inc.', + 'category': 'Tools', + 'summary': 'Integrate ChatGPT into Odoo Discuss', + 'depends': ['mail'], + 'data': [ + # 'views/assets.xml', + ], + 'assets': { + 'web.assets_backend': [ + # '/chatgpt_assistant/static/src/js/discuss_extension.js', + ], + }, + 'installable': True, + 'application': False, + 'license': 'LGPL-3', +} diff --git a/chatgpt_assistant/controllers/__init__.py b/chatgpt_assistant/controllers/__init__.py new file mode 100644 index 0000000..deec4a8 --- /dev/null +++ b/chatgpt_assistant/controllers/__init__.py @@ -0,0 +1 @@ +from . import main \ No newline at end of file diff --git a/chatgpt_assistant/controllers/main.py b/chatgpt_assistant/controllers/main.py new file mode 100644 index 0000000..b802d1e --- /dev/null +++ b/chatgpt_assistant/controllers/main.py @@ -0,0 +1,23 @@ + +from odoo import http +from odoo.http import request +import openai +import pandas as pd + +class DiscussChatGPT(http.Controller): + @http.route('/discuss/chatgpt', type='json', auth='user') + def chatgpt_respond(self, message, attachment_id=None): + openai.api_key = request.env['ir.config_parameter'].sudo().get_param('chatgpt.api_key') + + if attachment_id: + attachment = request.env['ir.attachment'].sudo().browse(attachment_id) + if attachment.mimetype == 'text/csv': + data = pd.read_csv(attachment._full_path(attachment.store_fname)) + processed_data = data.describe().to_string() + return {'response': f"Analyse des données : \n{processed_data}"} + + response = openai.ChatCompletion.create( + model="gpt-4", + messages=[{"role": "user", "content": message}] + ) + return {'response': response['choices'][0]['message']['content']} diff --git a/chatgpt_assistant/static/src/js/discuss_extension.js b/chatgpt_assistant/static/src/js/discuss_extension.js new file mode 100644 index 0000000..e20c00a --- /dev/null +++ b/chatgpt_assistant/static/src/js/discuss_extension.js @@ -0,0 +1,28 @@ +odoo.define('chatgpt_assistant.discuss_extension', [], function (Discuss, utils) { + "use strict"; + + const { patch } = utils; + + patch(Discuss.prototype, 'chatgpt_assistant.discuss_extension', { + async _onChatGPTSendMessage(event) { + const message = this.inputValue || ''; + if (!message.trim()) { + return; + } + + try { + const result = await this.env.services.rpc({ + route: '/discuss/chatgpt', + params: { message }, + }); + + if (result.response) { + this._insertMessage(result.response); + } + } catch (error) { + console.error('Erreur lors de l\'appel à ChatGPT:', error); + this._insertMessage('Erreur : Impossible de contacter ChatGPT.'); + } + }, + }); +}); \ No newline at end of file diff --git a/chatgpt_assistant/views/assets.xml b/chatgpt_assistant/views/assets.xml new file mode 100644 index 0000000..d34d300 --- /dev/null +++ b/chatgpt_assistant/views/assets.xml @@ -0,0 +1,8 @@ + + + + diff --git a/odoo_unifi_manager/__init__.py b/odoo_unifi_manager/__init__.py new file mode 100644 index 0000000..c536983 --- /dev/null +++ b/odoo_unifi_manager/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizard \ No newline at end of file diff --git a/odoo_unifi_manager/__manifest__.py b/odoo_unifi_manager/__manifest__.py new file mode 100644 index 0000000..694c23a --- /dev/null +++ b/odoo_unifi_manager/__manifest__.py @@ -0,0 +1,19 @@ +{ + 'name': 'Odoo Unifi Manager', + 'version': '1.0', + 'author': 'Bemade Inc.', + 'website': 'https://www.bemade.org', + 'license': 'LGPL-3', + 'depends': [ + 'base' + ], + 'data': [ + 'views/firewall_rule_wizard_action.xml', + 'views/firewall_rule_views.xml', + 'views/firewall_rule_template_views.xml', + # 'views/unify_menu.xml', + 'security/ir.model.access.csv' + ], + 'installable': True, + 'application': True, +} diff --git a/odoo_unifi_manager/models/__init__.py b/odoo_unifi_manager/models/__init__.py new file mode 100644 index 0000000..71d8e69 --- /dev/null +++ b/odoo_unifi_manager/models/__init__.py @@ -0,0 +1,2 @@ +from . import firewall_rule +from . import firewall_rule_template diff --git a/odoo_unifi_manager/models/firewall_rule.py b/odoo_unifi_manager/models/firewall_rule.py new file mode 100644 index 0000000..99a764b --- /dev/null +++ b/odoo_unifi_manager/models/firewall_rule.py @@ -0,0 +1,43 @@ +from odoo import models, fields, api + +class FirewallRule(models.Model): + _name = 'unifi.firewall.rule' + _description = 'Unifi Firewall Rule' + + name = fields.Char( + string='Rule Name', + required=True + ) + + action = fields.Selection( + selection=[ + ('accept', 'Accept'), + ('drop', 'Drop') + ], + string='Action', + required=True + ) + + src_ip = fields.Char( + string='Source IP', + help="Source IP address (can be left empty)" + ) + + dst_ip = fields.Char( + string='Destination IP', + help="Destination IP address (can be left empty)" + ) + + protocol = fields.Selection( + selection=[ + ('tcp', 'TCP'), + ('udp', 'UDP') + ], + string='Protocol', + required=True + ) + + port = fields.Char( + string='Port', + help="Port or port range (e.g., 80 or 8000-9000)" + ) diff --git a/odoo_unifi_manager/models/firewall_rule_template.py b/odoo_unifi_manager/models/firewall_rule_template.py new file mode 100644 index 0000000..fa5baa0 --- /dev/null +++ b/odoo_unifi_manager/models/firewall_rule_template.py @@ -0,0 +1,42 @@ +from odoo import models, fields + +class FirewallRuleTemplate(models.Model): + _name = 'unifi.firewall.rule.template' + _description = 'Firewall Rule Template' + + name = fields.Char( + string='Template Name', + required=True + ) + + action = fields.Selection( + selection=[ + ('accept', 'Accept'), + ('drop', 'Drop') + ], + string='Action', + required=True + ) + + src_ip = fields.Char( + string='Source IP', + help="Source IP address (can be left empty)" + ) + + dst_ip = fields.Char( + string='Destination IP', + help="Destination IP address (can be left empty)" + ) + + protocol = fields.Selection( + selection=[ + ('tcp', 'TCP'), + ('udp', 'UDP') + ], + string='Protocol', + required=True) + + port = fields.Char( + string='Port', + help="Port or port range (e.g., 80 or 8000-9000)" + ) diff --git a/odoo_unifi_manager/security/ir.model.access.csv b/odoo_unifi_manager/security/ir.model.access.csv new file mode 100644 index 0000000..d449dfc --- /dev/null +++ b/odoo_unifi_manager/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 +odoo_unifi_manager.access_unifi_firewall_rule,access_unifi_firewall_rule,odoo_unifi_manager.model_unifi_firewall_rule,base.group_user,1,0,0,0 +odoo_unifi_manager.access_unifi_firewall_rule_template,access_unifi_firewall_rule_template,odoo_unifi_manager.model_unifi_firewall_rule_template,base.group_user,1,0,0,0 +odoo_unifi_manager.access_unifi_firewall_rule_wizard,access_unifi_firewall_rule_wizard,odoo_unifi_manager.model_unifi_firewall_rule_wizard,base.group_user,1,0,0,0 diff --git a/odoo_unifi_manager/views/firewall_rule_template_views.xml b/odoo_unifi_manager/views/firewall_rule_template_views.xml new file mode 100644 index 0000000..17d7fe5 --- /dev/null +++ b/odoo_unifi_manager/views/firewall_rule_template_views.xml @@ -0,0 +1,23 @@ + + + firewall.rule.template.form + unifi.firewall.rule.template + +
+ + + + + + + + + +
+
+
+
+
+
+
diff --git a/odoo_unifi_manager/views/firewall_rule_views.xml b/odoo_unifi_manager/views/firewall_rule_views.xml new file mode 100644 index 0000000..616ee49 --- /dev/null +++ b/odoo_unifi_manager/views/firewall_rule_views.xml @@ -0,0 +1,16 @@ + + + firewall.rule.tree + unifi.firewall.rule + + + + + + + + + + + + diff --git a/odoo_unifi_manager/views/firewall_rule_wizard_action.xml b/odoo_unifi_manager/views/firewall_rule_wizard_action.xml new file mode 100644 index 0000000..9a8f813 --- /dev/null +++ b/odoo_unifi_manager/views/firewall_rule_wizard_action.xml @@ -0,0 +1,32 @@ + + + Create Firewall Rule + unifi.firewall.rule.wizard + form + new + {'default_template_id': active_id} + + + + Firewall Rules + unifi.firewall.rule + tree,form + +

+ Configure and manage your firewall rules here. +

+
+
+ + + + Firewall Templates + unifi.firewall.rule.template + tree,form + +

+ Create and manage reusable firewall templates here. +

+
+
+
diff --git a/odoo_unifi_manager/views/unify_menu.xml b/odoo_unifi_manager/views/unify_menu.xml new file mode 100644 index 0000000..11d4e88 --- /dev/null +++ b/odoo_unifi_manager/views/unify_menu.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/odoo_unifi_manager/wizard/__init__.py b/odoo_unifi_manager/wizard/__init__.py new file mode 100644 index 0000000..d01a64d --- /dev/null +++ b/odoo_unifi_manager/wizard/__init__.py @@ -0,0 +1 @@ +from . import firewall_rule_wizard \ No newline at end of file diff --git a/odoo_unifi_manager/wizard/firewall_rule_wizard.py b/odoo_unifi_manager/wizard/firewall_rule_wizard.py new file mode 100644 index 0000000..257d96c --- /dev/null +++ b/odoo_unifi_manager/wizard/firewall_rule_wizard.py @@ -0,0 +1,85 @@ +from odoo import models, fields, api +from odoo.exceptions import ValidationError + +class FirewallRuleWizard(models.TransientModel): + _name = 'unifi.firewall.rule.wizard' + _description = 'Wizard to Create Firewall Rule from Template' + + template_id = fields.Many2one( + 'unifi.firewall.rule.template', + string='Template', + required=True, + help="Select the template to create a firewall rule." + ) + + name = fields.Char( + string='Name of the new firewall rule', + required=True + ) + + action = fields.Selection( + selection=[ + ('accept', 'Accept'), + ('drop', 'Drop') + ], + string='Action', + required=True + ) + + src_ip = fields.Char( + string='Source IP', + help="Source IP address (can be left empty)" + ) + + dst_ip = fields.Char( + string='Destination IP', + help="Destination IP address (can be left empty)" + ) + + protocol = fields.Selection( + selection=[ + ('tcp', 'TCP'), + ('udp', 'UDP') + ], + string='Protocol', + required=True + ) + + port = fields.Char( + string='Port', + help="Port or port range (e.g., 80 or 8000-9000)" + ) + + def create_firewall_rule(self): + self.ensure_one() + template = self.template_id + self.env['unifi.firewall.rule'].create({ + 'name': self.name, + 'action': template.action, + 'src_ip': template.src_ip, + 'dst_ip': template.dst_ip, + 'protocol': template.protocol, + 'port': template.port, + }) + +@api.onchange('template_id') +def _onchange_template_id(self): + """Apply template values only if they are not empty.""" + if self.template_id: + # Appliquer les valeurs du modèle uniquement si elles ne sont pas vides + if self.template_id.action: + self.action = self.template_id.action + if self.template_id.src_ip: + self.src_ip = self.template_id.src_ip + if self.template_id.dst_ip: + self.dst_ip = self.template_id.dst_ip + if self.template_id.protocol: + self.protocol = self.template_id.protocol + if self.template_id.port: + self.port = self.template_id.port + + @api.constrains('port') + def _check_port(self): + if self.port: + if not self.port.isdigit() and '-' not in self.port: + raise ValidationError("Port must be a number or a valid range (e.g., 80 or 8000-9000).") \ No newline at end of file diff --git a/openai_partner_purchase_analysis/__init__.py b/openai_partner_purchase_analysis/__init__.py index c536983..159ad89 100644 --- a/openai_partner_purchase_analysis/__init__.py +++ b/openai_partner_purchase_analysis/__init__.py @@ -1,2 +1,3 @@ +from . import controlers from . import models from . import wizard \ No newline at end of file diff --git a/openai_partner_purchase_analysis/__manifest__.py b/openai_partner_purchase_analysis/__manifest__.py index 684df61..19750cd 100644 --- a/openai_partner_purchase_analysis/__manifest__.py +++ b/openai_partner_purchase_analysis/__manifest__.py @@ -21,6 +21,7 @@ 'data/queue_job_group.xml', 'views/res_config_settings_view.xml', 'views/res_partner_view.xml', + 'views/sale_analysis_templates.xml', 'wizard/partner_purchase_analysis_wizard_view.xml', ], 'external_dependencies': { diff --git a/openai_partner_purchase_analysis/controlers/__init__.py b/openai_partner_purchase_analysis/controlers/__init__.py new file mode 100644 index 0000000..853b48d --- /dev/null +++ b/openai_partner_purchase_analysis/controlers/__init__.py @@ -0,0 +1 @@ +from . import sale_analysis_controller \ No newline at end of file diff --git a/openai_partner_purchase_analysis/controlers/sale_analysis_controller.py b/openai_partner_purchase_analysis/controlers/sale_analysis_controller.py new file mode 100644 index 0000000..ecb6c86 --- /dev/null +++ b/openai_partner_purchase_analysis/controlers/sale_analysis_controller.py @@ -0,0 +1,14 @@ +from odoo import http +from odoo.http import request + +class SaleAnalysisController(http.Controller): + @http.route('/sale_analysis/render/', type='http', auth='user', csrf=False) + def render_sale_analysis(self, partner_id, **kwargs): + # Récupérer le partenaire et son contenu d'analyse + partner = request.env['res.partner'].sudo().browse(partner_id) + if not partner: + return request.not_found() + + # Générer le rendu HTML avec les scripts inclus + sale_analysis_content = partner.sale_analysis or "

No analysis available

" + return http.Response(sale_analysis_content, content_type='text/html', status=200) \ No newline at end of file diff --git a/openai_partner_purchase_analysis/models/partner_purchase_analysis_wizard.py b/openai_partner_purchase_analysis/models/partner_purchase_analysis_wizard.py deleted file mode 100644 index b1a6faa..0000000 --- a/openai_partner_purchase_analysis/models/partner_purchase_analysis_wizard.py +++ /dev/null @@ -1,93 +0,0 @@ -from odoo import models, fields, api, _ -from odoo.exceptions import UserError -import openai - -try: - from odoo.addons.queue_job.job import job -except ImportError: - job = None # Si queue_job n'est pas disponible, job reste None - - -class PartnerPurchaseAnalysisWizard(models.TransientModel): - _name = 'partner.purchase.analysis.wizard' - _description = 'Wizard for Partner Purchase Analysis with OpenAI' - - partner_id = fields.Many2one('res.partner', string="Customer", required=True, readonly=True) - date_start = fields.Date(string="Start Date") - date_end = fields.Date(string="End Date") - analysis_result = fields.Text(string="Analysis Result", readonly=True) - - def start_analysis(self): - use_queue = self.env['ir.config_parameter'].sudo().get_param('my_module.use_queue_job') - if use_queue and job: - return self.with_delay().perform_analysis() - else: - return self.perform_analysis() - - def perform_analysis(self): - """Effectue l'analyse des ventes pour le partenaire sélectionné.""" - selected_categories = self.env.company.product_categories_analyzed - if selected_categories: - category_ids = selected_categories.ids - domain = [ - ('order_id.partner_id', '=', self.partner_id.id), - ('product_id.categ_id', 'child_of', category_ids), - ] - else: - domain = [('order_id.partner_id', '=', self.partner_id.id)] - - if self.date_start: - domain.append(('order_id.date_order', '>=', self.date_start)) - if self.date_end: - domain.append(('order_id.date_order', '<=', self.date_end)) - - sale_order_lines = self.env['sale.order.line'].search(domain) - - if not sale_order_lines: - raise UserError(_("No relevant purchase history found for this customer based on the selected filters.")) - - # Préparation des données pour l'API d'OpenAI - purchase_data = {} - for line in sale_order_lines: - category_name = line.product_id.categ_id.name or "Uncategorized" - if category_name not in purchase_data: - purchase_data[category_name] = {} - product_name = line.product_id.display_name - if product_name not in purchase_data[category_name]: - purchase_data[category_name][product_name] = [] - purchase_data[category_name][product_name].append({ - 'date': line.order_id.date_order, - 'quantity': line.product_uom_qty, - 'unit_price': line.price_unit, - }) - - # Construire le prompt pour OpenAI - user_lang = self.env.user.lang or 'en_US' - user_lang_name = self.env['res.lang'].search([('code', '=', user_lang)], limit=1).name or "English" - - purchase_details = f"Customer Purchase Analysis Grouped by Product Category and Product (Response in {user_lang_name}):\n\n" - for category, products in purchase_data.items(): - purchase_details += f"Category: {category}\n" - for product, entries in products.items(): - purchase_details += f" Product: {product}\n" - for entry in entries: - purchase_details += ( - f" - Date: {entry['date']}, " - f"Qty: {entry['quantity']}, " - f"U.Price: {entry['unit_price']}\n" - ) - purchase_details += "\n" - purchase_details += "\n" - - try: - response = openai.ChatCompletion.create( - model="gpt-4", - messages=[ - {"role": "system", - "content": f"Analyze the following customer purchase history. Identify trends, product category preferences, and any significant deviations. Respond in {user_lang_name}."}, - {"role": "user", "content": purchase_details} - ] - ) - self.analysis_result = response.choices[0].message['content'] - except Exception as e: - raise UserError(_("Failed to get response from OpenAI. Error: %s") % str(e)) \ No newline at end of file diff --git a/openai_partner_purchase_analysis/models/res_partner.py b/openai_partner_purchase_analysis/models/res_partner.py index a37f8cf..2a4cff7 100644 --- a/openai_partner_purchase_analysis/models/res_partner.py +++ b/openai_partner_purchase_analysis/models/res_partner.py @@ -5,6 +5,23 @@ class ResPartner(models.Model): sale_analysis = fields.Html("Sale Analysis", help="Analysis of the partner's sales.") sale_analysis_date = fields.Date("Sale Analysis Date", help="Date of the latest sale analysis.") + sale_analysis_url = fields.Char(string="Sale Analysis URL", compute="_compute_sale_analysis_url") + + sale_analysis_iframe = fields.Html(string="Sale Analysis Iframe", compute="_compute_sale_analysis_iframe") + + def _compute_sale_analysis_iframe(self): + for partner in self: + # Construction de l'iframe directement dans le champ HTML + partner.sale_analysis_iframe = f""" + + """ + + def _compute_sale_analysis_url(self): + for partner in self: + partner.sale_analysis_url = f'/sale_analysis/render/{partner.id}' def action_open_purchase_analysis(self): """Ouvre le wizard d'analyse des achats pour le client actuel.""" diff --git a/openai_partner_purchase_analysis/views/res_partner_view.xml b/openai_partner_purchase_analysis/views/res_partner_view.xml index 9fb4018..d9652ce 100644 --- a/openai_partner_purchase_analysis/views/res_partner_view.xml +++ b/openai_partner_purchase_analysis/views/res_partner_view.xml @@ -7,8 +7,8 @@ - - + + diff --git a/openai_partner_purchase_analysis/views/sale_analysis_templates.xml b/openai_partner_purchase_analysis/views/sale_analysis_templates.xml new file mode 100644 index 0000000..55890e1 --- /dev/null +++ b/openai_partner_purchase_analysis/views/sale_analysis_templates.xml @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/repos_module_linker/__manifest__.py b/repos_module_linker/__manifest__.py new file mode 100644 index 0000000..3cb070c --- /dev/null +++ b/repos_module_linker/__manifest__.py @@ -0,0 +1,17 @@ +{ + 'name': 'Extended Module Manager', + 'version': '1.1', + 'summary': 'Gérer les modules via liens symboliques directement depuis ir.modules', + 'category': 'Tools', + 'author': 'Benoît Vézina', + 'depends': ['base'], + 'data': [ +# 'views/module_filter_view.xml', +# 'views/module_menu.xml', + 'views/module_view.xml', + 'security/ir.model.access.csv', + ], + 'installable': True, + 'application': False, + 'license': 'LGPL-3', +} \ No newline at end of file diff --git a/repos_module_linker/models/__init__.py b/repos_module_linker/models/__init__.py new file mode 100644 index 0000000..2ae61a5 --- /dev/null +++ b/repos_module_linker/models/__init__.py @@ -0,0 +1 @@ +from . import ir_module_extension \ No newline at end of file diff --git a/repos_module_linker/models/ir_module_extension.py b/repos_module_linker/models/ir_module_extension.py new file mode 100644 index 0000000..d868f14 --- /dev/null +++ b/repos_module_linker/models/ir_module_extension.py @@ -0,0 +1,75 @@ +import os +import logging +from odoo import models, fields, api + +_logger = logging.getLogger(__name__) + +class ModuleExtension(models.Model): + _inherit = 'ir.module.module' + + state = fields.Selection( + selection_add=[ + ('linkable', 'Linkable'), + ('linked', 'Linked'), + ], + ondelete={ + 'linkable': 'set default', + 'linked': 'cascade', + }, + ) + + @staticmethod + def _get_base_paths(): + """Obtenir les chemins de base pour les dépôts et les addons""" + cwd = os.getcwd() + repos_path = os.path.join(cwd, '.repos') + addons_path = os.path.join(cwd, 'addons') + return repos_path, addons_path + + @api.model + def scan_modules(self): + """Scanner les dépôts dans .repos et mettre à jour les statuts des modules.""" + repos_path, addons_path = self._get_base_paths() + + if not os.path.exists(repos_path): + _logger.error(f"Le répertoire {repos_path} n'existe pas.") + return + + existing_modules = self.search([]).mapped('name') + for repo in os.listdir(repos_path): + repo_path = os.path.join(repos_path, repo) + if os.path.isdir(repo_path): + for module in os.listdir(repo_path): + module_path = os.path.join(repo_path, module) + if os.path.isdir(module_path): + module_record = self.search([('name', '=', module)], limit=1) + if module_record: + if module_record.state == 'uninstalled': + module_record.write({'state': 'linkable'}) + else: + self.create({ + 'name': module, + 'state': 'linkable', + 'repo_path': module_path, + }) + + def toggle_link(self): + """Créer ou supprimer le lien symbolique pour un module.""" + repos_path, addons_path = self._get_base_paths() + + for module in self: + symlink_path = os.path.join(addons_path, module.name) + if module.state == 'linkable': + # Créer un lien symbolique et marquer comme 'linked' + if os.path.exists(module.repo_path): + os.symlink(module.repo_path, symlink_path) + module.write({'state': 'linked'}) + _logger.info(f"Lien symbolique créé pour le module {module.name}.") + else: + _logger.error(f"Le chemin {module.repo_path} n'existe pas.") + elif module.state == 'linked': + # Supprimer le lien symbolique et repasser en 'linkable' + if os.path.islink(symlink_path): + os.unlink(symlink_path) + module.write({'state': 'linkable'}) + _logger.info(f"Lien symbolique supprimé pour le module {module.name}.") \ No newline at end of file diff --git a/repos_module_linker/security/ir.model.access.csv b/repos_module_linker/security/ir.model.access.csv new file mode 100644 index 0000000..8af06f6 --- /dev/null +++ b/repos_module_linker/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_module_manager,Access to Module Manager,base.model_ir_module_module,,1,1,1,1 \ No newline at end of file diff --git a/repos_module_linker/views/module_filter_view.xml b/repos_module_linker/views/module_filter_view.xml new file mode 100644 index 0000000..c0a74e3 --- /dev/null +++ b/repos_module_linker/views/module_filter_view.xml @@ -0,0 +1,15 @@ + + + ir.module.module.tree.filter.inherit + ir.module.module + + + + + + + + + + + \ No newline at end of file diff --git a/repos_module_linker/views/module_menu.xml b/repos_module_linker/views/module_menu.xml new file mode 100644 index 0000000..9d414ad --- /dev/null +++ b/repos_module_linker/views/module_menu.xml @@ -0,0 +1,17 @@ + + + Update Linkable List + + code + + action = env['ir.module.module'].scan_modules() + + + + + Update Linkable List + + + + + \ No newline at end of file diff --git a/repos_module_linker/views/module_view.xml b/repos_module_linker/views/module_view.xml new file mode 100644 index 0000000..807f0a4 --- /dev/null +++ b/repos_module_linker/views/module_view.xml @@ -0,0 +1,26 @@ + + + ir.module.module.tree.inherit + ir.module.module + + + +