Files
tryton/trytond/ir/trigger.py
T

311 lines
11 KiB
Python
Raw Normal View History

2014-12-02 12:54:01 +01:00
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import datetime
import time
2015-02-09 22:58:56 +01:00
from sql import Literal, Null
2013-09-06 22:32:37 +02:00
from sql.aggregate import Count, Max
from ..model import ModelView, ModelSQL, fields, EvalEnvironment, Check
2015-03-09 19:40:41 +01:00
from ..pyson import Eval, PYSONDecoder
from ..tools import grouped_slice
2013-09-06 22:32:37 +02:00
from .. import backend
2011-12-14 17:00:13 +01:00
from ..tools import reduce_ids
from ..transaction import Transaction
from ..cache import Cache
from ..pool import Pool
2012-09-24 22:16:53 +02:00
__all__ = [
'Trigger', 'TriggerLog',
]
class Trigger(ModelSQL, ModelView):
"Trigger"
2012-09-24 22:16:53 +02:00
__name__ = 'ir.trigger'
name = fields.Char('Name', required=True, translate=True)
2012-03-05 10:54:32 +01:00
active = fields.Boolean('Active', select=True)
model = fields.Many2One('ir.model', 'Model', required=True, select=True)
on_time = fields.Boolean('On Time', select=True, states={
2011-08-21 11:19:06 +02:00
'invisible': (Eval('on_create', False)
| Eval('on_write', False)
| Eval('on_delete', False)),
2014-12-02 12:54:01 +01:00
}, depends=['on_create', 'on_write', 'on_delete'])
2012-03-05 10:54:32 +01:00
on_create = fields.Boolean('On Create', select=True, states={
'invisible': Eval('on_time', False),
2014-01-26 14:27:24 +01:00
}, depends=['on_time'])
2012-03-05 10:54:32 +01:00
on_write = fields.Boolean('On Write', select=True, states={
'invisible': Eval('on_time', False),
2014-01-26 14:27:24 +01:00
}, depends=['on_time'])
2012-03-05 10:54:32 +01:00
on_delete = fields.Boolean('On Delete', select=True, states={
'invisible': Eval('on_time', False),
2014-01-26 14:27:24 +01:00
}, depends=['on_time'])
condition = fields.Char('Condition', required=True,
help='A PYSON statement evaluated with record represented by '
2012-04-01 23:08:15 +02:00
'"self"\nIt triggers the action if true.')
limit_number = fields.Integer('Limit Number', required=True,
help='Limit the number of call to "Action Function" by records.\n'
2012-04-01 23:08:15 +02:00
'0 for no limit.')
2015-02-09 22:58:56 +01:00
minimum_time_delay = fields.TimeDelta('Minimum Delay',
help='Set a minimum time delay between call to "Action Function" '
'for the same record.\n'
'empty for no delay.')
action_model = fields.Many2One('ir.model', 'Action Model', required=True)
action_function = fields.Char('Action Function', required=True)
2012-09-24 22:16:53 +02:00
_get_triggers_cache = Cache('ir_trigger.get_triggers')
2012-09-24 22:16:53 +02:00
@classmethod
def __setup__(cls):
super(Trigger, cls).__setup__()
t = cls.__table__()
2012-09-24 22:16:53 +02:00
cls._sql_constraints += [
('on_exclusive',
Check(t, ~((t.on_time == True)
& ((t.on_create == True)
| (t.on_write == True)
| (t.on_delete == True)))),
'"On Time" and others are mutually exclusive!'),
]
2012-09-24 22:16:53 +02:00
cls._error_messages.update({
'invalid_condition': ('Condition "%(condition)s" is not a '
'valid PYSON expression on trigger "%(trigger)s".'),
})
2012-09-24 22:16:53 +02:00
cls._order.insert(0, ('name', 'ASC'))
2015-02-09 22:58:56 +01:00
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
cursor = Transaction().connection.cursor()
table = TableHandler(cls, module_name)
2015-02-09 22:58:56 +01:00
sql_table = cls.__table__()
super(Trigger, cls).__register__(module_name)
# Migration from 3.4:
# change minimum_delay into timedelta minimum_time_delay
2015-02-09 22:58:56 +01:00
if table.column_exist('minimum_delay'):
cursor.execute(*sql_table.select(
sql_table.id, sql_table.minimum_delay,
where=sql_table.minimum_delay != Null))
for id_, delay in cursor.fetchall():
delay = datetime.timedelta(hours=delay)
cursor.execute(*sql_table.update(
[sql_table.minimum_time_delay],
2015-02-09 22:58:56 +01:00
[delay],
where=sql_table.id == id_))
table.drop_column('minimum_delay')
@classmethod
def validate(cls, triggers):
super(Trigger, cls).validate(triggers)
cls.check_condition(triggers)
2012-09-24 22:16:53 +02:00
@classmethod
def check_condition(cls, triggers):
'''
Check condition
'''
2012-09-24 22:16:53 +02:00
for trigger in triggers:
try:
PYSONDecoder(noeval=True).decode(trigger.condition)
except Exception:
cls.raise_user_error('invalid_condition', {
'condition': trigger.condition,
'trigger': trigger.rec_name,
})
2012-09-24 22:16:53 +02:00
@staticmethod
def default_active():
return True
2012-09-24 22:16:53 +02:00
@staticmethod
def default_limit_number():
return 0
2014-01-26 14:27:24 +01:00
@fields.depends('on_time')
2012-09-24 22:16:53 +02:00
def on_change_on_time(self):
if self.on_time:
2014-10-29 20:38:17 +01:00
self.on_create = False
self.on_write = False
self.on_delete = False
2014-01-26 14:27:24 +01:00
@fields.depends('on_create')
2012-09-24 22:16:53 +02:00
def on_change_on_create(self):
if self.on_create:
2014-10-29 20:38:17 +01:00
self.on_time = False
2014-01-26 14:27:24 +01:00
@fields.depends('on_write')
2012-09-24 22:16:53 +02:00
def on_change_on_write(self):
if self.on_write:
2014-10-29 20:38:17 +01:00
self.on_time = False
2014-01-26 14:27:24 +01:00
@fields.depends('on_delete')
2012-09-24 22:16:53 +02:00
def on_change_on_delete(self):
if self.on_delete:
2014-10-29 20:38:17 +01:00
self.on_time = False
2012-09-24 22:16:53 +02:00
@classmethod
def get_triggers(cls, model_name, mode):
"""
2012-09-24 22:16:53 +02:00
Return triggers for a model and a mode
"""
assert mode in ['create', 'write', 'delete', 'time'], \
2013-02-27 22:50:31 +01:00
'Invalid trigger mode'
2013-09-20 00:32:04 +02:00
if Transaction().user == 0 and not Transaction().context.get('user'):
return []
2012-09-24 22:16:53 +02:00
key = (model_name, mode)
trigger_ids = cls._get_triggers_cache.get(key)
if trigger_ids is not None:
return cls.browse(trigger_ids)
2012-09-24 22:16:53 +02:00
triggers = cls.search([
('model.model', '=', model_name),
('on_%s' % mode, '=', True),
])
cls._get_triggers_cache.set(key, map(int, triggers))
return triggers
@staticmethod
def eval(trigger, record):
"""
Evaluate the condition of trigger
"""
env = {}
env['current_date'] = datetime.datetime.today()
env['time'] = time
2010-08-19 22:01:12 +02:00
env['context'] = Transaction().context
2015-03-09 19:40:41 +01:00
env['self'] = EvalEnvironment(record, record.__class__)
return bool(PYSONDecoder(env).decode(trigger.condition))
2012-09-24 22:16:53 +02:00
@classmethod
def trigger_action(cls, records, trigger):
"""
2012-09-24 22:16:53 +02:00
Trigger the action define on trigger for the records
"""
2011-07-03 13:20:58 +02:00
pool = Pool()
2012-09-24 22:16:53 +02:00
TriggerLog = pool.get('ir.trigger.log')
Model = pool.get(trigger.model.model)
ActionModel = pool.get(trigger.action_model.model)
cursor = Transaction().connection.cursor()
2013-09-06 22:32:37 +02:00
trigger_log = TriggerLog.__table__()
2012-09-24 22:16:53 +02:00
ids = map(int, records)
# Filter on limit_number
if trigger.limit_number:
new_ids = []
2014-07-17 18:53:05 +02:00
for sub_ids in grouped_slice(ids):
sub_ids = list(sub_ids)
2013-09-06 22:32:37 +02:00
red_sql = reduce_ids(trigger_log.record_id, sub_ids)
cursor.execute(*trigger_log.select(
trigger_log.record_id, Count(Literal(1)),
where=red_sql & (trigger_log.trigger == trigger.id),
group_by=trigger_log.record_id))
number = dict(cursor.fetchall())
for record_id in sub_ids:
if record_id not in number:
new_ids.append(record_id)
continue
if number[record_id] < trigger.limit_number:
new_ids.append(record_id)
ids = new_ids
2015-02-09 22:58:56 +01:00
# Filter on minimum_time_delay
if trigger.minimum_time_delay:
new_ids = []
2014-07-17 18:53:05 +02:00
for sub_ids in grouped_slice(ids):
sub_ids = list(sub_ids)
2013-09-06 22:32:37 +02:00
red_sql = reduce_ids(trigger_log.record_id, sub_ids)
cursor.execute(*trigger_log.select(
trigger_log.record_id, Max(trigger_log.create_date),
where=(red_sql & (trigger_log.trigger == trigger.id)),
2013-09-06 22:32:37 +02:00
group_by=trigger_log.record_id))
delay = dict(cursor.fetchall())
for record_id in sub_ids:
if record_id not in delay:
new_ids.append(record_id)
continue
# SQLite return string for MAX
if isinstance(delay[record_id], basestring):
datepart, timepart = delay[record_id].split(" ")
year, month, day = map(int, datepart.split("-"))
timepart_full = timepart.split(".")
2012-04-01 23:08:15 +02:00
hours, minutes, seconds = map(int,
timepart_full[0].split(":"))
if len(timepart_full) == 2:
microseconds = int(timepart_full[1])
2013-07-29 10:32:45 +02:00
else:
microseconds = 0
delay[record_id] = datetime.datetime(year, month,
day, hours, minutes, seconds, microseconds)
2012-04-01 23:08:15 +02:00
if (datetime.datetime.now() - delay[record_id]
2015-02-09 22:58:56 +01:00
>= trigger.minimum_time_delay):
new_ids.append(record_id)
ids = new_ids
2012-09-24 22:16:53 +02:00
records = Model.browse(ids)
if records:
getattr(ActionModel, trigger.action_function)(records, trigger)
2015-02-09 22:58:56 +01:00
if trigger.limit_number or trigger.minimum_time_delay:
2013-01-06 22:18:13 +01:00
to_create = []
2012-09-24 22:16:53 +02:00
for record in records:
2013-01-06 22:18:13 +01:00
to_create.append({
'trigger': trigger.id,
'record_id': record.id,
})
if to_create:
TriggerLog.create(to_create)
2012-09-24 22:16:53 +02:00
@classmethod
def trigger_time(cls):
'''
Trigger time actions
'''
2011-07-03 13:20:58 +02:00
pool = Pool()
2012-09-24 22:16:53 +02:00
triggers = cls.search([
('on_time', '=', True),
])
for trigger in triggers:
Model = pool.get(trigger.model.model)
triggered = []
# TODO add a domain
2012-09-24 22:16:53 +02:00
records = Model.search([])
for record in records:
if cls.eval(trigger, record):
triggered.append(record)
if triggered:
cls.trigger_action(triggered, trigger)
2012-09-24 22:16:53 +02:00
@classmethod
2013-01-06 22:18:13 +01:00
def create(cls, vlist):
res = super(Trigger, cls).create(vlist)
# Restart the cache on the get_triggers method of ir.trigger
2012-09-24 22:16:53 +02:00
cls._get_triggers_cache.clear()
return res
2012-09-24 22:16:53 +02:00
@classmethod
2013-12-19 14:44:01 +01:00
def write(cls, triggers, values, *args):
super(Trigger, cls).write(triggers, values, *args)
# Restart the cache on the get_triggers method of ir.trigger
2012-09-24 22:16:53 +02:00
cls._get_triggers_cache.clear()
2012-09-24 22:16:53 +02:00
@classmethod
def delete(cls, records):
super(Trigger, cls).delete(records)
# Restart the cache on the get_triggers method of ir.trigger
2012-09-24 22:16:53 +02:00
cls._get_triggers_cache.clear()
class TriggerLog(ModelSQL):
'Trigger Log'
2012-09-24 22:16:53 +02:00
__name__ = 'ir.trigger.log'
trigger = fields.Many2One('ir.trigger', 'Trigger', required=True)
record_id = fields.Integer('Record ID', required=True)
2012-09-24 22:16:53 +02:00
@classmethod
def __register__(cls, module_name):
2013-09-06 22:32:37 +02:00
TableHandler = backend.get('TableHandler')
2012-09-24 22:16:53 +02:00
super(TriggerLog, cls).__register__(module_name)
table = TableHandler(cls, module_name)
table.index_action(['trigger', 'record_id'], 'add')