From 3df8c5fe9a048eac11d3c50e69bf3263f5b3b980 Mon Sep 17 00:00:00 2001 From: Arnaud Pineux Date: Wed, 28 Nov 2012 10:29:28 +0100 Subject: [PATCH] [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 -