Files
tryton/trytond/workflow/workflow.py
T

328 lines
12 KiB
Python

"Workflow"
import os
from trytond.osv import fields, OSV, ExceptOSV
from trytond.netsvc import LocalService
from trytond.report import Report
from trytond.tools import exec_command_pipe
import base64
class Workflow(OSV):
"Workflow"
_name = "workflow"
_table = "wkf"
_log_access = False
_description = __doc__
name = fields.Char('Name', size=64, required=True)
osv = fields.Char('Resource Model', size=64, required=True)
on_create = fields.Boolean('On Create')
activities = fields.One2Many('workflow.activity', 'workflow',
'Activities')
def default_on_create(self, cursor, user, context=None):
return 1
def write(self, cursor, user, ids, vals, context=None):
wf_service = LocalService("workflow")
wf_service.clear_cache(cursor)
return super(Workflow, self).write(cursor, user, ids, vals,
context=context)
def create(self, cursor, user, vals, context=None):
wf_service = LocalService("workflow")
wf_service.clear_cache(cursor)
return super(Workflow, self).create(cursor, user, vals,
context=context)
Workflow()
class WorkflowActivity(OSV):
"Workflow activity"
_name = "workflow.activity"
_table = "wkf_activity"
_log_access = False
_description = __doc__
name = fields.Char('Name', size=64, required=True)
workflow = fields.Many2One('workflow', 'Workflow', required=True,
select=1, ondelete='cascade')
split_mode = fields.Selection([
('XOR', 'Xor'),
('OR', 'Or'),
('AND', 'And'),
], 'Split Mode', size=3, required=True)
join_mode = fields.Selection([
('XOR', 'Xor'),
('AND', 'And'),
], 'Join Mode', size=3, required=True)
kind = fields.Selection([
('dummy', 'Dummy'),
('function', 'Function'),
('subflow', 'Subflow'),
('stopall', 'Stop All'),
], 'Kind', size=64, required=True)
action = fields.Char('Action', size=None)
flow_start = fields.Boolean('Flow Start')
flow_stop = fields.Boolean('Flow Stop')
subflow = fields.Many2One('workflow', 'Subflow')
signal_send = fields.Char('Signal (subflow.*)', size=32)
out_transitions = fields.One2Many('workflow.transition', 'act_from',
'Outgoing transitions')
in_transitions = fields.One2Many('workflow.transition', 'act_to',
'Incoming transitions')
def default_kind(self, cursor, user, context=None):
return 'dummy'
def default_join_mode(self, cursor, user, context=None):
return 'XOR'
def default_split_mode(self, cursor, user, context=None):
return 'XOR'
WorkflowActivity()
class WorkflowTransition(OSV):
"Workflow transition"
_table = "wkf_transition"
_name = "workflow.transition"
_log_access = False
_rec_name = 'signal'
_description = __doc__
trigger_model = fields.Char('Trigger Type', size=128)
trigger_expr_id = fields.Char('Trigger Expr ID', size=128)
signal = fields.Char('Signal (button Name)', size=64)
group = fields.Many2One('res.group', 'Group Required')
condition = fields.Char('Condition', required=True, size=128)
act_from = fields.Many2One('workflow.activity', 'Source Activity',
required=True, select=1, ondelete='cascade')
act_to = fields.Many2One('workflow.activity', 'Destination Activity',
required=True, select=1, ondelete='cascade')
def default_condition(self, cursor, user, context=None):
return 'True'
WorkflowTransition()
class WorkflowInstance(OSV):
"Workflow instance"
_table = "wkf_instance"
_name = "workflow.instance"
_rec_name = 'res_type'
_log_access = False
_description = __doc__
workflow = fields.Many2One('workflow', 'Workflow', ondelete="restrict")
uid = fields.Integer('User ID')
res_id = fields.Integer('Resource ID')
res_type = fields.Char('Resource Model', size=64)
state = fields.Char('State', size=32)
def _auto_init(self, cursor, module_name):
super(WorkflowInstance, self)._auto_init(cursor, module_name)
cursor.execute('SELECT indexname FROM pg_indexes ' \
'WHERE indexname = ' \
'\'wkf_instance_res_id_res_type_state_index\'')
if not cursor.fetchone():
cursor.execute('CREATE INDEX ' \
'wkf_instance_res_id_res_type_state_index ' \
'ON wkf_instance (res_id, res_type, state)')
WorkflowInstance()
class WorkflowWorkitem(OSV):
"Workflow workitem"
_table = "wkf_workitem"
_name = "workflow.workitem"
_log_access = False
_rec_name = 'state'
_description = __doc__
activity = fields.Many2One('workflow.activity', 'Activity',
required=True, ondelete="cascade")
subflow = fields.Many2One('workflow.instance', 'Subflow',
ondelete="cascade")
instance = fields.Many2One('workflow.instance', 'Instance',
required=True, ondelete="cascade", select=1)
state = fields.Char('State', size=64)
WorkflowWorkitem()
class WorkflowTrigger(OSV):
"Workflow trigger"
_table = "wkf_trigger"
_name = "workflow.trigger"
_log_access = False
_description = __doc__
res_id = fields.Integer('Resource ID', size=128)
model = fields.Char('Model', size=128)
instance = fields.Many2One('workflow.instance',
'Destination Instance', ondelete="cascade")
workitem = fields.Many2One('workflow.workitem', 'Workitem',
required=True, ondelete="cascade")
def _auto_init(self, cursor, module_name):
super(WorkflowTrigger, self)._auto_init(cursor, module_name)
cursor.execute('SELECT indexname FROM pg_indexes ' \
'WHERE indexname = \'wkf_trigger_res_id_model_index\'')
if not cursor.fetchone():
cursor.execute('CREATE INDEX wkf_trigger_res_id_model_index ' \
'ON wkf_trigger (res_id, model)')
cursor.commit()
WorkflowTrigger()
class InstanceGraph(Report):
_name = 'workflow.instance.graph'
def execute(self, cursor, user, ids, datas, context=None):
import pydot
lang_obj = self.pool.get('ir.lang')
workflow_obj = self.pool.get('workflow')
instance_obj = self.pool.get('workflow.instance')
if context is None:
context = {}
lang_id = lang_obj.search(cursor, user, [
('code', '=', context.get('language', 'en_US')),
], limit=1, context=context)[0]
lang = lang_obj.browse(cursor, user, lang_id, context=context)
workflow_id = workflow_obj.search(cursor, user, [
('osv', '=', datas['model']),
], limit=1, context=context)
if not workflow_id:
raise ExceptOSV('UserError', 'No workflow defined!')
workflow_id = workflow_id[0]
workflow = workflow_obj.browse(cursor, user, workflow_id,
context=context)
instance_id = instance_obj.search(cursor, user, [
('res_id', '=', datas['id']),
('workflow', '=', workflow.id),
], order=[('id', 'DESC')], limit=1, context=context)
if not instance_id:
raise ExceptOSV('UserError', 'No workflow instance defined!')
instance_id = instance_id[0]
graph = pydot.Dot(fontsize=16,
label="\\n\\nWorkflow: %s\\n OSV: %s" % \
(workflow.name, workflow.osv))
graph.set('center', '1')
graph.set('ratio', 'auto')
if lang.direction == 'ltr':
graph.set('rankdir', 'LR')
else:
graph.set('rankdir', 'RL')
self.graph_instance_get(cursor, user, graph, instance_id,
datas.get('nested', False), context=context)
data = graph.create(prog='dot', format='png')
return ('png', base64.encodestring(data), False)
def graph_instance_get(self, cursor, user, graph, instance_id, nested=False,
context=None):
instance_obj = self.pool.get('workflow.instance')
instance = instance_obj.browse(cursor, user, instance_id,
context=context)
self.graph_get(cursor, user, graph, instance.workflow.id, nested,
self.workitem_get(cursor, user, instance.id, context=context),
context=context)
def workitem_get(self, cursor, user, instance_id, context=None):
res = {}
workitem_obj = self.pool.get('workflow.workitem')
workitem_ids = workitem_obj.search(cursor, user, [
('instance', '=', instance_id),
], context=context)
workitems = workitem_obj.browse(cursor, user, workitem_ids,
context=context)
for workitem in workitems:
res.setdefault(workitem.activity.id, 0)
res[workitem.activity.id] += 1
if workitem.subflow:
res.update(self.workitem_get(cursor, user,
workitem.subflow.id, context=context))
return res
def graph_get(self, cursor, user, graph, workflow_id, nested=False,
workitem=None, context=None):
import pydot
if workitem is None:
workitem = {}
activity_obj = self.pool.get('workflow.activity')
workflow_obj = self.pool.get('workflow')
transition_obj = self.pool.get('workflow.transition')
activity_ids = activity_obj.search(cursor, user, [
('workflow', '=', workflow_id),
], context=context)
id2activities = {}
actfrom = {}
actto = {}
activities = activity_obj.browse(cursor, user, activity_ids,
context=context)
start = 0
stop = {}
for activity in activities:
if activity.flow_start:
start = activity.id
if activity.flow_stop:
stop['subflow.' + activity.name] = activity.id
id2activities[activity.id] = activity
if activity.subflow and nested:
workflow = workflow_obj.browse(cursor, user,
activity.subflow.id, context=context)
subgraph = pydot.Cluster('subflow' + str(workflow.id),
fontsize=12, label="Subflow: " + activity.name + \
'\\nOSV: ' + workflow.osv)
(substart, substop) = self.graph_get(cursor, user,
subgraph, workflow.id, nested, workitem,
context=context)
graph.add_subgraph(subgraph)
actfrom[activity.id] = substart
actto[activity.id] = substop
else:
args = {}
if activity.flow_start or activity.flow_stop:
args['style'] = 'filled'
args['color'] = 'lightgrey'
args['label'] = activity.name
if activity.subflow:
args['shape'] = 'box'
if activity.id in workitem:
args['label'] += '\\nx ' + str(workitem[activity.id])
args['color'] = 'red'
graph.add_node(pydot.Node(activity.id, **args))
actfrom[activity.id] = (activity.id, {})
actto[activity.id] = (activity.id, {})
transition_ids = transition_obj.search(cursor, user, [
('act_from', 'in', [x.id for x in activities]),
], context=context)
transitions = transition_obj.browse(cursor, user, transition_ids,
context=context)
for transition in transitions:
args = {}
args['label'] = str(transition.condition).replace(' or ',
'\\nor ').replace(' and ', '\\nand ')
if transition.signal:
args['label'] += '\\n' + str(transition.signal)
args['style'] = 'bold'
if id2activities[transition.act_from.id].split_mode == 'AND':
args['arrowtail'] = 'box'
elif id2activities[transition.act_from.id].split_mode == 'OR':
args['arrowtail'] = 'inv'
if id2activities[transition.act_to.id].join_mode == 'AND':
args['arrowhead'] = 'crow'
activity_from = actfrom[transition.act_from.id][1].get(
transition.signal, actfrom[transition.act_from.id][0])
activity_to = actto[transition.act_to.id][1].get(
transition.signal, actto[transition.act_to.id][0])
graph.add_edge(pydot.Edge(activity_from, activity_to,
fontsize=10, **args))
return ((start, {}), (stop.values()[0], stop))
InstanceGraph()