[MIG] base_action_rule: Migrated to new API
This commit provides some code simplifications, the complete migration into new api. Thanks to rco-odoo for the review, and code improvements.
This commit is contained in:
@@ -17,13 +17,11 @@ trigger an automatic reminder email.
|
||||
""",
|
||||
'depends': ['base', 'resource', 'mail'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'data/base_action_rule_data.xml',
|
||||
'views/base_action_rule_view.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'demo': [
|
||||
'data/base_action_rule_demo.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="ir_cron_crm_action" model="ir.cron">
|
||||
@@ -7,12 +7,12 @@
|
||||
<field name="interval_number">4</field>
|
||||
<field name="interval_type">hours</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field eval="False" name="doall"/>
|
||||
<field eval="'base.action.rule'" name="model"/>
|
||||
<field eval="'_check'" name="function"/>
|
||||
<field eval="'(True,)'" name="args"/>
|
||||
<field name="doall" eval="False"/>
|
||||
<field name="model" eval="'base.action.rule'"/>
|
||||
<field name="function" eval="'_check'"/>
|
||||
<field name="args" eval="'(True,)'"/>
|
||||
<field name="active" eval="False" />
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
<record id="test_filter_draft" model="ir.filters">
|
||||
<field name="name">Test lead in state 'draft'</field>
|
||||
<field name="model_id">base.action.rule.lead.test</field>
|
||||
@@ -50,5 +49,4 @@
|
||||
<field name="model_id" ref="base_action_rule.model_base_action_rule_line_test"/>
|
||||
<field name="act_user_id" ref="base.user_demo"/>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from collections import defaultdict
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
import datetime as DT
|
||||
import dateutil
|
||||
import time
|
||||
import datetime
|
||||
import logging
|
||||
import time
|
||||
import traceback
|
||||
from collections import defaultdict
|
||||
|
||||
import openerp
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.modules.registry import RegistryManager
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from openerp.tools.safe_eval import safe_eval as eval
|
||||
import dateutil
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import api, fields, models, SUPERUSER_ID
|
||||
from odoo.modules.registry import RegistryManager
|
||||
from odoo.tools.safe_eval import safe_eval as eval
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -26,103 +24,116 @@ DATE_RANGE_FUNCTION = {
|
||||
False: lambda interval: relativedelta(0),
|
||||
}
|
||||
|
||||
def get_datetime(date_str):
|
||||
'''Return a datetime from a date string or a datetime string'''
|
||||
# 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):
|
||||
class BaseActionRule(models.Model):
|
||||
""" Base Action Rules """
|
||||
|
||||
_name = 'base.action.rule'
|
||||
_description = 'Action Rules'
|
||||
_order = 'sequence'
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Rule Name', required=True),
|
||||
'model_id': fields.many2one('ir.model', 'Related Document Model',
|
||||
required=True, domain=[('transient', '=', False)]),
|
||||
'model': fields.related('model_id', 'model', type="char", string='Model'),
|
||||
'create_date': fields.datetime('Create Date', readonly=1),
|
||||
'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."),
|
||||
'kind': fields.selection(
|
||||
[('on_create', 'On Creation'),
|
||||
('on_write', 'On Update'),
|
||||
('on_create_or_write', 'On Creation & Update'),
|
||||
('on_unlink', 'On Deletion'),
|
||||
('on_change', 'Based on Form Modification'),
|
||||
('on_time', 'Based on Timed Condition')],
|
||||
string='When to Run'),
|
||||
'trg_date_id': fields.many2one('ir.model.fields', string='Trigger Date',
|
||||
help="When should the condition be triggered. If present, will be checked by the scheduler. If empty, will be checked at creation and update.",
|
||||
domain="[('model_id', '=', model_id), ('ttype', 'in', ('date', 'datetime'))]"),
|
||||
'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'),
|
||||
'trg_date_calendar_id': fields.many2one(
|
||||
'resource.calendar', 'Use Calendar',
|
||||
help='When calculating a day-based timed condition, it is possible to use a calendar to compute the date based on working days.',
|
||||
ondelete='set null',
|
||||
),
|
||||
'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."),
|
||||
'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_pre_domain': fields.char(string='Before Update Domain', help="If present, this condition must be satisfied before the update of the record."),
|
||||
'filter_id': fields.many2one(
|
||||
'ir.filters', string='Filter',
|
||||
ondelete='restrict', domain="[('model_id', '=', model_id.model)]",
|
||||
help="If present, this condition must be satisfied before executing the action rule."),
|
||||
'filter_domain': fields.char(string='Domain', help="If present, this condition must be satisfied before executing the action rule."),
|
||||
'last_run': fields.datetime('Last Run', readonly=1, copy=False),
|
||||
'on_change_fields': fields.char(string="On Change Fields Trigger",
|
||||
help="Comma-separated list of field names that triggers the onchange."),
|
||||
}
|
||||
name = fields.Char(string='Rule Name', required=True)
|
||||
model_id = fields.Many2one('ir.model', string='Related Document Model', required=True, domain=[('transient', '=', False)])
|
||||
model = fields.Char(related='model_id.model', readonly=True)
|
||||
active = fields.Boolean(default=True, help="When unchecked, the rule is hidden and will not be executed.")
|
||||
sequence = fields.Integer(help="Gives the sequence order when displaying a list of rules.")
|
||||
kind = fields.Selection([('on_create', 'On Creation'),
|
||||
('on_write', 'On Update'),
|
||||
('on_create_or_write', 'On Creation & Update'),
|
||||
('on_unlink', 'On Deletion'),
|
||||
('on_change', 'Based on Form Modification'),
|
||||
('on_time', 'Based on Timed Condition')], string='When to Run', required=True)
|
||||
trg_date_id = fields.Many2one('ir.model.fields', string='Trigger Date',
|
||||
help="""When should the condition be triggered.
|
||||
If present, will be checked by the scheduler. If empty, will be checked at creation and update.""",
|
||||
domain="[('model_id', '=', model_id), ('ttype', 'in', ('date', 'datetime'))]")
|
||||
trg_date_range = fields.Integer(string='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')],
|
||||
string='Delay type', default='day')
|
||||
trg_date_calendar_id = fields.Many2one("resource.calendar", string='Use Calendar',
|
||||
help="When calculating a day-based timed condition, it is possible to use a calendar to compute the date based on working days.")
|
||||
act_user_id = fields.Many2one('res.users', string='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.")
|
||||
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_pre_domain = fields.Char(string='Before Update Domain',
|
||||
help="If present, this condition must be satisfied before the update of the record.")
|
||||
filter_id = fields.Many2one("ir.filters", string='Filter', ondelete='restrict', domain="[('model_id', '=', model_id.model)]",
|
||||
help="If present, this condition must be satisfied before executing the action rule.")
|
||||
filter_domain = fields.Char(string='Domain', help="If present, this condition must be satisfied before executing the action rule.")
|
||||
last_run = fields.Datetime(readonly=True, copy=False)
|
||||
on_change_fields = fields.Char(string="On Change Fields Trigger", help="Comma-separated list of field names that triggers the onchange.")
|
||||
|
||||
# which fields have an impact on the registry
|
||||
CRITICAL_FIELDS = ['model_id', 'active', 'kind', 'on_change_fields']
|
||||
|
||||
_defaults = {
|
||||
'active': True,
|
||||
'trg_date_range_type': 'day',
|
||||
}
|
||||
@api.onchange('model_id')
|
||||
def onchange_model_id(self):
|
||||
self.filter_pre_id = self.filter_id = False
|
||||
|
||||
def onchange_kind(self, cr, uid, ids, kind, context=None):
|
||||
clear_fields = []
|
||||
if kind in ['on_create', 'on_create_or_write', 'on_unlink']:
|
||||
clear_fields = ['filter_pre_id', 'filter_pre_domain', 'trg_date_id', 'trg_date_range', 'trg_date_range_type']
|
||||
elif kind in ['on_write', 'on_create_or_write']:
|
||||
clear_fields = ['trg_date_id', 'trg_date_range', 'trg_date_range_type']
|
||||
elif kind == 'on_time':
|
||||
clear_fields = ['filter_pre_id', 'filter_pre_domain']
|
||||
return {'value': dict.fromkeys(clear_fields, False)}
|
||||
@api.onchange('kind')
|
||||
def onchange_kind(self):
|
||||
if self.kind in ['on_create', 'on_create_or_write', 'on_unlink']:
|
||||
self.filter_pre_id = self.filter_pre_domain = self.trg_date_id = self.trg_date_range = self.trg_date_range_type = False
|
||||
elif self.kind in ['on_write', 'on_create_or_write']:
|
||||
self.trg_date_id = self.trg_date_range = self.trg_date_range_type = False
|
||||
elif self.kind == 'on_time':
|
||||
self.filter_pre_id = self.filter_pre_domain = False
|
||||
|
||||
def onchange_filter_pre_id(self, cr, uid, ids, filter_pre_id, context=None):
|
||||
ir_filter = self.pool['ir.filters'].browse(cr, uid, filter_pre_id, context=context)
|
||||
return {'value': {'filter_pre_domain': ir_filter.domain}}
|
||||
@api.onchange('filter_pre_id')
|
||||
def onchange_filter_pre_id(self):
|
||||
self.filter_pre_domain = self.filter_pre_id.domain
|
||||
|
||||
def onchange_filter_id(self, cr, uid, ids, filter_id, context=None):
|
||||
ir_filter = self.pool['ir.filters'].browse(cr, uid, filter_id, context=context)
|
||||
return {'value': {'filter_domain': ir_filter.domain}}
|
||||
@api.onchange('filter_id')
|
||||
def onchange_filter_id(self):
|
||||
self.filter_domain = self.filter_id.domain
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
base_action_rule = super(BaseActionRule, self).create(vals)
|
||||
self._update_cron()
|
||||
self._update_registry()
|
||||
return base_action_rule
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
res = super(BaseActionRule, self).write(vals)
|
||||
if set(vals).intersection(self.CRITICAL_FIELDS):
|
||||
self._update_cron()
|
||||
self._update_registry()
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
res = super(BaseActionRule, self).unlink()
|
||||
self._update_cron()
|
||||
self._update_registry()
|
||||
return res
|
||||
|
||||
def _update_cron(self):
|
||||
""" Activate the cron job depending on whether there exists action rules
|
||||
based on time conditions.
|
||||
"""
|
||||
cron = self.env.ref('base_action_rule.ir_cron_crm_action', raise_if_not_found=False)
|
||||
return cron and cron.toggle(model=self._name, domain=[('kind', '=', 'on_time')])
|
||||
|
||||
def _update_registry(self):
|
||||
""" Update the registry after a modification on action rules. """
|
||||
if self.env.registry.ready:
|
||||
# for the sake of simplicity, simply force the registry to reload
|
||||
self._cr.commit()
|
||||
self.env.reset()
|
||||
RegistryManager.new(self._cr.dbname)
|
||||
RegistryManager.signal_registry_change(self._cr.dbname)
|
||||
|
||||
@openerp.api.model
|
||||
def _get_actions(self, records, kinds):
|
||||
""" Return the actions of the given kinds for records' model. The
|
||||
returned actions' context contain an object to manage processing.
|
||||
returned actions' context contain an object to manage processing.
|
||||
"""
|
||||
if '__action_done' not in self._context:
|
||||
self = self.with_context(__action_done={})
|
||||
@@ -130,49 +141,42 @@ class base_action_rule(osv.osv):
|
||||
actions = self.with_context(active_test=True).search(domain)
|
||||
return actions.with_env(self.env)
|
||||
|
||||
@openerp.api.model
|
||||
def _get_eval_context(self):
|
||||
""" Prepare the context used when evaluating python code
|
||||
:returns: dict -- evaluation context given to (safe_)eval """
|
||||
:returns: dict -- evaluation context given to (safe_)eval
|
||||
"""
|
||||
return {
|
||||
'datetime': DT,
|
||||
'datetime': datetime,
|
||||
'dateutil': dateutil,
|
||||
'time': time,
|
||||
'uid': self.env.uid,
|
||||
'user': self.env.user,
|
||||
}
|
||||
|
||||
@openerp.api.model
|
||||
def _filter_pre(self, records):
|
||||
""" Filter the records that satisfy the precondition of action ``self``. """
|
||||
if self.filter_pre_id and records:
|
||||
eval_context = self._get_eval_context()
|
||||
domain = [('id', 'in', records.ids)] + eval(self.filter_pre_id.domain, eval_context)
|
||||
domain = [('id', 'in', records.ids)] + eval(self.filter_pre_id.domain, self._get_eval_context())
|
||||
ctx = eval(self.filter_pre_id.context)
|
||||
return records.with_context(**ctx).search(domain).with_env(records.env)
|
||||
elif self.filter_pre_domain and records:
|
||||
eval_context = self._get_eval_context()
|
||||
domain = [('id', 'in', records.ids)] + eval(self.filter_pre_domain, eval_context)
|
||||
domain = [('id', 'in', records.ids)] + eval(self.filter_pre_domain, self._get_eval_context())
|
||||
return records.search(domain)
|
||||
else:
|
||||
return records
|
||||
|
||||
@openerp.api.model
|
||||
def _filter_post(self, records):
|
||||
""" Filter the records that satisfy the postcondition of action ``self``. """
|
||||
if self.filter_id and records:
|
||||
eval_context = self._get_eval_context()
|
||||
domain = [('id', 'in', records.ids)] + eval(self.filter_id.domain, eval_context)
|
||||
domain = [('id', 'in', records.ids)] + eval(self.filter_id.domain, self._get_eval_context())
|
||||
ctx = eval(self.filter_id.context)
|
||||
return records.with_context(**ctx).search(domain).with_env(records.env)
|
||||
elif self.filter_domain and records:
|
||||
eval_context = self._get_eval_context()
|
||||
domain = [('id', 'in', records.ids)] + eval(self.filter_domain, eval_context)
|
||||
domain = [('id', 'in', records.ids)] + eval(self.filter_domain, self._get_eval_context())
|
||||
return records.search(domain)
|
||||
else:
|
||||
return records
|
||||
|
||||
@openerp.api.multi
|
||||
def _process(self, records):
|
||||
""" Process action ``self`` on the ``records`` that have not been done yet. """
|
||||
# filter out the records on which self has already been done, then mark
|
||||
@@ -186,7 +190,7 @@ class base_action_rule(osv.osv):
|
||||
# modify records
|
||||
values = {}
|
||||
if 'date_action_last' in records._fields:
|
||||
values['date_action_last'] = openerp.fields.Datetime.now()
|
||||
values['date_action_last'] = fields.Datetime.now()
|
||||
if self.act_user_id and 'user_id' in records._fields:
|
||||
values['user_id'] = self.act_user_id.id
|
||||
if values:
|
||||
@@ -202,9 +206,10 @@ class base_action_rule(osv.osv):
|
||||
ctx = {'active_model': record._name, 'active_ids': record.ids, 'active_id': record.id}
|
||||
self.server_action_ids.with_context(**ctx).run()
|
||||
|
||||
def _register_hook(self, cr):
|
||||
@api.model_cr
|
||||
def _register_hook(self):
|
||||
""" Patch models that should trigger action rules based on creation,
|
||||
modification, deletion of records and form onchanges.
|
||||
modification, deletion of records and form onchanges.
|
||||
"""
|
||||
#
|
||||
# Note: the patched methods must be defined inside another function,
|
||||
@@ -217,18 +222,15 @@ class base_action_rule(osv.osv):
|
||||
|
||||
def make_create():
|
||||
""" Instanciate a create method that processes action rules. """
|
||||
@openerp.api.model
|
||||
@api.model
|
||||
def create(self, vals, **kw):
|
||||
# retrieve the action rules to possibly execute
|
||||
actions = self.env['base.action.rule']._get_actions(self, ['on_create', 'on_create_or_write'])
|
||||
|
||||
# call original method
|
||||
record = create.origin(self.with_env(actions.env), vals, **kw)
|
||||
|
||||
# check postconditions, and execute actions on the records that satisfy them
|
||||
for action in actions.with_context(old_values=None):
|
||||
action._process(action._filter_post(record))
|
||||
|
||||
return record.with_env(self.env)
|
||||
|
||||
return create
|
||||
@@ -239,24 +241,20 @@ class base_action_rule(osv.osv):
|
||||
# Note: we patch method _write() instead of write() in order to
|
||||
# catch updates made by field recomputations.
|
||||
#
|
||||
@openerp.api.multi
|
||||
@api.multi
|
||||
def _write(self, vals, **kw):
|
||||
# retrieve the action rules to possibly execute
|
||||
actions = self.env['base.action.rule']._get_actions(self, ['on_write', 'on_create_or_write'])
|
||||
records = self.with_env(actions.env)
|
||||
|
||||
# check preconditions on records
|
||||
pre = {action: action._filter_pre(records) for action in actions}
|
||||
|
||||
# read old values before the update
|
||||
old_values = {
|
||||
old_vals.pop('id'): old_vals
|
||||
for old_vals in records.read(list(vals))
|
||||
}
|
||||
|
||||
# call original method
|
||||
_write.origin(records, vals, **kw)
|
||||
|
||||
# check postconditions, and execute actions on the records that satisfy them
|
||||
for action in actions.with_context(old_values=old_values):
|
||||
action._process(action._filter_post(pre[action]))
|
||||
@@ -266,16 +264,14 @@ class base_action_rule(osv.osv):
|
||||
|
||||
def make_unlink():
|
||||
""" Instanciate an unlink method that processes action rules. """
|
||||
@openerp.api.multi
|
||||
@api.multi
|
||||
def unlink(self, **kwargs):
|
||||
# retrieve the action rules to possibly execute
|
||||
actions = self.env['base.action.rule']._get_actions(self, ['on_unlink'])
|
||||
records = self.with_env(actions.env)
|
||||
|
||||
# check conditions, and execute actions on the records that satisfy them
|
||||
for action in actions:
|
||||
action._process(action._filter_post(pre[action]))
|
||||
|
||||
action._process(action._filter_post(records))
|
||||
# call original method
|
||||
return unlink.origin(self, **kwargs)
|
||||
|
||||
@@ -285,17 +281,17 @@ class base_action_rule(osv.osv):
|
||||
""" Instanciate an onchange method for the given action rule. """
|
||||
def base_action_rule_onchange(self):
|
||||
action_rule = self.env['base.action.rule'].browse(action_rule_id)
|
||||
server_actions = action_rule.server_action_ids.with_context(active_model=self._name, onchange_self=self)
|
||||
result = {}
|
||||
for server_action in server_actions:
|
||||
for server_action in action_rule.server_action_ids.with_context(active_model=self._name, onchange_self=self):
|
||||
res = server_action.run()
|
||||
if res and 'value' in res:
|
||||
res['value'].pop('id', None)
|
||||
self.update(self._convert_to_cache(res['value'], validate=False))
|
||||
if res and 'domain' in res:
|
||||
result.setdefault('domain', {}).update(res['domain'])
|
||||
if res and 'warning' in res:
|
||||
result['warning'] = res['warning']
|
||||
if res:
|
||||
if 'value' in res:
|
||||
res['value'].pop('id', None)
|
||||
self.update(self._convert_to_cache(res['value'], validate=False))
|
||||
if 'domain' in res:
|
||||
result.setdefault('domain', {}).update(res['domain'])
|
||||
if 'warning' in res:
|
||||
result['warning'] = res['warning']
|
||||
return result
|
||||
|
||||
return base_action_rule_onchange
|
||||
@@ -308,146 +304,87 @@ class base_action_rule(osv.osv):
|
||||
model._patch_method(name, method)
|
||||
|
||||
# retrieve all actions, and patch their corresponding model
|
||||
ids = self.search(cr, SUPERUSER_ID, [])
|
||||
for action_rule in self.browse(cr, SUPERUSER_ID, ids):
|
||||
model = action_rule.model_id.model
|
||||
model_obj = self.pool.get(model)
|
||||
if not model_obj:
|
||||
continue
|
||||
|
||||
for action_rule in self.with_context({}).search([]):
|
||||
Model = self.env[action_rule.model]
|
||||
if action_rule.kind == 'on_create':
|
||||
patch(model_obj, 'create', make_create())
|
||||
patch(Model, 'create', make_create())
|
||||
|
||||
elif action_rule.kind == 'on_create_or_write':
|
||||
patch(model_obj, 'create', make_create())
|
||||
patch(model_obj, '_write', make_write())
|
||||
patch(Model, 'create', make_create())
|
||||
patch(Model, '_write', make_write())
|
||||
|
||||
elif action_rule.kind == 'on_write':
|
||||
patch(model_obj, '_write', make_write())
|
||||
patch(Model, '_write', make_write())
|
||||
|
||||
elif action_rule.kind == 'on_unlink':
|
||||
patch(model_obj, 'unlink', make_unlink())
|
||||
patch(Model, 'unlink', make_unlink())
|
||||
|
||||
elif action_rule.kind == 'on_change':
|
||||
# register an onchange method for the action_rule
|
||||
method = make_onchange(action_rule.id)
|
||||
for field_name in action_rule.on_change_fields.split(","):
|
||||
field_name = field_name.strip()
|
||||
model_obj._onchange_methods[field_name].append(method)
|
||||
Model._onchange_methods[field_name.strip()].append(method)
|
||||
|
||||
def _update_cron(self, cr, uid, context=None):
|
||||
""" Activate the cron job depending on whether there exists action rules
|
||||
based on time conditions. """
|
||||
try:
|
||||
cron = self.pool['ir.model.data'].get_object(
|
||||
cr, uid, 'base_action_rule', 'ir_cron_crm_action', context=context)
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
return cron.toggle(model=self._name, domain=[('kind', '=', 'on_time')])
|
||||
|
||||
def _update_registry(self, cr, uid, context=None):
|
||||
""" Update the registry after a modification on action rules. """
|
||||
if self.pool.ready:
|
||||
# for the sake of simplicity, simply force the registry to reload
|
||||
cr.commit()
|
||||
openerp.api.Environment.reset()
|
||||
RegistryManager.new(cr.dbname)
|
||||
RegistryManager.signal_registry_change(cr.dbname)
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
res_id = super(base_action_rule, self).create(cr, uid, vals, context=context)
|
||||
self._update_cron(cr, uid, context=context)
|
||||
self._update_registry(cr, uid, context=context)
|
||||
return res_id
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
super(base_action_rule, self).write(cr, uid, ids, vals, context=context)
|
||||
if set(vals) & set(self.CRITICAL_FIELDS):
|
||||
self._update_cron(cr, uid, context=context)
|
||||
self._update_registry(cr, uid, context=context)
|
||||
return True
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
res = super(base_action_rule, self).unlink(cr, uid, ids, context=context)
|
||||
self._update_cron(cr, uid, context=context)
|
||||
self._update_registry(cr, uid, context=context)
|
||||
return res
|
||||
|
||||
def onchange_model_id(self, cr, uid, ids, model_id, context=None):
|
||||
data = {'model': False, 'filter_pre_id': False, 'filter_id': False}
|
||||
if model_id:
|
||||
model = self.pool.get('ir.model').browse(cr, uid, model_id, context=context)
|
||||
data.update({'model': model.model})
|
||||
return {'value': data}
|
||||
|
||||
def _check_delay(self, cr, uid, action, record, record_dt, context=None):
|
||||
@api.model
|
||||
def _check_delay(self, action, record, record_dt):
|
||||
if action.trg_date_calendar_id and action.trg_date_range_type == 'day':
|
||||
start_dt = get_datetime(record_dt)
|
||||
action_dt = self.pool['resource.calendar'].schedule_days_get_date(
|
||||
cr, uid, action.trg_date_calendar_id.id, action.trg_date_range,
|
||||
day_date=start_dt, compute_leaves=True, context=context
|
||||
)
|
||||
return action.trg_date_calendar_id.schedule_days_get_date(
|
||||
action.trg_date_range,
|
||||
day_date=fields.Datetime.from_string(record_dt),
|
||||
compute_leaves=True,
|
||||
)[0]
|
||||
else:
|
||||
delay = DATE_RANGE_FUNCTION[action.trg_date_range_type](action.trg_date_range)
|
||||
action_dt = get_datetime(record_dt) + delay
|
||||
return action_dt
|
||||
return fields.Datetime.from_string(record_dt) + delay
|
||||
|
||||
def _check(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
|
||||
@api.model
|
||||
def _check(self, automatic=False, use_new_cursor=False):
|
||||
""" This Function is called by scheduler. """
|
||||
context = context or {}
|
||||
if '__action_done' not in context:
|
||||
context = dict(context, __action_done={})
|
||||
if '__action_done' not in self._context:
|
||||
self = self.with_context(__action_done={})
|
||||
|
||||
# retrieve all the action rules to run based on a timed condition
|
||||
action_dom = [('kind', '=', 'on_time')]
|
||||
action_ids = self.search(cr, uid, action_dom, context=dict(context, active_test=True))
|
||||
eval_context = self._get_eval_context(cr, uid, context=context)
|
||||
for action in self.browse(cr, uid, action_ids, context=context):
|
||||
now = datetime.now()
|
||||
if action.last_run:
|
||||
last_run = get_datetime(action.last_run)
|
||||
else:
|
||||
last_run = datetime.utcfromtimestamp(0)
|
||||
eval_context = self._get_eval_context()
|
||||
for action in self.with_context(active_test=True).search([('kind', '=', 'on_time')]):
|
||||
last_run = fields.Datetime.from_string(action.last_run) or datetime.datetime.utcfromtimestamp(0)
|
||||
|
||||
# retrieve all the records that satisfy the action's condition
|
||||
model = self.pool[action.model_id.model]
|
||||
domain = []
|
||||
ctx = dict(context)
|
||||
if action.filter_domain is not False:
|
||||
context = dict(self._context)
|
||||
if action.filter_domain:
|
||||
domain = eval(action.filter_domain, eval_context)
|
||||
elif action.filter_id:
|
||||
domain = eval(action.filter_id.domain, eval_context)
|
||||
ctx.update(eval(action.filter_id.context))
|
||||
if 'lang' not in ctx:
|
||||
context.update(eval(action.filter_id.context))
|
||||
if 'lang' not in context:
|
||||
# Filters might be language-sensitive, attempt to reuse creator lang
|
||||
# as we are usually running this as super-user in background
|
||||
[filter_meta] = action.filter_id.get_metadata()
|
||||
user_id = filter_meta['write_uid'] and filter_meta['write_uid'][0] or \
|
||||
filter_meta['create_uid'][0]
|
||||
ctx['lang'] = self.pool['res.users'].browse(cr, uid, user_id).lang
|
||||
record_ids = model.search(cr, uid, domain, context=ctx)
|
||||
filter_meta = action.filter_id.get_metadata()[0]
|
||||
user_id = (filter_meta['write_uid'] or filter_meta['create_uid'])[0]
|
||||
context['lang'] = self.env['res.users'].browse(user_id).lang
|
||||
records = self.env[action.model].with_context(context).search(domain)
|
||||
|
||||
# determine when action should occur for the records
|
||||
date_field = action.trg_date_id.name
|
||||
if date_field == 'date_action_last' and 'create_date' in model._fields:
|
||||
get_record_dt = lambda record: record[date_field] or record.create_date
|
||||
if action.trg_date_id.name == 'date_action_last' and 'create_date' in records._fields:
|
||||
get_record_dt = lambda record: record[action.trg_date_id.name] or record.create_date
|
||||
else:
|
||||
get_record_dt = lambda record: record[date_field]
|
||||
get_record_dt = lambda record: record[action.trg_date_id.name]
|
||||
|
||||
# process action on the records that should be executed
|
||||
for record in model.browse(cr, uid, record_ids, context=context):
|
||||
now = datetime.datetime.now()
|
||||
for record in records:
|
||||
record_dt = get_record_dt(record)
|
||||
if not record_dt:
|
||||
continue
|
||||
action_dt = self._check_delay(cr, uid, action, record, record_dt, context=context)
|
||||
action_dt = self._check_delay(action, record, record_dt)
|
||||
if last_run <= action_dt < now:
|
||||
try:
|
||||
action._process(record)
|
||||
except Exception:
|
||||
import traceback
|
||||
_logger.error(traceback.format_exc())
|
||||
|
||||
action.write({'last_run': now.strftime(DEFAULT_SERVER_DATETIME_FORMAT)})
|
||||
action.write({'last_run': fields.Datetime.now()})
|
||||
|
||||
if automatic:
|
||||
# auto-commit for batch processing
|
||||
cr.commit()
|
||||
self._cr.commit()
|
||||
|
||||
@@ -1,51 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import openerp
|
||||
from openerp.osv import fields, osv
|
||||
from openerp import api
|
||||
from odoo import fields, models
|
||||
|
||||
AVAILABLE_STATES = [
|
||||
('draft', 'New'),
|
||||
('cancel', 'Cancelled'),
|
||||
('open', 'In Progress'),
|
||||
('pending', 'Pending'),
|
||||
('done', 'Closed')
|
||||
]
|
||||
|
||||
class lead_test(osv.Model):
|
||||
class LeadTest(models.Model):
|
||||
_name = "base.action.rule.lead.test"
|
||||
_description = "Action Rule Test"
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Subject', 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),
|
||||
'line_ids': fields.one2many('base.action.rule.line.test', 'lead_id'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'state' : 'draft',
|
||||
'active' : True,
|
||||
}
|
||||
|
||||
customer = openerp.fields.Boolean(related='partner_id.customer', readonly=True, store=True)
|
||||
|
||||
@api.cr_uid_ids_context
|
||||
def message_post(self, cr, uid, thread_id, body='', subject=None, message_type='notification', subtype=None, parent_id=False, attachments=None, context=None, **kwargs):
|
||||
pass
|
||||
|
||||
def message_subscribe(self, cr, uid, ids, partner_ids=None, channel_ids=None, subtype_ids=None, force=True, context=None):
|
||||
pass
|
||||
name = fields.Char(string='Subject', required=True, index=True)
|
||||
user_id = fields.Many2one('res.users', string='Responsible')
|
||||
state = fields.Selection([('draft', 'New'), ('cancel', 'Cancelled'), ('open', 'In Progress'),
|
||||
('pending', 'Pending'), ('done', 'Closed')],
|
||||
string="Status", readonly=True, default='draft')
|
||||
active = fields.Boolean(default=True)
|
||||
partner_id = fields.Many2one('res.partner', string='Partner')
|
||||
date_action_last = fields.Datetime(string='Last Action', readonly=True)
|
||||
customer = fields.Boolean(related='partner_id.customer', readonly=True, store=True)
|
||||
line_ids = fields.One2many('base.action.rule.line.test', 'lead_id')
|
||||
|
||||
|
||||
class line_test(osv.Model):
|
||||
class LineTest(models.Model):
|
||||
_name = "base.action.rule.line.test"
|
||||
_description = "Action Rule Line Test"
|
||||
|
||||
name = openerp.fields.Char()
|
||||
lead_id = openerp.fields.Many2one('base.action.rule.lead.test', ondelete='cascade')
|
||||
user_id = openerp.fields.Many2one('res.users')
|
||||
name = fields.Char()
|
||||
lead_id = fields.Many2one('base.action.rule.lead.test', ondelete='cascade')
|
||||
user_id = fields.Many2one('res.users')
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
<!-- Action Rule Form View -->
|
||||
<record id="view_base_action_rule_form" model="ir.ui.view">
|
||||
<field name="name">base.action.rule.form</field>
|
||||
@@ -12,7 +11,7 @@
|
||||
<h1><field name="name"/></h1>
|
||||
<group>
|
||||
<group>
|
||||
<field name="model_id" on_change="onchange_model_id(model_id, context)"/>
|
||||
<field name="model_id"/>
|
||||
<field name="model" invisible="1"/>
|
||||
</group>
|
||||
<group>
|
||||
@@ -26,25 +25,23 @@
|
||||
<p><b>Please choose the document type before setting the conditions.</b></p>
|
||||
</group>
|
||||
<group attrs="{'invisible': [('model', '=', False)]}">
|
||||
<field name="kind" required="1"
|
||||
on_change="onchange_kind(kind)"/>
|
||||
<field name="kind"/>
|
||||
<label for="filter_pre_id" attrs="{'invisible': [('kind', '!=', 'on_write')]}"/>
|
||||
<div attrs="{'invisible': [('kind', '!=', 'on_write')]}">
|
||||
<field name="filter_pre_domain" widget="char_domain" options="{'model_field': 'model'}"
|
||||
class="oe_inline"/>
|
||||
<div class="oe_edit_only oe_inline">Set selection based on a search filter:
|
||||
class="o_row"/>
|
||||
<div class="oe_edit_only o_row">Set selection based on a search filter:
|
||||
<field name="filter_pre_id" domain="[('model_id', '=', model), '|', ('user_id', '=', uid), ('user_id', '=', False)]"
|
||||
class="oe_inline" context="{'default_model_id': model, 'default_active': False, 'active_test': False}"
|
||||
on_change="onchange_filter_pre_id(filter_pre_id, context)"/></div>
|
||||
class="o_row" context="{'default_model_id': model, 'default_active': False, 'active_test': False}"/></div>
|
||||
</div>
|
||||
<label for="filter_id"/>
|
||||
<div>
|
||||
<field name="filter_domain" widget="char_domain" options="{'model_field': 'model'}"
|
||||
class="oe_inline"/>
|
||||
<div class="oe_edit_only oe_inline">Set selection based on a search filter:
|
||||
class="o_row"/>
|
||||
<div class="oe_edit_only o_row">Set selection based on a search filter:
|
||||
<field name="filter_id" domain="[('model_id', '=', model), '|', ('user_id', '=', uid), ('user_id', '=', False)]"
|
||||
class="oe_inline" context="{'default_model_id': model, 'default_active': False, 'active_test': False}"
|
||||
on_change="onchange_filter_id(filter_id, context)"/></div>
|
||||
class="o_row" context="{'default_model_id': model, 'default_active': False, 'active_test': False}"/>
|
||||
</div>
|
||||
</div>
|
||||
<field name="on_change_fields"
|
||||
attrs="{'invisible': [('kind', '!=', 'on_change')], 'required': [('kind', '=', 'on_change')]}"/>
|
||||
@@ -53,10 +50,10 @@
|
||||
<label for="trg_date_range" string="Delay After Trigger Date"
|
||||
attrs="{'invisible': [('trg_date_id','=',False)]}"/>
|
||||
<div attrs="{'invisible': [('trg_date_id','=',False)]}">
|
||||
<field name="trg_date_range" class="oe_inline"/>
|
||||
<field name="trg_date_range_type" class="oe_inline" attrs="{'required': [('trg_date_id','!=',False)]}"/>
|
||||
<field name="trg_date_range" class="o_row"/>
|
||||
<field name="trg_date_range_type" class="o_row" attrs="{'required': [('trg_date_id','!=',False)]}"/>
|
||||
</div>
|
||||
<field name="trg_date_calendar_id" class="oe_inline"
|
||||
<field name="trg_date_calendar_id" class="o_row"
|
||||
attrs="{'invisible': ['|', ('trg_date_id','=',False), ('trg_date_range_type', '!=', 'day')]}"/>
|
||||
</group>
|
||||
<div attrs="{'invisible': [('model', '=', False)]}">
|
||||
@@ -127,5 +124,4 @@
|
||||
<menuitem id="menu_base_action_rule_form"
|
||||
parent="base.menu_automation" action="base_action_rule_act" sequence="1"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
Reference in New Issue
Block a user