Use function name in workflow instead of python code

This commit is contained in:
Nicolas Évrard
2011-08-11 15:54:36 +02:00
parent b7ab7d3a2f
commit e09070dda0
12 changed files with 245 additions and 168 deletions
+1
View File
@@ -1,3 +1,4 @@
* Remove use of python code in workflow XML
* Remove ir.action.wizard_size
* Rename expand and fill attributes into yexpand and yfill
* Add xalign and yalign as field attributes
+2 -2
View File
@@ -105,12 +105,12 @@ CREATE TABLE wkf_activity (
subflow BIGINT,
split_mode VARCHAR(255),
join_mode VARCHAR(255),
kind VARCHAR(255) NOT NULL,
name VARCHAR(255),
signal_send VARCHAR(255),
flow_start BOOLEAN NOT NULL,
flow_stop BOOLEAN NOT NULL,
action TEXT,
stop_other BOOLEAN NOT NULL,
action VARCHAR(255),
PRIMARY KEY(id),
CONSTRAINT wkf_activity_workflow_fkey FOREIGN KEY (workflow) REFERENCES wkf (id) ON DELETE CASCADE,
CONSTRAINT wkf_activity_subflow_fkey FOREIGN KEY (subflow) REFERENCES wkf (id) ON DELETE SET NULL
+2 -2
View File
@@ -127,12 +127,12 @@ CREATE TABLE wkf_activity (
subflow INTEGER,
split_mode VARCHAR NOT NULL,
join_mode VARCHAR NOT NULL,
kind VARCHAR NOT NULL,
name VARCHAR NOT NULL,
signal_send VARCHAR,
flow_start BOOLEAN NOT NULL,
flow_stop BOOLEAN NOT NULL,
action TEXT,
stop_other BOOLEAN NOT NULL,
action VARCHAR,
PRIMARY KEY(id),
FOREIGN KEY (workflow) REFERENCES wkf (id) ON DELETE CASCADE,
FOREIGN KEY (subflow) REFERENCES wkf (id) ON DELETE SET NULL
+2 -2
View File
@@ -90,12 +90,12 @@ CREATE TABLE wkf_activity (
subflow INTEGER,
split_mode VARCHAR,
join_mode VARCHAR,
kind VARCHAR,
name VARCHAR,
signal_send VARCHAR,
flow_start BOOLEAN,
flow_stop BOOLEAN,
action TEXT
stop_other BOOLEAN,
action VARCHAR
);
CREATE TABLE wkf_transition (
+1
View File
@@ -9,3 +9,4 @@ from export_data import *
from trigger import *
from access import *
from wizard import *
from workflow import *
+2 -1
View File
@@ -5,10 +5,11 @@
'description': '''A module for unittest.
''',
'active': True,
'depends': ['ir', 'res'],
'depends': ['ir', 'res', 'workflow'],
'xml': [
'import_data.xml',
'sequence.xml',
'workflow.xml',
],
'translation': [
],
+35
View File
@@ -0,0 +1,35 @@
# 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, ModelWorkflow, fields
class WorkflowedModel(ModelWorkflow, ModelSQL):
_name = 'test.workflowed'
name = fields.Char('Name')
state = fields.Char('State')
value = fields.Integer('Value')
def default_transitions(self):
return 0
def default_value(self):
return 0
def workflow_start(self, workflowed):
self.write([workflowed.id], {'state': 'Start'})
def workflow_middle(self, workflowed):
self.write([workflowed.id], {'state': 'Middle'})
def workflow_end(self, workflowed):
self.write([workflowed.id], {'state': 'End'})
def start_middle_ok(self, workflowed):
return workflowed.value > 4
def middle_end_ok(self, workflowed):
return workflowed.value < 8
WorkflowedModel()
+41
View File
@@ -0,0 +1,41 @@
<?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="workflow" id="test_workflow">
<field name="name">Test workflow</field>
<field name="model">test.workflowed</field>
<field name="on_create" eval="True" />
</record>
<record model="workflow.activity" id="test_activity_start">
<field name="name">Start</field>
<field name="method">workflow_start</field>
<field name="workflow" ref="test_workflow" />
<field name="flow_start" eval="True" />
</record>
<record model="workflow.activity" id="test_activity_middle">
<field name="name">Middle</field>
<field name="method">workflow_middle</field>
<field name="workflow" ref="test_workflow" />
</record>
<record model="workflow.activity" id="test_activity_end">
<field name="name">End</field>
<field name="method">workflow_end</field>
<field name="workflow" ref="test_workflow" />
<field name="flow_stop" eval="True" />
</record>
<record model="workflow.transition"
id="test_transition_start_middle">
<field name="act_from" ref="test_activity_start" />
<field name="act_to" ref="test_activity_middle" />
<field name="condition">start_middle_ok</field>
</record>
<record model="workflow.transition"
id="tes_transition_middle_end">
<field name="act_from" ref="test_activity_middle" />
<field name="act_to" ref="test_activity_end" />
<field name="condition">middle_end_ok</field>
</record>
</data>
</tryton>
+66
View File
@@ -0,0 +1,66 @@
# 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
from trytond.transaction import Transaction
from trytond.tests.test_tryton import (POOL, DB_NAME, USER, CONTEXT,
install_module)
class WorkflowTestCase(unittest.TestCase):
def setUp(self):
install_module('test')
self.workflow_obj = POOL.get('test.workflowed')
def test0010object_creation(self):
'Test workflow object creation'
with Transaction().start(DB_NAME, USER, CONTEXT) as transaction:
wkf_id = self.workflow_obj.create({
'name': 'value = 3',
'value': 3,
})
self.assertEqual(self.workflow_obj.read(wkf_id)['state'], 'Start')
wkf_id = self.workflow_obj.create({
'name': 'value = 10',
'value': 10,
})
self.assertEqual(self.workflow_obj.read(wkf_id)['state'], 'Middle')
wkf_id = self.workflow_obj.create({
'name': 'value = 6',
'value': 6,
})
self.assertEqual(self.workflow_obj.read(wkf_id)['state'], 'End')
transaction.cursor.rollback()
def test0020object_modification(self):
'Test workflow object modification'
with Transaction().start(DB_NAME, USER, CONTEXT):
wkf_id = self.workflow_obj.create({
'name': 'value = 3',
'value': 3,
})
wkf_id = self.workflow_obj.create({
'name': 'value = 10',
'value': 10,
})
wkf_id, = self.workflow_obj.search([('name', '=', 'value = 3')])
self.workflow_obj.write(wkf_id, {'value': 11})
self.assertEqual(self.workflow_obj.read(wkf_id)['state'], 'Middle')
wkf_id, = self.workflow_obj.search([('name', '=', 'value = 10')])
self.workflow_obj.write(wkf_id, {'value': 6})
self.assertEqual(self.workflow_obj.read(wkf_id)['state'], 'End')
def suite():
return unittest.TestLoader().loadTestsFromTestCase(WorkflowTestCase)
if __name__ == '__main__':
unittest.TextTestRunner(verbosity=2).run(suite())
-80
View File
@@ -1,80 +0,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.
from trytond.pool import Pool
from trytond.tools import safe_eval
from trytond.transaction import Transaction
class EnvCall(object):
def __init__(self, obj, method, obj_id):
self.obj = obj
self.method = method
self.obj_id = obj_id
def __call__(self, *args, **kargs):
return getattr(self.obj, self.method)(self.obj_id, *args, **kargs)
class Env(dict):
def __init__(self, model, obj_id):
super(Env, self).__init__()
self.model = model
self.obj_id = obj_id
self.obj = Pool(Transaction().cursor.dbname).get(model)
self.browse = self.obj.browse(obj_id)
self.columns = self.obj._columns.keys() + \
self.obj._inherit_fields.keys()
def __getitem__(self, key):
if key == 'context':
return Transaction().context
elif key in self.columns:
res = self.browse[key]
return res
elif key in dir(self.obj):
return EnvCall(self.obj, key, self.obj_id)
else:
return super(Env, self).__getitem__(key)
def eval_expr(model, obj_id, action):
res = False
env = Env(model, obj_id)
for line in action.split('\n'):
if line == 'True':
res = True
elif line =='False':
res = False
else:
res = safe_eval(line, env)
return res
def execute(model, obj_id, activity):
'''
Execute
:param model: the model name
:param obj_id: the record id
:param activity: a BrowseRecord of workflow.activity
'''
return eval_expr(model, obj_id, activity.action)
def check(model, obj_id, transition, signal):
'''
Check
:param model: the model name
:param obj_id: the record id
:param transition: a BrowseRecord of workflow.transition
'''
if transition.signal:
if signal != transition.signal:
return False
if transition.group and Transaction().user != 0:
user_obj = Pool(Transaction().cursor.dbname).get('res.user')
user_groups = user_obj.get_groups()
if transition.group.id not in user_groups:
return False
return eval_expr(model, obj_id, transition.condition)
+81 -67
View File
@@ -1,13 +1,12 @@
#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 os
import expr
import base64
from trytond.model import ModelView, ModelSQL, fields
from trytond.report import Report
from trytond.tools import exec_command_pipe
from trytond.backend import TableHandler
from trytond.pyson import Eval, Equal, Not
from trytond.pyson import Eval, Equal, Not, Bool
from trytond.transaction import Transaction
from trytond.pool import Pool
@@ -63,30 +62,27 @@ class WorkflowActivity(ModelSQL, ModelView):
('XOR', 'Xor'),
('AND', 'And'),
], 'Join Mode', required=True)
kind = fields.Selection([
('dummy', 'Dummy'),
('function', 'Function'),
('subflow', 'Subflow'),
('stopall', 'Stop All'),
], 'Kind', required=True)
action = fields.Text('Action', states={
'readonly': Equal(Eval('kind'), 'dummy'),
'required': Equal(Eval('kind'), 'function'),
})
method = fields.Char('Method')
flow_start = fields.Boolean('Flow Start')
flow_stop = fields.Boolean('Flow Stop')
subflow = fields.Many2One('workflow', 'Subflow', states={
'readonly': Not(Equal(Eval('kind'), 'subflow')),
'required': Equal(Eval('kind'), 'subflow'),
})
stop_other = fields.Boolean('Stop Other')
subflow = fields.Many2One('workflow', 'Subflow')
signal_send = fields.Char('Signal (subflow.*)')
out_transitions = fields.One2Many('workflow.transition', 'act_from',
'Outgoing transitions')
in_transitions = fields.One2Many('workflow.transition', 'act_to',
'Incoming transitions')
#TODO add a _constraint on subflow without action
#to have the same model than the workflow
def init(self, module_name):
super(WorkflowActivity, self).init(module_name)
cursor = Transaction().cursor
table = TableHandler(cursor, self, module_name)
# Migration from 2.0
if table.column_exist('kind'):
cursor.execute('UPDATE "%s" SET stop_other = %%s '
"WHERE kind='stopall'" % self._table, (True,))
table.drop_column('kind', exception=True)
def default_kind(self):
return 'dummy'
@@ -103,6 +99,9 @@ class WorkflowActivity(ModelSQL, ModelView):
def default_flow_stop(self):
return False
def default_stop_other(self):
return False
WorkflowActivity()
@@ -113,7 +112,9 @@ class WorkflowTransition(ModelSQL, ModelView):
_rec_name = 'signal'
_description = __doc__
trigger_model = fields.Char('Trigger Type')
trigger_expr_id = fields.Char('Trigger Expr ID')
trigger_ids = fields.Char('Trigger Expr ID', states={
'required': Bool(Eval('trigger_model')),
})
signal = fields.Char('Signal (button Name)')
group = fields.Many2One('res.group', 'Group Required')
condition = fields.Char('Condition', required=True)
@@ -125,7 +126,7 @@ class WorkflowTransition(ModelSQL, ModelView):
'trans_id', 'inst_id', 'Instances')
def default_condition(self):
return 'True'
return ''
WorkflowTransition()
@@ -327,19 +328,20 @@ class WorkflowWorkitem(ModelSQL, ModelView):
triggers = triggers and not res
if triggers:
for transition in activity.out_transitions:
if transition.trigger_model:
ids = expr.eval_expr( workitem.instance.res_type,
workitem.instance.res_id,
transition.trigger_expr_id)
with Transaction().set_user(0):
for res_id in ids:
trigger_obj.create({
'model': transition.trigger_model,
'res_id': res_id,
'instance': workitem.instance.id,
'workitem': workitem.id,
})
trigger_transitions = (t for t in activity.out_transitions
if t.trigger_model)
for transition in trigger_transitions:
model_obj = pool.get(workitem.instance.res_type)
model_ids_fct = getattr(model_obj, transition.trigger_ids)
ids = model_ids_fct(model_obj.browse(workitem.instance.res_id))
with Transaction().set_user(0):
for res_id in ids:
trigger_obj.create({
'model': transition.trigger_model,
'res_id': res_id,
'instance': workitem.instance.id,
'workitem': workitem.id,
})
return True
def _state_set(self, workitem, state):
@@ -358,18 +360,9 @@ class WorkflowWorkitem(ModelSQL, ModelView):
instance_obj.validate(overflow.instance, activity.signal_send,
force_running=True)
if activity.kind == 'dummy':
if workitem.state == 'active':
self._state_set(workitem, 'complete')
elif activity.kind == 'function':
if workitem.state == 'active':
self._state_set(workitem, 'running')
expr.execute(workitem.instance.res_type,
workitem.instance.res_id, activity)
self._state_set(workitem, 'complete')
elif activity.kind == 'stopall':
if workitem.state == 'active':
self._state_set(workitem, 'running')
if workitem.state == 'active':
self._state_set(workitem, 'running')
if activity.stop_other:
ids = self.search([
('instance', '=', workitem.instance.id),
('id', '!=', workitem.id),
@@ -377,41 +370,64 @@ class WorkflowWorkitem(ModelSQL, ModelView):
#XXX check if delete must not be replace by _state_set 'complete'
with Transaction().set_user(0):
self.delete(ids)
if activity.action:
expr.execute(workitem.instance.res_type,
workitem.instance.res_id, activity)
self._state_set(workitem, 'complete')
elif activity.kind == 'subflow':
if workitem.state == 'active':
self._state_set(workitem, 'running')
if activity.action:
id_new = expr.execute(workitem.instance.res_type,
workitem.instance.res_id, activity)
if not id_new:
with Transaction().set_user(0):
self.delete(workitem.id)
return False
if activity.method:
id_new = self._execute_action(workitem, activity)
else:
id_new = None
if activity.subflow:
if activity.method and id_new:
with Transaction().set_user(0):
instance_id = instance_obj.search([
('res_id', '=', id_new),
('workflow', '=', activity.subflow.id),
], limit=1)[0]
else:
elif not activity.method:
instance_id = instance_obj.create({
'res_type': workitem.instance.res_type,
'res_id': workitem.instance.res_id,
'workflow': activity.subflow.id,
})
else:
with Transaction().set_user(0):
self.delete(workitem.id)
return False
self.write(workitem.id, {
'subflow': instance_id,
})
#XXX must be changed with a cache reset on BrowseRecord
workitem._data[workitem.id]['subflow'] = instance_id
elif workitem.state == 'running':
if workitem.subflow.state == 'complete':
self._state_set(workitem, 'complete')
else:
self._state_set(workitem, 'complete')
elif workitem.state == 'running' and activity.subflow:
if workitem.subflow.state == 'complete':
self._state_set(workitem, 'complete')
return True
def _execute_action(self, workitem, activity):
model_obj = Pool().get(workitem.instance.res_type)
wkf_action = getattr(model_obj, activity.method)
return wkf_action(model_obj.browse(workitem.instance.res_id))
def _activate_transition(self, workitem, transition, signal):
pool = Pool()
user_obj = pool.get('res.user')
if transition.signal:
if signal != transition.signal:
return False
if transition.group and Transaction().user != 0:
user_groups = user_obj.get_groups()
if transition.group.id not in user_groups:
return False
model_obj = pool.get(workitem.instance.res_type)
if transition.condition:
test_fct = getattr(model_obj, transition.condition)
return test_fct(model_obj.browse(workitem.instance.res_id))
else:
return True
def _split_test(self, workitem, split_mode, signal=None):
pool = Pool()
instance_obj = pool.get('workflow.instance')
@@ -419,8 +435,7 @@ class WorkflowWorkitem(ModelSQL, ModelView):
transitions = []
if split_mode == 'XOR' or split_mode == 'OR':
for transition in workitem.activity.out_transitions:
if expr.check(workitem.instance.res_type,
workitem.instance.res_id, transition, signal):
if self._activate_transition(workitem, transition, signal):
test = True
transitions.append(transition)
if split_mode == 'XOR':
@@ -428,8 +443,7 @@ class WorkflowWorkitem(ModelSQL, ModelView):
else:
test = True
for transition in workitem.activity.out_transitions:
if not expr.check(workitem.instance.res_type,
workitem.instance.res_id, transition, signal):
if not self._activate_transition(workitem, transition, signal):
test = False
break
if transition.id not in \
+12 -14
View File
@@ -88,10 +88,8 @@ this repository contains the full copyright notices and license terms. -->
<field name="name" colspan="3"/>
<label name="workflow"/>
<field name="workflow"/>
<label name="kind"/>
<field name="kind"/>
<label name="action"/>
<field name="action" colspan="3"/>
<label name="method"/>
<field name="method"/>
<label name="subflow"/>
<field name="subflow"/>
<label name="signal_send"/>
@@ -112,7 +110,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="group"/>
<field name="condition"/>
<field name="trigger_model"/>
<field name="trigger_expr_id"/>
<field name="trigger_ids"/>
</tree>
<form string="Transition">
<label name="act_to"/>
@@ -126,8 +124,8 @@ this repository contains the full copyright notices and license terms. -->
<field name="group"/>
<label name="trigger_model"/>
<field name="trigger_model"/>
<label name="trigger_expr_id"/>
<field name="trigger_expr_id"/>
<label name="trigger_ids"/>
<field name="trigger_ids"/>
</form>
</field>
<field name="in_transitions" colspan="4">
@@ -137,7 +135,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="group"/>
<field name="condition"/>
<field name="trigger_model"/>
<field name="trigger_expr_id"/>
<field name="trigger_ids"/>
</tree>
<form string="Transition">
<label name="act_from"/>
@@ -151,8 +149,8 @@ this repository contains the full copyright notices and license terms. -->
<field name="group"/>
<label name="trigger_model"/>
<field name="trigger_model"/>
<label name="trigger_expr_id"/>
<field name="trigger_expr_id"/>
<label name="trigger_ids"/>
<field name="trigger_ids"/>
</form>
</field>
</form>
@@ -167,7 +165,7 @@ this repository contains the full copyright notices and license terms. -->
<tree string="Activity">
<field name="name" select="1"/>
<field name="workflow" select="1"/>
<field name="kind" select="1"/>
<field name="method"/>
<field name="flow_start"/>
<field name="flow_stop"/>
</tree>
@@ -230,8 +228,8 @@ this repository contains the full copyright notices and license terms. -->
<field name="group"/>
<label name="trigger_model"/>
<field name="trigger_model"/>
<label name="trigger_expr_id"/>
<field name="trigger_expr_id"/>
<label name="trigger_ids"/>
<field name="trigger_ids"/>
</form>
]]>
</field>
@@ -248,7 +246,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="group"/>
<field name="condition"/>
<field name="trigger_model"/>
<field name="trigger_expr_id"/>
<field name="trigger_ids"/>
</tree>
]]>
</field>