diff --git a/setup/web_create_write_confirm/odoo/addons/web_create_write_confirm b/setup/web_create_write_confirm/odoo/addons/web_create_write_confirm new file mode 120000 index 000000000..46be3cc17 --- /dev/null +++ b/setup/web_create_write_confirm/odoo/addons/web_create_write_confirm @@ -0,0 +1 @@ +../../../../web_create_write_confirm \ No newline at end of file diff --git a/setup/web_create_write_confirm/setup.py b/setup/web_create_write_confirm/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/web_create_write_confirm/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/web_create_write_confirm/README.rst b/web_create_write_confirm/README.rst new file mode 100644 index 000000000..e69de29bb diff --git a/web_create_write_confirm/__init__.py b/web_create_write_confirm/__init__.py new file mode 100644 index 000000000..edbd405bf --- /dev/null +++ b/web_create_write_confirm/__init__.py @@ -0,0 +1,4 @@ +# (C) 2020 Smile () +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from . import models diff --git a/web_create_write_confirm/__manifest__.py b/web_create_write_confirm/__manifest__.py new file mode 100644 index 000000000..0f77454c0 --- /dev/null +++ b/web_create_write_confirm/__manifest__.py @@ -0,0 +1,21 @@ +# (C) 2020 Smile () +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +{ + "name": "Confirm/Alert pop-up before saving", + "version": "14.0.1.0.0", + "depends": ["web"], + "author": "Smile, Odoo Community Association (OCA)", + "license": "AGPL-3", + "website": "https://github.com/OCA/web", + "category": "Tools", + "sequence": 20, + "data": [ + "security/ir.model.access.csv", + "views/assets.xml", + "views/popup_message.xml", + ], + "auto_install": False, + "installable": True, + "application": False, +} diff --git a/web_create_write_confirm/models/__init__.py b/web_create_write_confirm/models/__init__.py new file mode 100644 index 000000000..9ef27cee6 --- /dev/null +++ b/web_create_write_confirm/models/__init__.py @@ -0,0 +1,5 @@ +# (C) 2020 Smile () +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from . import base +from . import popup_message diff --git a/web_create_write_confirm/models/base.py b/web_create_write_confirm/models/base.py new file mode 100644 index 000000000..b727d54a5 --- /dev/null +++ b/web_create_write_confirm/models/base.py @@ -0,0 +1,36 @@ +# (C) 2020 Smile () +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). +from odoo import models + + +class BaseModel(models.AbstractModel): + _inherit = "base" + + def get_message_informations(self, values=False): + """ + This function gives us the possibility to know + if we display the message or not + - In create self is empty + - In write self is not empty contains current ID + :param values: + - In create dictionary contains all recording + information self is False + - In write we find only values changed + :type values: dict + :return: return dict object popup.message + (self.env['popup.message'].read()) + """ + return False + + def execute_processing(self, values=False): + """ + This function gives us the possibility to execute a + specific treatment after the confirmation of the message + - In create self is empty + - In write self is not empty contains current ID + :param values : a list of dictionaries: + {'name': field, 'value': value of field} + :type dictionary list + :return boolean + """ + return False diff --git a/web_create_write_confirm/models/popup_message.py b/web_create_write_confirm/models/popup_message.py new file mode 100644 index 000000000..7e17516d2 --- /dev/null +++ b/web_create_write_confirm/models/popup_message.py @@ -0,0 +1,30 @@ +from odoo import api, fields, models + + +class PopupMessage(models.Model): + _name = "popup.message" + _rec_name = "title" + _description = "Popup message" + + model_id = fields.Many2one( + comodel_name="ir.model", string="Model", required=True, ondelete="cascade" + ) + model = fields.Char(related="model_id.model") + field_ids = fields.Many2many( + comodel_name="ir.model.fields", required=True, string="Fields" + ) + field_name = fields.Char(compute="_compute_field_name") + popup_type = fields.Selection( + string="Type", + required=True, + default="confirm", + selection=[("confirm", "Confirmation"), ("alert", "Alert")], + ) + title = fields.Char(string="Title") + message = fields.Text(string="Message", required=True) + active = fields.Boolean(string="Active", default=True) + + @api.depends("field_ids") + def _compute_field_name(self): + for message in self: + message.field_name = ",".join(f.name for f in message.field_ids) diff --git a/web_create_write_confirm/readme/CONTRIBUTORS.rst b/web_create_write_confirm/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..bc626348c --- /dev/null +++ b/web_create_write_confirm/readme/CONTRIBUTORS.rst @@ -0,0 +1,6 @@ +* `Smile `_ + + +* `Ooops404 `__: + + * Ilyas diff --git a/web_create_write_confirm/readme/DESCRIPTION.rst b/web_create_write_confirm/readme/DESCRIPTION.rst new file mode 100644 index 000000000..d6406d8c2 --- /dev/null +++ b/web_create_write_confirm/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +This module provides feature to create custom confirmation or alert dialog when user creates or writes record. +Module includes only methods that you can use in your code. That means programming is always required. +See usage section for more information. diff --git a/web_create_write_confirm/readme/USAGE.rst b/web_create_write_confirm/readme/USAGE.rst new file mode 100644 index 000000000..e3fb70d92 --- /dev/null +++ b/web_create_write_confirm/readme/USAGE.rst @@ -0,0 +1,34 @@ +Create popup.message record. Specify model_id, field_ids (which fields will trigger alert) and other fields. +Put you code into **get_message_informations** or **execute_processing** method of you model. +Return dict (perform read() to get it). +Here is some examples how you can use this module features in your code. + +Confirm res.partner change: + +.. code-block:: python + + msg = self.env['popup.message'].create( + { + 'model_id': self.env['ir.model'].search([('model', '=', 'res.partner')]).id, + 'field_ids': [(6, 0, self.env['ir.model.fields'].search([('model', '=', 'res.partner')]).ids)], + 'popup_type': 'confirm', + 'title': 'Warning', + 'message': 'Are you sure want to update record?', + } + ) + return msg.read() + +Sale order alert: + +.. code-block:: python + + msg = self.env['popup.message'].create( + { + 'model_id': self.env['ir.model'].search([('model', '=', 'sale.order')]).id, + 'field_ids': [(6, 0, self.env['ir.model.fields'].search([('model', '=', 'sale.order')]).ids)], + 'popup_type': 'alert', + 'title': 'Attention', + 'message': 'Sale order was updated.', + } + ) + return msg.read() diff --git a/web_create_write_confirm/security/ir.model.access.csv b/web_create_write_confirm/security/ir.model.access.csv new file mode 100644 index 000000000..c1abaa9ee --- /dev/null +++ b/web_create_write_confirm/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_popup_message,popup.message,model_popup_message,base.group_user,1,0,0,0 +access_popup_message_adm,popup.message.admin,model_popup_message,base.group_erp_manager,1,1,1,1 diff --git a/web_create_write_confirm/static/description/icon.png b/web_create_write_confirm/static/description/icon.png new file mode 100644 index 000000000..17984e2d0 Binary files /dev/null and b/web_create_write_confirm/static/description/icon.png differ diff --git a/web_create_write_confirm/static/src/js/pop_up_confirmation.js b/web_create_write_confirm/static/src/js/pop_up_confirmation.js new file mode 100644 index 000000000..815c0e3db --- /dev/null +++ b/web_create_write_confirm/static/src/js/pop_up_confirmation.js @@ -0,0 +1,107 @@ +odoo.define("web_create_write_confirm.pop_up_confirmation", function (require) { + "use strict"; + + var FormController = require("web.FormController"); + var Dialog = require("web.Dialog"); + + FormController.include({ + getMessageInformation: function (model, nameFunction, record_id, values) { + return this._rpc({ + model: model, + method: nameFunction, + args: [record_id, values], + }); + }, + + _onSave: function (ev) { + var self = this; + var modelName = self.modelName ? self.modelName : false; + var record = self.model.get(self.handle, {raw: true}); + var record_id = + record && record.data && record.data.id ? record.data.id : false; + var changes = self.model.localData[self.handle]._changes; + self.getMessageInformation( + modelName, + "get_message_informations", + record_id, + changes === null ? {} : changes + ).then(function (results) { + this.display_popup(results, record, record_id, ev, changes, modelName); + }); + }, + + display_popup: function ( + popup_values, + record, + record_id, + ev, + changes, + modelName + ) { + var self = this; + var index = 0; + var datas = []; + new Promise(function (resolve) { + if ( + typeof popup_values !== "undefined" && + typeof popup_values !== "boolean" && + popup_values.length + ) { + if (popup_values[index].popup_type === "confirm") { + Dialog.confirm(self, popup_values[index].message, { + title: popup_values[index].title, + confirm_callback: async () => { + var field_names = popup_values[index].field_name.split( + "," + ); + for (var j = 0; j < field_names.length; j++) { + datas.push({ + name: field_names[j], + value: + changes && changes[field_names[j]] + ? changes[field_names[j]] + : record.data[field_names[j]] === "" + ? record.data[field_names[j]] + : false, + }); + } + index++; + if (popup_values.length > index) { + this.display_popup( + popup_values, + record, + record_id, + ev + ); + } else if (popup_values.length === index) { + self.getMessageInformation( + modelName, + "execute_processing", + record_id, + datas + ); + this.save(ev); + } + }, + }).on("closed", null, resolve); + } else if (popup_values[index].popup_type === "alert") { + Dialog.alert(self, popup_values[index].message, { + title: popup_values[index].title, + }); + } + } else { + this.save(ev); + } + }); + }, + + save: function ev() { + var self = this; + ev.stopPropagation(); + self._disableButtons(); + self.saveRecord() + .then(self._enableButtons.bind(self)) + .guardedCatch(self._enableButtons.bind(self)); + }, + }); +}); diff --git a/web_create_write_confirm/tests/__init__.py b/web_create_write_confirm/tests/__init__.py new file mode 100644 index 000000000..9e7f8acd2 --- /dev/null +++ b/web_create_write_confirm/tests/__init__.py @@ -0,0 +1 @@ +from . import test_base_model diff --git a/web_create_write_confirm/tests/test_base_model.py b/web_create_write_confirm/tests/test_base_model.py new file mode 100644 index 000000000..7f10553fa --- /dev/null +++ b/web_create_write_confirm/tests/test_base_model.py @@ -0,0 +1,55 @@ +from odoo.tests.common import SavepointCase + + +class TestBaseModel(SavepointCase): + @classmethod + def setUpClass(cls): + super(TestBaseModel, cls).setUpClass() + cls.base_model = cls.env["base"] + + def test_compute_field_name(self): + """Test correct flow of changing field_name""" + model_field_name = self.env["ir.model.fields"]._get( + "popup.message", "field_name" + ) + vals = { + "model_id": model_field_name.model_id.id, + "field_ids": model_field_name, + "message": "test", + } + test_popup_message = self.env["popup.message"].sudo().create(vals) + self.assertEqual( + test_popup_message.field_name, + model_field_name.name, + msg="Must be equal to name of 'ir.model.fields' field", + ) + + model_field_title = self.env["ir.model.fields"]._get("popup.message", "title") + vals.update({"field_ids": (model_field_name.id, model_field_title.id)}) + test_popup_message_cycle = self.env["popup.message"].sudo().create(vals) + field_name = model_field_name.name + "," + model_field_title.name + self.assertEqual( + test_popup_message_cycle.field_name, + field_name, + msg="Must be equal to 2 join name of 'ir.model.fields' fields", + ) + + def test_get_message_informations(self): + """Test correct flow of get_message_informations method""" + ret_value_of_method = self.base_model.get_message_informations() + check_ret_value = False + if (ret_value_of_method is False) or isinstance( + ret_value_of_method, type(self.env["popup.message"]) + ): + check_ret_value = True + self.assertTrue( + check_ret_value, + msg="Return value must be False or dictionary of popup.message objects", + ) + self.env["popup.message"]._compute_field_name() + + def test_execute_processing(self): + """Test correct flow of execute_processing method""" + self.assertFalse( + self.base_model.execute_processing(), msg="Return value must be False" + ) diff --git a/web_create_write_confirm/views/assets.xml b/web_create_write_confirm/views/assets.xml new file mode 100644 index 000000000..6aec517eb --- /dev/null +++ b/web_create_write_confirm/views/assets.xml @@ -0,0 +1,15 @@ + + + + +