From 5fe2866cc44b7515c05377f39d6dca9fc1482fc3 Mon Sep 17 00:00:00 2001 From: "Thomas Lefebvre (thle)" Date: Thu, 29 Feb 2024 09:25:59 +0100 Subject: [PATCH] [FIX] base_automation: differentiate between creating and writing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Steps to reproduce: ------------------- [A] Create an automated action with: - Model: Tasks (project.task) - Trigger: On save - When updating: ID (to mimic "on_create" trigger) - Execute code: ```py record.message_post(body="This is a message from automated action (fields triggers: ['id'])") ``` [B] Create a server action (contextual action) with: - Type: execute code - Model: Tasks (project.task) - code: ```py record.write({}) ``` [C] Test: - create a new task --> message has been posted (OK) - write some values on the task --> no message has been posted (OK) - run the server action --> message has been posted (KO) Issue: ------ No message should be posted as the ID field has not been modified. Cause: ------ `if not self._context.get('old_values')` is considered as `True` because we have an empty dict. In fact, when we make a write on a record, old_values will be always a dict. ```py old_values = { old_vals.pop('id'): old_vals for old_vals in (records.read(list(vals)) if vals else []) } ``` Solution: --------- Old values are not defined in the context during a create. We can compare `self._context.get('old_values')` with `None` to differentiate between creating and writing on records. opw-3736068 closes odoo/odoo#155832 Signed-off-by: Rémy Voet (ryv) --- .../base_automation/models/base_automation.py | 2 +- .../base_automation/tests/test_automation.py | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/addons/base_automation/models/base_automation.py b/addons/base_automation/models/base_automation.py index ae69079c85c..d2e306fd94b 100644 --- a/addons/base_automation/models/base_automation.py +++ b/addons/base_automation/models/base_automation.py @@ -680,7 +680,7 @@ class BaseAutomation(models.Model): # all fields are implicit triggers return True - if not self._context.get('old_values'): + if self._context.get('old_values') is None: # this is a create: all fields are considered modified return True diff --git a/addons/base_automation/tests/test_automation.py b/addons/base_automation/tests/test_automation.py index 71a8b927c90..d7f93aeb322 100644 --- a/addons/base_automation/tests/test_automation.py +++ b/addons/base_automation/tests/test_automation.py @@ -112,3 +112,46 @@ class TestAutomation(TransactionCaseWithUserDemo): # simulate a onchange call on name result = self_portal.onchange({}, [], {"name": {}, "active": {}}) self.assertEqual(result["value"]["active"], False) + + def test_04_on_create_or_write_differentiate(self): + """ + The purpose is to differentiate create and empty write. + """ + model = self.env.ref("base.model_res_partner") + model_field_id = self.env['ir.model.fields'].search([('model', '=', model.model), ('name', '=', 'id')], limit=1) + automation = self.env["base.automation"].create({ + "name": "Test automated action", + "trigger": "on_create_or_write", + "model_id": model.id, + "trigger_field_ids": [Command.set([model_field_id.id])], + }) + action = self.env["ir.actions.server"].create({ + "name": "Modify name", + "base_automation_id": automation.id, + "model_id": model.id, + "state": "code", + "code": "record.write({'name': 'Modified Name'})" + }) + action.flush_recordset() + automation.write({"action_server_ids": [Command.link(action.id)]}) + # action cached was cached with admin, force CacheMiss + automation.env.clear() + + server_action = self.env["ir.actions.server"].create({ + "name": "Empty write", + "model_id": model.id, + "state": "code", + "code": "record.write({})" + }) + + partner = self.env[model.model].create({'name': 'Test Name'}) + self.assertEqual(partner.name, 'Modified Name', 'The automatic action must be performed') + partner.name = 'Reset Name' + self.assertEqual(partner.name, 'Reset Name', 'The automatic action must not be performed') + + context = { + 'active_model': model.model, + 'active_id': partner.id, + } + server_action.with_context(context).run() + self.assertEqual(partner.name, 'Reset Name', 'The automatic action must not be performed')