[FIX] base_action_rule: define patched methods inside a function to fix their closure.
authorRaphael Collet <rco@openerp.com>
Thu, 31 Jul 2014 09:17:20 +0000 (11:17 +0200)
committerOlivier Dony <odo@openerp.com>
Thu, 31 Jul 2014 10:43:41 +0000 (12:43 +0200)
addons/base_action_rule/base_action_rule.py

index b743603..d8fbec0 100644 (file)
@@ -156,6 +156,75 @@ class base_action_rule(osv.osv):
         """ Wrap the methods `create` and `write` of the models specified by
             the rules given by `ids` (or all existing rules if `ids` is `None`.)
         """
+        #
+        # Note: the patched methods create and write must be defined inside
+        # another function, otherwise their closure may be wrong. For instance,
+        # the function create refers to the outer variable 'create', which you
+        # expect to be bound to create itself. But that expectation is wrong if
+        # create is defined inside a loop; in that case, the variable 'create'
+        # is bound to the last function defined by the loop.
+        #
+
+        def make_create():
+            """ instanciate a create method that processes action rules """
+            def create(self, cr, uid, vals, context=None, **kwargs):
+                # avoid loops or cascading actions
+                if context and context.get('action'):
+                    return create.origin(self, cr, uid, vals, context=context)
+
+                # call original method with a modified context
+                context = dict(context or {}, action=True)
+                new_id = create.origin(self, cr, uid, vals, context=context, **kwargs)
+
+                # as it is a new record, we do not consider the actions that have a prefilter
+                action_model = self.pool.get('base.action.rule')
+                action_dom = [('model', '=', self._name),
+                              ('kind', 'in', ['on_create', 'on_create_or_write'])]
+                action_ids = action_model.search(cr, uid, action_dom, context=context)
+
+                # check postconditions, and execute actions on the records that satisfy them
+                for action in action_model.browse(cr, uid, action_ids, context=context):
+                    if action_model._filter(cr, uid, action, action.filter_id, [new_id], context=context):
+                        action_model._process(cr, uid, action, [new_id], context=context)
+                return new_id
+
+            return create
+
+        def make_write():
+            """ instanciate a write method that processes action rules """
+            def write(self, cr, uid, ids, vals, context=None, **kwargs):
+                # avoid loops or cascading actions
+                if context and context.get('action'):
+                    return write.origin(self, cr, uid, ids, vals, context=context)
+
+                # modify context
+                context = dict(context or {}, action=True)
+                ids = [ids] if isinstance(ids, (int, long, str)) else ids
+
+                # retrieve the action rules to possibly execute
+                action_model = self.pool.get('base.action.rule')
+                action_dom = [('model', '=', self._name),
+                              ('kind', 'in', ['on_write', 'on_create_or_write'])]
+                action_ids = action_model.search(cr, uid, action_dom, context=context)
+                actions = action_model.browse(cr, uid, action_ids, context=context)
+
+                # check preconditions
+                pre_ids = {}
+                for action in actions:
+                    pre_ids[action] = action_model._filter(cr, uid, action, action.filter_pre_id, ids, context=context)
+
+                # call original method
+                write.origin(self, cr, uid, ids, vals, context=context, **kwargs)
+
+                # check postconditions, and execute actions on the records that satisfy them
+                for action in actions:
+                    post_ids = action_model._filter(cr, uid, action, action.filter_id, pre_ids[action], context=context)
+                    if post_ids:
+                        action_model._process(cr, uid, action, post_ids, context=context)
+                return True
+
+            return write
+
         updated = False
         if ids is None:
             ids = self.search(cr, SUPERUSER_ID, [])
@@ -164,61 +233,8 @@ class base_action_rule(osv.osv):
             model_obj = self.pool[model]
             if not hasattr(model_obj, 'base_action_ruled'):
                 # monkey-patch methods create and write
-
-                def create(self, cr, uid, vals, context=None, **kwargs):
-                    # avoid loops or cascading actions
-                    if context and context.get('action'):
-                        return create.origin(self, cr, uid, vals, context=context)
-
-                    # call original method with a modified context
-                    context = dict(context or {}, action=True)
-                    new_id = create.origin(self, cr, uid, vals, context=context, **kwargs)
-
-                    # as it is a new record, we do not consider the actions that have a prefilter
-                    action_model = self.pool.get('base.action.rule')
-                    action_dom = [('model', '=', self._name),
-                                  ('kind', 'in', ['on_create', 'on_create_or_write'])]
-                    action_ids = action_model.search(cr, uid, action_dom, context=context)
-
-                    # check postconditions, and execute actions on the records that satisfy them
-                    for action in action_model.browse(cr, uid, action_ids, context=context):
-                        if action_model._filter(cr, uid, action, action.filter_id, [new_id], context=context):
-                            action_model._process(cr, uid, action, [new_id], context=context)
-                    return new_id
-
-                def write(self, cr, uid, ids, vals, context=None, **kwargs):
-                    # avoid loops or cascading actions
-                    if context and context.get('action'):
-                        return write.origin(self, cr, uid, ids, vals, context=context)
-
-                    # modify context
-                    context = dict(context or {}, action=True)
-                    ids = [ids] if isinstance(ids, (int, long, str)) else ids
-
-                    # retrieve the action rules to possibly execute
-                    action_model = self.pool.get('base.action.rule')
-                    action_dom = [('model', '=', self._name),
-                                  ('kind', 'in', ['on_write', 'on_create_or_write'])]
-                    action_ids = action_model.search(cr, uid, action_dom, context=context)
-                    actions = action_model.browse(cr, uid, action_ids, context=context)
-
-                    # check preconditions
-                    pre_ids = {}
-                    for action in actions:
-                        pre_ids[action] = action_model._filter(cr, uid, action, action.filter_pre_id, ids, context=context)
-
-                    # call original method
-                    write.origin(self, cr, uid, ids, vals, context=context, **kwargs)
-
-                    # check postconditions, and execute actions on the records that satisfy them
-                    for action in actions:
-                        post_ids = action_model._filter(cr, uid, action, action.filter_id, pre_ids[action], context=context)
-                        if post_ids:
-                            action_model._process(cr, uid, action, post_ids, context=context)
-                    return True
-
-                model_obj._patch_method('create', create)
-                model_obj._patch_method('write', write)
+                model_obj._patch_method('create', make_create())
+                model_obj._patch_method('write', make_write())
                 model_obj.base_action_ruled = True
                 updated = True