diff --git a/addons/base_automation/views/base_automation_views.xml b/addons/base_automation/views/base_automation_views.xml
index 319bf9be61a..36441a34010 100644
--- a/addons/base_automation/views/base_automation_views.xml
+++ b/addons/base_automation/views/base_automation_views.xml
@@ -138,6 +138,8 @@
+
+
- to
-
-
-
-
- "
-
- "
-
-
+
+ by clearing it
+
+
+ by adding
+
+
+ by removing
+
+
+ by setting it to
+
+
+ to
+ as
+
+
+
+
+
diff --git a/odoo/addons/base/models/ir_actions.py b/odoo/addons/base/models/ir_actions.py
index ac89d672444..cf4aa6dfb9a 100644
--- a/odoo/addons/base/models/ir_actions.py
+++ b/odoo/addons/base/models/ir_actions.py
@@ -527,6 +527,13 @@ class IrActionsServer(models.Model):
update_field_id = fields.Many2one('ir.model.fields', string='Field to Update', ondelete='cascade', compute='_compute_crud_relations', store=True, readonly=False)
update_path = fields.Char(string='Field to Update Path', help="Path to the field to update, e.g. 'partner_id.name'", default=_default_update_path)
update_related_model_id = fields.Many2one('ir.model', compute='_compute_crud_relations', store=True)
+ update_field_type = fields.Selection(related='update_field_id.ttype', readonly=True)
+ update_m2m_operation = fields.Selection([
+ ('add', 'Adding'),
+ ('remove', 'Removing'),
+ ('set', 'Setting it to'),
+ ('clear', 'Clearing it')
+ ], string='Many2many Operations', default='add')
value = fields.Text(help="For Python expressions, this field may hold a Python expression "
"that can use the same values as for the code field on the server action,"
@@ -944,7 +951,7 @@ class IrActionsServer(models.Model):
@api.depends('evaluation_type', 'update_field_id')
def _compute_value_field_to_show(self): # check if value_field_to_show can be removed and use ttype in xml view instead
for action in self:
- if action.update_field_id.ttype == 'many2one':
+ if action.update_field_id.ttype in ('many2one', 'many2many'):
action.value_field_to_show = 'resource_ref'
elif action.update_field_id.ttype == 'selection':
action.value_field_to_show = 'selection_value'
@@ -978,6 +985,16 @@ class IrActionsServer(models.Model):
expr = action.value
if action.evaluation_type == 'equation':
expr = safe_eval(action.value, eval_context)
+ elif action.update_field_id.ttype == 'many2many':
+ operation = action.update_m2m_operation
+ if operation == 'add':
+ expr = [Command.link(int(action.value))]
+ elif operation == 'remove':
+ expr = [Command.unlink(int(action.value))]
+ elif operation == 'set':
+ expr = [Command.set([int(action.value)])]
+ elif operation == 'clear':
+ expr = [Command.clear()]
elif action.update_field_id.ttype in ['many2one', 'integer']:
try:
expr = int(action.value)
diff --git a/odoo/addons/base/tests/test_ir_actions.py b/odoo/addons/base/tests/test_ir_actions.py
index 94d5eead0d9..5c57433b485 100644
--- a/odoo/addons/base/tests/test_ir_actions.py
+++ b/odoo/addons/base/tests/test_ir_actions.py
@@ -226,6 +226,59 @@ ZeroDivisionError: division by zero""" % self.test_server_action.id
# Test: country updated
self.assertEqual(self.test_country.name_position, 'after')
+ def test_36_crud_write_m2m_ops(self):
+ """ Test that m2m operations work as expected """
+ categ_1 = self.env['res.partner.category'].create({'name': 'TestCateg1'})
+ categ_2 = self.env['res.partner.category'].create({'name': 'TestCateg2'})
+ # set partner category
+ self.action.write({
+ 'state': 'object_write',
+ 'update_path': 'category_id',
+ 'update_m2m_operation': 'set',
+ 'resource_ref': categ_1,
+ })
+ run_res = self.action.with_context(self.context).run()
+ self.assertFalse(run_res, 'ir_actions_server: update record action correctly finished should return False')
+ # Test: partner updated
+ self.assertIn(categ_1, self.test_partner.category_id, 'ir_actions_server: tag should have been set')
+
+ # add partner category
+ self.action.write({
+ 'state': 'object_write',
+ 'update_path': 'category_id',
+ 'update_m2m_operation': 'add',
+ 'resource_ref': categ_2,
+ })
+ run_res = self.action.with_context(self.context).run()
+ self.assertFalse(run_res, 'ir_actions_server: update record action correctly finished should return False')
+ # Test: partner updated
+ self.assertIn(categ_2, self.test_partner.category_id, 'ir_actions_server: new tag should have been added')
+ self.assertIn(categ_1, self.test_partner.category_id, 'ir_actions_server: old tag should still be there')
+
+ # remove partner category
+ self.action.write({
+ 'state': 'object_write',
+ 'update_path': 'category_id',
+ 'update_m2m_operation': 'remove',
+ 'resource_ref': categ_1,
+ })
+ run_res = self.action.with_context(self.context).run()
+ self.assertFalse(run_res, 'ir_actions_server: update record action correctly finished should return False')
+ # Test: partner updated
+ self.assertNotIn(categ_1, self.test_partner.category_id, 'ir_actions_server: tag should have been removed')
+ self.assertIn(categ_2, self.test_partner.category_id, 'ir_actions_server: tag should still be there')
+
+ # clear partner category
+ self.action.write({
+ 'state': 'object_write',
+ 'update_path': 'category_id',
+ 'update_m2m_operation': 'clear',
+ })
+ run_res = self.action.with_context(self.context).run()
+ self.assertFalse(run_res, 'ir_actions_server: update record action correctly finished should return False')
+ # Test: partner updated
+ self.assertFalse(self.test_partner.category_id, 'ir_actions_server: tags should have been cleared')
+
def test_37_field_path_traversal(self):
""" Test the update_path field traversal - allowing records to be updated along relational links """
# update the country's name via the partner
diff --git a/odoo/addons/base/views/ir_actions_views.xml b/odoo/addons/base/views/ir_actions_views.xml
index 9a4a30699a2..7acc05ea7d8 100644
--- a/odoo/addons/base/views/ir_actions_views.xml
+++ b/odoo/addons/base/views/ir_actions_views.xml
@@ -346,9 +346,12 @@
- to
+
+ by
+
+ to
-
+
Set a value...