[IMP] base_automation,base: support m2m operations in server actions
Before this commit, server actions of the 'Update Record' type could only write on m2m fields entirely - meaning that there was no support for commands like adding or removing records from the set without writing it completely. This commit introduces this possibility, along with some UI changes to come from it regarding the display of such actions in automation rules. Task-3450200 Part-of: odoo/odoo#138804
This commit is contained in:
@@ -138,6 +138,8 @@
|
||||
<field name="value" invisible="1" />
|
||||
<field name="selection_value" invisible="1" />
|
||||
<field name="value_field_to_show" invisible="1"/>
|
||||
<field name="update_field_type" invisible="1"/>
|
||||
<field name="update_m2m_operation" invisible="1"/>
|
||||
|
||||
<!-- Icon section -->
|
||||
<i
|
||||
@@ -158,16 +160,26 @@
|
||||
/>
|
||||
<field name="name" class="text-truncate" />
|
||||
<t invisible="state != 'object_write'">
|
||||
<span>to</span>
|
||||
<code invisible="evaluation_type != 'equation'" t-esc="record.value.raw_value"/>
|
||||
<span invisible="evaluation_type == 'equation'" >
|
||||
<field name="selection_value" invisible="value_field_to_show != 'selection_value'" class="d-inline"/>
|
||||
<span invisible="value_field_to_show == 'selection_value'">
|
||||
<span invisible="value == False or evaluation_type == False">"</span>
|
||||
<field name="value" class="d-inline"/>
|
||||
<span invisible="value == False or evaluation_type == False">"</span>
|
||||
</span>
|
||||
</span>
|
||||
<t invisible="not (update_field_type == 'many2many' and update_m2m_operation == 'clear') or evaluation_type != 'value'">
|
||||
<span>by clearing it</span>
|
||||
</t>
|
||||
<t invisible="not (update_field_type == 'many2many' and update_m2m_operation == 'add') or evaluation_type != 'value'">
|
||||
<span>by adding</span><field name="resource_ref"/>
|
||||
</t>
|
||||
<t invisible="not (update_field_type == 'many2many' and update_m2m_operation == 'remove') or evaluation_type != 'value'">
|
||||
<span>by removing</span><field name="resource_ref"/>
|
||||
</t>
|
||||
<t invisible="not (update_field_type == 'many2many' and update_m2m_operation == 'set') or evaluation_type != 'value'">
|
||||
<span>by setting it to</span><field name="resource_ref"/>
|
||||
</t>
|
||||
<t invisible="update_field_type == 'many2many' and evaluation_type == 'value'">
|
||||
<span invisible="evaluation_type != 'value'">to</span>
|
||||
<span invisible="evaluation_type != 'equation'">as</span>
|
||||
<field name="resource_ref" invisible="not (value_field_to_show == 'resource_ref' and evaluation_type == 'value')" />
|
||||
<field name="selection_value" invisible="not (value_field_to_show == 'selection_value' and evaluation_type == 'value')" class="d-inline"/>
|
||||
<em invisible="not (value_field_to_show == 'value' and evaluation_type == 'value')" t-out="record.value.raw_value" class="d-inline"/>
|
||||
<code invisible="not (evaluation_type == 'equation')" t-out="record.value.raw_value"/>
|
||||
</t>
|
||||
</t>
|
||||
<button type="delete" name="delete" class="btn fa fa-trash fa-xl px-3 ms-auto" title="Delete Action" />
|
||||
</div>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -346,9 +346,12 @@
|
||||
<field name="update_field_id" invisible="1"/>
|
||||
<field name="value_field_to_show" invisible="1"/>
|
||||
<field name="update_related_model_id" invisible="1"/>
|
||||
<span invisible="evaluation_type != 'value'">to</span>
|
||||
<field name="update_field_type" invisible="1"/>
|
||||
<span invisible="evaluation_type != 'value' or not update_field_type == 'many2many'">by</span>
|
||||
<field name="update_m2m_operation" class="oe_inline" invisible="evaluation_type != 'value' or not update_field_type == 'many2many'" required="update_field_type == 'many2many'"/>
|
||||
<span invisible="evaluation_type != 'value' or update_field_type == 'many2many'">to</span>
|
||||
<field name="value" class="oe_inline" placeholder="Set a value..." invisible="update_field_id == False or value_field_to_show != 'value' or evaluation_type != 'value'" string="Custom Value"/>
|
||||
<field name="resource_ref" class="oe_inline" placeholder="Choose a value..." string="Custom Value" options="{'model_field': 'update_related_model_id', 'no_create': True, 'no_open': True}" invisible=" update_field_id == False or value_field_to_show != 'resource_ref' or evaluation_type == 'equation'"/>
|
||||
<field name="resource_ref" class="oe_inline" placeholder="Choose a value..." string="Custom Value" options="{'model_field': 'update_related_model_id', 'no_create': True, 'no_open': True}" invisible=" update_field_id == False or value_field_to_show != 'resource_ref' or evaluation_type == 'equation' or update_m2m_operation == 'clear'"/>
|
||||
<field name="selection_value" class="oe_inline" placeholder="Choose a value..." options="{'no_create': True, 'no_open': True}" invisible=" update_field_id == False or value_field_to_show != 'selection_value' or evaluation_type == 'equation'"/>
|
||||
<span invisible="update_field_id != False or evaluation_type == 'equation'" class="text-muted">Set a value...</span>
|
||||
<span invisible="evaluation_type != 'equation'">
|
||||
|
||||
Reference in New Issue
Block a user