summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNans Lefebvre <len@odoo.com>2019-01-11 13:20:41 +0000
committerRaphael Collet <rco@odoo.com>2019-02-15 12:37:25 +0000
commitee775c7548f6b428a1ef4f2ef40222ad4a8746c5 (patch)
treefeddd7cfbc8bdb6f74cf65f045b26648c23c09c5
parentb3b29f3b86f22b3157aba35e66e8ec3c16ee47bc (diff)
[IMP] base: add to automated actions the fields to watch during update
When users create an automated action to trigger when a record is updated, they assume each update is 'atomic' in some sense. It is not the case, as one write may trigger many write, some in recompute. As a result, any update may trigger a dozen actions (or more). We add a field wich allows us to specify which field we want to watch. For instance, you could select the field 'state on quotation', and thus only action would be triggered on confirming the quotation. The link_field_id is a field that allows to link a record of the source model to a new record of the target model, through a many2one. However many target records can be created, so every one of them (except the last) ends up orphan. We allow for one2many and many2many fields to be used, and in these cases we add the newly created record to the list. opw 1910671
-rw-r--r--addons/base_automation/data/base_automation_demo.xml13
-rw-r--r--addons/base_automation/models/base_automation.py31
-rw-r--r--addons/base_automation/tests/test_base_automation.py34
-rw-r--r--addons/base_automation/views/base_automation_view.xml2
-rw-r--r--odoo/addons/base/views/ir_actions_views.xml3
5 files changed, 80 insertions, 3 deletions
diff --git a/addons/base_automation/data/base_automation_demo.xml b/addons/base_automation/data/base_automation_demo.xml
index 246320ecf4a..3e864e34565 100644
--- a/addons/base_automation/data/base_automation_demo.xml
+++ b/addons/base_automation/data/base_automation_demo.xml
@@ -71,4 +71,17 @@ if 'user_id' in env.context['old_values'][record.id]:
</field>
</record>
+ <record id="test_rule_with_trigger" model="base.automation">
+ <field name="name">Base Automation: test rule with trigger</field>
+ <field name="model_id" ref="base_automation.model_base_automation_lead_test"/>
+ <field name="trigger_field_ids" eval="[(4,ref('base_automation.field_base_automation_lead_test__state'))]"/>
+ <field name="trigger">on_write</field>
+ <field name="active" eval="True"/>
+ <field name="state">code</field>
+ <field name="code">
+record = model.browse(env.context['active_id'])
+record['name'] = record.name + 'X'
+ </field>
+ </record>
+
</odoo>
diff --git a/addons/base_automation/models/base_automation.py b/addons/base_automation/models/base_automation.py
index ac3e6305c6b..ab460f1e875 100644
--- a/addons/base_automation/models/base_automation.py
+++ b/addons/base_automation/models/base_automation.py
@@ -61,6 +61,9 @@ class BaseAutomation(models.Model):
filter_domain = fields.Char(string='Apply on', help="If present, this condition must be satisfied before executing the action rule.")
last_run = fields.Datetime(readonly=True, copy=False)
on_change_fields = fields.Char(string="On Change Fields Trigger", help="Comma-separated list of field names that triggers the onchange.")
+ trigger_field_ids = fields.Many2many('ir.model.fields', string='Watched fields',
+ help="The action will be triggered if and only if one of these fields is updated."
+ "If empty, all fields are watched.")
# which fields have an impact on the registry
CRITICAL_FIELDS = ['model_id', 'active', 'trigger', 'on_change_fields']
@@ -193,8 +196,32 @@ class BaseAutomation(models.Model):
# execute server actions
if self.action_server_id:
for record in records:
- ctx = {'active_model': record._name, 'active_ids': record.ids, 'active_id': record.id}
- self.action_server_id.with_context(**ctx).run()
+ # we process the action if any watched field has been modified
+ if self._check_trigger_fields(record):
+ ctx = {'active_model': record._name, 'active_ids': record.ids, 'active_id': record.id}
+ self.action_server_id.with_context(**ctx).run()
+
+ def _check_trigger_fields(self, record):
+ """ Return whether any of the trigger fields has been modified on ``record``. """
+ if not self.trigger_field_ids:
+ # all fields are implicit triggers
+ return True
+
+ if not self._context.get('old_values'):
+ # this is a create: all fields are considered modified
+ return True
+
+ # Note: old_vals are in the format of read()
+ old_vals = self._context['old_values'].get(record.id, {})
+
+ def differ(name):
+ field = record._fields[name]
+ return (
+ name in old_vals and
+ field.convert_to_cache(record[name], record, validate=False) !=
+ field.convert_to_cache(old_vals[name], record, validate=False)
+ )
+ return any(differ(field.name) for field in self.trigger_field_ids)
@api.model_cr
def _register_hook(self):
diff --git a/addons/base_automation/tests/test_base_automation.py b/addons/base_automation/tests/test_base_automation.py
index d71423f4e69..6b34c65c6a7 100644
--- a/addons/base_automation/tests/test_base_automation.py
+++ b/addons/base_automation/tests/test_base_automation.py
@@ -131,3 +131,37 @@ class base_automation_test(common.TransactionCase):
self.assertEqual(lead.user_id, self.user_demo, "Responsible should change on creation of Lead test line.")
self.assertEqual(len(lead.line_ids), 1, "New test line is not created")
self.assertEqual(lead.line_ids.user_id, self.user_demo, "Responsible should be change on creation of Lead test line.")
+
+ def test_21_trigger_fields(self):
+ """
+ Check that the rule with trigger is executed only once per pertinent update.
+ """
+ lead = self.create_lead(name="X")
+ lead.priority = True
+ partner1 = self.env.ref('base.res_partner_1')
+ lead.partner_id = partner1.id
+ self.assertEqual(lead.name, 'X', "No update until now.")
+
+ lead.state = 'open'
+ self.assertEqual(lead.name, 'XX', "One update should have happened.")
+ lead.state = 'done'
+ self.assertEqual(lead.name, 'XXX', "One update should have happened.")
+ lead.state = 'done'
+ self.assertEqual(lead.name, 'XXX', "No update should have happened.")
+ lead.state = 'cancel'
+ self.assertEqual(lead.name, 'XXXX', "One update should have happened.")
+
+ # change the rule to trigger on partner_id
+ rule = self.env.ref('base_automation.test_rule_with_trigger')
+ rule.trigger_field_ids = self.env.ref('base_automation.field_base_automation_lead_test__partner_id')
+
+ partner2 = self.env.ref('base.res_partner_2')
+ lead.name = 'X'
+ lead.state = 'open'
+ self.assertEqual(lead.name, 'X', "No update should have happened.")
+ lead.partner_id = partner2
+ self.assertEqual(lead.name, 'XX', "One update should have happened.")
+ lead.partner_id = partner2
+ self.assertEqual(lead.name, 'XX', "No update should have happened.")
+ lead.partner_id = partner1
+ self.assertEqual(lead.name, 'XXX', "One update should have happened.")
diff --git a/addons/base_automation/views/base_automation_view.xml b/addons/base_automation/views/base_automation_view.xml
index 206f7897c75..ae56d4ea8a2 100644
--- a/addons/base_automation/views/base_automation_view.xml
+++ b/addons/base_automation/views/base_automation_view.xml
@@ -24,6 +24,8 @@
</div>
<xpath expr="//group[@name='action_content']" position="inside">
<field name="trigger"/>
+ <field name="trigger_field_ids" domain="[('model_id', '=', model_id)]"
+ attrs="{'invisible': [('trigger', 'not in', ['on_write', 'on_create_or_write'])]}"/>
<field name="filter_pre_domain" widget="domain" options="{'model': 'model_name', 'in_dialog': True}" attrs="{'invisible': [('trigger', 'not in', ['on_write','on_create_or_write'])]}"/>
<field name="filter_domain" widget="domain" options="{'model': 'model_name', 'in_dialog': True}"/>
diff --git a/odoo/addons/base/views/ir_actions_views.xml b/odoo/addons/base/views/ir_actions_views.xml
index 2eac4eccf98..8b279699a6d 100644
--- a/odoo/addons/base/views/ir_actions_views.xml
+++ b/odoo/addons/base/views/ir_actions_views.xml
@@ -288,7 +288,8 @@
attrs="{'invisible': [('state', '!=', 'object_create')], 'required': [('state', '=', 'object_create')]}"/>
<field name="crud_model_name" invisible="1"/>
<field name="link_field_id"
- domain="[('model_id', '=', model_id), ('relation', '=', crud_model_name), ('ttype', 'in', ['many2one'])]"
+ domain="[('model_id', '=', model_id), ('relation', '=', crud_model_name),
+ ('ttype', 'in', ['many2one', 'one2many', 'many2many'])]"
attrs="{'invisible': [('state', '!=', 'object_create')]}"/>
</group>
</group>