From f56882723d656977776f2394d646542ec059ce4f Mon Sep 17 00:00:00 2001 From: Juan Jose Scarafia Date: Thu, 10 Aug 2023 23:42:04 -0300 Subject: [PATCH] [ADD] base_dynamic_message_type --- base_dynamic_message/README.rst | 52 +++++++++ base_dynamic_message/__init__.py | 1 + base_dynamic_message/__manifest__.py | 44 ++++++++ base_dynamic_message/models/__init__.py | 2 + .../models/ir_model_dynamic_message.py | 106 ++++++++++++++++++ .../models/ir_model_dynamic_message_line.py | 40 +++++++ .../security/ir.model.access.csv | 5 + base_dynamic_message/security/res_groups.xml | 13 +++ .../views/ir_model_dynamic_message_views.xml | 89 +++++++++++++++ 9 files changed, 352 insertions(+) create mode 100644 base_dynamic_message/README.rst create mode 100644 base_dynamic_message/__init__.py create mode 100644 base_dynamic_message/__manifest__.py create mode 100644 base_dynamic_message/models/__init__.py create mode 100644 base_dynamic_message/models/ir_model_dynamic_message.py create mode 100644 base_dynamic_message/models/ir_model_dynamic_message_line.py create mode 100644 base_dynamic_message/security/ir.model.access.csv create mode 100644 base_dynamic_message/security/res_groups.xml create mode 100644 base_dynamic_message/views/ir_model_dynamic_message_views.xml diff --git a/base_dynamic_message/README.rst b/base_dynamic_message/README.rst new file mode 100644 index 00000000..b7379da9 --- /dev/null +++ b/base_dynamic_message/README.rst @@ -0,0 +1,52 @@ +.. |company| replace:: ADHOC SA + +.. |company_logo| image:: https://raw.githubusercontent.com/ingadhoc/maintainer-tools/master/resources/adhoc-logo.png + :alt: ADHOC SA + :target: https://www.adhoc.com.ar + +.. |icon| image:: https://raw.githubusercontent.com/ingadhoc/maintainer-tools/master/resources/adhoc-icon.png + +.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: https://www.gnu.org/licenses/agpl + :alt: License: AGPL-3 + +============================== +Dynamic Messages on form views +============================== + +Este modulo sirve para generar mensajes dinamicos en vistas form de cualquier modelo de Odoo. +Algunos ejemplos: + +#. Dar un determinado warning cuando un partner es de un determinado país +#. Advertir cuando un ticket es de un cliente de una determinada categoría + +Roadmap: +#. Mejorar readme + +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 smashing it by providing a detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* |company| |icon| + +Contributors +------------ + +Maintainer +---------- + +|company_logo| + +This module is maintained by the |company|. + +To contribute to this module, please visit https://www.adhoc.com.ar. diff --git a/base_dynamic_message/__init__.py b/base_dynamic_message/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/base_dynamic_message/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/base_dynamic_message/__manifest__.py b/base_dynamic_message/__manifest__.py new file mode 100644 index 00000000..0ce8a5e3 --- /dev/null +++ b/base_dynamic_message/__manifest__.py @@ -0,0 +1,44 @@ +############################################################################## +# +# Copyright (C) 2019 ADHOC SA (http://www.adhoc.com.ar) +# All Rights Reserved. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +{ + 'name': 'Dynamic Message', + 'version': '16.0.1.1.0', + 'category': 'Base', + 'sequence': 14, + 'summary': '', + 'author': 'ADHOC SA', + 'website': 'www.adhoc.com.ar', + 'license': 'AGPL-3', + 'images': [ + ], + 'depends': [ + 'base', + ], + 'data': [ + 'security/res_groups.xml', + 'security/ir.model.access.csv', + 'views/ir_model_dynamic_message_views.xml', + ], + 'demo': [ + ], + 'installable': True, + 'auto_install': False, + 'application': False, +} diff --git a/base_dynamic_message/models/__init__.py b/base_dynamic_message/models/__init__.py new file mode 100644 index 00000000..f39c10aa --- /dev/null +++ b/base_dynamic_message/models/__init__.py @@ -0,0 +1,2 @@ +from . import ir_model_dynamic_message +from . import ir_model_dynamic_message_line diff --git a/base_dynamic_message/models/ir_model_dynamic_message.py b/base_dynamic_message/models/ir_model_dynamic_message.py new file mode 100644 index 00000000..9f868892 --- /dev/null +++ b/base_dynamic_message/models/ir_model_dynamic_message.py @@ -0,0 +1,106 @@ +from odoo import models, fields, api +import ast +import textwrap + + +class IrModelDynamicMessage(models.Model): + _name = 'ir.model.dynamic_message' + _description = 'ir.model.dynamic_message' + + name = fields.Char(required=True) + description = fields.Text() + model_id = fields.Many2one('ir.model', required=True, ondelete='cascade') + model_name = fields.Char(related='model_id.model', string='Model Name') + line_ids = fields.One2many('ir.model.dynamic_message.line', 'dynamic_message_id', copy=True) + code = fields.Text(compute='_compute_code') + depends = fields.Char(compute='_compute_depends', store=True, readonly=False, required=True) + field_id = fields.Many2one('ir.model.fields', readonly=True, copy=False) + view_to_inherit_id = fields.Many2one( + 'ir.ui.view', compute='_compute_view_to_inherit', readonly=False, store=True, required=True) + view_id = fields.Many2one('ir.ui.view', readonly=True, copy=False) + alert_type = fields.Selection([('info', 'info'), ('warning', 'warning'), ('danger', 'danger')], required=True, default='info') + + @api.depends('model_id') + def _compute_view_to_inherit(self): + for rec in self: + rec.view_to_inherit_id = rec.env['ir.ui.view'].search( + [('type', '=', 'form'), ('model', '=', rec.model_id.model), ('mode', '=', 'primary')], limit=1) + + @api.depends('line_ids.code') + def _compute_code(self): + for rec in self: + sub_string = ''.join(rec.line_ids.filtered('code').mapped('code')) + sub_string = textwrap.indent(sub_string, prefix=' ') + field_name = 'x_dynamic_message_%i' % rec._origin.id or 0 + rec.code = """ +for rec in self: + messages = [] +%s + if not messages: + rec['%s'] = False + else: + rec['%s'] = "
    %%s
" %% "".join(["
  • %%s
  • " %% message for message in messages]) +""" % (sub_string, field_name, field_name) + + @api.depends('line_ids.domain') + def _compute_depends(self): + for rec in self: + dep_fields = [] + for line in rec.line_ids.filtered('domain'): + domain = ast.literal_eval(line.domain) + for element in domain: + if type(element) is not tuple: + continue + # TODO deberiamos chequear que el campo sea sercheable (en smart search teniamos algo de esto) + if '.' in element[0]: + dep_fields.append(element[0].split('.')[0]) + elif element[0] != 'id': + dep_fields.append(element[0]) + rec.depends = ','.join(list(set(dep_fields))) if dep_fields else False + + def confirm(self): + for rec in self: + field_name = 'x_dynamic_message_%i' % rec.id + field_vals = { + 'name': field_name, + 'field_description': 'Dynamic Message', + 'state': 'manual', + 'store': False, + 'ttype': 'html', + 'model_id': rec.model_id.id, + 'compute': rec.code, + 'depends': rec.depends, + } + if rec.field_id: + rec.field_id.sudo().write(field_vals) + else: + rec.field_id = rec.field_id.sudo().create(field_vals) + + view_vals = { + 'name': 'Dynamic Message for %s' % rec.model_id.name, + 'inherit_id': rec.view_to_inherit_id.id, + 'model': rec.model_id.model, + 'priority': 999, + 'arch_db': """ + + + +""" % (rec.alert_type, field_name, field_name), + } + if rec.view_id: + rec.view_id.sudo().write(view_vals) + else: + rec.view_id = rec.view_id.sudo().create(view_vals) + + @api.ondelete(at_uninstall=True) + def _delete_field_and_view(self): + self.mapped('field_id').sudo().unlink() + self.mapped('view_id').sudo().unlink() + + _sql_constraints = [ + ( + 'unique_level_per_model', 'UNIQUE(model_id, alert_type, view_to_inherit_id)', + 'Debe haber un unico registro por modelo, vista heredada y tipo de alerta') + ] diff --git a/base_dynamic_message/models/ir_model_dynamic_message_line.py b/base_dynamic_message/models/ir_model_dynamic_message_line.py new file mode 100644 index 00000000..3b7fb837 --- /dev/null +++ b/base_dynamic_message/models/ir_model_dynamic_message_line.py @@ -0,0 +1,40 @@ +from odoo import models, fields, api +import re + + +class IrModelDynamicMessageLine(models.Model): + _name = 'ir.model.dynamic_message.line' + _description = 'ir.model.dynamic_message.line' + + dynamic_message_id = fields.Many2one('ir.model.dynamic_message', required=True, ondelete='cascade') + description = fields.Text() + domain = fields.Char() + message = fields.Html(required=True,) + model_name = fields.Char(related='dynamic_message_id.model_id.model') + code = fields.Text(compute='_compute_code', store=True, readonly=False) + + @api.depends('message', 'domain') + def _compute_code(self): + for rec in self: + if rec.domain: + # si el doinio tiene expresiones "EXP:" recorremos las tuplas y lo dejamos sin string y limpiamos el "EXP:"" + domain = rec.domain + if "EXP:" in rec.domain: + # construido con chatgpt + # Original string + # Regular expression pattern to extract the expression parts + pattern = r'"EXP: (.+?)"' + + matches = re.finditer(pattern, domain) + + for match in matches: + expression = match.group(1) + domain = domain.replace(f'"EXP: {expression}"', expression) + + rec.code = """ +if rec in rec.filtered_domain(%s): + messages.append('%s') +""" % (domain, str(rec.message).replace('

    ', '').replace('

    ', '')) + else: + rec.code = False + # code = self.filtered_domain(self.env['account.payment.method']._get_payment_method_domain(payment_method_code)) diff --git a/base_dynamic_message/security/ir.model.access.csv b/base_dynamic_message/security/ir.model.access.csv new file mode 100644 index 00000000..56246d89 --- /dev/null +++ b/base_dynamic_message/security/ir.model.access.csv @@ -0,0 +1,5 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_ir_model_dynamic_message,access_ir_model_dynamic_message,model_ir_model_dynamic_message,base.group_user,1,0,0,0 +access_ir_model_dynamic_message_line,access_ir_model_dynamic_message_line,model_ir_model_dynamic_message_line,base.group_user,1,0,0,0 +access_ir_model_dynamic_message_admin,access_ir_model_dynamic_message,model_ir_model_dynamic_message,group_dynamic_messages_manager,1,1,1,1 +access_ir_model_dynamic_message_line_admin,access_ir_model_dynamic_message_line,model_ir_model_dynamic_message_line,group_dynamic_messages_manager,1,1,1,1 diff --git a/base_dynamic_message/security/res_groups.xml b/base_dynamic_message/security/res_groups.xml new file mode 100644 index 00000000..735a3e23 --- /dev/null +++ b/base_dynamic_message/security/res_groups.xml @@ -0,0 +1,13 @@ + + + + + + Manage Dynamic Messages + + + + diff --git a/base_dynamic_message/views/ir_model_dynamic_message_views.xml b/base_dynamic_message/views/ir_model_dynamic_message_views.xml new file mode 100644 index 00000000..c251d598 --- /dev/null +++ b/base_dynamic_message/views/ir_model_dynamic_message_views.xml @@ -0,0 +1,89 @@ + + + ir.model.dynamic_message.tree + ir.model.dynamic_message + + + + + + + + + + + ir.model.dynamic_message.form + ir.model.dynamic_message + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    En las expresiones, del lado derecho, se puede usar el string "EXP:[expresion]" para poner codigo dinamico. A la hora de pasar el dominio al calculo solo se + dejará la expresion. Es importante que "EXP" se ponga con comillas dobles y no comillas simples tal como se muestra en los ejemplos. Las variables disponibles son: +

      +
    • rec: registro que se está evaluando
    • +
    • las mismas variables disponibles en un campo calculado, por ej "datetime.datetime.now()" y "dateutil.relativedelta.relativedelta":
    • +
    + Por ej: +
      +
    • [("create_date", "=", "EXP: datetime.datetime.now() - dateutil.relativedelta.relativedelta(days=-10)")]
    • +
    • [("partner_id", "in", ["EXP: rec.child_ids.ids"])]
    • +
    +

    + + + + + + + + +
    +
    + + + +
    +
    + +
    +
    + + + Dynamic Messages + ir.model.dynamic_message + current + tree,form + + + +