diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py
index 105fd4004a9..f16988db116 100644
--- a/addons/base_action_rule/base_action_rule.py
+++ b/addons/base_action_rule/base_action_rule.py
@@ -1,12 +1,14 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from collections import defaultdict
from datetime import datetime, timedelta
import time
import logging
import openerp
from openerp import SUPERUSER_ID
+from openerp.modules.registry import RegistryManager
from openerp.osv import fields, osv
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
from openerp.tools.safe_eval import safe_eval as eval
@@ -50,6 +52,8 @@ class base_action_rule(osv.osv):
[('on_create', 'On Creation'),
('on_write', 'On Update'),
('on_create_or_write', 'On Creation & Update'),
+ ('on_unlink', 'On Deletion'),
+ ('on_change', 'Based on Form Modification'),
('on_time', 'Based on Timed Condition')],
string='When to Run'),
'trg_date_id': fields.many2one('ir.model.fields', string='Trigger Date',
@@ -82,8 +86,13 @@ class base_action_rule(osv.osv):
help="If present, this condition must be satisfied before executing the action rule."),
'filter_domain': fields.char(string='Domain', help="If present, this condition must be satisfied before executing the action rule."),
'last_run': fields.datetime('Last Run', readonly=1, copy=False),
+ 'on_change_fields': fields.char(string="On Change Fields Trigger",
+ help="Comma-separated list of field names that triggers the onchange."),
}
+ # which fields have an impact on the registry
+ CRITICAL_FIELDS = ['model_id', 'active', 'kind', 'on_change_fields']
+
_defaults = {
'active': True,
'trg_date_range_type': 'day',
@@ -91,7 +100,7 @@ class base_action_rule(osv.osv):
def onchange_kind(self, cr, uid, ids, kind, context=None):
clear_fields = []
- if kind in ['on_create', 'on_create_or_write']:
+ if kind in ['on_create', 'on_create_or_write', 'on_unlink']:
clear_fields = ['filter_pre_id', 'trg_date_id', 'trg_date_range', 'trg_date_range_type']
elif kind in ['on_write', 'on_create_or_write']:
clear_fields = ['trg_date_id', 'trg_date_range', 'trg_date_range_type']
@@ -147,21 +156,21 @@ class base_action_rule(osv.osv):
return True
- def _register_hook(self, cr, ids=None):
- """ Wrap the methods `create` and `write` of the models specified by
- the rules given by `ids` (or all existing rules if `ids` is `None`.)
+ def _register_hook(self, cr):
+ """ Patch models that should trigger action rules based on creation,
+ modification, deletion of records and form onchanges.
"""
#
- # 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.
+ # Note: the patched methods 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 """
+ """ 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'):
@@ -186,7 +195,7 @@ class base_action_rule(osv.osv):
return create
def make_write():
- """ instanciate a write method that processes action rules """
+ """ 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'):
@@ -208,6 +217,11 @@ class base_action_rule(osv.osv):
for action in actions:
pre_ids[action] = action_model._filter(cr, uid, action, action.filter_pre_id, ids, domain=action.filter_pre_domain, context=context)
+ # read old values before the update
+ old_values = {}
+ for old_vals in self.read(cr, uid, ids, list(vals), context=context):
+ old_values[old_vals.pop('id')] = old_vals
+
# call original method
write.origin(self, cr, uid, ids, vals, context=context, **kwargs)
@@ -215,27 +229,96 @@ class base_action_rule(osv.osv):
for action in actions:
post_ids = action_model._filter(cr, uid, action, action.filter_id, pre_ids[action], domain=action.filter_domain, context=context)
if post_ids:
- action_model._process(cr, uid, action, post_ids, context=context)
+ action_model._process(cr, uid, action, post_ids, context=dict(context, old_values=old_values))
return True
return write
- updated = False
- if ids is None:
- ids = self.search(cr, SUPERUSER_ID, [])
+ def make_unlink():
+ """ Instanciate an unlink method that processes action rules. """
+ def unlink(self, cr, uid, ids, context=None, **kwargs):
+ if context and context.get('action'):
+ return unlink.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', '=', 'on_unlink')]
+ action_ids = action_model.search(cr, uid, action_dom, context=context)
+ actions = action_model.browse(cr, uid, action_ids, context=context)
+
+ # check conditions, and execute actions on the records that satisfy them
+ for action in actions:
+ pre_ids = action_model._filter(cr, uid, action, action.filter_id, ids, domain=action.filter_domain, context=context)
+ if pre_ids:
+ action_model._process(cr, uid, action, pre_ids, context=context)
+
+ # call original method
+ return unlink.origin(self, cr, uid, ids, context=context, **kwargs)
+
+ return unlink
+
+ def make_onchange(action_rule_id):
+ """ Instanciate an onchange method for the given action rule. """
+ def base_action_rule_onchange(self):
+ action_rule = self.env['base.action.rule'].browse(action_rule_id)
+ server_actions = action_rule.server_action_ids.with_context(active_model=self._name, onchange_self=self)
+ result = {}
+ for server_action in server_actions:
+ res = server_action.run()
+ if res and 'value' in res:
+ res['value'].pop('id', None)
+ self.update(self._convert_to_cache(res['value'], validate=False))
+ if res and 'domain' in res:
+ result.setdefault('domain', {}).update(res['domain'])
+ if res and 'warning' in res:
+ result['warning'] = res['warning']
+ return result
+
+ return base_action_rule_onchange
+
+ patched_models = defaultdict(set)
+ def patch(model, name, method):
+ """ Patch method `name` on `model`, unless it has been patched already. """
+ if model not in patched_models[name]:
+ patched_models[name].add(model)
+ model._patch_method(name, method)
+
+ # retrieve all actions, and patch their corresponding model
+ ids = self.search(cr, SUPERUSER_ID, [])
for action_rule in self.browse(cr, SUPERUSER_ID, ids):
model = action_rule.model_id.model
model_obj = self.pool.get(model)
- if model_obj and not hasattr(model_obj, 'base_action_ruled'):
- # monkey-patch methods create and write
- model_obj._patch_method('create', make_create())
- model_obj._patch_method('write', make_write())
- model_obj.base_action_ruled = True
- updated = True
+ if not model_obj:
+ continue
- return updated
+ if action_rule.kind == 'on_create':
+ patch(model_obj, 'create', make_create())
+
+ elif action_rule.kind == 'on_create_or_write':
+ patch(model_obj, 'create', make_create())
+ patch(model_obj, 'write', make_write())
+
+ elif action_rule.kind == 'on_write':
+ patch(model_obj, 'write', make_write())
+
+ elif action_rule.kind == 'on_unlink':
+ patch(model_obj, 'unlink', make_unlink())
+
+ elif action_rule.kind == 'on_change':
+ # register an onchange method for the action_rule
+ method = make_onchange(action_rule.id)
+ for field_name in action_rule.on_change_fields.split(","):
+ field_name = field_name.strip()
+ model_obj._onchange_methods[field_name].append(method)
def _update_cron(self, cr, uid, context=None):
+ """ Activate the cron job depending on whether there exists action rules
+ based on time conditions. """
try:
cron = self.pool['ir.model.data'].get_object(
cr, uid, 'base_action_rule', 'ir_cron_crm_action', context=context)
@@ -244,25 +327,32 @@ class base_action_rule(osv.osv):
return cron.toggle(model=self._name, domain=[('kind', '=', 'on_time')])
+ def _update_registry(self, cr, uid, context=None):
+ """ Update the registry after a modification on action rules. """
+ if self.pool.ready:
+ # for the sake of simplicity, simply force the registry to reload
+ cr.commit()
+ openerp.api.Environment.reset()
+ RegistryManager.new(cr.dbname)
+ RegistryManager.signal_registry_change(cr.dbname)
+
def create(self, cr, uid, vals, context=None):
res_id = super(base_action_rule, self).create(cr, uid, vals, context=context)
- if self._register_hook(cr, [res_id]):
- openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
self._update_cron(cr, uid, context=context)
+ self._update_registry(cr, uid, context=context)
return res_id
def write(self, cr, uid, ids, vals, context=None):
- if isinstance(ids, (int, long)):
- ids = [ids]
super(base_action_rule, self).write(cr, uid, ids, vals, context=context)
- if self._register_hook(cr, ids):
- openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
- self._update_cron(cr, uid, context=context)
+ if set(vals) & set(self.CRITICAL_FIELDS):
+ self._update_cron(cr, uid, context=context)
+ self._update_registry(cr, uid, context=context)
return True
def unlink(self, cr, uid, ids, context=None):
res = super(base_action_rule, self).unlink(cr, uid, ids, context=context)
self._update_cron(cr, uid, context=context)
+ self._update_registry(cr, uid, context=context)
return res
def onchange_model_id(self, cr, uid, ids, model_id, context=None):
diff --git a/addons/base_action_rule/base_action_rule_view.xml b/addons/base_action_rule/base_action_rule_view.xml
index 8e5ac4a651c..8daf7ed1c6c 100644
--- a/addons/base_action_rule/base_action_rule_view.xml
+++ b/addons/base_action_rule/base_action_rule_view.xml
@@ -46,6 +46,8 @@
class="oe_inline" context="{'default_model_id': model}"
on_change="onchange_filter_id(filter_id, context)"/>
+