Add ir.trigger to trigger ModelStorage change

This commit is contained in:
Cédric Krier
2010-05-29 15:58:49 +02:00
parent 8c6537bafb
commit 1513426987
13 changed files with 1020 additions and 0 deletions
+1
View File
@@ -1,3 +1,4 @@
* Add ir.trigger to trigger ModelStorage change
* Add default value to Boolean fields at database level
Version 1.6.0 - 2010-05-08
+21
View File
@@ -203,6 +203,11 @@ Instance methods:
Create a record. ``values`` is a dictionary with fields names as key and
created values as value.
.. method:: ModelStorage.trigger_create(cursor, user, id[, context])
Trigger create actions. It will call actions defined in ``ir.trigger`` if
``on_create`` is set and ``condition`` is true.
.. method:: ModelStorage.read(cursor, user, ids[, fields_names[, context]])
Return values for the ids. If ``fields_names`` is set, there will be only
@@ -217,10 +222,26 @@ Instance methods:
``values`` is a dictionary with fields names as key and writen values as
value.
.. method:: ModelStorage.trigger_write_get_eligibles(cursor, user, ids[, context])
Return eligible record ids for write actions by triggers. This dictionary
is to pass to :method:`~ModelStorage.trigger_write`.
.. method:: ModelStorage.trigger_write(cursor, user, eligibles[, context])
Trigger write actions. It will call actions defined in ``ir.trigger`` if
``on_write`` is set and ``condition`` was false before
:method:`~ModelStorage.write` and true after.
.. method:: ModelStorage.delete(cursor, user, ids[, context])
Delete records. ``ids`` can be a list of ids or an id.
.. method:: ModelStorage.trigger_delete(cursor, user, ids[, context])
Trigger delete actions. It will call actions defined in ``ir.trigger`` if
``on_delete`` is set and ``condition`` is true.
.. method:: ModelStorage.copy(cursor, user, ids[, default[, context]])
Duplicate the records. ``ids`` can be a list of ids or an id. ``default``
+1
View File
@@ -15,3 +15,4 @@ import module
from default import *
from cache import *
from date import *
from trigger import *
+1
View File
@@ -40,6 +40,7 @@ Fournit les concepts et l'administration des modèles, actions, séquences, loca
'property.xml',
'module/module.xml',
'default.xml',
'trigger.xml',
],
'translation': [
'fr_FR.csv',
+287
View File
@@ -0,0 +1,287 @@
#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
from trytond.model import ModelView, ModelSQL, fields
from trytond.pyson import Eval, Or
from trytond.tools import Cache, safe_eval
from trytond.backend import TableHandler
from trytond.tools import reduce_ids
class Trigger(ModelSQL, ModelView):
"Trigger"
_name = 'ir.trigger'
_description = __doc__
name = fields.Char('Name', required=True, translate=True)
active = fields.Boolean('Active', select=2)
model = fields.Many2One('ir.model', 'Model', required=True, select=1)
on_time = fields.Boolean('On Time', select=1, states={
'invisible': Or(Eval('on_create', False), Eval('on_write', False),
Eval('on_delete', False)),
}, depends=['on_create', 'on_write', 'on_delete'],
on_change=['on_time'])
on_create = fields.Boolean('On Create', select=1, states={
'invisible': Eval('on_time', False),
}, depends=['on_time'],
on_change=['on_create'])
on_write = fields.Boolean('On Write', select=1, states={
'invisible': Eval('on_time', False),
}, depends=['on_time'],
on_change=['on_write'])
on_delete = fields.Boolean('On Delete', select=1, states={
'invisible': Eval('on_time', False),
}, depends=['on_time'],
on_change=['on_delete'])
condition = fields.Char('Condition', required=True,
help='A Python statement evaluated with record represented by '
'"self"\nIt triggers the action if true.')
limit_number = fields.Integer('Limit Number', help='Limit the number of '
'call to "Action Function" by records.\n'
'0 for no limit.')
minimum_delay = fields.Float('Minimum Delay', help='Set a minimu delay '
'in minutes between call to "Action Function" for the same record.\n'
'0 for no delay.')
action_model = fields.Many2One('ir.model', 'Action Model', required=True)
action_function = fields.Char('Action Function', required=True)
def __init__(self):
super(Trigger, self).__init__()
self._sql_constraints += [
('on_exclusive',
'CHECK(NOT(on_time AND (on_create OR on_write OR on_delete)))',
'"On Time" and others are mutually exclusive!'),
]
self._constraints += [
('check_condition', 'invalid_condition'),
]
self._error_messages.update({
'invalid_condition': 'Condition must be a python expression!',
})
self._order.insert(0, ('name', 'ASC'))
def check_condition(self, cursor, user, ids):
'''
Check condition
'''
for trigger in self.browse(cursor, user, ids):
try:
compile(trigger.condition, '', 'eval')
except (SyntaxError, TypeError):
return False
return True
def default_active(self, cursor, user, context=None):
return True
def on_change_on_time(self, cursor, user, values, context=None):
if values.get('on_time'):
return {
'on_create': False,
'on_write': False,
'on_delete': False,
}
return {}
def on_change_on_create(self, cursor, user, values, context=None):
if values.get('on_create'):
return {
'on_time': False,
}
return {}
def on_change_on_write(self, cursor, user, values, context=None):
if values.get('on_write'):
return {
'on_time': False,
}
return {}
def on_change_on_delete(self, cursor, user, values, context=None):
if values.get('on_delete'):
return {
'on_time': False,
}
return {}
@Cache('ir_trigger.get_triggers')
def get_triggers(self, cursor, user, model_name, mode):
"""
Return trigger ids for a model and a mode
:param cursor: the database cursor
:param user: the user id
:param model_name: the name of the model
:param mode: the mode that can be 'create', 'write', 'delete' or 'time'
:return: a list of ir.trigger ids
"""
assert mode in ['create', 'write', 'delete', 'time'], \
'Invalid trigger mode'
if user == 0:
return [] # XXX is it want we want?
trigger_ids = self.search(cursor, user, [
('model.model', '=', model_name),
('on_%s' % mode, '=', True),
])
return trigger_ids
def eval(self, cursor, user, trigger, record, context=None):
"""
Evaluate the condition of trigger
:param cursor: the database cursor
:param user: the user id
:param trigger: a BrowseRecord of ir.trigger
:param record: a BrowseRecord of the tested model
:param context: the context
:return: a boolean
"""
if context is None:
context = {}
model_obj = self.pool.get(trigger.model.model)
env = {}
env.update(context)
env['current_date'] = datetime.datetime.today()
env['time'] = time
env['context'] = context
env['self'] = record
return bool(safe_eval(trigger.condition, env))
def trigger_action(self, cursor, user, ids, trigger_id, context=None):
"""
Trigger the action define on trigger_id for the ids
:param cursor: the database cursor
:param user: the user id
:param ids: the list of record ids triggered
:param trigger_id: the trigger id
:param context: the context
"""
trigger_log_obj = self.pool.get('ir.trigger.log')
trigger = self.browse(cursor, user, trigger_id, context=context)
model_obj = self.pool.get(trigger.action_model.model)
# Filter on limit_number
if trigger.limit_number:
new_ids = []
for i in range(0, len(ids), cursor.IN_MAX):
sub_ids = ids[i:i + cursor.IN_MAX]
red_sql, red_ids = reduce_ids('"record_id"', sub_ids)
cursor.execute('SELECT "record_id", COUNT(1) FROM "%s" '
'WHERE %s AND "trigger" = %%s '
'GROUP BY "record_id"'
% (trigger_log_obj._table, red_sql),
red_ids + [trigger.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
# Filter on minimum_delay
if trigger.minimum_delay:
new_ids = []
for i in range(0, len(ids), cursor.IN_MAX):
sub_ids = ids[i:i + cursor.IN_MAX]
red_sql, red_ids = reduce_ids('"record_id"', sub_ids)
cursor.execute('SELECT "record_id", MAX("create_date") '
'FROM "%s" '
'WHERE %s AND "trigger" = %%s '
'GROUP BY "record_id"'
% (trigger_log_obj._table, red_sql),
red_ids + [trigger.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(".")
hours, minutes, seconds = map(int, timepart_full[0].split(":"))
if len(timepart_full) == 2:
microseconds = int(timepart_full[1])
delay[record_id] = datetime.datetime(year, month, day,
hours, minutes, seconds, microseconds)
if datetime.datetime.now() - delay[record_id] \
>= datetime.timedelta(minutes=trigger.minimum_delay):
new_ids.append(record_id)
ids = new_ids
if ids:
getattr(model_obj, trigger.action_function)(cursor, user, ids,
trigger_id, context=context)
if trigger.limit_number or trigger.minimum_delay:
for record_id in ids:
trigger_log_obj.create(cursor, user, {
'trigger': trigger.id,
'record_id': record_id,
}, context=context)
def trigger_time(self, cursor, user, context=None):
'''
Trigger time actions
:param cursor: the database cursor
:param user: the user id
:param context: the context
'''
trigger_ids = self.search(cursor, user, [
('on_time', '=', True),
], context=context)
for trigger in self.browse(cursor, user, trigger_ids, context=context):
model_obj = self.pool.get(trigger.model.model)
triggered_ids = []
# TODO add a domain
record_ids = model_obj.search(cursor, user, [], context=context)
for record in model_obj.browse(cursor, user, record_ids,
context=context):
if self.eval(cursor, user, trigger, record, context=context):
triggered_ids.append(record.id)
if triggered_ids:
self.trigger_action(cursor, user, triggered_ids, trigger.id,
context=context)
def create(self, cursor, user, values, context=None):
res = super(Trigger, self).create(cursor, user, values, context=context)
# Restart the cache on the get_triggers method of ir.trigger
self.get_triggers(cursor.dbname)
return res
def write(self, cursor, user, ids, values, context=None):
res = super(Trigger, self).write(cursor, user, ids, values,
context=context)
# Restart the cache on the get_triggers method of ir.trigger
self.get_triggers(cursor.dbname)
return res
def delete(self, cursor, user, ids, context=None):
res = super(Trigger, self).delete(cursor, user, ids, context=context)
# Restart the cache on the get_triggers method of ir.trigger
self.get_triggers(cursor.dbname)
return res
Trigger()
class TriggerLog(ModelSQL):
'Trigger Log'
_name = 'ir.trigger.log'
_description = __doc__
trigger = fields.Many2One('ir.trigger', 'Trigger', required=True)
record_id = fields.Integer('Record ID', required=True)
def init(self, cursor, module_name):
super(TriggerLog, self).init(cursor, module_name)
table = TableHandler(cursor, self, module_name)
table.index_action(['trigger', 'record_id'], 'add')
TriggerLog()
+78
View File
@@ -0,0 +1,78 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="trigger_view_form">
<field name="model">ir.trigger</field>
<field name="type">form</field>
<field name="arch" type="xml">
<![CDATA[
<form string="Trigger">
<label name="name"/>
<field name="name" colspan="3"/>
<label name="model"/>
<field name="model"/>
<group colspan="2" col="10" id="checkboxes">
<label name="active"/>
<field name="active" xexpand="0"/>
<label name="on_time"/>
<field name="on_time" xexpand="0"/>
<label name="on_create"/>
<field name="on_create" xexpand="0"/>
<label name="on_write"/>
<field name="on_write" xexpand="0"/>
<label name="on_delete"/>
<field name="on_delete" xexpand="0"/>
</group>
<label name="condition"/>
<field name="condition" colspan="3"/>
<label name="limit_number"/>
<field name="limit_number"/>
<label name="minimum_delay"/>
<field name="minimum_delay" widget="float_time"/>
<label name="action_model"/>
<field name="action_model"/>
<label name="action_function"/>
<field name="action_function"/>
</form>
]]>
</field>
</record>
<record model="ir.ui.view" id="trigger_view_tree">
<field name="model">ir.trigger</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<![CDATA[
<tree string="Triggers">
<field name="name" select="1"/>
<field name="model" select="1"/>
<field name="on_time" select="2"/>
<field name="on_create" select="2"/>
<field name="on_write" select="2"/>
<field name="on_delete" select="2"/>
<field name="active" select="2"/>
</tree>
]]>
</field>
</record>
<record model="ir.action.act_window" id="act_trigger_form">
<field name="name">Triggers</field>
<field name="res_model">ir.trigger</field>
<field name="view_type">form</field>
</record>
<record model="ir.action.act_window.view" id="act_trigger_form_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="trigger_view_tree"/>
<field name="act_window" ref="act_trigger_form"/>
</record>
<record model="ir.action.act_window.view" id="act_trigger_form_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="trigger_view_form"/>
<field name="act_window" ref="act_trigger_form"/>
</record>
<menuitem parent="menu_models"
action="act_trigger_form" id="menu_trigger_form"/>
</data>
</tryton>
+12
View File
@@ -417,6 +417,8 @@ class ModelSQL(ModelStorage):
self._update_tree(cursor, user, id_new, k, field.left,
field.right)
self.trigger_create(cursor, user, id_new, context=context)
return id_new
def read(self, cursor, user, ids, fields_names=None, context=None):
@@ -716,6 +718,11 @@ class ModelSQL(ModelStorage):
return res
def write(self, cursor, user, ids, values, context=None):
# Call before cursor cache cleaning
trigger_eligibles = self.trigger_write_get_eligibles(cursor, user,
isinstance(ids, (int, long)) and [ids] or ids, context=context)
super(ModelSQL, self).write(cursor, user, ids, values, context=context)
if context is None:
@@ -966,6 +973,9 @@ class ModelSQL(ModelStorage):
field.left, field.right)
else:
self._rebuild_tree(cursor, 0, k, False, 0)
self.trigger_write(cursor, user, trigger_eligibles, context=context)
return True
def delete(self, cursor, user, ids, context=None):
@@ -1067,6 +1077,8 @@ class ModelSQL(ModelStorage):
self.raise_user_error(cursor, 'access_error',
self._description, context=context)
self.trigger_delete(cursor, user, ids, context=context)
for i in range(0, len(ids), cursor.IN_MAX):
sub_ids = ids[i:i + cursor.IN_MAX]
red_sql, red_ids = reduce_ids('id', sub_ids)
+96
View File
@@ -88,6 +88,26 @@ class ModelStorage(Model):
self.__clean_xxx2many_cache(cursor, user, context=context)
return False
def trigger_create(self, cursor, user, id, context=None):
'''
Trigger create actions
:param cursor: the database cursor
:param user: the user id
:param id: the created id
:param context: the context
'''
trigger_obj = self.pool.get('ir.trigger')
trigger_ids = trigger_obj.get_triggers(cursor, user, self._name, 'create')
if not trigger_ids:
return
record = self.browse(cursor, user, id, context=context)
triggers = trigger_obj.browse(cursor, user, trigger_ids, context=context)
for trigger in triggers:
if trigger_obj.eval(cursor, user, trigger, record, context=context):
trigger_obj.trigger_action(cursor, user, [id], trigger.id,
context=context)
def read(self, cursor, user, ids, fields_names=None, context=None):
'''
Read records.
@@ -144,6 +164,58 @@ class ModelStorage(Model):
self.__clean_xxx2many_cache(cursor, user, context=context)
return False
def trigger_write_get_eligibles(self, cursor, user, ids, context=None):
'''
Return eligible ids for write actions by triggers
:param cursor: the database cursor
:param user: the user id
:param ids: a list of ids
:param context: the context
:return: a dictionary of the lists of eligible ids by triggers
'''
trigger_obj = self.pool.get('ir.trigger')
trigger_ids = trigger_obj.get_triggers(cursor, user, self._name, 'write')
if not trigger_ids:
return {}
records = self.browse(cursor, user, ids, context=context)
triggers = trigger_obj.browse(cursor, user, trigger_ids, context=context)
eligibles = {}
for trigger in triggers:
eligibles[trigger.id] = []
for record in records:
if not trigger_obj.eval(cursor, user, trigger, record,
context=context):
eligibles[trigger.id].append(record.id)
return eligibles
def trigger_write(self, cursor, user, eligibles, context=None):
'''
Trigger write actions
:param cursor: the database cursor
:param user: the user id
:param eligibles: a dictionary of the lists of eligible ids by triggers
:param context: the context
'''
trigger_obj = self.pool.get('ir.trigger')
trigger_ids = eligibles.keys()
if not trigger_ids:
return
records = self.browse(cursor, user, chain(*eligibles.values()),
context=context)
id2record = dict((x.id, x) for x in records)
triggers = trigger_obj.browse(cursor, user, trigger_ids, context=context)
for trigger in triggers:
triggered_ids = []
for record_id in eligibles[trigger.id]:
record = id2record[record_id]
if trigger_obj.eval(cursor, user, trigger, record, context=context):
triggered_ids.append(record.id)
if triggered_ids:
trigger_obj.trigger_action(cursor, user, triggered_ids,
trigger.id, context=context)
def delete(self, cursor, user, ids, context=None):
'''
Delete records.
@@ -176,6 +248,30 @@ class ModelStorage(Model):
self.__clean_xxx2many_cache(cursor, user, context=context)
return False
def trigger_delete(self, cursor, user, ids, context=None):
'''
Trigger delete actions
:param cursor: the database cursor
:param user: the user id
:param ids: the deleted ids
:param context: the context
'''
trigger_obj = self.pool.get('ir.trigger')
trigger_ids = trigger_obj.get_triggers(cursor, user, self._name, 'delete')
if not trigger_ids:
return
records = self.browse(cursor, user, ids, context=context)
triggers = trigger_obj.browse(cursor, user, trigger_ids, context=context)
for trigger in triggers:
triggered_ids = []
for record in records:
if trigger_obj.eval(cursor, user, trigger, record, context=context):
triggered_ids.append(record.id)
if triggered_ids:
trigger_obj.trigger_action(cursor, user, triggered_ids,
trigger.id, context=context)
def copy(self, cursor, user, ids, default=None, context=None):
'''
Duplicate the record(s) in ids.
+49
View File
@@ -348,6 +348,36 @@ this repository contains the full copyright notices and license terms. -->
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_ir_trigger">
<field name="model" search="[('model', '=', 'ir.trigger')]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_ir_trigger_admin">
<field name="model" search="[('model', '=', 'ir.trigger')]"/>
<field name="group" ref="group_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_ir_trigger_log">
<field name="model" search="[('model', '=', 'ir.trigger.log')]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_ir_trigger_log_admin">
<field name="model" search="[('model', '=', 'ir.trigger.log')]"/>
<field name="group" ref="group_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.rule.group" id="rule_group_menu">
<field name="model" search="[('model', '=', 'ir.ui.menu')]"/>
<field name="global_p" eval="True"/>
@@ -494,5 +524,24 @@ this repository contains the full copyright notices and license terms. -->
<field name="rule_group" ref="rule_group_sequence_strict"/>
</record>
<record model="res.user" id="user_trigger">
<field name="login">user_cron_trigger</field>
<field name="name">Cron Trigger</field>
<field name="groups" eval="[('add', ref('group_admin'))]"/>
</record>
<record model="ir.cron" id="cron_trigger_time">
<field name="name">Run On Time Triggers</field>
<field name="request_user" ref="user_admin"/>
<field name="user" ref="user_trigger"/>
<field name="active" eval="True"/>
<field name="interval_number">5</field>
<field name="interval_type">minutes</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
<field name="model">ir.trigger</field>
<field name="function">trigger_time</field>
</record>
</data>
</tryton>
+1
View File
@@ -6,3 +6,4 @@ from model import *
from mptt import *
from import_data import *
from export_data import *
from trigger import *
+20
View File
@@ -0,0 +1,20 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from trytond.model import ModelSQL, fields
TRIGGER_LOGS = []
class Triggered(ModelSQL):
'Triggered'
_name = 'test.triggered'
_description = __doc__
name = fields.Char('Name')
def trigger(self, cursor, user, ids, trigger_id, context=None):
'''
Trigger function for test
'''
TRIGGER_LOGS.append((ids, trigger_id))
Triggered()
+451
View File
@@ -0,0 +1,451 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#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 unittest
import sys
import time
try:
from itertools import combinations
except ImportError:
def combinations(iterable, r):
pool = tuple(iterable)
n = len(pool)
if r > n:
return
indices = range(r)
yield tuple(pool[i] for i in indices)
while True:
for i in reversed(range(r)):
if indices[i] != i + n - r:
break
else:
return
indices[i] += 1
for j in range(i+1, r):
indices[j] = indices[j-1] + 1
yield tuple(pool[i] for i in indices)
from trytond.tests.test_tryton import POOL, DB, USER, CONTEXT, install_module
from trytond.test.trigger import TRIGGER_LOGS
class TriggerTestCase(unittest.TestCase):
'''
Test Trigger
'''
def setUp(self):
install_module('test')
self.triggered = POOL.get('test.triggered')
self.trigger = POOL.get('ir.trigger')
self.trigger_log = POOL.get('ir.trigger.log')
self.model = POOL.get('ir.model')
def test0010constraints(self):
'''
Test constraints
'''
cursor = DB.cursor()
model_id = self.model.search(cursor, USER, [
('model', '=', 'test.triggered'),
], context=CONTEXT)[0]
values = {
'name': 'Test',
'model': model_id,
'on_time': True,
'condition': 'True',
'action_model': model_id,
'action_function': 'test',
}
self.assert_(self.trigger.create(cursor, USER, values, context=CONTEXT))
# on_exclusive
for i in range(1, 4):
for combination in combinations(['create', 'write', 'delete'], i):
combination_values = values.copy()
for mode in combination:
combination_values['on_%s' % mode] = True
self.failUnlessRaises(Exception, self.trigger.create, cursor, USER,
combination_values, context=CONTEXT)
# check_condition
condition_values = values.copy()
condition_values['condition'] = '='
self.failUnlessRaises(Exception, self.trigger.create, cursor, USER,
condition_values, context=CONTEXT)
# Restart the cache on the get_triggers method of ir.trigger
self.trigger.get_triggers(cursor.dbname)
cursor.rollback()
cursor.close()
def test0020on_create(self):
'''
Test on_create
'''
cursor = DB.cursor()
model_id = self.model.search(cursor, USER, [
('model', '=', 'test.triggered'),
], context=CONTEXT)[0]
trigger_id = self.trigger.create(cursor, USER, {
'name': 'Test',
'model': model_id,
'on_create': True,
'condition': 'True',
'action_model': model_id,
'action_function': 'trigger',
}, context=CONTEXT)
triggered_id = self.triggered.create(cursor, USER, {
'name': 'Test',
}, context=CONTEXT)
self.assert_(TRIGGER_LOGS == [([triggered_id], trigger_id)])
TRIGGER_LOGS.pop()
# Trigger with condition
self.trigger.write(cursor, USER, trigger_id, {
'condition': 'self.name == "Bar"',
}, context=CONTEXT)
# Matching condition
triggered_id = self.triggered.create(cursor, USER, {
'name': 'Bar',
}, context=CONTEXT)
self.assert_(TRIGGER_LOGS == [([triggered_id], trigger_id)])
TRIGGER_LOGS.pop()
# Non matching condition
triggered_id = self.triggered.create(cursor, USER, {
'name': 'Foo',
}, context=CONTEXT)
self.assert_(TRIGGER_LOGS == [])
# With limit number
self.trigger.write(cursor, USER, trigger_id, {
'condition': 'True',
'limit_number': 1,
}, context=CONTEXT)
triggered_id = self.triggered.create(cursor, USER, {
'name': 'Test',
}, context=CONTEXT)
self.assert_(TRIGGER_LOGS == [([triggered_id], trigger_id)])
TRIGGER_LOGS.pop()
# With minimum delay
self.trigger.write(cursor, USER, trigger_id, {
'limit_number': 0,
'minimum_delay': 1,
}, context=CONTEXT)
triggered_id = self.triggered.create(cursor, USER, {
'name': 'Test',
}, context=CONTEXT)
self.assert_(TRIGGER_LOGS == [([triggered_id], trigger_id)])
TRIGGER_LOGS.pop()
# Restart the cache on the get_triggers method of ir.trigger
self.trigger.get_triggers(cursor.dbname)
cursor.rollback()
cursor.close()
def test0030on_write(self):
'''
Test on_write
'''
cursor = DB.cursor()
model_id = self.model.search(cursor, USER, [
('model', '=', 'test.triggered'),
], context=CONTEXT)[0]
trigger_id = self.trigger.create(cursor, USER, {
'name': 'Test',
'model': model_id,
'on_write': True,
'condition': 'True',
'action_model': model_id,
'action_function': 'trigger',
}, context=CONTEXT)
triggered_id = self.triggered.create(cursor, USER, {
'name': 'Test',
}, context=CONTEXT)
self.triggered.write(cursor, USER, triggered_id, {
'name': 'Foo',
}, context=CONTEXT)
self.assert_(TRIGGER_LOGS == [])
# Trigger with condition
self.trigger.write(cursor, USER, trigger_id, {
'condition': 'self.name == "Bar"',
}, context=CONTEXT)
# Matching condition
self.triggered.write(cursor, USER, triggered_id, {
'name': 'Bar',
}, context=CONTEXT)
self.assert_(TRIGGER_LOGS == [([triggered_id], trigger_id)])
TRIGGER_LOGS.pop()
# No change in condition
self.triggered.write(cursor, USER, triggered_id, {
'name': 'Bar',
}, context=CONTEXT)
self.assert_(TRIGGER_LOGS == [])
# Different change in condition
self.triggered.write(cursor, USER, triggered_id, {
'name': 'Foo',
}, context=CONTEXT)
self.assert_(TRIGGER_LOGS == [])
# With limit number
self.trigger.write(cursor, USER, trigger_id, {
'condition': 'self.name == "Bar"',
'limit_number': 1,
}, context=CONTEXT)
triggered_id = self.triggered.create(cursor, USER, {
'name': 'Foo',
}, context=CONTEXT)
self.triggered.write(cursor, USER, triggered_id, {
'name': 'Bar',
}, context=CONTEXT)
self.triggered.write(cursor, USER, triggered_id, {
'name': 'Foo',
}, context=CONTEXT)
self.triggered.write(cursor, USER, triggered_id, {
'name': 'Bar',
}, context=CONTEXT)
self.assert_(TRIGGER_LOGS == [([triggered_id], trigger_id)])
TRIGGER_LOGS.pop()
# With minimum delay
self.trigger.write(cursor, USER, trigger_id, {
'limit_number': 0,
'minimum_delay': sys.maxint,
}, context=CONTEXT)
triggered_id = self.triggered.create(cursor, USER, {
'name': 'Foo',
}, context=CONTEXT)
for name in ('Bar', 'Foo', 'Bar'):
self.triggered.write(cursor, USER, triggered_id, {
'name': name,
}, context=CONTEXT)
self.assert_(TRIGGER_LOGS == [([triggered_id], trigger_id)])
TRIGGER_LOGS.pop()
self.trigger.write(cursor, USER, trigger_id, {
'minimum_delay': 0.02,
}, context=CONTEXT)
triggered_id = self.triggered.create(cursor, USER, {
'name': 'Foo',
}, context=CONTEXT)
for name in ('Bar', 'Foo'):
self.triggered.write(cursor, USER, triggered_id, {
'name': name,
}, context=CONTEXT)
time.sleep(1.2)
self.triggered.write(cursor, USER, triggered_id, {
'name': 'Bar',
}, context=CONTEXT)
self.assert_(TRIGGER_LOGS == [([triggered_id], trigger_id),
([triggered_id], trigger_id)])
TRIGGER_LOGS.pop()
TRIGGER_LOGS.pop()
# Restart the cache on the get_triggers method of ir.trigger
self.trigger.get_triggers(cursor.dbname)
cursor.rollback()
cursor.close()
def test0040on_delete(self):
'''
Test on_delete
'''
cursor = DB.cursor()
model_id = self.model.search(cursor, USER, [
('model', '=', 'test.triggered'),
], context=CONTEXT)[0]
triggered_id = self.triggered.create(cursor, USER, {
'name': 'Test',
}, context=CONTEXT)
trigger_id = self.trigger.create(cursor, USER, {
'name': 'Test',
'model': model_id,
'on_delete': True,
'condition': 'True',
'action_model': model_id,
'action_function': 'trigger',
}, context=CONTEXT)
self.triggered.delete(cursor, USER, triggered_id, context=CONTEXT)
self.assert_(TRIGGER_LOGS == [([triggered_id], triggered_id)])
TRIGGER_LOGS.pop()
# Trigger with condition
self.trigger.write(cursor, USER, trigger_id, {
'condition': 'self.name == "Bar"',
}, context=CONTEXT)
triggered_id = self.triggered.create(cursor, USER, {
'name': 'Bar',
}, context=CONTEXT)
# Matching condition
self.triggered.delete(cursor, USER, triggered_id, context=CONTEXT)
self.assert_(TRIGGER_LOGS == [([triggered_id], triggered_id)])
TRIGGER_LOGS.pop()
triggered_id = self.triggered.create(cursor, USER, {
'name': 'Foo',
}, context=CONTEXT)
# Non matching condition
self.triggered.delete(cursor, USER, triggered_id, context=CONTEXT)
self.assert_(TRIGGER_LOGS == [])
triggered_id = self.triggered.create(cursor, USER, {
'name': 'Test',
}, context=CONTEXT)
# With limit number
self.trigger.write(cursor, USER, trigger_id, {
'condition': 'True',
'limit_number': 1,
}, context=CONTEXT)
self.triggered.delete(cursor, USER, triggered_id, context=CONTEXT)
self.assert_(TRIGGER_LOGS == [([triggered_id], triggered_id)])
TRIGGER_LOGS.pop()
# Delete trigger logs because SQLite reuse the same triggered_id
self.trigger_log.delete(cursor, USER,
self.trigger_log.search(cursor, USER, [
('trigger', '=', trigger_id),
], context=CONTEXT))
triggered_id = self.triggered.create(cursor, USER, {
'name': 'Test',
}, context=CONTEXT)
# With minimum delay
self.trigger.write(cursor, USER, trigger_id, {
'limit_number': 0,
'minimum_delay': 1,
}, context=CONTEXT)
self.triggered.delete(cursor, USER, triggered_id, context=CONTEXT)
self.assert_(TRIGGER_LOGS == [([triggered_id], triggered_id)])
TRIGGER_LOGS.pop()
# Restart the cache on the get_triggers method of ir.trigger
self.trigger.get_triggers(cursor.dbname)
cursor.rollback()
cursor.close()
def test0050on_time(self):
'''
Test on_time
'''
cursor = DB.cursor()
model_id = self.model.search(cursor, USER, [
('model', '=', 'test.triggered'),
], context=CONTEXT)[0]
trigger_id = self.trigger.create(cursor, USER, {
'name': 'Test',
'model': model_id,
'on_time': True,
'condition': 'True',
'action_model': model_id,
'action_function': 'trigger',
}, context=CONTEXT)
triggered_id = self.triggered.create(cursor, USER, {
'name': 'Test',
}, context=CONTEXT)
self.trigger.trigger_time(cursor, USER, context=CONTEXT)
self.assert_(TRIGGER_LOGS == [([triggered_id], trigger_id)])
TRIGGER_LOGS.pop()
# Trigger with condition
self.trigger.write(cursor, USER, trigger_id, {
'condition': 'self.name == "Bar"',
}, context=CONTEXT)
# Matching condition
self.triggered.write(cursor, USER, triggered_id, {
'name': 'Bar',
}, context=CONTEXT)
self.trigger.trigger_time(cursor, USER, context=CONTEXT)
self.assert_(TRIGGER_LOGS == [([triggered_id], trigger_id)])
TRIGGER_LOGS.pop()
# Non matching condition
self.triggered.write(cursor, USER, triggered_id, {
'name': 'Foo',
}, context=CONTEXT)
self.trigger.trigger_time(cursor, USER, context=CONTEXT)
self.assert_(TRIGGER_LOGS == [])
# With limit number
self.trigger.write(cursor, USER, trigger_id, {
'condition': 'True',
'limit_number': 1,
}, context=CONTEXT)
self.trigger.trigger_time(cursor, USER, context=CONTEXT)
self.trigger.trigger_time(cursor, USER, context=CONTEXT)
self.assert_(TRIGGER_LOGS == [([triggered_id], trigger_id)])
TRIGGER_LOGS.pop()
# Delete trigger logs of limit number test
self.trigger_log.delete(cursor, USER,
self.trigger_log.search(cursor, USER, [
('trigger', '=', trigger_id),
], context=CONTEXT))
# With minimum delay
self.trigger.write(cursor, USER, trigger_id, {
'limit_number': 0,
'minimum_delay': sys.maxint,
}, context=CONTEXT)
self.trigger.trigger_time(cursor, USER, context=CONTEXT)
self.trigger.trigger_time(cursor, USER, context=CONTEXT)
self.assert_(TRIGGER_LOGS == [([triggered_id], trigger_id)])
TRIGGER_LOGS.pop()
# Delete trigger logs of previous minimum delay test
self.trigger_log.delete(cursor, USER,
self.trigger_log.search(cursor, USER, [
('trigger', '=', trigger_id),
], context=CONTEXT))
self.trigger.write(cursor, USER, trigger_id, {
'minimum_delay': 0.02,
}, context=CONTEXT)
self.trigger.trigger_time(cursor, USER, context=CONTEXT)
time.sleep(1.2)
self.trigger.trigger_time(cursor, USER, context=CONTEXT)
self.assert_(TRIGGER_LOGS == [([triggered_id], trigger_id),
([triggered_id], trigger_id)])
TRIGGER_LOGS.pop()
TRIGGER_LOGS.pop()
# Restart the cache on the get_triggers method of ir.trigger
self.trigger.get_triggers(cursor.dbname)
cursor.rollback()
cursor.close()
def suite():
return unittest.TestLoader().loadTestsFromTestCase(TriggerTestCase)
if __name__ == '__main__':
suite = suite()
unittest.TextTestRunner(verbosity=2).run(suite)
+2
View File
@@ -172,6 +172,8 @@ def all_suite():
suite_.addTests(test_importdata.suite())
import trytond.tests.test_exportdata as test_exportdata
suite_.addTests(test_exportdata.suite())
import trytond.tests.test_trigger as test_trigger
suite_.addTests(test_trigger.suite())
return suite_
def modules_suite():