From 2a1d6f7641c1f8f475629c2b43e6da378b78261b Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Thu, 12 Nov 2015 11:53:33 +0100 Subject: [PATCH] [IMP] base_action_rule: manage recursive triggering of automatic actions The base action rules manage a kind of checklist with the records already processed for each action. This checklist may be populated by recursive calls on other models (such as the update of relational fields), and guarantees that a given action on a given record is executed at most once. --- addons/base_action_rule/base_action_rule.py | 65 +++++++++------------ 1 file changed, 27 insertions(+), 38 deletions(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 1161bfd13b9..7d229ce5bca 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -121,7 +121,11 @@ class base_action_rule(osv.osv): @openerp.api.model def _get_actions(self, records, kinds): - """ Return the actions of the given kinds for records' model. """ + """ Return the actions of the given kinds for records' model. The + returned actions' context contain an object to manage processing. + """ + if '__action_done' not in self._context: + self = self.with_context(__action_done={}) domain = [('model', '=', records._name), ('kind', 'in', kinds)] actions = self.with_context(active_test=True).search(domain) return actions.with_env(self.env) @@ -170,7 +174,15 @@ class base_action_rule(osv.osv): @openerp.api.multi def _process(self, records): - """ Process the action ``self`` on ``records``. """ + """ Process action ``self`` on the ``records`` that have not been done yet. """ + # filter out the records on which self has already been done, then mark + # remaining records as done (to avoid recursive processing) + action_done = self._context['__action_done'] + records -= action_done.setdefault(self, records.browse()) + if not records: + return + action_done[self] |= records + # modify records values = {} if 'date_action_last' in records._fields: @@ -207,20 +219,15 @@ class base_action_rule(osv.osv): """ Instanciate a create method that processes action rules. """ @openerp.api.model def create(self, vals): - # avoid loops or cascading actions - if self._context.get('action'): - return create.origin(self, vals) - - # call original method with a modified context - record = create.origin(self.with_context(action=True), vals) - # retrieve the action rules to possibly execute - actions = record.env['base.action.rule']._get_actions(record, ['on_create', 'on_create_or_write']) + actions = self.env['base.action.rule']._get_actions(self, ['on_create', 'on_create_or_write']) + + # call original method + record = create.origin(self.with_env(actions.env), vals) # check postconditions, and execute actions on the records that satisfy them for action in actions: - if action._filter_post(record): - action._process(record) + action._process(action._filter_post(record)) return record.with_env(self.env) @@ -234,21 +241,12 @@ class base_action_rule(osv.osv): # @openerp.api.multi def _write(self, vals): - # avoid loops or cascading actions - if self._context.get('action'): - return _write.origin(self, vals) - - # modify context - records = self.with_context(action=True) - # retrieve the action rules to possibly execute - actions = records.env['base.action.rule']._get_actions(records, ['on_write', 'on_create_or_write']) + actions = self.env['base.action.rule']._get_actions(self, ['on_write', 'on_create_or_write']) + records = self.with_env(actions.env) - # check preconditions - pre = { - action: action._filter_pre(records) - for action in actions - } + # check preconditions on records + pre = {action: action._filter_pre(records) for action in actions} # read old values before the update old_values = { @@ -261,9 +259,7 @@ class base_action_rule(osv.osv): # check postconditions, and execute actions on the records that satisfy them for action in actions.with_context(old_values=old_values): - post = action._filter_post(pre[action]) - if post: - action._process(post) + action._process(action._filter_post(pre[action])) return True return _write @@ -272,20 +268,13 @@ class base_action_rule(osv.osv): """ Instanciate an unlink method that processes action rules. """ @openerp.api.multi def unlink(self, **kwargs): - if self._context.get('action'): - return unlink.origin(self, **kwargs) - - # modify context - records = self.with_context(action=True) - # retrieve the action rules to possibly execute - actions = records.env['base.action.rule']._get_actions(records, ['on_unlink']) + actions = self.env['base.action.rule']._get_actions(self, ['on_unlink']) + records = self.with_env(actions.env) # check conditions, and execute actions on the records that satisfy them for action in actions: - pre = action._filter_post(records) - if pre: - action._process(pre) + action._process(action._filter_post(pre[action])) # call original method return unlink.origin(self, **kwargs)