From 16e8bdc6b292082f962cfb6a193a21ef6cbda090 Mon Sep 17 00:00:00 2001 From: Yannick Vaucher Date: Thu, 10 May 2012 15:50:20 +0200 Subject: [PATCH 01/49] [IMP] base_action_rule - Allow using any object dates as a triggering date bzr revid: yannick.vaucher@camptocamp.com-20120510135020-x7m08ojkxjzy4fs9 --- addons/base_action_rule/base_action_rule.py | 47 ++++++++----------- .../base_action_rule_view.xml | 4 +- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 50ebd96a3c2..c558939470e 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -37,7 +37,7 @@ def get_datetime(date_field): date_split = date_field.split(' ') if len(date_split) == 1: date_field = date_split[0] + " 00:00:00" - + return datetime.strptime(date_field[:19], '%Y-%m-%d %H:%M:%S') @@ -79,13 +79,10 @@ class base_action_rule(osv.osv): it will allow you to hide the rule without removing it."), 'sequence': fields.integer('Sequence', help="Gives the sequence order \ when displaying a list of rules."), - 'trg_date_type': fields.selection([ - ('none', 'None'), - ('create', 'Creation Date'), - ('action_last', 'Last Action Date'), - ('date', 'Date'), - ('deadline', 'Deadline'), - ], 'Trigger Date', size=16), + 'trg_date_id': fields.many2one('ir.model.fields', + 'Trigger Date', + domain="[('model_id', '=', model_id)," + "('ttype','in',('date','datetime'))]"), 'trg_date_range': fields.integer('Delay after trigger date', \ help="Delay After Trigger Date,\ specifies you can put a negative number. If you need a delay before the \ @@ -132,7 +129,6 @@ the rule to mark CC(mail to any other person defined in actions)."), _defaults = { 'active': lambda *a: True, - 'trg_date_type': lambda *a: 'none', 'trg_date_range_type': lambda *a: 'day', 'act_mail_to_user': lambda *a: 0, 'act_remind_partner': lambda *a: 0, @@ -168,7 +164,7 @@ the rule to mark CC(mail to any other person defined in actions)."), obj = self.pool.get(obj_name) # If the rule doesn't involve a time condition, run it immediately # Otherwise we let the scheduler run the action - if self.browse(cr, uid, rule_id, context=context).trg_date_type == 'none': + if not self.browse(cr, uid, rule_id, context=context).trg_date_id: self._action(cr, uid, [rule_id], obj.browse(cr, uid, ids, context=context), context=context) return True @@ -185,7 +181,7 @@ the rule to mark CC(mail to any other person defined in actions)."), self.post_action(cr, uid, [new_id], model, context=context) return new_id return wrapper - + def _write(self, old_write, model, context=None): """ Return a wrapper around `old_write` calling both `old_write` and @@ -230,7 +226,7 @@ the rule to mark CC(mail to any other person defined in actions)."), def _check(self, cr, uid, automatic=False, use_new_cursor=False, \ context=None): """ - This Function is call by scheduler. + This Function is called by scheduler. """ rule_pool = self.pool.get('base.action.rule') rule_ids = rule_pool.search(cr, uid, [], context=context) @@ -248,22 +244,17 @@ the rule to mark CC(mail to any other person defined in actions)."), obj = model_pool.browse(cr, uid, obj_id, context=context) # Calculate when this action should next occur for this object base = False - if rule.trg_date_type=='create' and hasattr(obj, 'create_date'): - base = obj.create_date - elif (rule.trg_date_type=='action_last' - and hasattr(obj, 'create_date')): - if hasattr(obj, 'date_action_last') and obj.date_action_last: - base = obj.date_action_last - else: - base = obj.create_date - elif (rule.trg_date_type=='deadline' - and hasattr(obj, 'date_deadline') - and obj.date_deadline): - base = obj.date_deadline - elif (rule.trg_date_type=='date' - and hasattr(obj, 'date') - and obj.date): - base = obj.date + if rule.trg_date_id: + date_type = rule.trg_date_id.name + if (date_type=='date_action_last' + and hasattr(obj, 'create_date')): + if hasattr(obj, 'date_action_last') and obj.date_action_last: + base = obj.date_action_last + else: + base = obj.create_date + elif (hasattr(obj, date_type) + and obj.read([date_type])[0][date_type]): + base = obj.read([date_type])[0][date_type] if base: fnct = { 'minutes': lambda interval: timedelta(minutes=interval), diff --git a/addons/base_action_rule/base_action_rule_view.xml b/addons/base_action_rule/base_action_rule_view.xml index 4ba7e6f9018..b041d322193 100644 --- a/addons/base_action_rule/base_action_rule_view.xml +++ b/addons/base_action_rule/base_action_rule_view.xml @@ -39,8 +39,8 @@ - - + + From db5a4e70751cc3f1bdd7f72cacdcf3c7292614c5 Mon Sep 17 00:00:00 2001 From: Arnaud Pineux Date: Fri, 23 Nov 2012 11:31:13 +0100 Subject: [PATCH 02/49] [FIX] 1st test for automated action bzr revid: api@openerp.com-20121123103113-x9rspq1oh1887153 --- addons/base_action_rule/base_action_rule.py | 50 +++++++++++++-------- addons/base_status/base_stage.py | 8 +--- addons/base_status/base_state.py | 3 +- addons/crm/crm_action_rule.py | 4 +- addons/crm/crm_lead.py | 13 ++++-- addons/crm_claim/crm_claim.py | 6 ++- 6 files changed, 51 insertions(+), 33 deletions(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 856d2626f69..349721db8c4 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -109,7 +109,7 @@ trigger date, like sending a reminder 15 minutes before a meeting."), _order = 'sequence' - def post_action(self, cr, uid, ids, model, context=None): + def post_action(self, cr, uid, ids, model, old_records_states=None, context=None): # Searching for action rules cr.execute("SELECT model.model, rule.id FROM base_action_rule rule \ LEFT JOIN ir_model model on (model.id = rule.model_id) \ @@ -117,11 +117,11 @@ trigger date, like sending a reminder 15 minutes before a meeting."), res = cr.fetchall() # Check if any rule matching with current object for obj_name, rule_id in res: - obj = self.pool.get(obj_name) + model_pool = self.pool.get(obj_name) # If the rule doesn't involve a time condition, run it immediately # Otherwise we let the scheduler run the action if self.browse(cr, uid, rule_id, context=context).trg_date_type == 'none': - self._action(cr, uid, [rule_id], obj.browse(cr, uid, ids, context=context), context=context) + self._action(cr, uid, [rule_id], model_pool.browse(cr, uid, ids, context=context), old_records_states=old_records_states, context=context) return True def _create(self, old_create, model, context=None): @@ -144,13 +144,23 @@ trigger date, like sending a reminder 15 minutes before a meeting."), `post_action`, in that order. """ def wrapper(cr, uid, ids, vals, context=context): + old_records_states = {} if context is None: context = {} if isinstance(ids, (str, int, long)): ids = [ids] + model_pool = self.pool.get(model) + # get the old records (before the write) + if model and ids: + for record in model_pool.browse(cr,uid,ids,context=context): + old_records_states[record.id] = record.state + #old_records = model_pool.browse(cr,uid,ids,context=context) + #print old_records[0] + #print old_records[0].state old_write(cr, uid, ids, vals, context=context) + #print old_records[0].state if not context.get('action'): - self.post_action(cr, uid, ids, model, context=context) + self.post_action(cr, uid, ids, model, old_records_states=old_records_states, context=context) return True return wrapper @@ -242,7 +252,7 @@ trigger date, like sending a reminder 15 minutes before a meeting."), - def do_check(self, cr, uid, action, obj, context=None): + def do_check(self, cr, uid, action, obj, old_records_states=None, context=None): """ check Action """ if context is None: context = {} @@ -263,14 +273,21 @@ trigger date, like sending a reminder 15 minutes before a meeting."), (action.trg_partner_categ_id.id in map(lambda x: x.id, obj.partner_id.category_id or [])) ) ) - state_to = context.get('state_to', False) - state = getattr(obj, 'state', False) - if state: - ok = ok and (not action.trg_state_from or action.trg_state_from==state) - if state_to: - ok = ok and (not action.trg_state_to or action.trg_state_to==state_to) - elif action.trg_state_to: - ok = False + #state_to = the state after a write or a create + state_to = getattr(obj, 'state', False) + #state_from = nothing or the state of old_records + state_from = "" + if old_records_states != None: + if old_records_states[obj.id]: + state_from = old_records_states[obj.id] + #if we have an action that check the status + if action.trg_state_from: + print action.trg_state_from, " and ", state_to + ok = ok and action.trg_state_from==state_to + if action.trg_state_to: + print action.trg_state_to, " for ", state_from, " and ", state_to + ok = state_from!=state_to + ok = ok and action.trg_state_to==state_to reg_name = action.regex_name result_name = True if reg_name: @@ -312,19 +329,16 @@ trigger date, like sending a reminder 15 minutes before a meeting."), model_obj.message_subscribe(cr, uid, [obj.id], new_followers, context=context) return True - def _action(self, cr, uid, ids, objects, scrit=None, context=None): + def _action(self, cr, uid, ids, objects, scrit=None, old_records_states=None, context=None): """ Do Action """ if context is None: context = {} - context.update({'action': True}) - if not scrit: - scrit = [] if not isinstance(objects, list): objects = [objects] for action in self.browse(cr, uid, ids, context=context): for obj in objects: - if self.do_check(cr, uid, action, obj, context=context): + if self.do_check(cr, uid, action, obj, old_records_states=old_records_states, context=context): self.do_action(cr, uid, action, obj, context=context) context.update({'action': False}) diff --git a/addons/base_status/base_stage.py b/addons/base_status/base_stage.py index 86edff46920..ba4f62a181a 100644 --- a/addons/base_status/base_stage.py +++ b/addons/base_status/base_stage.py @@ -19,6 +19,7 @@ # ############################################################################## +import pdb from osv import fields, osv from tools.translate import _ @@ -280,15 +281,10 @@ class base_stage(object): # 2. update values if values_to_update: self.write(cr, uid, ids, values_to_update, context=context) - # 3. call _action for base action rule - if new_state_name: - self._action(cr, uid, cases, new_state_name, context=context) - elif not (new_stage_id is None): - new_state_name = self.read(cr, uid, ids, ['state'], context=context)[0]['state'] - self._action(cr, uid, cases, new_state_name, context=context) return True def _action(self, cr, uid, cases, state_to, scrit=None, context=None): + print "--- Base_Stage (_action) ---" if context is None: context = {} context['state_to'] = state_to diff --git a/addons/base_status/base_state.py b/addons/base_status/base_state.py index cebf5537485..aad9129406e 100644 --- a/addons/base_status/base_state.py +++ b/addons/base_status/base_state.py @@ -157,13 +157,14 @@ class base_state(object): :params: update_values: values that will be added with the state update when writing values to the record. """ + print "-------------------------BASE STATE--------------------------------" cases = self.browse(cr, uid, ids, context=context) cases[0].state # fill browse record cache, for _action having old and new values if update_values is None: update_values = {} update_values['state'] = state_name self.write(cr, uid, ids, update_values, context=context) - self._action(cr, uid, cases, state_name, context=context) + #self._action(cr, uid, cases, state_name, context=context) def _action(self, cr, uid, cases, state_to, scrit=None, context=None): if context is None: diff --git a/addons/crm/crm_action_rule.py b/addons/crm/crm_action_rule.py index 4a8ee98fbae..d7caafd7d1d 100644 --- a/addons/crm/crm_action_rule.py +++ b/addons/crm/crm_action_rule.py @@ -43,8 +43,8 @@ class base_action_rule(osv.osv): 'act_categ_id': fields.many2one('crm.case.categ', 'Set Category to'), } - def do_check(self, cr, uid, action, obj, context=None): - ok = super(base_action_rule, self).do_check(cr, uid, action, obj, context=context) + def do_check(self, cr, uid, action, obj, old_records_states=None, context=None): + ok = super(base_action_rule, self).do_check(cr, uid, action, obj, old_records_states=old_records_states, context=context) if hasattr(obj, 'section_id'): ok = ok and (not action.trg_section_id or action.trg_section_id.id == obj.section_id.id) diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 0a824aef83e..b9d92639e63 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -29,6 +29,7 @@ from tools.translate import _ from tools import html2plaintext from base.res.res_partner import format_address +import pdb CRM_LEAD_PENDING_STATES = ( crm.AVAILABLE_STATES[2][0], # Cancelled @@ -380,8 +381,10 @@ class crm_lead(base_stage, format_address, osv.osv): """ Mark the case as lost: state=cancel and probability=0 """ for lead in self.browse(cr, uid, ids): stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 0.0)], context=context) - if stage_id: - self.case_set(cr, uid, [lead.id], values_to_update={'probability': 0.0}, new_stage_id=stage_id, context=context) + if not stage_id: + return False + else: + self.write(cr, uid, ids, {'probability' : 0.0, 'stage_id' : stage_id}, context=context) self.case_mark_lost_send_note(cr, uid, ids, context=context) return True @@ -389,8 +392,10 @@ class crm_lead(base_stage, format_address, osv.osv): """ Mark the case as lost: state=done and probability=100 """ for lead in self.browse(cr, uid, ids): stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 100.0)], context=context) - if stage_id: - self.case_set(cr, uid, [lead.id], values_to_update={'probability': 100.0}, new_stage_id=stage_id, context=context) + if not stage_id: + return False + else: + self.write(cr, uid, ids, {'probability' : 100.0, 'stage_id' : stage_id}, context=context) self.case_mark_won_send_note(cr, uid, ids, context=context) return True diff --git a/addons/crm_claim/crm_claim.py b/addons/crm_claim/crm_claim.py index 70dcc18baf7..5c141c81080 100644 --- a/addons/crm_claim/crm_claim.py +++ b/addons/crm_claim/crm_claim.py @@ -167,8 +167,10 @@ class crm_claim(base_stage, osv.osv): """ Mark the case as refused: state=done and case_refused=True """ for lead in self.browse(cr, uid, ids): stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, ['&', ('state', '=', 'done'), ('case_refused', '=', True)], context=context) - if stage_id: - self.case_set(cr, uid, [lead.id], values_to_update={}, new_stage_id=stage_id, context=context) + if not stage_id: + return False + else: + self.write(cr, uid, ids, {'stage_id' : stage_id}, context=context) return self.case_refuse_send_note(cr, uid, ids, context=context) def onchange_partner_id(self, cr, uid, ids, part, email=False): From 72616d0ba2b355004f28590d3815ca18137c66c9 Mon Sep 17 00:00:00 2001 From: Arnaud Pineux Date: Mon, 26 Nov 2012 12:22:48 +0100 Subject: [PATCH 03/49] [FIX] Automated Action Rules bzr revid: api@openerp.com-20121126112248-oiq40hn9lgro0lq4 --- addons/base_action_rule/base_action_rule.py | 29 +++++++++++-------- .../base_action_rule_view.xml | 2 +- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 0e9db7fc60d..71b376f6946 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -23,6 +23,7 @@ from datetime import datetime from datetime import timedelta import re import time +from openerp import SUPERUSER_ID from osv import fields, osv, orm from tools.translate import _ @@ -54,7 +55,7 @@ class base_action_rule(osv.osv): def state_get(self, cr, uid, context=None): """ Get State """ - return [('', '')] + return [('', ''), ('na','N/A (No previous state)')] def priority_get(self, cr, uid, context=None): """ Get Priority """ @@ -86,8 +87,8 @@ trigger date, like sending a reminder 15 minutes before a meeting."), 'trg_user_id': fields.many2one('res.users', 'Responsible'), 'trg_partner_id': fields.many2one('res.partner', 'Partner'), 'trg_partner_categ_id': fields.many2one('res.partner.category', 'Partner Category'), - 'trg_state_from': fields.selection(_state_get, 'Status', size=16), - 'trg_state_to': fields.selection(_state_get, 'Button Pressed', size=16), + 'trg_state_from': fields.selection(_state_get, 'and previously was', size=16), + 'trg_state_to': fields.selection(_state_get, 'Status changes to', size=16), 'act_user_id': fields.many2one('res.users', 'Set Responsible to'), 'act_state': fields.selection(_state_get, 'Set State to', size=16), @@ -162,29 +163,30 @@ trigger date, like sending a reminder 15 minutes before a meeting."), return True return wrapper - def _register_hook(self, cr, uid, ids, context=None): + def _register_hook(self, cr): """ Wrap every `create` and `write` methods of the models specified by the rules (given by `ids`). """ - for action_rule in self.browse(cr, uid, ids, context=context): + ids = self.search(cr,SUPERUSER_ID,[]) + for action_rule in self.browse(cr, SUPERUSER_ID, ids, context=None): model = action_rule.model_id.model obj_pool = self.pool.get(model) if not hasattr(obj_pool, 'base_action_ruled'): - obj_pool.create = self._create(obj_pool.create, model, context=context) - obj_pool.write = self._write(obj_pool.write, model, context=context) + obj_pool.create = self._create(obj_pool.create, model, context=None) + obj_pool.write = self._write(obj_pool.write, model, context=None) obj_pool.base_action_ruled = True return True def create(self, cr, uid, vals, context=None): res_id = super(base_action_rule, self).create(cr, uid, vals, context=context) - self._register_hook(cr, uid, [res_id], context=context) + self._register_hook(cr) return res_id def write(self, cr, uid, ids, vals, context=None): super(base_action_rule, self).write(cr, uid, ids, vals, context=context) - self._register_hook(cr, uid, ids, context=context) + self._register_hook(cr) return True def _check(self, cr, uid, automatic=False, use_new_cursor=False, \ @@ -278,11 +280,14 @@ trigger date, like sending a reminder 15 minutes before a meeting."), if old_records != None: for record in old_records: state_from = record.state + else: + state_from = "na" #if we have an action that check the status - if action.trg_state_from: - ok = ok and action.trg_state_from==state_to if action.trg_state_to: - ok = state_from!=state_to + if action.trg_state_from: + ok = ok and action.trg_state_from==state_from + else: + ok = state_from!=state_to ok = ok and action.trg_state_to==state_to reg_name = action.regex_name result_name = True diff --git a/addons/base_action_rule/base_action_rule_view.xml b/addons/base_action_rule/base_action_rule_view.xml index bb711be9928..4815baa9ebb 100644 --- a/addons/base_action_rule/base_action_rule_view.xml +++ b/addons/base_action_rule/base_action_rule_view.xml @@ -33,8 +33,8 @@ - + From 041e43967c7a705a4a487d02577b476ff8ad8b57 Mon Sep 17 00:00:00 2001 From: Arnaud Pineux Date: Mon, 26 Nov 2012 12:44:05 +0100 Subject: [PATCH 04/49] [IMP] add comment bzr revid: api@openerp.com-20121126114405-s3b3lo6qncho2n37 --- addons/base_action_rule/base_action_rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 71b376f6946..8892bec34f2 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -281,7 +281,7 @@ trigger date, like sending a reminder 15 minutes before a meeting."), for record in old_records: state_from = record.state else: - state_from = "na" + state_from = "na" #it means that there was no state before (creation for example) #if we have an action that check the status if action.trg_state_to: if action.trg_state_from: From 8474567cdf5a0df9be8963abf6a8598eeabee17c Mon Sep 17 00:00:00 2001 From: Arnaud Pineux Date: Mon, 26 Nov 2012 13:51:42 +0100 Subject: [PATCH 05/49] [FIX] Automated Action rule (register_hook) bzr revid: api@openerp.com-20121126125142-9gpsupy7617vddya --- addons/base_action_rule/base_action_rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 8892bec34f2..693d46b9021 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -195,7 +195,7 @@ trigger date, like sending a reminder 15 minutes before a meeting."), This Function is call by scheduler. """ rule_ids = self.search(cr, uid, [], context=context) - self._register_hook(cr, uid, rule_ids, context=context) + self._register_hook(cr) if context is None: context = {} for rule in self.browse(cr, uid, rule_ids, context=context): From 41e9eadc6197b9082ac6e32c45330956ee469096 Mon Sep 17 00:00:00 2001 From: Arnaud Pineux Date: Mon, 26 Nov 2012 14:10:17 +0100 Subject: [PATCH 06/49] [FIX] _register_hook refactored bzr revid: api@openerp.com-20121126131017-55e2x4e75nmabt27 --- addons/base_action_rule/base_action_rule.py | 23 +++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 693d46b9021..11d62404502 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -169,7 +169,22 @@ trigger date, like sending a reminder 15 minutes before a meeting."), the rules (given by `ids`). """ ids = self.search(cr,SUPERUSER_ID,[]) - for action_rule in self.browse(cr, SUPERUSER_ID, ids, context=None): + return self._register_hook_(cr,SUPERUSER_ID,ids,context=None) + #for action_rule in self.browse(cr, SUPERUSER_ID, ids, context=None): + # model = action_rule.model_id.model + # obj_pool = self.pool.get(model) + # if not hasattr(obj_pool, 'base_action_ruled'): + # obj_pool.create = self._create(obj_pool.create, model, context=None) + # obj_pool.write = self._write(obj_pool.write, model, context=None) + # obj_pool.base_action_ruled = True + #return True + + def _register_hook_(self, cr, uid, ids, context=None): + """ + Wrap every `create` and `write` methods of the models specified by + the rules (given by `ids`). + """ + for action_rule in self.browse(cr, uid, ids, context=context): model = action_rule.model_id.model obj_pool = self.pool.get(model) if not hasattr(obj_pool, 'base_action_ruled'): @@ -181,12 +196,12 @@ trigger date, like sending a reminder 15 minutes before a meeting."), def create(self, cr, uid, vals, context=None): res_id = super(base_action_rule, self).create(cr, uid, vals, context=context) - self._register_hook(cr) + self._register_hook_(cr, uid, res_id,context=context) return res_id def write(self, cr, uid, ids, vals, context=None): super(base_action_rule, self).write(cr, uid, ids, vals, context=context) - self._register_hook(cr) + self._register_hook_(cr, uid, ids, context=context) return True def _check(self, cr, uid, automatic=False, use_new_cursor=False, \ @@ -195,7 +210,7 @@ trigger date, like sending a reminder 15 minutes before a meeting."), This Function is call by scheduler. """ rule_ids = self.search(cr, uid, [], context=context) - self._register_hook(cr) + self._register_hook_(cr, uid, rule_ids, context=context) if context is None: context = {} for rule in self.browse(cr, uid, rule_ids, context=context): From 9137243cdc48d5f3e89b16b7ec56f95cb1ef894a Mon Sep 17 00:00:00 2001 From: Arnaud Pineux Date: Mon, 26 Nov 2012 14:32:57 +0100 Subject: [PATCH 07/49] [FIX] _register_hook refactored (test) bzr revid: api@openerp.com-20121126133257-f9j7vfgdfkqjewyn --- addons/base_action_rule/base_action_rule.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 11d62404502..14da227a367 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -170,28 +170,24 @@ trigger date, like sending a reminder 15 minutes before a meeting."), """ ids = self.search(cr,SUPERUSER_ID,[]) return self._register_hook_(cr,SUPERUSER_ID,ids,context=None) - #for action_rule in self.browse(cr, SUPERUSER_ID, ids, context=None): - # model = action_rule.model_id.model - # obj_pool = self.pool.get(model) - # if not hasattr(obj_pool, 'base_action_ruled'): - # obj_pool.create = self._create(obj_pool.create, model, context=None) - # obj_pool.write = self._write(obj_pool.write, model, context=None) - # obj_pool.base_action_ruled = True - #return True def _register_hook_(self, cr, uid, ids, context=None): """ Wrap every `create` and `write` methods of the models specified by the rules (given by `ids`). """ - for action_rule in self.browse(cr, uid, ids, context=context): + reg_ids = [] + if not isinstance(ids, list): + reg_ids.append(ids) + else: + reg_ids.extend(ids) + for action_rule in self.browse(cr, uid, reg_ids, context=context): model = action_rule.model_id.model obj_pool = self.pool.get(model) if not hasattr(obj_pool, 'base_action_ruled'): obj_pool.create = self._create(obj_pool.create, model, context=None) obj_pool.write = self._write(obj_pool.write, model, context=None) obj_pool.base_action_ruled = True - return True def create(self, cr, uid, vals, context=None): From 100f3199e7120948dcc24aed555e8323ba0871aa Mon Sep 17 00:00:00 2001 From: Arnaud Pineux Date: Mon, 26 Nov 2012 15:03:30 +0100 Subject: [PATCH 08/49] [IMP] Test for automated action rules bzr revid: api@openerp.com-20121126140330-maut337p5q8fsglo --- .../test/base_action_rule_test.py | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 addons/base_action_rule/test/base_action_rule_test.py diff --git a/addons/base_action_rule/test/base_action_rule_test.py b/addons/base_action_rule/test/base_action_rule_test.py new file mode 100644 index 00000000000..41eb0e31746 --- /dev/null +++ b/addons/base_action_rule/test/base_action_rule_test.py @@ -0,0 +1,111 @@ +import tools +from openerp.tests import common + +class myLead(orm.Model): + + _name = name('base_action_rule.myLead') + + _columns = { + 'partner_id': fields.many2one('res.partner', 'Partner', ondelete='set null', + select=True, help="Linked partner (optional). Usually created when converting the lead."), + 'id': fields.integer('ID', readonly=True), + 'name': fields.char('Subject', size=64, required=True, select=1), + 'active': fields.boolean('Active', required=False), + 'date_action_last': fields.datetime('Last Action', readonly=1), + 'date_action_next': fields.datetime('Next Action', readonly=1), + 'email_from': fields.char('Email', size=128, help="Email address of the contact", select=1), + 'section_id': fields.many2one('crm.case.section', 'Sales Team', \ + select=True, help='When sending mails, the default email address is taken from the sales team.'), + 'create_date': fields.datetime('Creation Date' , readonly=True), + 'email_cc': fields.text('Global CC', size=252 , help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"), + 'description': fields.text('Notes'), + 'write_date': fields.datetime('Update Date' , readonly=True), + 'categ_ids': fields.many2many('crm.case.categ', 'crm_lead_category_rel', 'lead_id', 'category_id', 'Categories', \ + domain="['|',('section_id','=',section_id),('section_id','=',False), ('object_id.model', '=', 'crm.lead')]"), + 'type_id': fields.many2one('crm.case.resource.type', 'Campaign', \ + domain="['|',('section_id','=',section_id),('section_id','=',False)]", help="From which campaign (seminar, marketing campaign, mass mailing, ...) did this contact come from?"), + 'channel_id': fields.many2one('crm.case.channel', 'Channel', help="Communication channel (mail, direct, phone, ...)"), + 'contact_name': fields.char('Contact Name', size=64), + 'partner_name': fields.char("Customer Name", size=64,help='The name of the future partner company that will be created while converting the lead into opportunity', select=1), + 'opt_out': fields.boolean('Opt-Out', oldname='optout', help="If opt-out is checked, this contact has refused to receive emails or unsubscribed to a campaign."), + 'type':fields.selection([ ('lead','Lead'), ('opportunity','Opportunity'), ],'Type', help="Type is used to separate Leads and Opportunities"), + 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority', select=True), + 'date_closed': fields.datetime('Closed', readonly=True), + 'stage_id': fields.many2one('crm.case.stage', 'Stage', + domain="['&', ('fold', '=', False), '&', '|', ('section_ids', '=', section_id), ('case_default', '=', True), '|', ('type', '=', type), ('type', '=', 'both')]"), + 'user_id': fields.many2one('res.users', 'Salesperson'), + 'referred': fields.char('Referred By', size=64), + 'date_open': fields.datetime('Opened', readonly=True), + 'day_open': fields.function(_compute_day, string='Days to Open', \ + multi='day_open', type="float", store=True), + 'day_close': fields.function(_compute_day, string='Days to Close', \ + multi='day_close', type="float", store=True), + 'state': fields.related('stage_id', 'state', type="selection", store=True, + selection=crm.AVAILABLE_STATES, string="Status", readonly=True, + help='The Status is set to \'Draft\', when a case is created. If the case is in progress the Status is set to \'Open\'. When the case is over, the Status is set to \'Done\'. If the case needs to be reviewed then the Status is set to \'Pending\'.'), + + # Only used for type opportunity + 'probability': fields.float('Success Rate (%)',group_operator="avg"), + 'planned_revenue': fields.float('Expected Revenue'), + 'ref': fields.reference('Reference', selection=crm._links_get, size=128), + 'ref2': fields.reference('Reference 2', selection=crm._links_get, size=128), + 'phone': fields.char("Phone", size=64), + 'date_deadline': fields.date('Expected Closing', help="Estimate of the date on which the opportunity will be won."), + 'date_action': fields.date('Next Action Date', select=True), + 'title_action': fields.char('Next Action', size=64), + 'color': fields.integer('Color Index'), + 'partner_address_name': fields.related('partner_id', 'name', type='char', string='Partner Contact Name', readonly=True), + 'partner_address_email': fields.related('partner_id', 'email', type='char', string='Partner Contact Email', readonly=True), + 'company_currency': fields.related('company_id', 'currency_id', type='many2one', string='Currency', readonly=True, relation="res.currency"), + 'user_email': fields.related('user_id', 'email', type='char', string='User Email', readonly=True), + 'user_login': fields.related('user_id', 'login', type='char', string='User Login', readonly=True), + + # Fields for address, due to separation from crm and res.partner + 'street': fields.char('Street', size=128), + 'street2': fields.char('Street2', size=128), + 'zip': fields.char('Zip', change_default=True, size=24), + 'city': fields.char('City', size=128), + 'state_id': fields.many2one("res.country.state", 'State'), + 'country_id': fields.many2one('res.country', 'Country'), + 'phone': fields.char('Phone', size=64), + 'fax': fields.char('Fax', size=64), + 'mobile': fields.char('Mobile', size=64), + 'function': fields.char('Function', size=128), + 'title': fields.many2one('res.partner.title', 'Title'), + 'company_id': fields.many2one('res.company', 'Company', select=1), + 'payment_mode': fields.many2one('crm.payment.mode', 'Payment Mode', \ + domain="[('section_id','=',section_id)]"), + 'planned_cost': fields.float('Planned Costs'), + } + + _defaults = { + 'active': 1, + 'type': 'lead', + 'user_id': lambda s, cr, uid, c: s._get_default_user(cr, uid, c), + 'email_from': lambda s, cr, uid, c: s._get_default_email(cr, uid, c), + 'stage_id': lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c), + 'section_id': lambda s, cr, uid, c: s._get_default_section_id(cr, uid, c), + 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.lead', context=c), + 'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0], + 'color': 0, + } + + +class base_action_rule_test(common.TransactionCase): + + def setUp(self): + """*****setUp*****""" + super(base_action_rule_test, self).setUp() + cr, uid = self.cr, self.uid + + """********TeamCreation******""" + self.team_pool = self.pool.get('crm.case.section') + self.team_1_id = self.team_pool.create(cr, uid, { + + }, context=None) + + self.myLead_pool = self.pool.get('base_action_rule.myLead') + self.new_myLead_id = self.myLead_pool.create(cr, uid, { + 'name' : "my_first_lead", + 'section_id' : + }, context=None) From a8f351029755cfa4564c8c90931d28f3a3143420 Mon Sep 17 00:00:00 2001 From: Arnaud Pineux Date: Tue, 27 Nov 2012 10:07:55 +0100 Subject: [PATCH 09/49] [TEST] automated action rule tests bzr revid: api@openerp.com-20121127090755-03sfycnz8nw9ldnv --- .../test/base_action_rule_test.py | 111 ------------------ addons/crm/tests/__init__.py | 27 +++++ addons/crm/tests/base_action_rule_test.py | 69 +++++++++++ 3 files changed, 96 insertions(+), 111 deletions(-) delete mode 100644 addons/base_action_rule/test/base_action_rule_test.py create mode 100644 addons/crm/tests/__init__.py create mode 100644 addons/crm/tests/base_action_rule_test.py diff --git a/addons/base_action_rule/test/base_action_rule_test.py b/addons/base_action_rule/test/base_action_rule_test.py deleted file mode 100644 index 41eb0e31746..00000000000 --- a/addons/base_action_rule/test/base_action_rule_test.py +++ /dev/null @@ -1,111 +0,0 @@ -import tools -from openerp.tests import common - -class myLead(orm.Model): - - _name = name('base_action_rule.myLead') - - _columns = { - 'partner_id': fields.many2one('res.partner', 'Partner', ondelete='set null', - select=True, help="Linked partner (optional). Usually created when converting the lead."), - 'id': fields.integer('ID', readonly=True), - 'name': fields.char('Subject', size=64, required=True, select=1), - 'active': fields.boolean('Active', required=False), - 'date_action_last': fields.datetime('Last Action', readonly=1), - 'date_action_next': fields.datetime('Next Action', readonly=1), - 'email_from': fields.char('Email', size=128, help="Email address of the contact", select=1), - 'section_id': fields.many2one('crm.case.section', 'Sales Team', \ - select=True, help='When sending mails, the default email address is taken from the sales team.'), - 'create_date': fields.datetime('Creation Date' , readonly=True), - 'email_cc': fields.text('Global CC', size=252 , help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"), - 'description': fields.text('Notes'), - 'write_date': fields.datetime('Update Date' , readonly=True), - 'categ_ids': fields.many2many('crm.case.categ', 'crm_lead_category_rel', 'lead_id', 'category_id', 'Categories', \ - domain="['|',('section_id','=',section_id),('section_id','=',False), ('object_id.model', '=', 'crm.lead')]"), - 'type_id': fields.many2one('crm.case.resource.type', 'Campaign', \ - domain="['|',('section_id','=',section_id),('section_id','=',False)]", help="From which campaign (seminar, marketing campaign, mass mailing, ...) did this contact come from?"), - 'channel_id': fields.many2one('crm.case.channel', 'Channel', help="Communication channel (mail, direct, phone, ...)"), - 'contact_name': fields.char('Contact Name', size=64), - 'partner_name': fields.char("Customer Name", size=64,help='The name of the future partner company that will be created while converting the lead into opportunity', select=1), - 'opt_out': fields.boolean('Opt-Out', oldname='optout', help="If opt-out is checked, this contact has refused to receive emails or unsubscribed to a campaign."), - 'type':fields.selection([ ('lead','Lead'), ('opportunity','Opportunity'), ],'Type', help="Type is used to separate Leads and Opportunities"), - 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority', select=True), - 'date_closed': fields.datetime('Closed', readonly=True), - 'stage_id': fields.many2one('crm.case.stage', 'Stage', - domain="['&', ('fold', '=', False), '&', '|', ('section_ids', '=', section_id), ('case_default', '=', True), '|', ('type', '=', type), ('type', '=', 'both')]"), - 'user_id': fields.many2one('res.users', 'Salesperson'), - 'referred': fields.char('Referred By', size=64), - 'date_open': fields.datetime('Opened', readonly=True), - 'day_open': fields.function(_compute_day, string='Days to Open', \ - multi='day_open', type="float", store=True), - 'day_close': fields.function(_compute_day, string='Days to Close', \ - multi='day_close', type="float", store=True), - 'state': fields.related('stage_id', 'state', type="selection", store=True, - selection=crm.AVAILABLE_STATES, string="Status", readonly=True, - help='The Status is set to \'Draft\', when a case is created. If the case is in progress the Status is set to \'Open\'. When the case is over, the Status is set to \'Done\'. If the case needs to be reviewed then the Status is set to \'Pending\'.'), - - # Only used for type opportunity - 'probability': fields.float('Success Rate (%)',group_operator="avg"), - 'planned_revenue': fields.float('Expected Revenue'), - 'ref': fields.reference('Reference', selection=crm._links_get, size=128), - 'ref2': fields.reference('Reference 2', selection=crm._links_get, size=128), - 'phone': fields.char("Phone", size=64), - 'date_deadline': fields.date('Expected Closing', help="Estimate of the date on which the opportunity will be won."), - 'date_action': fields.date('Next Action Date', select=True), - 'title_action': fields.char('Next Action', size=64), - 'color': fields.integer('Color Index'), - 'partner_address_name': fields.related('partner_id', 'name', type='char', string='Partner Contact Name', readonly=True), - 'partner_address_email': fields.related('partner_id', 'email', type='char', string='Partner Contact Email', readonly=True), - 'company_currency': fields.related('company_id', 'currency_id', type='many2one', string='Currency', readonly=True, relation="res.currency"), - 'user_email': fields.related('user_id', 'email', type='char', string='User Email', readonly=True), - 'user_login': fields.related('user_id', 'login', type='char', string='User Login', readonly=True), - - # Fields for address, due to separation from crm and res.partner - 'street': fields.char('Street', size=128), - 'street2': fields.char('Street2', size=128), - 'zip': fields.char('Zip', change_default=True, size=24), - 'city': fields.char('City', size=128), - 'state_id': fields.many2one("res.country.state", 'State'), - 'country_id': fields.many2one('res.country', 'Country'), - 'phone': fields.char('Phone', size=64), - 'fax': fields.char('Fax', size=64), - 'mobile': fields.char('Mobile', size=64), - 'function': fields.char('Function', size=128), - 'title': fields.many2one('res.partner.title', 'Title'), - 'company_id': fields.many2one('res.company', 'Company', select=1), - 'payment_mode': fields.many2one('crm.payment.mode', 'Payment Mode', \ - domain="[('section_id','=',section_id)]"), - 'planned_cost': fields.float('Planned Costs'), - } - - _defaults = { - 'active': 1, - 'type': 'lead', - 'user_id': lambda s, cr, uid, c: s._get_default_user(cr, uid, c), - 'email_from': lambda s, cr, uid, c: s._get_default_email(cr, uid, c), - 'stage_id': lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c), - 'section_id': lambda s, cr, uid, c: s._get_default_section_id(cr, uid, c), - 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.lead', context=c), - 'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0], - 'color': 0, - } - - -class base_action_rule_test(common.TransactionCase): - - def setUp(self): - """*****setUp*****""" - super(base_action_rule_test, self).setUp() - cr, uid = self.cr, self.uid - - """********TeamCreation******""" - self.team_pool = self.pool.get('crm.case.section') - self.team_1_id = self.team_pool.create(cr, uid, { - - }, context=None) - - self.myLead_pool = self.pool.get('base_action_rule.myLead') - self.new_myLead_id = self.myLead_pool.create(cr, uid, { - 'name' : "my_first_lead", - 'section_id' : - }, context=None) diff --git a/addons/crm/tests/__init__.py b/addons/crm/tests/__init__.py new file mode 100644 index 00000000000..a2de50226da --- /dev/null +++ b/addons/crm/tests/__init__.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Business Applications +# Copyright (c) 2012-TODAY OpenERP S.A. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from . import base_action_rule_test + +checks = [ + base_action_rule_test, +] + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/crm/tests/base_action_rule_test.py b/addons/crm/tests/base_action_rule_test.py new file mode 100644 index 00000000000..caca0c635da --- /dev/null +++ b/addons/crm/tests/base_action_rule_test.py @@ -0,0 +1,69 @@ +import tools +from openerp.tests import common + +class base_action_rule_test(common.TransactionCase): + + def setUp(self): + """*****setUp*****""" + super(base_action_rule_test, self).setUp() + cr, uid = self.cr, self.uid + + """*****TeamCreation******""" + self.team_pool = self.registry('crm.case.section') + self.team_1_id = self.team_pool.create(cr, uid, { + 'name' : "Team 1", + 'active' : 1, + 'user_id' : uid, + 'complete_name' : "Test / Team 1", + }, context=None) + self.team_2_id = self.team_pool.create(cr, uid, { + 'name' : "Team 2", + 'active' : 1, + 'user_id' : uid, + 'complete_name' : "Test / Team 2", + }, context=None) + + def create_rule_1(self, cr, uid, context=None): + """ + The "Rule 1" says that when a lead goes to the 'open' state, the team responsible for that lead changes to "Team 1" + """ + self.action_pool = self.registry('base.action.rule') + self.rule_1_id = self.action_pool.create(cr,uid,{ + 'name' : "Rule 1", + 'model_id' : self.registry('ir.model').search(cr, uid, [('model','=','crm.lead')], context=context)[0], + 'active' : 1, + 'trg_state_to' : 'open', + 'trg_date_type' : 'none', + 'act_section_id' : self.team_1_id, + }, context=context) + + def create_rule_2(self, cr, uid, context=None): + """ + The "Rule 2" says that when a lead goes from 'open' state to 'done' state, the team responsible for that lead changes to "Team 2" + """ + self.action_pool = self.registry('base.action.rule') + self.rule_2_id = self.action_pool.create(cr,uid,{ + 'name' : "Rule 2", + 'model_id' : self.registry('ir.model').search(cr, uid, [('model','=','crm.lead')], context=context)[0], + 'active' : 1, + 'trg_state_to' : 'done', + 'trg_state_from' : 'open', + 'trg_date_type' : 'none', + 'act_section_id' : self.team_2_id, + }, context=context) + + def test_00_check_to_state_draft(self): + """ + This test check that the team change when the state is open but doesn't change when the state is different + """ + cr, uid = self.cr, self.uid + context = {} + #First we need to create the Rule 1 + self.create_rule_1(cr, uid, context) + #Once it is done, we can create a new lead (with a state = 'draft') + #first we get our team 1 + self.team_1 = self.team_pool.browse(cr,uid,self.team_1_id,context=context) + #We get a lead + self.lead_1 = self.registry('ir.model.data').get_object_reference(cr, uid, 'crm', 'crm_case_1') + #We change the team of crm_case_1 + From 3df8c5fe9a048eac11d3c50e69bf3263f5b3b980 Mon Sep 17 00:00:00 2001 From: Arnaud Pineux Date: Wed, 28 Nov 2012 10:29:28 +0100 Subject: [PATCH 10/49] [IMP] Base Action Rule (only filter conditions) bzr revid: api@openerp.com-20121128092928-7kbs1d9g8ung4oul --- addons/base_action_rule/__init__.py | 1 + addons/base_action_rule/base_action_rule.py | 73 ++++----- .../base_action_rule_view.xml | 32 ++-- addons/base_action_rule/test_models.py | 32 ++++ .../tests/__init__.py | 0 .../tests/base_action_rule_test.py | 142 ++++++++++++++++++ addons/crm/crm_action_rule.py | 4 +- addons/crm/crm_action_rule_view.xml | 10 -- addons/crm/tests/base_action_rule_test.py | 69 --------- 9 files changed, 228 insertions(+), 135 deletions(-) create mode 100644 addons/base_action_rule/test_models.py rename addons/{crm => base_action_rule}/tests/__init__.py (100%) create mode 100644 addons/base_action_rule/tests/base_action_rule_test.py delete mode 100644 addons/crm/tests/base_action_rule_test.py diff --git a/addons/base_action_rule/__init__.py b/addons/base_action_rule/__init__.py index 207ed3cb30f..c8c827aaba2 100644 --- a/addons/base_action_rule/__init__.py +++ b/addons/base_action_rule/__init__.py @@ -20,5 +20,6 @@ ############################################################################## import base_action_rule +import test_models # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 14da227a367..2b6877a7c9c 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -97,7 +97,8 @@ trigger date, like sending a reminder 15 minutes before a meeting."), \ne.g.: 'urgent.*' will search for records having name starting with the string 'urgent'\ \nNote: This is case sensitive search."), 'server_action_ids': fields.one2many('ir.actions.server', 'action_rule_id', 'Server Action', help="Define Server actions.\neg:Email Reminders, Call Object Service, etc.."), #TODO: set domain [('model_id','=',model_id)] - 'filter_id':fields.many2one('ir.filters', 'Filter', required=False), #TODO: set domain [('model_id','=',model_id.model)] + 'filter_id':fields.many2one('ir.filters', 'Precondition Filter', required=False), #TODO: set domain [('model_id','=',model_id.model)] + 'filter_post_id': fields.many2one('ir.filters', 'Postcondition Filter', required=False), 'last_run': fields.datetime('Last Run', readonly=1), } @@ -110,7 +111,7 @@ trigger date, like sending a reminder 15 minutes before a meeting."), _order = 'sequence' - def post_action(self, cr, uid, ids, model, old_records=None, context=None): + def post_action(self, cr, uid, ids, model, precondition_ok=None, context=None): # Searching for action rules cr.execute("SELECT model.model, rule.id FROM base_action_rule rule \ LEFT JOIN ir_model model on (model.id = rule.model_id) \ @@ -122,7 +123,7 @@ trigger date, like sending a reminder 15 minutes before a meeting."), # If the rule doesn't involve a time condition, run it immediately # Otherwise we let the scheduler run the action if self.browse(cr, uid, rule_id, context=context).trg_date_type == 'none': - self._action(cr, uid, [rule_id], model_pool.browse(cr, uid, ids, context=context), old_records=old_records, context=context) + self._action(cr, uid, [rule_id], model_pool.browse(cr, uid, ids, context=context), precondition_ok=precondition_ok, context=context) return True def _create(self, old_create, model, context=None): @@ -134,8 +135,17 @@ trigger date, like sending a reminder 15 minutes before a meeting."), if context is None: context = {} new_id = old_create(cr, uid, vals, context=context) + #As it is a new record, we can assume that the precondition is true for every filter. + #(There is nothing before the create so no condition) + precondition_ok = {} + precondition_ok[new_id] = {} + for action in self.browse(cr, uid, self.search(cr, uid, [], context=context), context=context): + if action.filter_id: + precondition_ok[new_id][action.id] = False + else: + precondition_ok[new_id][action.id] = True if not context.get('action'): - self.post_action(cr, uid, [new_id], model, context=context) + self.post_action(cr, uid, [new_id], model, precondition_ok=precondition_ok, context=context) return new_id return wrapper @@ -151,15 +161,22 @@ trigger date, like sending a reminder 15 minutes before a meeting."), if isinstance(ids, (str, int, long)): ids = [ids] model_pool = self.pool.get(model) - # get the old records (before the write) - if model and ids: - old_records = model_pool.browse(cr,uid,ids,context=context) - # look at records' states to fill the record cache - for record in old_records: - record.state + # We check for the pre-filter. We must apply it before the write + precondition_ok = {} + for id in ids: + precondition_ok[id] = {} + for action in self.browse(cr, uid, self.search(cr, uid, [], context=context), context=context): + precondition_ok[id][action.id] = True + if action.filter_id and action.model_id.model == action.filter_id.model_id: + ctx = dict(context) + ctx.update(eval(action.filter_id.context)) + obj_ids = [] + if self.pool.get(action.model_id.model)!=None: + obj_ids = self.pool.get(action.model_id.model).search(cr, uid, eval(action.filter_id.domain), context=ctx) + precondition_ok[id][action.id] = id in obj_ids old_write(cr, uid, ids, vals, context=context) if not context.get('action'): - self.post_action(cr, uid, ids, model, old_records=old_records, context=context) + self.post_action(cr, uid, ids, model, precondition_ok=precondition_ok, context=context) return True return wrapper @@ -263,15 +280,15 @@ trigger date, like sending a reminder 15 minutes before a meeting."), - def do_check(self, cr, uid, action, obj, old_records=None, context=None): + def do_check(self, cr, uid, action, obj, precondition_ok=True, context=None): """ check Action """ if context is None: context = {} - ok = True - if action.filter_id and action.model_id.model == action.filter_id.model_id: + ok = precondition_ok + if action.filter_post_id and action.model_id.model == action.filter_post_id.model_id: ctx = dict(context) - ctx.update(eval(action.filter_id.context)) - obj_ids = self.pool.get(action.model_id.model).search(cr, uid, eval(action.filter_id.domain), context=ctx) + ctx.update(eval(action.filter_post_id.context)) + obj_ids = self.pool.get(action.model_id.model).search(cr, uid, eval(action.filter_post_id.domain), context=ctx) ok = ok and obj.id in obj_ids if getattr(obj, 'user_id', False): ok = ok and (not action.trg_user_id.id or action.trg_user_id.id==obj.user_id.id) @@ -284,22 +301,6 @@ trigger date, like sending a reminder 15 minutes before a meeting."), (action.trg_partner_categ_id.id in map(lambda x: x.id, obj.partner_id.category_id or [])) ) ) - #state_to = the state after a write or a create - state_to = getattr(obj, 'state', False) - #state_from = nothing or the state of old_records - state_from = "" - if old_records != None: - for record in old_records: - state_from = record.state - else: - state_from = "na" #it means that there was no state before (creation for example) - #if we have an action that check the status - if action.trg_state_to: - if action.trg_state_from: - ok = ok and action.trg_state_from==state_from - else: - ok = state_from!=state_to - ok = ok and action.trg_state_to==state_to reg_name = action.regex_name result_name = True if reg_name: @@ -341,7 +342,7 @@ trigger date, like sending a reminder 15 minutes before a meeting."), model_obj.message_subscribe(cr, uid, [obj.id], new_followers, context=context) return True - def _action(self, cr, uid, ids, objects, scrit=None, old_records=None, context=None): + def _action(self, cr, uid, ids, objects, scrit=None, precondition_ok=None, context=None): """ Do Action """ if context is None: context = {} @@ -350,9 +351,11 @@ trigger date, like sending a reminder 15 minutes before a meeting."), objects = [objects] for action in self.browse(cr, uid, ids, context=context): for obj in objects: - if self.do_check(cr, uid, action, obj, old_records=old_records, context=context): + ok = True + if precondition_ok!=None: + ok = precondition_ok[obj.id][action.id] + if self.do_check(cr, uid, action, obj, precondition_ok=ok, context=context): self.do_action(cr, uid, action, obj, context=context) - context.update({'action': False}) return True diff --git a/addons/base_action_rule/base_action_rule_view.xml b/addons/base_action_rule/base_action_rule_view.xml index 4815baa9ebb..3bd90457828 100644 --- a/addons/base_action_rule/base_action_rule_view.xml +++ b/addons/base_action_rule/base_action_rule_view.xml @@ -15,36 +15,31 @@ - - - + + +

+ Select a filter or a timer as condition.
+ To create a new filter,
+ - Go to your Model's page and set the filter parameters in the "Search" view
+ - In this same "Search" view, select the menu "Save Current Filter", enter the name and add the option "Share with all users"
+ The filter must therefore be available in this page. +

- - - + + + - - - - - - - - - + - -
@@ -63,7 +58,6 @@
- diff --git a/addons/base_action_rule/test_models.py b/addons/base_action_rule/test_models.py new file mode 100644 index 00000000000..9469b42c705 --- /dev/null +++ b/addons/base_action_rule/test_models.py @@ -0,0 +1,32 @@ +from osv import osv, fields + +AVAILABLE_STATES = [ + ('draft', 'New'), + ('cancel', 'Cancelled'), + ('open', 'In Progress'), + ('pending', 'Pending'), + ('done', 'Closed') +] + +class lead_test(osv.Model): + _name = "base.action.rule.lead.test" + + _columns = { + 'name': fields.char('Subject', size=64, required=True, select=1), + 'user_id': fields.many2one('res.users', 'Responsible'), + 'state': fields.selection(AVAILABLE_STATES, string="Status", readonly=True), + 'active': fields.boolean('Active', required=False), + 'partner_id': fields.many2one('res.partner', 'Partner', ondelete='set null'), + 'date_action_last': fields.datetime('Last Action', readonly=1), + } + + _defaults = { + 'state' : 'draft', + 'active' : True, + } + + def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification', subtype=None, parent_id=False, attachments=None, context=None, **kwargs): + pass + + def message_subscribe(self, cr, uid, ids, partner_ids, subtype_ids=None, context=None): + pass diff --git a/addons/crm/tests/__init__.py b/addons/base_action_rule/tests/__init__.py similarity index 100% rename from addons/crm/tests/__init__.py rename to addons/base_action_rule/tests/__init__.py diff --git a/addons/base_action_rule/tests/base_action_rule_test.py b/addons/base_action_rule/tests/base_action_rule_test.py new file mode 100644 index 00000000000..6f1c885df69 --- /dev/null +++ b/addons/base_action_rule/tests/base_action_rule_test.py @@ -0,0 +1,142 @@ +import tools +from openerp.tests import common +from .. import test_models + +class base_action_rule_test(common.TransactionCase): + + def setUp(self): + """*****setUp*****""" + super(base_action_rule_test, self).setUp() + cr, uid = self.cr, self.uid + self.demo_user = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'user_demo') + self.admin_user = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'user_admin') + + def create_filter_done(self, cr, uid, context=None): + filter_pool = self.registry('ir.filters') + return filter_pool.create(cr, uid, { + 'name': "Lead is in done state", + 'is_default': False, + 'model_id': 'base.action.rule.lead.test', + 'domain' : "[('state','=','done')]", + }, context=context) + + def create_filter_draft(self, cr, uid, context=None): + filter_pool = self.registry('ir.filters') + return filter_pool.create(cr, uid, { + 'name': "Lead is in draft state", + 'is_default': False, + 'model_id': "base.action.rule.lead.test", + 'domain' : "[('state','=','draft')]", + }, context=context) + + def create_lead_test_1(self, cr, uid, context=None): + """ + Create a new lead_test + """ + lead_pool = self.registry('base.action.rule.lead.test') + return lead_pool.create(cr, uid, { + 'name': "Lead Test 1", + 'user_id': self.admin_user[1], + }, context=context) + + def create_rule(self, cr, uid, filter_id=None, filter_post_id=None, context=None): + """ + The "Rule 1" says that when a lead goes to the 'draft' state, the responsible for that lead changes to "demo_user" + """ + self.action_pool = self.registry('base.action.rule') + return self.action_pool.create(cr,uid,{ + 'name' : "Rule 1", + 'model_id': self.registry('ir.model').search(cr, uid, [('model','=','base.action.rule.lead.test')], context=context)[0], + 'active' : 1, + 'trg_date_type' : 'none', + 'filter_post_id' : filter_post_id, + 'filter_id' : filter_id, + 'act_user_id': self.demo_user[1], + }, context=context) + + def test_00_check_to_state_draft_pre(self): + """ + Check that a new record (with state = draft) doesn't change its responsible when there is a precondition filter which check that the state is draft. + """ + cr, uid = self.cr, self.uid + context = {} + filter_draft = self.create_filter_draft(cr, uid, context=context) + rule_1_id = self.create_rule(cr, uid, filter_id=filter_draft, context=context) + new_lead_id = self.create_lead_test_1(cr,uid,context=context) + new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) + self.assertTrue(new_lead.state=='draft') + self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin_user[1], context=context)) + + def test_01_check_to_state_draft_post(self): + """ + Check that a new record (with state = draft) changes its responsible when there is a postcondition filter which check that the state is draft. + """ + cr, uid = self.cr, self.uid + context = {} + filter_draft = self.create_filter_draft(cr, uid, context=context) + rule_1_id = self.create_rule(cr, uid, filter_post_id=filter_draft, context=context) + new_lead_id = self.create_lead_test_1(cr,uid,context=context) + new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) + self.assertTrue(new_lead.state=='draft') + self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.demo_user[1], context=context)) + + def test_02_check_from_draft_to_done_with_steps(self): + """ + A new record will be created and will goes from draft to done state via the other states (open, pending and cancel) + We will create a rule that says in precondition that the record must be in the "draft" state while a postcondition filter says + that the record will be done. If the state goes from 'draft' to 'done' the responsible will change. If those two conditions aren't + verified, the responsible will stay the same + The responsible in that test will never change + """ + cr, uid = self.cr, self.uid + context = {} + filter_draft = self.create_filter_draft(cr, uid, context=context) + filter_done = self.create_filter_done(cr, uid, context=context) + self.create_rule(cr, uid, filter_id=filter_draft, filter_post_id=filter_done, context=context) + new_lead_id = self.create_lead_test_1(cr,uid,context=context) + new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) + self.assertTrue(new_lead.state=='draft') + self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin_user[1], context=context)) + """ change the state of new_lead to open and check that responsible doen't change""" + new_lead.write({'state': 'open'}, context=context) + new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) + self.assertTrue(new_lead.state=='open') + self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin_user[1], context=context)) + """ change the state of new_lead to pending and check that responsible doen't change""" + new_lead.write({'state': 'pending'}, context=context) + new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) + self.assertTrue(new_lead.state=='pending') + self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin_user[1], context=context)) + """ change the state of new_lead to cancel and check that responsible doen't change""" + new_lead.write({'state': 'cancel'}, context=context) + new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) + self.assertTrue(new_lead.state=='cancel') + self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin_user[1], context=context)) + """ change the state of new_lead to done and check that responsible doen't change """ + new_lead.write({'state': 'done'}, context=context) + new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) + self.assertTrue(new_lead.state=='done') + self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin_user[1], context=context)) + + def test_02_check_from_draft_to_done_without_steps(self): + """ + A new record will be created and will goes from draft to done in one operation + We will create a rule that says in precondition that the record must be in the "draft" state while a postcondition filter says + that the record will be done. If the state goes from 'draft' to 'done' the responsible will change. If those two conditions aren't + verified, the responsible will stay the same + The responsible in that test will change to "demo_user" + """ + cr, uid = self.cr, self.uid + context = {} + filter_draft = self.create_filter_draft(cr, uid, context=context) + filter_done = self.create_filter_done(cr, uid, context=context) + self.create_rule(cr, uid, filter_id=filter_draft, filter_post_id=filter_done, context=context) + new_lead_id = self.create_lead_test_1(cr,uid,context=context) + new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) + self.assertTrue(new_lead.state=='draft') + self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin_user[1], context=context)) + """ change the state of new_lead to done and check that responsible change to Demo_user""" + new_lead.write({'state': 'done'}, context=context) + new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) + self.assertTrue(new_lead.state=='done') + self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.demo_user[1], context=context)) \ No newline at end of file diff --git a/addons/crm/crm_action_rule.py b/addons/crm/crm_action_rule.py index 72582a1f21e..74fc749f33c 100644 --- a/addons/crm/crm_action_rule.py +++ b/addons/crm/crm_action_rule.py @@ -43,8 +43,8 @@ class base_action_rule(osv.osv): 'act_categ_id': fields.many2one('crm.case.categ', 'Set Category to'), } - def do_check(self, cr, uid, action, obj, old_records=None, context=None): - ok = super(base_action_rule, self).do_check(cr, uid, action, obj, old_records=old_records, context=context) + def do_check(self, cr, uid, action, obj, precondition_ok=True, context=None): + ok = super(base_action_rule, self).do_check(cr, uid, action, obj, precondition_ok=precondition_ok, context=context) if hasattr(obj, 'section_id'): ok = ok and (not action.trg_section_id or action.trg_section_id.id == obj.section_id.id) diff --git a/addons/crm/crm_action_rule_view.xml b/addons/crm/crm_action_rule_view.xml index e21b74de382..347e54c46ac 100644 --- a/addons/crm/crm_action_rule_view.xml +++ b/addons/crm/crm_action_rule_view.xml @@ -7,16 +7,6 @@ base.action.rule - - - - - - - - - - diff --git a/addons/crm/tests/base_action_rule_test.py b/addons/crm/tests/base_action_rule_test.py deleted file mode 100644 index caca0c635da..00000000000 --- a/addons/crm/tests/base_action_rule_test.py +++ /dev/null @@ -1,69 +0,0 @@ -import tools -from openerp.tests import common - -class base_action_rule_test(common.TransactionCase): - - def setUp(self): - """*****setUp*****""" - super(base_action_rule_test, self).setUp() - cr, uid = self.cr, self.uid - - """*****TeamCreation******""" - self.team_pool = self.registry('crm.case.section') - self.team_1_id = self.team_pool.create(cr, uid, { - 'name' : "Team 1", - 'active' : 1, - 'user_id' : uid, - 'complete_name' : "Test / Team 1", - }, context=None) - self.team_2_id = self.team_pool.create(cr, uid, { - 'name' : "Team 2", - 'active' : 1, - 'user_id' : uid, - 'complete_name' : "Test / Team 2", - }, context=None) - - def create_rule_1(self, cr, uid, context=None): - """ - The "Rule 1" says that when a lead goes to the 'open' state, the team responsible for that lead changes to "Team 1" - """ - self.action_pool = self.registry('base.action.rule') - self.rule_1_id = self.action_pool.create(cr,uid,{ - 'name' : "Rule 1", - 'model_id' : self.registry('ir.model').search(cr, uid, [('model','=','crm.lead')], context=context)[0], - 'active' : 1, - 'trg_state_to' : 'open', - 'trg_date_type' : 'none', - 'act_section_id' : self.team_1_id, - }, context=context) - - def create_rule_2(self, cr, uid, context=None): - """ - The "Rule 2" says that when a lead goes from 'open' state to 'done' state, the team responsible for that lead changes to "Team 2" - """ - self.action_pool = self.registry('base.action.rule') - self.rule_2_id = self.action_pool.create(cr,uid,{ - 'name' : "Rule 2", - 'model_id' : self.registry('ir.model').search(cr, uid, [('model','=','crm.lead')], context=context)[0], - 'active' : 1, - 'trg_state_to' : 'done', - 'trg_state_from' : 'open', - 'trg_date_type' : 'none', - 'act_section_id' : self.team_2_id, - }, context=context) - - def test_00_check_to_state_draft(self): - """ - This test check that the team change when the state is open but doesn't change when the state is different - """ - cr, uid = self.cr, self.uid - context = {} - #First we need to create the Rule 1 - self.create_rule_1(cr, uid, context) - #Once it is done, we can create a new lead (with a state = 'draft') - #first we get our team 1 - self.team_1 = self.team_pool.browse(cr,uid,self.team_1_id,context=context) - #We get a lead - self.lead_1 = self.registry('ir.model.data').get_object_reference(cr, uid, 'crm', 'crm_case_1') - #We change the team of crm_case_1 - From 7e8533d278979a3f31c13c342aa9eafe757df493 Mon Sep 17 00:00:00 2001 From: Arnaud Pineux Date: Wed, 28 Nov 2012 13:01:00 +0100 Subject: [PATCH 11/49] [FIX] old actions based on filters still work bzr revid: api@openerp.com-20121128120100-58xrtp54txigz0b5 --- addons/base_action_rule/base_action_rule.py | 18 +++++++++--------- .../base_action_rule/base_action_rule_view.xml | 2 +- .../tests/base_action_rule_test.py | 12 ++++++------ 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 2b6877a7c9c..de3362c6644 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -97,8 +97,8 @@ trigger date, like sending a reminder 15 minutes before a meeting."), \ne.g.: 'urgent.*' will search for records having name starting with the string 'urgent'\ \nNote: This is case sensitive search."), 'server_action_ids': fields.one2many('ir.actions.server', 'action_rule_id', 'Server Action', help="Define Server actions.\neg:Email Reminders, Call Object Service, etc.."), #TODO: set domain [('model_id','=',model_id)] - 'filter_id':fields.many2one('ir.filters', 'Precondition Filter', required=False), #TODO: set domain [('model_id','=',model_id.model)] - 'filter_post_id': fields.many2one('ir.filters', 'Postcondition Filter', required=False), + 'filter_id':fields.many2one('ir.filters', 'Postcondition Filter', required=False), #TODO: set domain [('model_id','=',model_id.model)] + 'filter_pre_id': fields.many2one('ir.filters', 'Precondition Filter', required=False), 'last_run': fields.datetime('Last Run', readonly=1), } @@ -140,7 +140,7 @@ trigger date, like sending a reminder 15 minutes before a meeting."), precondition_ok = {} precondition_ok[new_id] = {} for action in self.browse(cr, uid, self.search(cr, uid, [], context=context), context=context): - if action.filter_id: + if action.filter_pre_id: precondition_ok[new_id][action.id] = False else: precondition_ok[new_id][action.id] = True @@ -167,12 +167,12 @@ trigger date, like sending a reminder 15 minutes before a meeting."), precondition_ok[id] = {} for action in self.browse(cr, uid, self.search(cr, uid, [], context=context), context=context): precondition_ok[id][action.id] = True - if action.filter_id and action.model_id.model == action.filter_id.model_id: + if action.filter_pre_id and action.model_id.model == action.filter_pre_id.model_id: ctx = dict(context) - ctx.update(eval(action.filter_id.context)) + ctx.update(eval(action.filter_pre_id.context)) obj_ids = [] if self.pool.get(action.model_id.model)!=None: - obj_ids = self.pool.get(action.model_id.model).search(cr, uid, eval(action.filter_id.domain), context=ctx) + obj_ids = self.pool.get(action.model_id.model).search(cr, uid, eval(action.filter_pre_id.domain), context=ctx) precondition_ok[id][action.id] = id in obj_ids old_write(cr, uid, ids, vals, context=context) if not context.get('action'): @@ -285,10 +285,10 @@ trigger date, like sending a reminder 15 minutes before a meeting."), if context is None: context = {} ok = precondition_ok - if action.filter_post_id and action.model_id.model == action.filter_post_id.model_id: + if action.filter_id and action.model_id.model == action.filter_id.model_id: ctx = dict(context) - ctx.update(eval(action.filter_post_id.context)) - obj_ids = self.pool.get(action.model_id.model).search(cr, uid, eval(action.filter_post_id.domain), context=ctx) + ctx.update(eval(action.filter_id.context)) + obj_ids = self.pool.get(action.model_id.model).search(cr, uid, eval(action.filter_id.domain), context=ctx) ok = ok and obj.id in obj_ids if getattr(obj, 'user_id', False): ok = ok and (not action.trg_user_id.id or action.trg_user_id.id==obj.user_id.id) diff --git a/addons/base_action_rule/base_action_rule_view.xml b/addons/base_action_rule/base_action_rule_view.xml index 3bd90457828..257465b490a 100644 --- a/addons/base_action_rule/base_action_rule_view.xml +++ b/addons/base_action_rule/base_action_rule_view.xml @@ -31,8 +31,8 @@

+ - diff --git a/addons/base_action_rule/tests/base_action_rule_test.py b/addons/base_action_rule/tests/base_action_rule_test.py index 6f1c885df69..0452401ce23 100644 --- a/addons/base_action_rule/tests/base_action_rule_test.py +++ b/addons/base_action_rule/tests/base_action_rule_test.py @@ -39,7 +39,7 @@ class base_action_rule_test(common.TransactionCase): 'user_id': self.admin_user[1], }, context=context) - def create_rule(self, cr, uid, filter_id=None, filter_post_id=None, context=None): + def create_rule(self, cr, uid, filter_id=None, filter_pre_id=None, context=None): """ The "Rule 1" says that when a lead goes to the 'draft' state, the responsible for that lead changes to "demo_user" """ @@ -49,7 +49,7 @@ class base_action_rule_test(common.TransactionCase): 'model_id': self.registry('ir.model').search(cr, uid, [('model','=','base.action.rule.lead.test')], context=context)[0], 'active' : 1, 'trg_date_type' : 'none', - 'filter_post_id' : filter_post_id, + 'filter_pre_id' : filter_pre_id, 'filter_id' : filter_id, 'act_user_id': self.demo_user[1], }, context=context) @@ -61,7 +61,7 @@ class base_action_rule_test(common.TransactionCase): cr, uid = self.cr, self.uid context = {} filter_draft = self.create_filter_draft(cr, uid, context=context) - rule_1_id = self.create_rule(cr, uid, filter_id=filter_draft, context=context) + rule_1_id = self.create_rule(cr, uid, filter_pre_id=filter_draft, context=context) new_lead_id = self.create_lead_test_1(cr,uid,context=context) new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) self.assertTrue(new_lead.state=='draft') @@ -74,7 +74,7 @@ class base_action_rule_test(common.TransactionCase): cr, uid = self.cr, self.uid context = {} filter_draft = self.create_filter_draft(cr, uid, context=context) - rule_1_id = self.create_rule(cr, uid, filter_post_id=filter_draft, context=context) + rule_1_id = self.create_rule(cr, uid, filter_id=filter_draft, context=context) new_lead_id = self.create_lead_test_1(cr,uid,context=context) new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) self.assertTrue(new_lead.state=='draft') @@ -92,7 +92,7 @@ class base_action_rule_test(common.TransactionCase): context = {} filter_draft = self.create_filter_draft(cr, uid, context=context) filter_done = self.create_filter_done(cr, uid, context=context) - self.create_rule(cr, uid, filter_id=filter_draft, filter_post_id=filter_done, context=context) + self.create_rule(cr, uid, filter_pre_id=filter_draft, filter_id=filter_done, context=context) new_lead_id = self.create_lead_test_1(cr,uid,context=context) new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) self.assertTrue(new_lead.state=='draft') @@ -130,7 +130,7 @@ class base_action_rule_test(common.TransactionCase): context = {} filter_draft = self.create_filter_draft(cr, uid, context=context) filter_done = self.create_filter_done(cr, uid, context=context) - self.create_rule(cr, uid, filter_id=filter_draft, filter_post_id=filter_done, context=context) + self.create_rule(cr, uid, filter_pre_id=filter_draft, filter_id=filter_done, context=context) new_lead_id = self.create_lead_test_1(cr,uid,context=context) new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) self.assertTrue(new_lead.state=='draft') From ffe7fd9bcc800498681c20215141139478635b32 Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Mon, 3 Dec 2012 09:51:16 +0100 Subject: [PATCH 12/49] [FIX] etherpad: When a user access to the edit mode from a kanban view or list view, etherpad is dipslay on readonly mode but with the oe_editing class. Why: The deferer for read only arrive after the display of edit mode. Fix: check in the deferer if the etherpad is already in readonly mode. bzr revid: chm@openerp.com-20121203085116-jn83em3pd5le28rp --- addons/pad/static/src/js/pad.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/addons/pad/static/src/js/pad.js b/addons/pad/static/src/js/pad.js index 9e1b73e1f3b..5b33fd3eaa1 100644 --- a/addons/pad/static/src/js/pad.js +++ b/addons/pad/static/src/js/pad.js @@ -4,6 +4,16 @@ openerp.pad = function(instance) { template: 'FieldPad', configured: false, content: "", + start: function() { + this._super.apply(this, arguments); + var self = this; + this.on('click', '.oe_pad_switch', function(){ + self.$el.toggleClass('oe_pad_fullscreen'); + }); + this.on('change:effective_readonly',this,function(){ + self.renderElement(); + }); + }, render_value: function() { var self = this; var _super = _.bind(this._super, this); @@ -37,23 +47,23 @@ openerp.pad = function(instance) { }else{ this.content = '
... Loading pad ...
'; $.get(value+'/export/html').success(function(data){ + if(!self.get('effective_readonly')){ + return false; + } groups = /\<\s*body\s*\>(.*?)\<\s*\/body\s*\>/.exec(data); data = (groups || []).length >= 2 ? groups[1] : ''; self.$('.oe_pad_content').html('
'); self.$('.oe_pad_readonly').html(data); }).error(function(){ + if(!self.get('effective_readonly')){ + return false; + } self.$('.oe_pad_content').text('Unable to load pad'); }); } } this._super(); this.$('.oe_pad_content').html(this.content); - this.$('.oe_pad_switch').click(function(){ - self.$el.toggleClass('oe_pad_fullscreen'); - }); - this.on('change:effective_readonly',this,function(){ - self.renderElement(); - }); }, }); From b04a134a16e2559b594a536276928ef9cc6f56a4 Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Tue, 4 Dec 2012 11:23:04 +0100 Subject: [PATCH 13/49] [FIX] view_form: m2m_tags, when a user quick create a tag, select a tags in the list or blurred and click on the arrow, the last selection is persistant. Overwrite onSetInputData to reset suggestions if input is empty. bzr revid: chm@openerp.com-20121204102304-iru8pcjlca8zh5fj --- addons/web/static/src/js/view_form.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 21c3b3f43d9..5a573a1244e 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -3986,6 +3986,14 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in return item.name; }, }, + core: { + onSetInputData: function(e, data) { + if (data == '') { + this._plugins.autocomplete._suggestions = null; + } + this.input().val(data); + }, + }, }, }).bind('getSuggestions', function(e, data) { var _this = this; From 638dcfce3488a6849a109a7220d8164e15bbd777 Mon Sep 17 00:00:00 2001 From: Arnaud Pineux Date: Tue, 4 Dec 2012 16:36:17 +0100 Subject: [PATCH 14/49] [IMP] base_action_rule : delete unused fields and add explanation lp bug: https://launchpad.net/bugs/944197 fixed bzr revid: api@openerp.com-20121204153617-ju3yyj2tt3d84jl9 --- addons/base_action_rule/base_action_rule.py | 57 ------------------- .../base_action_rule_view.xml | 8 +-- addons/crm/crm_action_rule.py | 32 ----------- addons/crm/crm_action_rule_demo.xml | 1 - addons/crm/test/process/action_rule.yml | 20 ------- 5 files changed, 4 insertions(+), 114 deletions(-) delete mode 100644 addons/crm/test/process/action_rule.yml diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index de3362c6644..00b179655f0 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -84,18 +84,9 @@ specifies you can put a negative number. If you need a delay before the \ trigger date, like sending a reminder 15 minutes before a meeting."), 'trg_date_range_type': fields.selection([('minutes', 'Minutes'), ('hour', 'Hours'), \ ('day', 'Days'), ('month', 'Months')], 'Delay type'), - 'trg_user_id': fields.many2one('res.users', 'Responsible'), - 'trg_partner_id': fields.many2one('res.partner', 'Partner'), - 'trg_partner_categ_id': fields.many2one('res.partner.category', 'Partner Category'), - 'trg_state_from': fields.selection(_state_get, 'and previously was', size=16), - 'trg_state_to': fields.selection(_state_get, 'Status changes to', size=16), - 'act_user_id': fields.many2one('res.users', 'Set Responsible to'), 'act_state': fields.selection(_state_get, 'Set State to', size=16), 'act_followers': fields.many2many("res.partner", string="Set Followers"), - 'regex_name': fields.char('Regex on Resource Name', size=128, help="Regular expression for matching name of the resource\ -\ne.g.: 'urgent.*' will search for records having name starting with the string 'urgent'\ -\nNote: This is case sensitive search."), 'server_action_ids': fields.one2many('ir.actions.server', 'action_rule_id', 'Server Action', help="Define Server actions.\neg:Email Reminders, Call Object Service, etc.."), #TODO: set domain [('model_id','=',model_id)] 'filter_id':fields.many2one('ir.filters', 'Postcondition Filter', required=False), #TODO: set domain [('model_id','=',model_id.model)] 'filter_pre_id': fields.many2one('ir.filters', 'Precondition Filter', required=False), @@ -290,26 +281,6 @@ trigger date, like sending a reminder 15 minutes before a meeting."), ctx.update(eval(action.filter_id.context)) obj_ids = self.pool.get(action.model_id.model).search(cr, uid, eval(action.filter_id.domain), context=ctx) ok = ok and obj.id in obj_ids - if getattr(obj, 'user_id', False): - ok = ok and (not action.trg_user_id.id or action.trg_user_id.id==obj.user_id.id) - if getattr(obj, 'partner_id', False): - ok = ok and (not action.trg_partner_id.id or action.trg_partner_id.id==obj.partner_id.id) - ok = ok and ( - not action.trg_partner_categ_id.id or - ( - obj.partner_id.id and - (action.trg_partner_categ_id.id in map(lambda x: x.id, obj.partner_id.category_id or [])) - ) - ) - reg_name = action.regex_name - result_name = True - if reg_name: - ptrn = re.compile(ustr(reg_name)) - _result = ptrn.search(ustr(obj.name)) - if not _result: - result_name = False - regex_n = not reg_name or result_name - ok = ok and regex_n return ok def do_action(self, cr, uid, action, obj, context=None): @@ -359,38 +330,10 @@ trigger date, like sending a reminder 15 minutes before a meeting."), context.update({'action': False}) return True -base_action_rule() - class actions_server(osv.osv): _inherit = 'ir.actions.server' _columns = { 'action_rule_id': fields.many2one("base.action.rule", string="Action Rule") } -actions_server() - -class ir_cron(osv.osv): - _inherit = 'ir.cron' - _init_done = False - - def _poolJobs(self, db_name, check=False): - if not self._init_done: - self._init_done = True - try: - db = pooler.get_db(db_name) - except: - return False - cr = db.cursor() - try: - next = datetime.now().strftime('%Y-%m-%d %H:00:00') - # Putting nextcall always less than current time in order to call it every time - cr.execute('UPDATE ir_cron set nextcall = \'%s\' where numbercall<>0 and active and model=\'base.action.rule\' ' % (next)) - finally: - cr.commit() - cr.close() - - super(ir_cron, self)._poolJobs(db_name, check=check) - -ir_cron() - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/base_action_rule/base_action_rule_view.xml b/addons/base_action_rule/base_action_rule_view.xml index 257465b490a..aab9ce0000c 100644 --- a/addons/base_action_rule/base_action_rule_view.xml +++ b/addons/base_action_rule/base_action_rule_view.xml @@ -23,10 +23,10 @@

- Select a filter or a timer as condition.
- To create a new filter,
- - Go to your Model's page and set the filter parameters in the "Search" view
- - In this same "Search" view, select the menu "Save Current Filter", enter the name and add the option "Share with all users"
+ Select a filter or a timer as condition.
An action rule is checked when you create or modify the "Related Document Model". The precondition filter is checked right before the modification while the postcondition filter is checked after the modification. A precondition filter will therefore not work during a creation.
+ To create a new filter:
+ - Go to your "Related Document Model" page and set the filter parameters in the "Search" view (Example of filter based on Leads/Opportunities: Creation Date "is equal to" 01/01/2012)
+ - In this same "Search" view, select the menu "Save Current Filter", enter the name (Ex: Create the 01/01/2012) and add the option "Share with all users"
The filter must therefore be available in this page.

diff --git a/addons/crm/crm_action_rule.py b/addons/crm/crm_action_rule.py index 74fc749f33c..1344b170b9a 100644 --- a/addons/crm/crm_action_rule.py +++ b/addons/crm/crm_action_rule.py @@ -35,42 +35,10 @@ class base_action_rule(osv.osv): _description = 'Action Rules' _columns = { - 'trg_section_id': fields.many2one('crm.case.section', 'Sales Team'), - 'trg_max_history': fields.integer('Maximum Communication History'), - 'trg_categ_id': fields.many2one('crm.case.categ', 'Category'), - 'regex_history' : fields.char('Regular Expression on Case History', size=128), 'act_section_id': fields.many2one('crm.case.section', 'Set Team to'), 'act_categ_id': fields.many2one('crm.case.categ', 'Set Category to'), } - def do_check(self, cr, uid, action, obj, precondition_ok=True, context=None): - ok = super(base_action_rule, self).do_check(cr, uid, action, obj, precondition_ok=precondition_ok, context=context) - - if hasattr(obj, 'section_id'): - ok = ok and (not action.trg_section_id or action.trg_section_id.id == obj.section_id.id) - if hasattr(obj, 'categ_ids'): - ok = ok and (not action.trg_categ_id or action.trg_categ_id.id in [x.id for x in obj.categ_ids]) - - #Cheking for history - regex = action.regex_history - if regex: - res = False - ptrn = re.compile(ustr(regex)) - for history in obj.message_ids: - _result = ptrn.search(ustr(history.subject)) - if _result: - res = True - break - ok = ok and res - - if action.trg_max_history: - res_count = False - history_ids = filter(lambda x: x.email_from, obj.message_ids) - if len(history_ids) <= action.trg_max_history: - res_count = True - ok = ok and res_count - return ok - def do_action(self, cr, uid, action, obj, context=None): res = super(base_action_rule, self).do_action(cr, uid, action, obj, context=context) model_obj = self.pool.get(action.model_id.model) diff --git a/addons/crm/crm_action_rule_demo.xml b/addons/crm/crm_action_rule_demo.xml index 83f5681f311..31f07e59034 100644 --- a/addons/crm/crm_action_rule_demo.xml +++ b/addons/crm/crm_action_rule_demo.xml @@ -72,7 +72,6 @@ Thanks, Set Auto Followers on leads which are urgent and come from USA. 2 - urgent.* diff --git a/addons/crm/test/process/action_rule.yml b/addons/crm/test/process/action_rule.yml deleted file mode 100644 index f76e6c250bc..00000000000 --- a/addons/crm/test/process/action_rule.yml +++ /dev/null @@ -1,20 +0,0 @@ -- - I create a record rule. -- - !python {model: base.action.rule}: | - model_ids = self.pool.get("ir.model").search(cr, uid, [('model', '=', 'crm.lead')]) - from datetime import datetime - new_id = self.create(cr, uid, {'name': 'New Rule', 'model_id': model_ids[0], 'trg_user_id': ref('base.user_root'), 'trg_partner_id': ref('base.res_partner_1'), 'act_user_id': ref('base.user_demo') }) - self._check(cr, uid) -- - I create a new lead to check the record rule. -- - !record {model: crm.lead, id: crm_lead_test_rules_id}: - name: 'Test lead rules' - partner_id: base.res_partner_1 -- - I check if the record rule is applied and the responsible is changed. -- - !python {model: crm.lead}: | - lead_user = self.browse(cr, uid, ref('crm_lead_test_rules_id')) - assert lead_user.user_id.id == ref('base.user_demo'), "Responsible of lead is not changed." From 5827f6c200dbf70e8b0e7aeb13facda32ac3c1bb Mon Sep 17 00:00:00 2001 From: Arnaud Pineux Date: Tue, 4 Dec 2012 16:52:10 +0100 Subject: [PATCH 15/49] [IMP] base_action_rule : clear state and stage lp bug: https://launchpad.net/bugs/944197 fixed bzr revid: api@openerp.com-20121204155210-3cjh1153idyax1sh --- addons/base_status/base_stage.py | 13 ------------- addons/base_status/base_state.py | 12 ------------ 2 files changed, 25 deletions(-) diff --git a/addons/base_status/base_stage.py b/addons/base_status/base_stage.py index 5710a31d6a0..72d58553092 100644 --- a/addons/base_status/base_stage.py +++ b/addons/base_status/base_stage.py @@ -217,7 +217,6 @@ class base_stage(object): self.write(cr, uid, [case.id], data, context=context) case.case_escalate_send_note(case.section_id.parent_id, context=context) cases = self.browse(cr, uid, ids, context=context) - self._action(cr, uid, cases, 'escalate', context=context) return True def case_open(self, cr, uid, ids, context=None): @@ -283,18 +282,6 @@ class base_stage(object): self.write(cr, uid, ids, values_to_update, context=context) return True - def _action(self, cr, uid, cases, state_to, scrit=None, context=None): - if context is None: - context = {} - context['state_to'] = state_to - rule_obj = self.pool.get('base.action.rule') - if not rule_obj: - return True - model_obj = self.pool.get('ir.model') - model_ids = model_obj.search(cr, uid, [('model','=',self._name)], context=context) - rule_ids = rule_obj.search(cr, uid, [('model_id','=',model_ids[0])], context=context) - return rule_obj._action(cr, uid, rule_ids, cases, scrit=scrit, context=context) - def _check(self, cr, uid, ids=False, context=None): """ Function called by the scheduler to process cases for date actions. diff --git a/addons/base_status/base_state.py b/addons/base_status/base_state.py index 97d70268bdf..e8a1dcc6b9f 100644 --- a/addons/base_status/base_state.py +++ b/addons/base_status/base_state.py @@ -107,7 +107,6 @@ class base_state(object): raise osv.except_osv(_('Error !'), _('You can not escalate, you are already at the top level regarding your sales-team category.')) self.write(cr, uid, [case.id], data, context=context) case.case_escalate_send_note(parent_id, context=context) - self._action(cr, uid, cases, 'escalate', context=context) return True def case_open(self, cr, uid, ids, context=None): @@ -163,17 +162,6 @@ class base_state(object): update_values = {} update_values['state'] = state_name self.write(cr, uid, ids, update_values, context=context) - #self._action(cr, uid, cases, state_name, context=context) - - def _action(self, cr, uid, cases, state_to, scrit=None, context=None): - if context is None: - context = {} - context['state_to'] = state_to - rule_obj = self.pool.get('base.action.rule') - model_obj = self.pool.get('ir.model') - model_ids = model_obj.search(cr, uid, [('model','=',self._name)]) - rule_ids = rule_obj.search(cr, uid, [('model_id','=',model_ids[0])]) - return rule_obj._action(cr, uid, rule_ids, cases, scrit=scrit, context=context) # ****************************** # Notifications From 8381a0c92f5dd5d6c01e1dc53481509ad1ee7a1a Mon Sep 17 00:00:00 2001 From: Arnaud Pineux Date: Tue, 4 Dec 2012 17:20:52 +0100 Subject: [PATCH 16/49] [FIX] base_stage & state bzr revid: api@openerp.com-20121204162052-u3t2vvf4ttcppdic --- addons/base_status/base_stage.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/addons/base_status/base_stage.py b/addons/base_status/base_stage.py index 6e2313be4ac..f88a25c57b4 100644 --- a/addons/base_status/base_stage.py +++ b/addons/base_status/base_stage.py @@ -277,9 +277,6 @@ class base_stage(object): def write(self, cr, uid, ids, vals, context=None): res = super(base_stage,self).write(cr, uid, ids, vals, context) - if vals.get('stage_id'): - for case in self.browse(cr, uid, ids, context=context): - self._action(cr, uid, case, case.stage_id.state, context=context) return res From 0ca480a80d4e87883941f84508c58d36c97d6329 Mon Sep 17 00:00:00 2001 From: Arnaud Pineux Date: Tue, 4 Dec 2012 17:23:27 +0100 Subject: [PATCH 17/49] [FIX] base_stage & state bzr revid: api@openerp.com-20121204162327-uu941ixjjojgc15v --- addons/crm/__openerp__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py index 63d5d3da84b..a438dd2858e 100644 --- a/addons/crm/__openerp__.py +++ b/addons/crm/__openerp__.py @@ -111,7 +111,6 @@ Dashboard for CRM will include: 'test/process/lead2opportunity2win.yml', 'test/process/merge_opportunity.yml', 'test/process/cancel_lead.yml', - 'test/process/action_rule.yml', 'test/process/segmentation.yml', 'test/process/phonecalls.yml', 'test/ui/crm_demo.yml', From 23b6b96fbc4dccf5d6bba6eda2e04ddf8a9d3224 Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Thu, 13 Dec 2012 15:57:14 +0100 Subject: [PATCH 18/49] [IMP] pad: add abord for $.get request bzr revid: chm@openerp.com-20121213145714-b9d4f2u8iqdg8r1m --- addons/pad/static/src/js/pad.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/addons/pad/static/src/js/pad.js b/addons/pad/static/src/js/pad.js index 5b33fd3eaa1..5cd2ac622cb 100644 --- a/addons/pad/static/src/js/pad.js +++ b/addons/pad/static/src/js/pad.js @@ -7,9 +7,6 @@ openerp.pad = function(instance) { start: function() { this._super.apply(this, arguments); var self = this; - this.on('click', '.oe_pad_switch', function(){ - self.$el.toggleClass('oe_pad_fullscreen'); - }); this.on('change:effective_readonly',this,function(){ self.renderElement(); }); @@ -37,6 +34,9 @@ openerp.pad = function(instance) { renderElement: function(){ var self = this; var value = this.get('value'); + if (this.pad_loading_request) { + this.pad_loading_request.abort(); + } if(!_.str.startsWith(value,'http')){ this.configured = false; this.content = ""; @@ -46,24 +46,22 @@ openerp.pad = function(instance) { this.content = ''; }else{ this.content = '
... Loading pad ...
'; - $.get(value+'/export/html').success(function(data){ - if(!self.get('effective_readonly')){ - return false; - } + this.pad_loading_request = $.get(value+'/export/html') + .done(function(data){ groups = /\<\s*body\s*\>(.*?)\<\s*\/body\s*\>/.exec(data); data = (groups || []).length >= 2 ? groups[1] : ''; self.$('.oe_pad_content').html('
'); self.$('.oe_pad_readonly').html(data); }).error(function(){ - if(!self.get('effective_readonly')){ - return false; - } self.$('.oe_pad_content').text('Unable to load pad'); }); } } this._super(); this.$('.oe_pad_content').html(this.content); + this.$('.oe_pad_switch').click(function(){ + self.$el.toggleClass('oe_pad_fullscreen'); + }); }, }); From d010441fe362537571d27f7bf0b21cdf498f96b0 Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Thu, 13 Dec 2012 15:58:55 +0100 Subject: [PATCH 19/49] [IMP] pad: remove attributes for super bzr revid: chm@openerp.com-20121213145855-0aq9wqtovyrxzxkw --- addons/pad/static/src/js/pad.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/pad/static/src/js/pad.js b/addons/pad/static/src/js/pad.js index 5cd2ac622cb..b96047340ad 100644 --- a/addons/pad/static/src/js/pad.js +++ b/addons/pad/static/src/js/pad.js @@ -5,7 +5,7 @@ openerp.pad = function(instance) { configured: false, content: "", start: function() { - this._super.apply(this, arguments); + this._super(); var self = this; this.on('change:effective_readonly',this,function(){ self.renderElement(); From 86a1802556cae70401f450c15a9bd51f5ccf9249 Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Thu, 13 Dec 2012 16:58:21 +0100 Subject: [PATCH 20/49] [IMP] note pad: with 100% bzr revid: chm@openerp.com-20121213155821-ufh8exnm2qid01b5 --- addons/note/static/src/css/note.css | 5 ++++- addons/note_pad/note_pad_view.xml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/addons/note/static/src/css/note.css b/addons/note/static/src/css/note.css index 16d71e28aa5..ecf07a37b62 100644 --- a/addons/note/static/src/css/note.css +++ b/addons/note/static/src/css/note.css @@ -2,13 +2,16 @@ .oe_kanban_column .note_text_line_through { text-decoration: line-through; } - .openerp .oe_form .oe_form_field.oe_memo { margin: 0 -16px 0 -16px; padding: 0px; width: 100%; min-height: 200px; } +.openerp .oe_form .oe_pad.oe_memo { + margin: 0 -16px 0 -16px; + width: auto; +} .openerp .oe_form .oe_form_field.oe_memo .cleditorMain { border: none; padding: 0px; diff --git a/addons/note_pad/note_pad_view.xml b/addons/note_pad/note_pad_view.xml index 9e45a3952f1..0032905b6b5 100644 --- a/addons/note_pad/note_pad_view.xml +++ b/addons/note_pad/note_pad_view.xml @@ -7,7 +7,7 @@ - + From 9dd9f47df84cb8ab91249ae211b892e784bca9c1 Mon Sep 17 00:00:00 2001 From: "Randhir Mayatra (OpenERP)" Date: Wed, 19 Dec 2012 15:01:21 +0530 Subject: [PATCH 21/49] [IMP] improve the string res_company and base_data_xml bzr revid: rma@tinyerp.com-20121219093121-em1gzrsrbu83y40m --- openerp/addons/base/base_data.xml | 2 +- openerp/addons/base/res/res_company.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openerp/addons/base/base_data.xml b/openerp/addons/base/base_data.xml index 7526c4d02d9..2cf7d00600e 100644 --- a/openerp/addons/base/base_data.xml +++ b/openerp/addons/base/base_data.xml @@ -62,7 +62,7 @@ Your Company - Your Company Slogan + Your Company Tagline diff --git a/openerp/addons/base/res/res_company.py b/openerp/addons/base/res/res_company.py index 9e01e9a4f94..5917b76aee2 100644 --- a/openerp/addons/base/res/res_company.py +++ b/openerp/addons/base/res/res_company.py @@ -118,7 +118,7 @@ class res_company(osv.osv): 'child_ids': fields.one2many('res.company', 'parent_id', 'Child Companies'), 'partner_id': fields.many2one('res.partner', 'Partner', required=True), 'rml_header': fields.text('RML Header', required=True), - 'rml_header1': fields.char('Company Slogan', size=200, help="Appears by default on the top right corner of your printed documents (report header)."), + 'rml_header1': fields.char('Company Tagline', size=200, help="Appears by default on the top right corner of your printed documents (report header)."), 'rml_header2': fields.text('RML Internal Header', required=True), 'rml_header3': fields.text('RML Internal Header for Landscape Reports', required=True), 'rml_footer': fields.text('Report Footer', help="Footer text displayed at the bottom of all reports."), @@ -330,7 +330,7 @@ class res_company(osv.osv): - produced by OpenERP.com + generated by OpenERP.com From 87a7c9f8dd810f07a45202cde05de4a7d3e54340 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Wed, 19 Dec 2012 14:14:59 +0100 Subject: [PATCH 22/49] [IMP] crm: remove unused import bzr revid: rco@openerp.com-20121219131459-xtbfvfll740dq9rg --- addons/crm/crm_lead.py | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 309ef14efc6..11776f55756 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -29,7 +29,6 @@ from openerp.tools.translate import _ from openerp.tools import html2plaintext from base.res.res_partner import format_address -import pdb CRM_LEAD_FIELDS_TO_MERGE = ['name', 'partner_id', From 080761cb276136dbcc3c1c8a5b2a68c3ca91f3d0 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Wed, 19 Dec 2012 15:20:21 +0100 Subject: [PATCH 23/49] [FIX] base_action_rule: fix tests bzr revid: rco@openerp.com-20121219142021-15h9an87hhhkbhi8 --- .../tests/base_action_rule_test.py | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/addons/base_action_rule/tests/base_action_rule_test.py b/addons/base_action_rule/tests/base_action_rule_test.py index 0452401ce23..ebefebe3ff4 100644 --- a/addons/base_action_rule/tests/base_action_rule_test.py +++ b/addons/base_action_rule/tests/base_action_rule_test.py @@ -1,4 +1,4 @@ -import tools +from openerp import SUPERUSER_ID from openerp.tests import common from .. import test_models @@ -8,8 +8,8 @@ class base_action_rule_test(common.TransactionCase): """*****setUp*****""" super(base_action_rule_test, self).setUp() cr, uid = self.cr, self.uid - self.demo_user = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'user_demo') - self.admin_user = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'user_admin') + self.demo = self.registry('ir.model.data').get_object(cr, uid, 'base', 'user_demo').id + self.admin = SUPERUSER_ID def create_filter_done(self, cr, uid, context=None): filter_pool = self.registry('ir.filters') @@ -36,12 +36,12 @@ class base_action_rule_test(common.TransactionCase): lead_pool = self.registry('base.action.rule.lead.test') return lead_pool.create(cr, uid, { 'name': "Lead Test 1", - 'user_id': self.admin_user[1], + 'user_id': self.admin, }, context=context) def create_rule(self, cr, uid, filter_id=None, filter_pre_id=None, context=None): """ - The "Rule 1" says that when a lead goes to the 'draft' state, the responsible for that lead changes to "demo_user" + The "Rule 1" says that when a lead goes to the 'draft' state, the responsible for that lead changes to user "demo" """ self.action_pool = self.registry('base.action.rule') return self.action_pool.create(cr,uid,{ @@ -51,7 +51,7 @@ class base_action_rule_test(common.TransactionCase): 'trg_date_type' : 'none', 'filter_pre_id' : filter_pre_id, 'filter_id' : filter_id, - 'act_user_id': self.demo_user[1], + 'act_user_id': self.demo, }, context=context) def test_00_check_to_state_draft_pre(self): @@ -65,7 +65,7 @@ class base_action_rule_test(common.TransactionCase): new_lead_id = self.create_lead_test_1(cr,uid,context=context) new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) self.assertTrue(new_lead.state=='draft') - self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin_user[1], context=context)) + self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin, context=context)) def test_01_check_to_state_draft_post(self): """ @@ -78,7 +78,7 @@ class base_action_rule_test(common.TransactionCase): new_lead_id = self.create_lead_test_1(cr,uid,context=context) new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) self.assertTrue(new_lead.state=='draft') - self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.demo_user[1], context=context)) + self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.demo, context=context)) def test_02_check_from_draft_to_done_with_steps(self): """ @@ -96,27 +96,27 @@ class base_action_rule_test(common.TransactionCase): new_lead_id = self.create_lead_test_1(cr,uid,context=context) new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) self.assertTrue(new_lead.state=='draft') - self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin_user[1], context=context)) + self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin, context=context)) """ change the state of new_lead to open and check that responsible doen't change""" new_lead.write({'state': 'open'}, context=context) new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) self.assertTrue(new_lead.state=='open') - self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin_user[1], context=context)) + self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin, context=context)) """ change the state of new_lead to pending and check that responsible doen't change""" new_lead.write({'state': 'pending'}, context=context) new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) self.assertTrue(new_lead.state=='pending') - self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin_user[1], context=context)) + self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin, context=context)) """ change the state of new_lead to cancel and check that responsible doen't change""" new_lead.write({'state': 'cancel'}, context=context) new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) self.assertTrue(new_lead.state=='cancel') - self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin_user[1], context=context)) + self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin, context=context)) """ change the state of new_lead to done and check that responsible doen't change """ new_lead.write({'state': 'done'}, context=context) new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) self.assertTrue(new_lead.state=='done') - self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin_user[1], context=context)) + self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin, context=context)) def test_02_check_from_draft_to_done_without_steps(self): """ @@ -124,7 +124,7 @@ class base_action_rule_test(common.TransactionCase): We will create a rule that says in precondition that the record must be in the "draft" state while a postcondition filter says that the record will be done. If the state goes from 'draft' to 'done' the responsible will change. If those two conditions aren't verified, the responsible will stay the same - The responsible in that test will change to "demo_user" + The responsible in that test will change to user "demo" """ cr, uid = self.cr, self.uid context = {} @@ -134,9 +134,9 @@ class base_action_rule_test(common.TransactionCase): new_lead_id = self.create_lead_test_1(cr,uid,context=context) new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) self.assertTrue(new_lead.state=='draft') - self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin_user[1], context=context)) + self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin, context=context)) """ change the state of new_lead to done and check that responsible change to Demo_user""" new_lead.write({'state': 'done'}, context=context) new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) self.assertTrue(new_lead.state=='done') - self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.demo_user[1], context=context)) \ No newline at end of file + self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.demo, context=context)) From b35e1cb3217cdbb432d9bf93dc49ae214b4d9da9 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Wed, 19 Dec 2012 15:22:35 +0100 Subject: [PATCH 24/49] [IMP] base_action_rule: remove unused stuff bzr revid: rco@openerp.com-20121219142235-uqs7m7jmeqh38bdt --- addons/base_action_rule/base_action_rule.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index adcc0f25023..c401cf076d0 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -21,16 +21,11 @@ from datetime import datetime from datetime import timedelta -import re import time from openerp import SUPERUSER_ID -from openerp.osv import fields, osv, orm +from openerp.osv import fields, osv from openerp.tools.translate import _ -from openerp.tools.safe_eval import safe_eval -from openerp.tools import ustr -from openerp import pooler -from openerp import tools def get_datetime(date_field): @@ -57,10 +52,6 @@ class base_action_rule(osv.osv): """ Get State """ return [('', ''), ('na','N/A (No previous state)')] - def priority_get(self, cr, uid, context=None): - """ Get Priority """ - return [('', '')] - _columns = { 'name': fields.char('Rule Name', size=64, required=True), 'model_id': fields.many2one('ir.model', 'Related Document Model', required=True, domain=[('osv_memory','=', False)]), @@ -101,7 +92,6 @@ trigger date, like sending a reminder 15 minutes before a meeting."), _order = 'sequence' - def post_action(self, cr, uid, ids, model, precondition_ok=None, context=None): # Searching for action rules cr.execute("SELECT model.model, rule.id FROM base_action_rule rule \ From e6cfbc5807dfa36154298c500e09f13480268e1c Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Wed, 19 Dec 2012 15:23:45 +0100 Subject: [PATCH 25/49] [IMP] base_action_rule: improve field definitions, add missing domains and restrictions bzr revid: rco@openerp.com-20121219142345-pya9tjhd9ilepxy7 --- addons/base_action_rule/base_action_rule.py | 38 +++++++++++++-------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index c401cf076d0..3b310519ed2 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -50,17 +50,18 @@ class base_action_rule(osv.osv): def state_get(self, cr, uid, context=None): """ Get State """ - return [('', ''), ('na','N/A (No previous state)')] + return [('', '')] _columns = { 'name': fields.char('Rule Name', size=64, required=True), - 'model_id': fields.many2one('ir.model', 'Related Document Model', required=True, domain=[('osv_memory','=', False)]), + 'model_id': fields.many2one('ir.model', 'Related Document Model', + required=True, domain=[('osv_memory', '=', False)]), 'model': fields.related('model_id', 'model', type="char", size=256, string='Model'), 'create_date': fields.datetime('Create Date', readonly=1), - 'active': fields.boolean('Active', help="If the active field is set to False,\ - it will allow you to hide the rule without removing it."), - 'sequence': fields.integer('Sequence', help="Gives the sequence order \ -when displaying a list of rules."), + 'active': fields.boolean('Active', + help="When unchecked, the rule is hidden and will not be executed."), + 'sequence': fields.integer('Sequence', + help="Gives the sequence order when displaying a list of rules."), 'trg_date_type': fields.selection([ ('none', 'None'), ('create', 'Creation Date'), @@ -69,18 +70,27 @@ when displaying a list of rules."), ('date', 'Date'), ('deadline', 'Deadline'), ], 'Trigger Date', size=16), - 'trg_date_range': fields.integer('Delay after trigger date', \ - help="Delay After Trigger Date,\ -specifies you can put a negative number. If you need a delay before the \ -trigger date, like sending a reminder 15 minutes before a meeting."), - 'trg_date_range_type': fields.selection([('minutes', 'Minutes'), ('hour', 'Hours'), \ + 'trg_date_range': fields.integer('Delay after trigger date', + help="Delay after the trigger date." \ + "You can put a negative number if you need a delay before the" \ + "trigger date, like sending a reminder 15 minutes before a meeting."), + 'trg_date_range_type': fields.selection([('minutes', 'Minutes'), ('hour', 'Hours'), ('day', 'Days'), ('month', 'Months')], 'Delay type'), 'act_user_id': fields.many2one('res.users', 'Set Responsible to'), 'act_state': fields.selection(_state_get, 'Set State to', size=16), 'act_followers': fields.many2many("res.partner", string="Set Followers"), - 'server_action_ids': fields.one2many('ir.actions.server', 'action_rule_id', 'Server Action', help="Define Server actions.\neg:Email Reminders, Call Object Service, etc.."), #TODO: set domain [('model_id','=',model_id)] - 'filter_id':fields.many2one('ir.filters', 'Postcondition Filter', required=False), #TODO: set domain [('model_id','=',model_id.model)] - 'filter_pre_id': fields.many2one('ir.filters', 'Precondition Filter', required=False), + 'server_action_ids': fields.one2many('ir.actions.server', 'action_rule_id', + domain="[('model_id', '=', model_id)]", + string='Server Action', + help="Example: email reminders, call object service, etc."), + 'filter_pre_id': fields.many2one('ir.filters', string='Before Filter', + ondelete='restrict', + domain="[('model_id', '=', model_id.model)]", + help="If present, this condition must be satisfied before the update of the record."), + 'filter_id': fields.many2one('ir.filters', string='After Filter', + ondelete='restrict', + domain="[('model_id', '=', model_id.model)]", + help="If present, this condition must be satisfied after the update of the record."), 'last_run': fields.datetime('Last Run', readonly=1), } From 07a3cf28a017d6e7353d3e7e5245d14cd7776f29 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Wed, 19 Dec 2012 16:05:33 +0100 Subject: [PATCH 26/49] [IMP] base_action_rule: improve method _register_hook of base.action.rule bzr revid: rco@openerp.com-20121219150533-t513zasagd4a73sx --- addons/base_action_rule/base_action_rule.py | 45 ++++++++------------- 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 3b310519ed2..0017a8d0794 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -117,7 +117,7 @@ class base_action_rule(osv.osv): self._action(cr, uid, [rule_id], model_pool.browse(cr, uid, ids, context=context), precondition_ok=precondition_ok, context=context) return True - def _create(self, old_create, model, context=None): + def _wrap_create(self, old_create, model): """ Return a wrapper around `old_create` calling both `old_create` and `post_action`, in that order. @@ -140,7 +140,7 @@ class base_action_rule(osv.osv): return new_id return wrapper - def _write(self, old_write, model, context=None): + def _wrap_write(self, old_write, model): """ Return a wrapper around `old_write` calling both `old_write` and `post_action`, in that order. @@ -171,41 +171,31 @@ class base_action_rule(osv.osv): return True return wrapper - def _register_hook(self, cr): + 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 `Ǹone`.) """ - Wrap every `create` and `write` methods of the models specified by - the rules (given by `ids`). - """ - ids = self.search(cr,SUPERUSER_ID,[]) - return self._register_hook_(cr,SUPERUSER_ID,ids,context=None) - - def _register_hook_(self, cr, uid, ids, context=None): - """ - Wrap every `create` and `write` methods of the models specified by - the rules (given by `ids`). - """ - reg_ids = [] - if not isinstance(ids, list): - reg_ids.append(ids) - else: - reg_ids.extend(ids) - for action_rule in self.browse(cr, uid, reg_ids, context=context): + if ids is None: + ids = self.search(cr, SUPERUSER_ID, []) + for action_rule in self.browse(cr, SUPERUSER_ID, ids): model = action_rule.model_id.model - obj_pool = self.pool.get(model) - if not hasattr(obj_pool, 'base_action_ruled'): - obj_pool.create = self._create(obj_pool.create, model, context=None) - obj_pool.write = self._write(obj_pool.write, model, context=None) - obj_pool.base_action_ruled = True + model_obj = self.pool.get(model) + if not hasattr(model_obj, 'base_action_ruled'): + model_obj.create = self._wrap_create(model_obj.create, model) + model_obj.write = self._wrap_write(model_obj.write, model) + model_obj.base_action_ruled = True return True def create(self, cr, uid, vals, context=None): res_id = super(base_action_rule, self).create(cr, uid, vals, context=context) - self._register_hook_(cr, uid, res_id,context=context) + self._register_hook(cr, [res_id]) 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) - self._register_hook_(cr, uid, ids, context=context) + self._register_hook(cr, ids) return True def _check(self, cr, uid, automatic=False, use_new_cursor=False, \ @@ -214,7 +204,6 @@ class base_action_rule(osv.osv): This Function is call by scheduler. """ rule_ids = self.search(cr, uid, [], context=context) - self._register_hook_(cr, uid, rule_ids, context=context) if context is None: context = {} for rule in self.browse(cr, uid, rule_ids, context=context): From 0228273ffb0e6bef4d8f1cbbf1b38d82508148cc Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Wed, 19 Dec 2012 16:07:23 +0100 Subject: [PATCH 27/49] [FIX] base_action_rule: fix in wrapper method bzr revid: rco@openerp.com-20121219150723-n25qqmczh3oau1cn --- addons/base_action_rule/base_action_rule.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 0017a8d0794..ed630248957 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -122,7 +122,7 @@ class base_action_rule(osv.osv): Return a wrapper around `old_create` calling both `old_create` and `post_action`, in that order. """ - def wrapper(cr, uid, vals, context=context): + def wrapper(cr, uid, vals, context=None): if context is None: context = {} new_id = old_create(cr, uid, vals, context=context) @@ -145,7 +145,7 @@ class base_action_rule(osv.osv): Return a wrapper around `old_write` calling both `old_write` and `post_action`, in that order. """ - def wrapper(cr, uid, ids, vals, context=context): + def wrapper(cr, uid, ids, vals, context=None): old_records = {} if context is None: context = {} From b0fd6e6ee744bb5a24739a60538b0f18ea5edada Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Thu, 20 Dec 2012 10:09:19 +0100 Subject: [PATCH 28/49] [IMP] base_action_rule: tests bzr revid: rco@openerp.com-20121220090919-eelugxam0kmyn7s2 --- .../tests/base_action_rule_test.py | 102 +++++++++--------- 1 file changed, 49 insertions(+), 53 deletions(-) diff --git a/addons/base_action_rule/tests/base_action_rule_test.py b/addons/base_action_rule/tests/base_action_rule_test.py index ebefebe3ff4..b0f169b3bdb 100644 --- a/addons/base_action_rule/tests/base_action_rule_test.py +++ b/addons/base_action_rule/tests/base_action_rule_test.py @@ -10,6 +10,7 @@ class base_action_rule_test(common.TransactionCase): cr, uid = self.cr, self.uid self.demo = self.registry('ir.model.data').get_object(cr, uid, 'base', 'user_demo').id self.admin = SUPERUSER_ID + self.model = self.registry('base.action.rule.lead.test') def create_filter_done(self, cr, uid, context=None): filter_pool = self.registry('ir.filters') @@ -33,13 +34,12 @@ class base_action_rule_test(common.TransactionCase): """ Create a new lead_test """ - lead_pool = self.registry('base.action.rule.lead.test') - return lead_pool.create(cr, uid, { + return self.model.create(cr, uid, { 'name': "Lead Test 1", 'user_id': self.admin, }, context=context) - def create_rule(self, cr, uid, filter_id=None, filter_pre_id=None, context=None): + def create_rule(self, cr, uid, filter_id=False, filter_pre_id=False, context=None): """ The "Rule 1" says that when a lead goes to the 'draft' state, the responsible for that lead changes to user "demo" """ @@ -59,26 +59,24 @@ class base_action_rule_test(common.TransactionCase): Check that a new record (with state = draft) doesn't change its responsible when there is a precondition filter which check that the state is draft. """ cr, uid = self.cr, self.uid - context = {} - filter_draft = self.create_filter_draft(cr, uid, context=context) - rule_1_id = self.create_rule(cr, uid, filter_pre_id=filter_draft, context=context) - new_lead_id = self.create_lead_test_1(cr,uid,context=context) - new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) - self.assertTrue(new_lead.state=='draft') - self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin, context=context)) + filter_draft = self.create_filter_draft(cr, uid) + rule_1_id = self.create_rule(cr, uid, filter_pre_id=filter_draft) + new_lead_id = self.create_lead_test_1(cr, uid) + new_lead = self.model.browse(cr, uid, new_lead_id) + self.assertEquals(new_lead.state, 'draft') + self.assertEquals(new_lead.user_id.id, self.admin) def test_01_check_to_state_draft_post(self): """ Check that a new record (with state = draft) changes its responsible when there is a postcondition filter which check that the state is draft. """ cr, uid = self.cr, self.uid - context = {} - filter_draft = self.create_filter_draft(cr, uid, context=context) - rule_1_id = self.create_rule(cr, uid, filter_id=filter_draft, context=context) - new_lead_id = self.create_lead_test_1(cr,uid,context=context) - new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) - self.assertTrue(new_lead.state=='draft') - self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.demo, context=context)) + filter_draft = self.create_filter_draft(cr, uid) + rule_1_id = self.create_rule(cr, uid, filter_id=filter_draft) + new_lead_id = self.create_lead_test_1(cr, uid) + new_lead = self.model.browse(cr, uid, new_lead_id) + self.assertEquals(new_lead.state, 'draft') + self.assertEquals(new_lead.user_id.id, self.demo) def test_02_check_from_draft_to_done_with_steps(self): """ @@ -89,34 +87,33 @@ class base_action_rule_test(common.TransactionCase): The responsible in that test will never change """ cr, uid = self.cr, self.uid - context = {} - filter_draft = self.create_filter_draft(cr, uid, context=context) - filter_done = self.create_filter_done(cr, uid, context=context) - self.create_rule(cr, uid, filter_pre_id=filter_draft, filter_id=filter_done, context=context) - new_lead_id = self.create_lead_test_1(cr,uid,context=context) - new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) - self.assertTrue(new_lead.state=='draft') - self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin, context=context)) + filter_draft = self.create_filter_draft(cr, uid) + filter_done = self.create_filter_done(cr, uid) + self.create_rule(cr, uid, filter_pre_id=filter_draft, filter_id=filter_done) + new_lead_id = self.create_lead_test_1(cr, uid) + new_lead = self.model.browse(cr, uid, new_lead_id) + self.assertEquals(new_lead.state, 'draft') + self.assertEquals(new_lead.user_id.id, self.admin) """ change the state of new_lead to open and check that responsible doen't change""" - new_lead.write({'state': 'open'}, context=context) - new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) - self.assertTrue(new_lead.state=='open') - self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin, context=context)) + new_lead.write({'state': 'open'}) + new_lead = self.model.browse(cr, uid, new_lead_id) + self.assertEquals(new_lead.state, 'open') + self.assertEquals(new_lead.user_id.id, self.admin) """ change the state of new_lead to pending and check that responsible doen't change""" - new_lead.write({'state': 'pending'}, context=context) - new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) - self.assertTrue(new_lead.state=='pending') - self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin, context=context)) + new_lead.write({'state': 'pending'}) + new_lead = self.model.browse(cr, uid, new_lead_id) + self.assertEquals(new_lead.state, 'pending') + self.assertEquals(new_lead.user_id.id, self.admin) """ change the state of new_lead to cancel and check that responsible doen't change""" - new_lead.write({'state': 'cancel'}, context=context) - new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) - self.assertTrue(new_lead.state=='cancel') - self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin, context=context)) + new_lead.write({'state': 'cancel'}) + new_lead = self.model.browse(cr, uid, new_lead_id) + self.assertEquals(new_lead.state, 'cancel') + self.assertEquals(new_lead.user_id.id, self.admin) """ change the state of new_lead to done and check that responsible doen't change """ - new_lead.write({'state': 'done'}, context=context) - new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) - self.assertTrue(new_lead.state=='done') - self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin, context=context)) + new_lead.write({'state': 'done'}) + new_lead = self.model.browse(cr, uid, new_lead_id) + self.assertEquals(new_lead.state, 'done') + self.assertEquals(new_lead.user_id.id, self.admin) def test_02_check_from_draft_to_done_without_steps(self): """ @@ -127,16 +124,15 @@ class base_action_rule_test(common.TransactionCase): The responsible in that test will change to user "demo" """ cr, uid = self.cr, self.uid - context = {} - filter_draft = self.create_filter_draft(cr, uid, context=context) - filter_done = self.create_filter_done(cr, uid, context=context) - self.create_rule(cr, uid, filter_pre_id=filter_draft, filter_id=filter_done, context=context) - new_lead_id = self.create_lead_test_1(cr,uid,context=context) - new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) - self.assertTrue(new_lead.state=='draft') - self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.admin, context=context)) + filter_draft = self.create_filter_draft(cr, uid) + filter_done = self.create_filter_done(cr, uid) + self.create_rule(cr, uid, filter_pre_id=filter_draft, filter_id=filter_done) + new_lead_id = self.create_lead_test_1(cr, uid) + new_lead = self.model.browse(cr, uid, new_lead_id) + self.assertEquals(new_lead.state, 'draft') + self.assertEquals(new_lead.user_id.id, self.admin) """ change the state of new_lead to done and check that responsible change to Demo_user""" - new_lead.write({'state': 'done'}, context=context) - new_lead = self.registry('base.action.rule.lead.test').browse(cr, uid, new_lead_id, context=context) - self.assertTrue(new_lead.state=='done') - self.assertTrue(new_lead.user_id==self.registry('res.users').browse(cr, uid, self.demo, context=context)) + new_lead.write({'state': 'done'}) + new_lead = self.model.browse(cr, uid, new_lead_id) + self.assertEquals(new_lead.state, 'done') + self.assertEquals(new_lead.user_id.id, self.demo) From da4226d8072dfb46c8b4d7d3d00fda14096e146c Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Thu, 20 Dec 2012 10:44:50 +0100 Subject: [PATCH 29/49] [FIX] base_action_rule: tests bzr revid: rco@openerp.com-20121220094450-wwkk1beszml5y9z6 --- .../tests/base_action_rule_test.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/addons/base_action_rule/tests/base_action_rule_test.py b/addons/base_action_rule/tests/base_action_rule_test.py index b0f169b3bdb..cc679a6b1a2 100644 --- a/addons/base_action_rule/tests/base_action_rule_test.py +++ b/addons/base_action_rule/tests/base_action_rule_test.py @@ -11,6 +11,7 @@ class base_action_rule_test(common.TransactionCase): self.demo = self.registry('ir.model.data').get_object(cr, uid, 'base', 'user_demo').id self.admin = SUPERUSER_ID self.model = self.registry('base.action.rule.lead.test') + self.base_action_rule = self.registry('base.action.rule') def create_filter_done(self, cr, uid, context=None): filter_pool = self.registry('ir.filters') @@ -43,8 +44,7 @@ class base_action_rule_test(common.TransactionCase): """ The "Rule 1" says that when a lead goes to the 'draft' state, the responsible for that lead changes to user "demo" """ - self.action_pool = self.registry('base.action.rule') - return self.action_pool.create(cr,uid,{ + return self.base_action_rule.create(cr,uid,{ 'name' : "Rule 1", 'model_id': self.registry('ir.model').search(cr, uid, [('model','=','base.action.rule.lead.test')], context=context)[0], 'active' : 1, @@ -54,17 +54,23 @@ class base_action_rule_test(common.TransactionCase): 'act_user_id': self.demo, }, context=context) + def delete_rules(self, cr, uid, context=None): + """ delete all the rules on model 'base.action.rule.lead.test' """ + action_ids = self.base_action_rule.search(cr, uid, [('model', '=', self.model._name)], context=context) + return self.base_action_rule.unlink(cr, uid, action_ids, context=context) + def test_00_check_to_state_draft_pre(self): """ Check that a new record (with state = draft) doesn't change its responsible when there is a precondition filter which check that the state is draft. """ cr, uid = self.cr, self.uid filter_draft = self.create_filter_draft(cr, uid) - rule_1_id = self.create_rule(cr, uid, filter_pre_id=filter_draft) + self.create_rule(cr, uid, filter_pre_id=filter_draft) new_lead_id = self.create_lead_test_1(cr, uid) new_lead = self.model.browse(cr, uid, new_lead_id) self.assertEquals(new_lead.state, 'draft') self.assertEquals(new_lead.user_id.id, self.admin) + self.delete_rules(cr, uid) def test_01_check_to_state_draft_post(self): """ @@ -72,11 +78,12 @@ class base_action_rule_test(common.TransactionCase): """ cr, uid = self.cr, self.uid filter_draft = self.create_filter_draft(cr, uid) - rule_1_id = self.create_rule(cr, uid, filter_id=filter_draft) + self.create_rule(cr, uid, filter_id=filter_draft) new_lead_id = self.create_lead_test_1(cr, uid) new_lead = self.model.browse(cr, uid, new_lead_id) self.assertEquals(new_lead.state, 'draft') self.assertEquals(new_lead.user_id.id, self.demo) + self.delete_rules(cr, uid) def test_02_check_from_draft_to_done_with_steps(self): """ @@ -114,6 +121,7 @@ class base_action_rule_test(common.TransactionCase): new_lead = self.model.browse(cr, uid, new_lead_id) self.assertEquals(new_lead.state, 'done') self.assertEquals(new_lead.user_id.id, self.admin) + self.delete_rules(cr, uid) def test_02_check_from_draft_to_done_without_steps(self): """ @@ -136,3 +144,4 @@ class base_action_rule_test(common.TransactionCase): new_lead = self.model.browse(cr, uid, new_lead_id) self.assertEquals(new_lead.state, 'done') self.assertEquals(new_lead.user_id.id, self.demo) + self.delete_rules(cr, uid) From d29a1bf9b170835f366991216379173d062a6085 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Thu, 20 Dec 2012 14:23:18 +0100 Subject: [PATCH 30/49] [IMP] base_action_rule: change the field 'server_action_ids' into a many2many bzr revid: rco@openerp.com-20121220132318-mjc46h4mnw63svok --- addons/base_action_rule/base_action_rule.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index ed630248957..5ce38865314 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -79,7 +79,7 @@ class base_action_rule(osv.osv): 'act_user_id': fields.many2one('res.users', 'Set Responsible to'), 'act_state': fields.selection(_state_get, 'Set State to', size=16), 'act_followers': fields.many2many("res.partner", string="Set Followers"), - 'server_action_ids': fields.one2many('ir.actions.server', 'action_rule_id', + 'server_action_ids': fields.many2many('ir.actions.server', domain="[('model_id', '=', model_id)]", string='Server Action', help="Example: email reminders, call object service, etc."), @@ -319,10 +319,4 @@ class base_action_rule(osv.osv): context.update({'action': False}) return True -class actions_server(osv.osv): - _inherit = 'ir.actions.server' - _columns = { - 'action_rule_id': fields.many2one("base.action.rule", string="Action Rule") - } - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 36e5b6e88e24877b34a99be7fea1d92c1b85aa6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 20 Dec 2012 15:18:42 +0100 Subject: [PATCH 31/49] [FIX] _search not uniquifies the results, because when using _auto_join we could have duplicates in the 'left part' of the result, i.e. a lead with several unread messages. This is done using a custom method instead of a set, because sets are unordered. bzr revid: tde@openerp.com-20121220141842-or3bigz11qkb13qn --- openerp/addons/base/ir/ir_needaction.py | 2 +- openerp/osv/orm.py | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/openerp/addons/base/ir/ir_needaction.py b/openerp/addons/base/ir/ir_needaction.py index 1f51f4667e6..745546ad0f2 100644 --- a/openerp/addons/base/ir/ir_needaction.py +++ b/openerp/addons/base/ir/ir_needaction.py @@ -61,5 +61,5 @@ class ir_needaction_mixin(osv.AbstractModel): dom = self._needaction_domain_get(cr, uid, context=context) if not dom: return 0 - res = self.search(cr, uid, (domain or []) + dom, limit=100, order='id DESC', context=context) + res = self.search(cr, uid, (domain or []) + dom, limit=1000, order='id DESC', context=context) return len(res) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 92eb8dad776..840758f753d 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -4857,7 +4857,15 @@ class BaseModel(object): return res[0][0] cr.execute('SELECT "%s".id FROM ' % self._table + from_clause + where_str + order_by + limit_str + offset_str, where_clause_params) res = cr.fetchall() - return [x[0] for x in res] + + # TDE note: with auto_join, we could have several lines about the same result + # i.e. a lead with several unread messages; we uniquify the result using + # a fast way to do it while preserving order (http://www.peterbe.com/plog/uniqifiers-benchmark) + def _uniquify_list(seq): + seen = set() + return [x for x in seq if x not in seen and not seen.add(x)] + + return _uniquify_list([x[0] for x in res]) # returns the different values ever entered for one field # this is used, for example, in the client when the user hits enter on From 56627207732167229f467d5c102a576c6688d27e Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Thu, 20 Dec 2012 15:24:45 +0100 Subject: [PATCH 32/49] [IMP] base_action_rule: refactor code bzr revid: rco@openerp.com-20121220142445-emzzvhlw400q37c9 --- addons/base_action_rule/base_action_rule.py | 291 ++++++++------------ 1 file changed, 121 insertions(+), 170 deletions(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 5ce38865314..ab1400f492f 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -22,11 +22,29 @@ from datetime import datetime from datetime import timedelta import time -from openerp import SUPERUSER_ID +import logging +from openerp import SUPERUSER_ID from openerp.osv import fields, osv from openerp.tools.translate import _ +_logger = logging.getLogger(__name__) + +DATE_TYPE_TO_FIELD = { + 'create': 'create_date', + 'write': 'write_date', + 'action_last': 'date_action_last', + 'date': 'date', + 'deadline': 'date_deadline', +} + +DATE_RANGE_FUNCTION = { + 'minutes': lambda interval: timedelta(minutes=interval), + 'hour': lambda interval: timedelta(hours=interval), + 'day': lambda interval: timedelta(days=interval), + 'month': lambda interval: timedelta(months=interval), + False: lambda interval: timedelta(0), +} def get_datetime(date_field): '''Return a datetime from a date string or a datetime string''' @@ -102,73 +120,92 @@ class base_action_rule(osv.osv): _order = 'sequence' - def post_action(self, cr, uid, ids, model, precondition_ok=None, context=None): - # Searching for action rules - cr.execute("SELECT model.model, rule.id FROM base_action_rule rule \ - LEFT JOIN ir_model model on (model.id = rule.model_id) \ - WHERE active and model = %s", (model,)) - res = cr.fetchall() - # Check if any rule matching with current object - for obj_name, rule_id in res: - model_pool = self.pool.get(obj_name) - # If the rule doesn't involve a time condition, run it immediately - # Otherwise we let the scheduler run the action - if self.browse(cr, uid, rule_id, context=context).trg_date_type == 'none': - self._action(cr, uid, [rule_id], model_pool.browse(cr, uid, ids, context=context), precondition_ok=precondition_ok, context=context) + def _filter(self, cr, uid, action_filter, record_ids=None, context=None): + """ filter the list record_ids that satisfy the action_filter """ + if record_ids and action_filter: + model = self.pool.get(action_filter.model_id) + domain = [('id', 'in', record_ids)] + eval(action_filter.domain) + ctx = dict(context or {}) + ctx.update(eval(action_filter.context)) + record_ids = model.search(cr, uid, domain, context=ctx) + return record_ids + + def _process(self, cr, uid, action, record_ids, context=None): + """ process the given action on the records """ + # IMPORTANT: add 'action':True in the context to avoid cascading actions + context = dict(context or {}, action=True) + + # execute server actions + model = self.pool.get(action.model_id.model) + if action.server_action_ids: + server_action_ids = map(int, action.server_action_ids) + for record in model.browse(cr, uid, record_ids, context): + action_server_obj = self.pool.get('ir.actions.server') + ctx = dict(context, active_model=model._name, active_ids=[record.id], active_id=record.id) + action_server_obj.run(cr, uid, server_action_ids, context=ctx) + + # modify records + values = {} + if action.act_user_id and 'user_id' in model._all_columns: + values['user_id'] = action.act_user_id.id + if 'date_action_last' in model._all_columns: + values['date_action_last'] = time.strftime('%Y-%m-%d %H:%M:%S') + if action.act_state and 'state' in model._all_columns: + values['state'] = action.act_state + + if values: + model.write(cr, uid, record_ids, values, context=context) + if values.get('state') and hasattr(model, 'message_post'): + model.message_post(cr, uid, record_ids, _(action.act_state), context=context) + if action.act_followers and hasattr(model, 'message_subscribe'): + model.message_subscribe(cr, uid, record_ids, map(int, action.act_followers), context=context) + return True def _wrap_create(self, old_create, model): - """ - Return a wrapper around `old_create` calling both `old_create` and - `post_action`, in that order. + """ Return a wrapper around `old_create` calling both `old_create` and + `_process`, in that order. """ def wrapper(cr, uid, vals, context=None): - if context is None: - context = {} new_id = old_create(cr, uid, vals, context=context) - #As it is a new record, we can assume that the precondition is true for every filter. - #(There is nothing before the create so no condition) - precondition_ok = {} - precondition_ok[new_id] = {} - for action in self.browse(cr, uid, self.search(cr, uid, [], context=context), context=context): - if action.filter_pre_id: - precondition_ok[new_id][action.id] = False - else: - precondition_ok[new_id][action.id] = True - if not context.get('action'): - self.post_action(cr, uid, [new_id], model, precondition_ok=precondition_ok, context=context) + if not (context and context.get('action')): + # as it is a new record, we do not consider the actions that have a prefilter + action_dom = [('model', '=', model), ('trg_date_type', 'in', ('none', False)), ('filter_pre_id', '=', False)] + action_ids = self.search(cr, uid, action_dom, context=context) + # check postconditions, and execute actions on the records that satisfy them + for action in self.browse(cr, uid, action_ids, context=context): + if self._filter(cr, uid, action.filter_id, [new_id], context=context): + self._process(cr, uid, action, [new_id], context=context) return new_id + return wrapper def _wrap_write(self, old_write, model): - """ - Return a wrapper around `old_write` calling both `old_write` and - `post_action`, in that order. + """ Return a wrapper around `old_write` calling both `old_write` and + `_process`, in that order. """ def wrapper(cr, uid, ids, vals, context=None): - old_records = {} - if context is None: - context = {} - if isinstance(ids, (str, int, long)): - ids = [ids] - model_pool = self.pool.get(model) - # We check for the pre-filter. We must apply it before the write - precondition_ok = {} - for id in ids: - precondition_ok[id] = {} - for action in self.browse(cr, uid, self.search(cr, uid, [], context=context), context=context): - precondition_ok[id][action.id] = True - if action.filter_pre_id and action.model_id.model == action.filter_pre_id.model_id: - ctx = dict(context) - ctx.update(eval(action.filter_pre_id.context)) - obj_ids = [] - if self.pool.get(action.model_id.model)!=None: - obj_ids = self.pool.get(action.model_id.model).search(cr, uid, eval(action.filter_pre_id.domain), context=ctx) - precondition_ok[id][action.id] = id in obj_ids + if context and context.get('action'): + return old_write(cr, uid, ids, vals, context=context) + + ids = [ids] if isinstance(ids, (int, long, str)) else ids + # retrieve the action rules to possibly execute + action_dom = [('model', '=', model), ('trg_date_type', 'in', ('none', False))] + action_ids = self.search(cr, uid, action_dom, context=context) + actions = self.browse(cr, uid, action_ids, context=context) + # check preconditions + pre_ids = {} + for action in actions: + pre_ids[action] = self._filter(cr, uid, action.filter_pre_id, ids, context=context) + # execute write old_write(cr, uid, ids, vals, context=context) - if not context.get('action'): - self.post_action(cr, uid, ids, model, precondition_ok=precondition_ok, context=context) + # check postconditions, and execute actions on the records that satisfy them + for action in actions: + post_ids = self._filter(cr, uid, action.filter_id, pre_ids[action], context=context) + if post_ids: + self._process(cr, uid, action, post_ids, context=context) return True + return wrapper def _register_hook(self, cr, ids=None): @@ -198,125 +235,39 @@ class base_action_rule(osv.osv): self._register_hook(cr, ids) return True - def _check(self, cr, uid, automatic=False, use_new_cursor=False, \ - context=None): - """ - This Function is call by scheduler. - """ - rule_ids = self.search(cr, uid, [], context=context) - if context is None: - context = {} - for rule in self.browse(cr, uid, rule_ids, context=context): - model = rule.model_id.model - model_pool = self.pool.get(model) - last_run = False - if rule.last_run: - last_run = get_datetime(rule.last_run) + def _check(self, cr, uid, automatic=False, use_new_cursor=False, context=None): + """ This Function is called by scheduler. """ + context = context or {} + # retrieve all the action rules that have a trg_date_type and no precondition + action_dom = [('trg_date_type', 'not in', ('none', False)), ('filter_pre_id', '=', False)] + action_ids = self.search(cr, uid, action_dom, context=context) + for action in self.browse(cr, uid, action_ids, context=context): now = datetime.now() - ctx = dict(context) - if rule.filter_id and rule.model_id.model == rule.filter_id.model_id: - ctx.update(eval(rule.filter_id.context)) - obj_ids = model_pool.search(cr, uid, eval(rule.filter_id.domain), context=ctx) - else: - obj_ids = model_pool.search(cr, uid, [], context=ctx) - for obj in model_pool.browse(cr, uid, obj_ids, context=ctx): - # Calculate when this action should next occur for this object - base = False - if rule.trg_date_type=='create' and hasattr(obj, 'create_date'): - base = obj.create_date - elif rule.trg_date_type=='write' and hasattr(obj, 'write_date'): - base = obj.write_date - elif (rule.trg_date_type=='action_last' - and hasattr(obj, 'create_date')): - if hasattr(obj, 'date_action_last') and obj.date_action_last: - base = obj.date_action_last - else: - base = obj.create_date - elif (rule.trg_date_type=='deadline' - and hasattr(obj, 'date_deadline') - and obj.date_deadline): - base = obj.date_deadline - elif (rule.trg_date_type=='date' - and hasattr(obj, 'date') - and obj.date): - base = obj.date - if base: - fnct = { - 'minutes': lambda interval: timedelta(minutes=interval), - 'day': lambda interval: timedelta(days=interval), - 'hour': lambda interval: timedelta(hours=interval), - 'month': lambda interval: timedelta(months=interval), - } - base = get_datetime(base) - delay = fnct[rule.trg_date_range_type](rule.trg_date_range) - action_date = base + delay - if (not last_run or (last_run <= action_date < now)): - try: - self._action(cr, uid, [rule.id], obj, context=ctx) - self.write(cr, uid, [rule.id], {'last_run': now}, context=context) - except Exception, e: - import traceback - print traceback.format_exc() - - + last_run = get_datetime(action.last_run) if action.last_run else False - def do_check(self, cr, uid, action, obj, precondition_ok=True, context=None): - """ check Action """ - if context is None: - context = {} - ok = precondition_ok - if action.filter_id and action.model_id.model == action.filter_id.model_id: + # retrieve all the records that satisfy the action's condition + model = self.pool.get(action.model_id.model) + domain = [] ctx = dict(context) - ctx.update(eval(action.filter_id.context)) - obj_ids = self.pool.get(action.model_id.model).search(cr, uid, eval(action.filter_id.domain), context=ctx) - ok = ok and obj.id in obj_ids - return ok + if action.filter_id: + domain = eval(action.filter_id.domain) + ctx.update(eval(action.filter_id.context)) + record_ids = model.search(cr, uid, domain, context=ctx) - def do_action(self, cr, uid, action, obj, context=None): - """ Do Action """ - if context is None: - context = {} - ctx = dict(context) - model_obj = self.pool.get(action.model_id.model) - action_server_obj = self.pool.get('ir.actions.server') - if action.server_action_ids: - ctx.update({'active_model': action.model_id.model, 'active_id':obj.id, 'active_ids':[obj.id]}) - action_server_obj.run(cr, uid, [x.id for x in action.server_action_ids], context=ctx) + # determine when action should occur for the records + date_field = DATE_TYPE_TO_FIELD.get(action.trg_date_type) + if date_field not in model._all_columns: + continue + delay = DATE_RANGE_FUNCTION[action.trg_date_range_type](action.trg_date_range) - write = {} - if hasattr(obj, 'user_id') and action.act_user_id: - write['user_id'] = action.act_user_id.id - if hasattr(obj, 'date_action_last'): - write['date_action_last'] = time.strftime('%Y-%m-%d %H:%M:%S') - if hasattr(obj, 'state') and action.act_state: - write['state'] = action.act_state + # process action on the records that should be executed + for record in model.browse(cr, uid, record_ids, context=context): + action_dt = get_datetime(record[date_field]) + delay + if last_run and (last_run <= action_dt < now) or (action_dt < now): + try: + self._process(cr, uid, action, [record.id], context=context) + except Exception: + import traceback + _logger.error(traceback.format_exc()) - model_obj.write(cr, uid, [obj.id], write, context) - if hasattr(obj, 'state') and hasattr(obj, 'message_post') and action.act_state: - model_obj.message_post(cr, uid, [obj], _(action.act_state), context=context) - - if hasattr(obj, 'message_subscribe') and action.act_followers: - exits_followers = [x.id for x in obj.message_follower_ids] - new_followers = [x.id for x in action.act_followers if x.id not in exits_followers] - if new_followers: - model_obj.message_subscribe(cr, uid, [obj.id], new_followers, context=context) - return True - - def _action(self, cr, uid, ids, objects, scrit=None, precondition_ok=None, context=None): - """ Do Action """ - if context is None: - context = {} - context.update({'action': True}) - if not isinstance(objects, list): - objects = [objects] - for action in self.browse(cr, uid, ids, context=context): - for obj in objects: - ok = True - if precondition_ok!=None: - ok = precondition_ok[obj.id][action.id] - if self.do_check(cr, uid, action, obj, precondition_ok=ok, context=context): - self.do_action(cr, uid, action, obj, context=context) - context.update({'action': False}) - return True - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: + action.write({'last_run': now}) From 5301d154298f8f7f675f433ed34ab02cf8dc5cb7 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Thu, 20 Dec 2012 15:39:14 +0100 Subject: [PATCH 33/49] [IMP] base_action_rule: improve code using datetimes bzr revid: rco@openerp.com-20121220143914-qwxmiixc4d455u5h --- addons/base_action_rule/base_action_rule.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index ab1400f492f..6fc069c0e73 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -19,13 +19,13 @@ # ############################################################################## -from datetime import datetime -from datetime import timedelta +from datetime import datetime, timedelta import time import logging from openerp import SUPERUSER_ID from openerp.osv import fields, osv +from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT from openerp.tools.translate import _ _logger = logging.getLogger(__name__) @@ -46,14 +46,12 @@ DATE_RANGE_FUNCTION = { False: lambda interval: timedelta(0), } -def get_datetime(date_field): +def get_datetime(date_str): '''Return a datetime from a date string or a datetime string''' - #complete date time if date_field contains only a date - date_split = date_field.split(' ') - if len(date_split) == 1: - date_field = date_split[0] + " 00:00:00" - - return datetime.strptime(date_field[:19], '%Y-%m-%d %H:%M:%S') + # complete date time if date_str contains only a date + if ' ' not in date_str: + date_str = date_str + " 00:00:00" + return datetime.strptime(date_str, DEFAULT_SERVER_DATETIME_FORMAT) class base_action_rule(osv.osv): @@ -149,7 +147,7 @@ class base_action_rule(osv.osv): if action.act_user_id and 'user_id' in model._all_columns: values['user_id'] = action.act_user_id.id if 'date_action_last' in model._all_columns: - values['date_action_last'] = time.strftime('%Y-%m-%d %H:%M:%S') + values['date_action_last'] = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) if action.act_state and 'state' in model._all_columns: values['state'] = action.act_state @@ -270,4 +268,4 @@ class base_action_rule(osv.osv): import traceback _logger.error(traceback.format_exc()) - action.write({'last_run': now}) + action.write({'last_run': now.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}) From b2864c4feb86ffe9fd3c6495099ef4df1051b800 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Thu, 20 Dec 2012 17:26:15 +0100 Subject: [PATCH 34/49] [FIX] crm: adapt overridden method of base_action_rule bzr revid: rco@openerp.com-20121220162615-zerev9bxpcqxtip1 --- addons/base_action_rule/base_action_rule.py | 4 ++-- addons/crm/crm_action_rule.py | 25 ++++++++++----------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 6fc069c0e73..b785726758a 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -144,10 +144,10 @@ class base_action_rule(osv.osv): # modify records values = {} - if action.act_user_id and 'user_id' in model._all_columns: - values['user_id'] = action.act_user_id.id if 'date_action_last' in model._all_columns: values['date_action_last'] = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + if action.act_user_id and 'user_id' in model._all_columns: + values['user_id'] = action.act_user_id.id if action.act_state and 'state' in model._all_columns: values['state'] = action.act_state diff --git a/addons/crm/crm_action_rule.py b/addons/crm/crm_action_rule.py index 4b9867db09c..1ea3d34494e 100644 --- a/addons/crm/crm_action_rule.py +++ b/addons/crm/crm_action_rule.py @@ -39,17 +39,20 @@ class base_action_rule(osv.osv): 'act_categ_id': fields.many2one('crm.case.categ', 'Set Category to'), } - def do_action(self, cr, uid, action, obj, context=None): - res = super(base_action_rule, self).do_action(cr, uid, action, obj, context=context) - model_obj = self.pool.get(action.model_id.model) - write = {} - if hasattr(action, 'act_section_id') and action.act_section_id: - write['section_id'] = action.act_section_id.id + def _process(self, cr, uid, action, record_ids, context=None): + """ process the given action on the records """ + res = super(base_action_rule, self)._process(cr, uid, action, record_ids, context=context) - if hasattr(action, 'act_categ_id') and action.act_categ_id: - write['categ_ids'] = [(4, action.act_categ_id.id)] + # add record modifications + context = dict(context or {}, action=True) + model = self.pool.get(action.model_id.model) + values = {} + if action.act_section_id and 'section_id' in model._all_columns: + values['section_id'] = action.act_section_id.id + if action.act_categ_id and 'categ_ids' in model._all_columns: + values['categ_ids'] = [(4, action.act_categ_id.id)] + model.write(cr, uid, record_ids, values, context=context) - model_obj.write(cr, uid, [obj.id], write, context) return res def state_get(self, cr, uid, context=None): @@ -57,8 +60,4 @@ class base_action_rule(osv.osv): res = super(base_action_rule, self).state_get(cr, uid, context=context) return res + crm.AVAILABLE_STATES - def priority_get(self, cr, uid, context=None): - res = super(base_action_rule, self).priority_get(cr, uid, context=context) - return res + crm.AVAILABLE_PRIORITIES - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From e2728306b3e2adf0c1175f07f1dd43df9c3f6048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 20 Dec 2012 21:51:02 +0100 Subject: [PATCH 35/49] [IMP] OROM: order_spec is overidden by self._order only if None (aka, not specified at all). False or void order_spec allows to avoid having any order_by clause. This is used now in needaction, to speedup the search. bzr revid: tde@openerp.com-20121220205102-lx9ftf3l8qg0xqcu --- openerp/addons/base/ir/ir_needaction.py | 2 +- openerp/osv/orm.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/openerp/addons/base/ir/ir_needaction.py b/openerp/addons/base/ir/ir_needaction.py index 745546ad0f2..f17cdd1420b 100644 --- a/openerp/addons/base/ir/ir_needaction.py +++ b/openerp/addons/base/ir/ir_needaction.py @@ -61,5 +61,5 @@ class ir_needaction_mixin(osv.AbstractModel): dom = self._needaction_domain_get(cr, uid, context=context) if not dom: return 0 - res = self.search(cr, uid, (domain or []) + dom, limit=1000, order='id DESC', context=context) + res = self.search(cr, uid, (domain or []) + dom, limit=1000, order='', context=context) return len(res) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 840758f753d..ca58ffa7577 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -4785,7 +4785,8 @@ class BaseModel(object): :raise" except_orm in case order_spec is malformed """ order_by_clause = '' - order_spec = order_spec or self._order + if order_spec is None: + order_spec = self._order if order_spec: order_by_elements = [] self._check_qorder(order_spec) From 919c0d1152ba869af49e4d9186698d35dbb89541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 21 Dec 2012 10:59:34 +0100 Subject: [PATCH 36/49] [REV] Reverted last improvement, because this was messing with the menus. bzr revid: tde@openerp.com-20121221095934-ocbnwz2zvbdbmfug --- openerp/osv/orm.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index ca58ffa7577..840758f753d 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -4785,8 +4785,7 @@ class BaseModel(object): :raise" except_orm in case order_spec is malformed """ order_by_clause = '' - if order_spec is None: - order_spec = self._order + order_spec = order_spec or self._order if order_spec: order_by_elements = [] self._check_qorder(order_spec) From 49b843d65f5f1fb8b0162b5e496bc54c9c2c312f Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Fri, 21 Dec 2012 11:21:51 +0100 Subject: [PATCH 37/49] [IMP] crm: remove inheritance on base.action.rule, and change demo action rules to use standard stuff bzr revid: rco@openerp.com-20121221102151-fxepq652rkeba62z --- addons/crm/__init__.py | 1 - addons/crm/__openerp__.py | 1 - addons/crm/crm_action_rule.py | 63 ------------------------- addons/crm/crm_action_rule_demo.xml | 12 ++++- addons/crm/crm_action_rule_view.xml | 17 ------- addons/crm/security/ir.model.access.csv | 2 +- 6 files changed, 12 insertions(+), 84 deletions(-) delete mode 100644 addons/crm/crm_action_rule.py delete mode 100644 addons/crm/crm_action_rule_view.xml diff --git a/addons/crm/__init__.py b/addons/crm/__init__.py index 729d54e0fc6..d78fb09eae7 100644 --- a/addons/crm/__init__.py +++ b/addons/crm/__init__.py @@ -20,7 +20,6 @@ ############################################################################## import crm -import crm_action_rule import crm_segmentation import crm_lead import crm_meeting diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py index 674fb489311..4a792abc9ee 100644 --- a/addons/crm/__openerp__.py +++ b/addons/crm/__openerp__.py @@ -77,7 +77,6 @@ Dashboard for CRM will include: 'crm_view.xml', - 'crm_action_rule_view.xml', 'crm_lead_view.xml', 'crm_lead_menu.xml', diff --git a/addons/crm/crm_action_rule.py b/addons/crm/crm_action_rule.py deleted file mode 100644 index 1ea3d34494e..00000000000 --- a/addons/crm/crm_action_rule.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -import re -from openerp import tools - -from openerp.tools.translate import _ -from openerp.tools import ustr -from openerp.osv import fields -from openerp.osv import osv - -import crm - -class base_action_rule(osv.osv): - """ Base Action Rule """ - _inherit = 'base.action.rule' - _description = 'Action Rules' - - _columns = { - 'act_section_id': fields.many2one('crm.case.section', 'Set Team to'), - 'act_categ_id': fields.many2one('crm.case.categ', 'Set Category to'), - } - - def _process(self, cr, uid, action, record_ids, context=None): - """ process the given action on the records """ - res = super(base_action_rule, self)._process(cr, uid, action, record_ids, context=context) - - # add record modifications - context = dict(context or {}, action=True) - model = self.pool.get(action.model_id.model) - values = {} - if action.act_section_id and 'section_id' in model._all_columns: - values['section_id'] = action.act_section_id.id - if action.act_categ_id and 'categ_ids' in model._all_columns: - values['categ_ids'] = [(4, action.act_categ_id.id)] - model.write(cr, uid, record_ids, values, context=context) - - return res - - def state_get(self, cr, uid, context=None): - """Gets available states for crm""" - res = super(base_action_rule, self).state_get(cr, uid, context=context) - return res + crm.AVAILABLE_STATES - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/crm/crm_action_rule_demo.xml b/addons/crm/crm_action_rule_demo.xml index b861909eb0c..0ace3bb0f25 100644 --- a/addons/crm/crm_action_rule_demo.xml +++ b/addons/crm/crm_action_rule_demo.xml @@ -40,6 +40,16 @@ Description: [[object.description]] [('country_id','=','United States')] + + Set team to Sales Department + + True + ir.actions.server + code + sales_team = self.pool.get('ir.model.data').get_object(cr, uid, 'crm', 'section_sales_department') +object.write({'section_id': sales_team.id}) + + Set Auto Followers on leads which are urgent and come from USA. @@ -47,10 +57,10 @@ Description: [[object.description]] - create 0 minutes + diff --git a/addons/crm/crm_action_rule_view.xml b/addons/crm/crm_action_rule_view.xml deleted file mode 100644 index 347e54c46ac..00000000000 --- a/addons/crm/crm_action_rule_view.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - base.action.rule.form.inherit - base.action.rule - - - - - - - - - - diff --git a/addons/crm/security/ir.model.access.csv b/addons/crm/security/ir.model.access.csv index 17bef812e4b..74394d3c5ce 100644 --- a/addons/crm/security/ir.model.access.csv +++ b/addons/crm/security/ir.model.access.csv @@ -30,7 +30,7 @@ access_res_partner,res.partner.crm.user,base.model_res_partner,base.group_sale_s access_res_partner_category,res.partner.category.crm.user,base.model_res_partner_category,base.group_sale_salesman,1,1,1,0 mail_mailgate_thread,mail.thread,mail.model_mail_thread,base.group_sale_salesman,1,1,1,1 access_crm_case_categ_manager,crm.case.categ manager,model_crm_case_categ,base.group_sale_manager,1,1,1,1 -access_base_action_rule_manager,base.action.rule manager,model_base_action_rule,base.group_sale_manager,1,1,1,1 +access_base_action_rule_manager,base.action.rule manager,base_action_rule.model_base_action_rule,base.group_sale_manager,1,1,1,1 access_crm_lead_report_user,crm.lead.report user,model_crm_lead_report,base.group_sale_salesman,1,1,1,1 access_res_partner_bank_type_crm_user,res.partner.bank.type.crm.user,base.model_res_partner_bank_type,base.group_sale_salesman,1,0,0,0 access_crm_lead_partner_manager,crm.lead.partner.manager,model_crm_lead,base.group_partner_manager,1,0,0,0 From ee5121a5906fb2f92bc3d5b77c318a2c44298686 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Fri, 21 Dec 2012 11:27:53 +0100 Subject: [PATCH 38/49] [IMP] base_action_rule: remove deprecated field act_state from base.action.rule bzr revid: rco@openerp.com-20121221102753-qejsv25g952gz8nx --- addons/base_action_rule/base_action_rule.py | 15 +-------------- addons/base_action_rule/base_action_rule_view.xml | 5 +---- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index b785726758a..304133a5926 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -60,14 +60,6 @@ class base_action_rule(osv.osv): _name = 'base.action.rule' _description = 'Action Rules' - def _state_get(self, cr, uid, context=None): - """ Get State """ - return self.state_get(cr, uid, context=context) - - def state_get(self, cr, uid, context=None): - """ Get State """ - return [('', '')] - _columns = { 'name': fields.char('Rule Name', size=64, required=True), 'model_id': fields.many2one('ir.model', 'Related Document Model', @@ -93,7 +85,6 @@ class base_action_rule(osv.osv): 'trg_date_range_type': fields.selection([('minutes', 'Minutes'), ('hour', 'Hours'), ('day', 'Days'), ('month', 'Months')], 'Delay type'), 'act_user_id': fields.many2one('res.users', 'Set Responsible to'), - 'act_state': fields.selection(_state_get, 'Set State to', size=16), 'act_followers': fields.many2many("res.partner", string="Set Followers"), 'server_action_ids': fields.many2many('ir.actions.server', domain="[('model_id', '=', model_id)]", @@ -148,13 +139,9 @@ class base_action_rule(osv.osv): values['date_action_last'] = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) if action.act_user_id and 'user_id' in model._all_columns: values['user_id'] = action.act_user_id.id - if action.act_state and 'state' in model._all_columns: - values['state'] = action.act_state - if values: model.write(cr, uid, record_ids, values, context=context) - if values.get('state') and hasattr(model, 'message_post'): - model.message_post(cr, uid, record_ids, _(action.act_state), context=context) + if action.act_followers and hasattr(model, 'message_subscribe'): model.message_subscribe(cr, uid, record_ids, map(int, action.act_followers), context=context) diff --git a/addons/base_action_rule/base_action_rule_view.xml b/addons/base_action_rule/base_action_rule_view.xml index aab9ce0000c..39096668fd3 100644 --- a/addons/base_action_rule/base_action_rule_view.xml +++ b/addons/base_action_rule/base_action_rule_view.xml @@ -42,12 +42,9 @@ - - - - + From fed5651f1cb6a6a38a3962b72d4aa8ff3f409ef0 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Fri, 21 Dec 2012 11:30:03 +0100 Subject: [PATCH 39/49] [IMP] base_action_rule: small code improvement bzr revid: rco@openerp.com-20121221103003-tl689nm2ser3u001 --- addons/base_action_rule/base_action_rule.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 304133a5926..5f337ef9db1 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -143,7 +143,8 @@ class base_action_rule(osv.osv): model.write(cr, uid, record_ids, values, context=context) if action.act_followers and hasattr(model, 'message_subscribe'): - model.message_subscribe(cr, uid, record_ids, map(int, action.act_followers), context=context) + follower_ids = map(int, action.act_followers) + model.message_subscribe(cr, uid, record_ids, follower_ids, context=context) return True From 306267ef55478b791326a6067f440e125e195a14 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Fri, 21 Dec 2012 11:32:33 +0100 Subject: [PATCH 40/49] [IMP] base_action_rule: improve field labels bzr revid: rco@openerp.com-20121221103233-lllh3c5f1azuffc7 --- addons/base_action_rule/base_action_rule.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 5f337ef9db1..1c66aeb1389 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -86,15 +86,14 @@ class base_action_rule(osv.osv): ('day', 'Days'), ('month', 'Months')], 'Delay type'), 'act_user_id': fields.many2one('res.users', 'Set Responsible to'), 'act_followers': fields.many2many("res.partner", string="Set Followers"), - 'server_action_ids': fields.many2many('ir.actions.server', + 'server_action_ids': fields.many2many('ir.actions.server', string='Server Actions', domain="[('model_id', '=', model_id)]", - string='Server Action', - help="Example: email reminders, call object service, etc."), - 'filter_pre_id': fields.many2one('ir.filters', string='Before Filter', + help="Examples: email reminders, call object service, etc."), + 'filter_pre_id': fields.many2one('ir.filters', string='Before Update Filter', ondelete='restrict', domain="[('model_id', '=', model_id.model)]", help="If present, this condition must be satisfied before the update of the record."), - 'filter_id': fields.many2one('ir.filters', string='After Filter', + 'filter_id': fields.many2one('ir.filters', string='After Update Filter', ondelete='restrict', domain="[('model_id', '=', model_id.model)]", help="If present, this condition must be satisfied after the update of the record."), From d17d474fc706f0bd2263382b564131120a0c5d52 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Fri, 21 Dec 2012 11:38:12 +0100 Subject: [PATCH 41/49] [IMP] base_action_rule: remove unused import bzr revid: rco@openerp.com-20121221103812-u6ejn5rifw2ctjsx --- addons/base_action_rule/base_action_rule.py | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 1c66aeb1389..5df9d3ec6ee 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -26,7 +26,6 @@ import logging from openerp import SUPERUSER_ID from openerp.osv import fields, osv from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT -from openerp.tools.translate import _ _logger = logging.getLogger(__name__) From a7ef28fd94b0c3ca3c7296f2e9e9b26d1d3f2956 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Fri, 21 Dec 2012 11:54:36 +0100 Subject: [PATCH 42/49] [IMP] base_action_rule: make code more robust bzr revid: rco@openerp.com-20121221105436-3t553dlzdvjb5hfe --- addons/base_action_rule/base_action_rule.py | 27 ++++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 5df9d3ec6ee..678b1a4f555 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -107,9 +107,10 @@ class base_action_rule(osv.osv): _order = 'sequence' - def _filter(self, cr, uid, action_filter, record_ids=None, context=None): - """ filter the list record_ids that satisfy the action_filter """ + def _filter(self, cr, uid, action, action_filter, record_ids, context=None): + """ filter the list record_ids that satisfy the action filter """ if record_ids and action_filter: + assert action.model == action_filter.model_id, "Filter model different from action rule model" model = self.pool.get(action_filter.model_id) domain = [('id', 'in', record_ids)] + eval(action_filter.domain) ctx = dict(context or {}) @@ -151,15 +152,17 @@ class base_action_rule(osv.osv): `_process`, in that order. """ def wrapper(cr, uid, vals, context=None): + if context and context.get('action'): + return old_create(cr, uid, vals, context=context) + new_id = old_create(cr, uid, vals, context=context) - if not (context and context.get('action')): - # as it is a new record, we do not consider the actions that have a prefilter - action_dom = [('model', '=', model), ('trg_date_type', 'in', ('none', False)), ('filter_pre_id', '=', False)] - action_ids = self.search(cr, uid, action_dom, context=context) - # check postconditions, and execute actions on the records that satisfy them - for action in self.browse(cr, uid, action_ids, context=context): - if self._filter(cr, uid, action.filter_id, [new_id], context=context): - self._process(cr, uid, action, [new_id], context=context) + # as it is a new record, we do not consider the actions that have a prefilter + action_dom = [('model', '=', model), ('trg_date_type', 'in', ('none', False)), ('filter_pre_id', '=', False)] + action_ids = self.search(cr, uid, action_dom, context=context) + # check postconditions, and execute actions on the records that satisfy them + for action in self.browse(cr, uid, action_ids, context=context): + if self._filter(cr, uid, action, action.filter_id, [new_id], context=context): + self._process(cr, uid, action, [new_id], context=context) return new_id return wrapper @@ -180,12 +183,12 @@ class base_action_rule(osv.osv): # check preconditions pre_ids = {} for action in actions: - pre_ids[action] = self._filter(cr, uid, action.filter_pre_id, ids, context=context) + pre_ids[action] = self._filter(cr, uid, action, action.filter_pre_id, ids, context=context) # execute write old_write(cr, uid, ids, vals, context=context) # check postconditions, and execute actions on the records that satisfy them for action in actions: - post_ids = self._filter(cr, uid, action.filter_id, pre_ids[action], context=context) + post_ids = self._filter(cr, uid, action, action.filter_id, pre_ids[action], context=context) if post_ids: self._process(cr, uid, action, post_ids, context=context) return True From 4fd1bc7b3c67ab531aca854486c6793340cba502 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Fri, 21 Dec 2012 14:37:51 +0100 Subject: [PATCH 43/49] [IMP] cron: remove useless pooljobs and schedule_cron_jobs methods The pooljobs and scheduled_cron_jobs stuff was only used to delay the processing of cron jobs until after the registry was fully loaded. However this is already the case because RegistryManager.new() only sets the flag at the end of the init step. The flag was named `registry.cron` but simply meant that the registry was fully loaded and ready, so it is simpler to rename it to `registry.ready`. In multiprocess mode this flag is enterily irrelevant so there is no need to selectively set it to True or False. `registry.ready` is simpler. bzr revid: odo@openerp.com-20121221133751-h4x670vblfr3d09e --- openerp/cli/server.py | 7 ++----- openerp/modules/registry.py | 32 ++++++++++---------------------- openerp/pooler.py | 11 +++-------- openerp/service/cron.py | 2 +- 4 files changed, 16 insertions(+), 36 deletions(-) diff --git a/openerp/cli/server.py b/openerp/cli/server.py index 7c768206354..7fb252f905f 100644 --- a/openerp/cli/server.py +++ b/openerp/cli/server.py @@ -95,10 +95,7 @@ def preload_registry(dbname): """ Preload a registry, and start the cron.""" try: update_module = True if openerp.tools.config['init'] or openerp.tools.config['update'] else False - db, registry = openerp.pooler.get_db_and_pool(dbname, update_module=update_module, pooljobs=False) - - # jobs will start to be processed later, when openerp.cron.start_master_thread() is called by openerp.service.start_services() - registry.schedule_cron_jobs() + db, registry = openerp.pooler.get_db_and_pool(dbname,update_module=update_module) except Exception: _logger.exception('Failed to initialize database `%s`.', dbname) @@ -106,7 +103,7 @@ def run_test_file(dbname, test_file): """ Preload a registry, possibly run a test file, and start the cron.""" try: config = openerp.tools.config - db, registry = openerp.pooler.get_db_and_pool(dbname, update_module=config['init'] or config['update'], pooljobs=False) + db, registry = openerp.pooler.get_db_and_pool(dbname, update_module=config['init'] or config['update']) cr = db.cursor() _logger.info('loading test file %s', test_file) openerp.tools.convert_yaml_import(cr, 'base', file(test_file), 'test', {}, 'test', True) diff --git a/openerp/modules/registry.py b/openerp/modules/registry.py index 23dbc280803..4aa20f3349d 100644 --- a/openerp/modules/registry.py +++ b/openerp/modules/registry.py @@ -58,8 +58,8 @@ class Registry(object): self.db_name = db_name self.db = openerp.sql_db.db_connect(db_name) - # In monoprocess cron jobs flag (pooljobs) - self.cron = False + # Indicates that the registry is + self.ready = False # Inter-process signaling (used only when openerp.multi_process is True): # The `base_registry_signaling` sequence indicates the whole registry @@ -121,15 +121,6 @@ class Registry(object): models_to_load.append(model._name) return [self.models[m] for m in models_to_load] - def schedule_cron_jobs(self): - """ Make the cron thread care about this registry/database jobs. - This will initiate the cron thread to check for any pending jobs for - this registry/database as soon as possible. Then it will continuously - monitor the ir.cron model for future jobs. See openerp.cron for - details. - """ - self.cron = True - def clear_caches(self): """ Clear the caches This clears the caches associated to methods decorated with @@ -192,18 +183,17 @@ class RegistryManager(object): registries_lock = threading.RLock() @classmethod - def get(cls, db_name, force_demo=False, status=None, update_module=False, - pooljobs=True): + def get(cls, db_name, force_demo=False, status=None, update_module=False): """ Return a registry for a given database name.""" try: return cls.registries[db_name] except KeyError: return cls.new(db_name, force_demo, status, - update_module, pooljobs) + update_module) @classmethod def new(cls, db_name, force_demo=False, status=None, - update_module=False, pooljobs=True): + update_module=False): """ Create and return a new registry for a given database name. The (possibly) previous registry for that database name is discarded. @@ -235,8 +225,7 @@ class RegistryManager(object): finally: cr.close() - if pooljobs: - registry.schedule_cron_jobs() + registry.ready = True return registry @@ -273,7 +262,7 @@ class RegistryManager(object): @classmethod def check_registry_signaling(cls, db_name): if openerp.multi_process and db_name in cls.registries: - registry = cls.get(db_name, pooljobs=False) + registry = cls.get(db_name) cr = registry.db.cursor() try: cr.execute(""" @@ -285,8 +274,7 @@ class RegistryManager(object): # database has been updated by another process). if registry.base_registry_signaling_sequence != r: _logger.info("Reloading the model registry after database signaling.") - # Don't run the cron in the Gunicorn worker. - registry = cls.new(db_name, pooljobs=False) + registry = cls.new(db_name) registry.base_registry_signaling_sequence = r # Check if the model caches must be invalidated (e.g. after a write # occured on another process). Don't clear right after a registry @@ -311,7 +299,7 @@ class RegistryManager(object): if openerp.multi_process and db_name in cls.registries: # Check the registries if any cache has been cleared and signal it # through the database to other processes. - registry = cls.get(db_name, pooljobs=False) + registry = cls.get(db_name) if registry.any_cache_cleared(): _logger.info("At least one model cache has been cleared, signaling through the database.") cr = registry.db.cursor() @@ -327,7 +315,7 @@ class RegistryManager(object): @classmethod def signal_registry_change(cls, db_name): if openerp.multi_process and db_name in cls.registries: - registry = cls.get(db_name, pooljobs=False) + registry = cls.get(db_name) cr = registry.db.cursor() r = 1 try: diff --git a/openerp/pooler.py b/openerp/pooler.py index d5588acf6ff..58cf4936b27 100644 --- a/openerp/pooler.py +++ b/openerp/pooler.py @@ -28,22 +28,17 @@ from openerp.modules.registry import RegistryManager -def get_db_and_pool(db_name, force_demo=False, status=None, update_module=False, pooljobs=True): +def get_db_and_pool(db_name, force_demo=False, status=None, update_module=False): """Create and return a database connection and a newly initialized registry.""" - registry = RegistryManager.get(db_name, force_demo, status, update_module, pooljobs) + registry = RegistryManager.get(db_name, force_demo, status, update_module) return registry.db, registry def restart_pool(db_name, force_demo=False, status=None, update_module=False): """Delete an existing registry and return a database connection and a newly initialized registry.""" - # preserve previous `cron-active` status of registry if it existed already - previous_registry = RegistryManager.registries.get(db_name) - cron_active = previous_registry and previous_registry.cron or False - - registry = RegistryManager.new(db_name, force_demo, status, update_module, pooljobs=cron_active) + registry = RegistryManager.new(db_name, force_demo, status, update_module) return registry.db, registry - def get_db(db_name): """Return a database connection. The corresponding registry is initialized.""" return get_db_and_pool(db_name)[0] diff --git a/openerp/service/cron.py b/openerp/service/cron.py index 337583b65e4..f3c81fe785f 100644 --- a/openerp/service/cron.py +++ b/openerp/service/cron.py @@ -43,7 +43,7 @@ def cron_runner(number): registries = openerp.modules.registry.RegistryManager.registries _logger.debug('cron%d polling for jobs', number) for db_name, registry in registries.items(): - while True and registry.cron: + while True and registry.ready: # acquired = openerp.addons.base.ir.ir_cron.ir_cron._acquire_job(db_name) # TODO why isnt openerp.addons.base defined ? import sys From e9f9e1191ca4e782d8014a4ba13a1feedf3fc4d7 Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Fri, 21 Dec 2012 14:03:02 +0100 Subject: [PATCH 44/49] [FIX] account_voucher: bug fixed in the supplier partial payment. There is no need to take care of the sign as the residual amount of account.move.line is already taking care of it bzr revid: qdp-launchpad@openerp.com-20121221130302-23v55ygcqekdlywi --- addons/account_voucher/account_voucher.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/addons/account_voucher/account_voucher.py b/addons/account_voucher/account_voucher.py index 4d510853660..842e50ebff1 100644 --- a/addons/account_voucher/account_voucher.py +++ b/addons/account_voucher/account_voucher.py @@ -599,12 +599,11 @@ class account_voucher(osv.osv): This function returns True if the line is considered as noise and should not be displayed """ if line.reconcile_partial_id: - sign = 1 if ttype == 'receipt' else -1 if currency_id == line.currency_id.id: - if line.amount_residual_currency * sign <= 0: + if line.amount_residual_currency <= 0: return True else: - if line.amount_residual * sign <= 0: + if line.amount_residual <= 0: return True return False From 0cd5dc119507555558ec59b83b1c55165b9a4158 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Fri, 21 Dec 2012 15:22:20 +0100 Subject: [PATCH 45/49] [IMP] base_action_rule: put the 'loop avoidance' code together bzr revid: rco@openerp.com-20121221142220-c5wjr9m4suq3jfdm --- addons/base_action_rule/base_action_rule.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 7b3b9e7a33c..1a3a1f4fc74 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -105,9 +105,6 @@ class base_action_rule(osv.osv): def _process(self, cr, uid, action, record_ids, context=None): """ process the given action on the records """ - # IMPORTANT: add 'action':True in the context to avoid cascading actions - context = dict(context or {}, action=True) - # execute server actions model = self.pool.get(action.model_id.model) if action.server_action_ids: @@ -137,13 +134,17 @@ class base_action_rule(osv.osv): `_process`, in that order. """ def wrapper(cr, uid, vals, context=None): + # avoid loops or cascading actions if context and context.get('action'): return old_create(cr, uid, vals, context=context) + context = dict(context or {}, action=True) new_id = old_create(cr, uid, vals, context=context) + # as it is a new record, we do not consider the actions that have a prefilter action_dom = [('model', '=', model), ('trg_date_id', '=', False), ('filter_pre_id', '=', False)] action_ids = self.search(cr, uid, action_dom, context=context) + # check postconditions, and execute actions on the records that satisfy them for action in self.browse(cr, uid, action_ids, context=context): if self._filter(cr, uid, action, action.filter_id, [new_id], context=context): @@ -157,20 +158,26 @@ class base_action_rule(osv.osv): `_process`, in that order. """ def wrapper(cr, uid, ids, vals, context=None): + # avoid loops or cascading actions if context and context.get('action'): return old_write(cr, uid, ids, vals, context=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_dom = [('model', '=', model), ('trg_date_id', '=', False)] action_ids = self.search(cr, uid, action_dom, context=context) actions = self.browse(cr, uid, action_ids, context=context) + # check preconditions pre_ids = {} for action in actions: pre_ids[action] = self._filter(cr, uid, action, action.filter_pre_id, ids, context=context) + # execute write old_write(cr, uid, ids, vals, context=context) + # check postconditions, and execute actions on the records that satisfy them for action in actions: post_ids = self._filter(cr, uid, action, action.filter_id, pre_ids[action], context=context) From 6502ec2af88f6b6a8d2780f179144ceec1378550 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Fri, 21 Dec 2012 15:23:33 +0100 Subject: [PATCH 46/49] [IMP] base_action_rule: typo bzr revid: rco@openerp.com-20121221142333-ml6saych44rl99ok --- addons/base_action_rule/base_action_rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 1a3a1f4fc74..0e98cd5e975 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -189,7 +189,7 @@ class base_action_rule(osv.osv): 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 `Ǹone`.) + the rules given by `ids` (or all existing rules if `ids` is `None`.) """ if ids is None: ids = self.search(cr, SUPERUSER_ID, []) From 603b12edbdfc0a27be6132d654400a9ea597656e Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Fri, 21 Dec 2012 15:24:02 +0100 Subject: [PATCH 47/49] packaging win32 bzr revid: al@openerp.com-20121221142402-zfrwws823zl9wzuc --- win32/setup.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/win32/setup.py b/win32/setup.py index 0feedce01e0..b0b36b5c6b4 100644 --- a/win32/setup.py +++ b/win32/setup.py @@ -24,11 +24,6 @@ import glob from distutils.core import setup import py2exe -def datas(): - r = [] - if os.name == 'nt': - r.append(("Microsoft.VC90.CRT", glob.glob('C:\Microsoft.VC90.CRT\*.*'))) - return r meta = {} execfile(os.path.join(os.path.dirname(__file__), '..', 'openerp', 'release.py'), meta) @@ -40,6 +35,8 @@ def generate_files(): } files = [] + if os.name == 'nt': + files.append(("Microsoft.VC90.CRT", glob.glob('C:\Microsoft.VC90.CRT\*.*'))) for action, steps in actions.items(): fname = action + '.bat' files.append(fname) @@ -69,7 +66,6 @@ setup(service = ["OpenERPServerService"], "skip_archive": 1, "optimize": 2, }}, - data_files=datas(), ) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 1eea63fc9266750b2d918e6dd6ff42dcfed8226a Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Fri, 21 Dec 2012 15:34:04 +0100 Subject: [PATCH 48/49] [IMP] base_action_rule: form view improvement bzr revid: rco@openerp.com-20121221143404-wl0khu9mnykjo3nc --- addons/base_action_rule/base_action_rule_view.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/base_action_rule/base_action_rule_view.xml b/addons/base_action_rule/base_action_rule_view.xml index 0c4a3ec7fb3..3b3da18cca1 100644 --- a/addons/base_action_rule/base_action_rule_view.xml +++ b/addons/base_action_rule/base_action_rule_view.xml @@ -36,10 +36,10 @@ - From 1671b9e03a20dd1e961190f3658daf523d5ebb8f Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Fri, 21 Dec 2012 15:49:34 +0100 Subject: [PATCH 49/49] [IMP] base_action_rule: form view improvement bzr revid: rco@openerp.com-20121221144934-erkp8md6kq14070a --- addons/base_action_rule/base_action_rule.py | 4 +- .../base_action_rule_view.xml | 58 +++++++++---------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 0e98cd5e975..a3237c1620b 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -69,8 +69,8 @@ class base_action_rule(osv.osv): "trigger date, like sending a reminder 15 minutes before a meeting."), 'trg_date_range_type': fields.selection([('minutes', 'Minutes'), ('hour', 'Hours'), ('day', 'Days'), ('month', 'Months')], 'Delay type'), - 'act_user_id': fields.many2one('res.users', 'Set Responsible to'), - 'act_followers': fields.many2many("res.partner", string="Set Followers"), + 'act_user_id': fields.many2one('res.users', 'Set Responsible'), + 'act_followers': fields.many2many("res.partner", string="Add Followers"), 'server_action_ids': fields.many2many('ir.actions.server', string='Server Actions', domain="[('model_id', '=', model_id)]", help="Examples: email reminders, call object service, etc."), diff --git a/addons/base_action_rule/base_action_rule_view.xml b/addons/base_action_rule/base_action_rule_view.xml index 3b3da18cca1..25ea4048bad 100644 --- a/addons/base_action_rule/base_action_rule_view.xml +++ b/addons/base_action_rule/base_action_rule_view.xml @@ -4,31 +4,27 @@ - - - base.action.rule.form + + + base.action.rule.form base.action.rule
- - - - - - +
+

+ Select a filter or a timer as condition.
An action rule is checked when you create or modify the "Related Document Model". The precondition filter is checked right before the modification while the postcondition filter is checked after the modification. A precondition filter will therefore not work during a creation.
+ To create a new filter:
+ - Go to your "Related Document Model" page and set the filter parameters in the "Search" view (Example of filter based on Leads/Opportunities: Creation Date "is equal to" 01/01/2012)
+ - In this same "Search" view, select the menu "Save Current Filter", enter the name (Ex: Create the 01/01/2012) and add the option "Share with all users"
+ The filter must therefore be available in this page. +

- + @@ -63,24 +66,22 @@ - + - - - - base.action.rule.tree + + + base.action.rule.tree base.action.rule - + - - - + + Automated Actions base.action.rule @@ -103,6 +104,5 @@ -