|
1 # copyright 2017 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
|
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
3 # |
|
4 # This file is part of CubicWeb. |
|
5 # |
|
6 # CubicWeb is free software: you can redistribute it and/or modify it under the |
|
7 # terms of the GNU Lesser General Public License as published by the Free |
|
8 # Software Foundation, either version 2.1 of the License, or (at your option) |
|
9 # any later version. |
|
10 # |
|
11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT |
|
12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|
13 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
|
14 # details. |
|
15 # |
|
16 # You should have received a copy of the GNU Lesser General Public License along |
|
17 # with CubicWeb. If not, see <http://www.gnu.org/licenses/>. |
|
18 """Workflow setup utilities. |
|
19 |
|
20 These functions work with a declarative workflow definition: |
|
21 |
|
22 .. code-block:: python |
|
23 |
|
24 { |
|
25 'etypes': 'CWGroup', |
|
26 'default': True, |
|
27 'initial_state': u'draft', |
|
28 'states': [u'draft', u'published'], |
|
29 'transitions': { |
|
30 u'publish': { |
|
31 'fromstates': u'draft', |
|
32 'tostate': u'published', |
|
33 'requiredgroups': u'managers' |
|
34 'conditions': ( |
|
35 'U in_group X', |
|
36 'X owned_by U' |
|
37 ) |
|
38 } |
|
39 } |
|
40 } |
|
41 |
|
42 .. autofunction:: setup_workflow |
|
43 .. autofunction:: cleanupworkflow |
|
44 """ |
|
45 |
|
46 import collections |
|
47 |
|
48 from six import text_type |
|
49 |
|
50 from cubicweb import NoResultError |
|
51 |
|
52 |
|
53 def get_tuple_or_list(value): |
|
54 if value is None: |
|
55 return None |
|
56 if not isinstance(value, (tuple, list)): |
|
57 value = (value,) |
|
58 return value |
|
59 |
|
60 |
|
61 def cleanupworkflow(cnx, wf, wfdef): |
|
62 """Cleanup an existing workflow by removing the states and transitions that |
|
63 do not exist in the given definition. |
|
64 |
|
65 :param cnx: A connexion with enough permissions to define a workflow |
|
66 :param wf: A `Workflow` entity |
|
67 :param wfdef: A workflow definition |
|
68 """ |
|
69 cnx.execute( |
|
70 'DELETE State S WHERE S state_of WF, WF eid %%(wf)s, ' |
|
71 'NOT S name IN (%s)' % ( |
|
72 ', '.join('"%s"' % s for s in wfdef['states'])), |
|
73 {'wf': wf.eid}) |
|
74 |
|
75 cnx.execute( |
|
76 'DELETE Transition T WHERE T transition_of WF, WF eid %%(wf)s, ' |
|
77 'NOT T name IN (%s)' % ( |
|
78 ', '.join('"%s"' % s for s in wfdef['transitions'])), |
|
79 {'wf': wf.eid}) |
|
80 |
|
81 |
|
82 def setup_workflow(cnx, name, wfdef, cleanup=True): |
|
83 """Create or update a workflow definition so it matches the given |
|
84 definition. |
|
85 |
|
86 :param cnx: A connexion with enough permissions to define a workflow |
|
87 :param name: The workflow name. Used to create the `Workflow` entity, or |
|
88 to find an existing one. |
|
89 :param wfdef: A workflow definition. |
|
90 :param cleanup: Remove extra states and transitions. Can be done separatly |
|
91 by calling :func:`cleanupworkflow`. |
|
92 :return: The created/updated workflow entity |
|
93 """ |
|
94 name = text_type(name) |
|
95 try: |
|
96 wf = cnx.find('Workflow', name=name).one() |
|
97 except NoResultError: |
|
98 wf = cnx.create_entity('Workflow', name=name) |
|
99 |
|
100 etypes = get_tuple_or_list(wfdef['etypes']) |
|
101 cnx.execute('DELETE WF workflow_of ETYPE WHERE WF eid %%(wf)s, ' |
|
102 'NOT ETYPE name IN (%s)' % ','.join('"%s"' for e in etypes), |
|
103 {'wf': wf.eid}) |
|
104 cnx.execute('SET WF workflow_of ETYPE WHERE' |
|
105 ' NOT WF workflow_of ETYPE, WF eid %%(wf)s, ETYPE name IN (%s)' |
|
106 % ','.join('"%s"' % e for e in etypes), |
|
107 {'wf': wf.eid}) |
|
108 if wfdef['default']: |
|
109 cnx.execute( |
|
110 'SET ETYPE default_workflow X ' |
|
111 'WHERE ' |
|
112 'NOT ETYPE default_workflow X, ' |
|
113 'X eid %%(x)s, ETYPE name IN (%s)' % ','.join( |
|
114 '"%s"' % e for e in etypes), |
|
115 {'x': wf.eid}) |
|
116 |
|
117 states = {} |
|
118 states_transitions = collections.defaultdict(list) |
|
119 for state in wfdef['states']: |
|
120 st = wf.state_by_name(state) or wf.add_state(state) |
|
121 states[state] = st |
|
122 |
|
123 if 'initial_state' in wfdef: |
|
124 wf.cw_set(initial_state=states[wfdef['initial_state']]) |
|
125 |
|
126 for trname, trdef in wfdef['transitions'].items(): |
|
127 tr = (wf.transition_by_name(trname) or |
|
128 cnx.create_entity('Transition', name=trname)) |
|
129 tr.cw_set(transition_of=wf) |
|
130 if trdef.get('tostate'): |
|
131 tr.cw_set(destination_state=states[trdef['tostate']]) |
|
132 fromstates = get_tuple_or_list(trdef.get('fromstates', ())) |
|
133 for stname in fromstates: |
|
134 states_transitions[stname].append(tr) |
|
135 |
|
136 requiredgroups = get_tuple_or_list(trdef.get('requiredgroups', ())) |
|
137 conditions = get_tuple_or_list(trdef.get('conditions', ())) |
|
138 |
|
139 tr.set_permissions(requiredgroups, conditions, reset=True) |
|
140 |
|
141 for stname, transitions in states_transitions.items(): |
|
142 state = states[stname] |
|
143 state.cw_set(allowed_transition=None) |
|
144 state.cw_set(allowed_transition=transitions) |
|
145 |
|
146 if cleanup: |
|
147 cleanupworkflow(cnx, wf, wfdef) |
|
148 |
|
149 return wf |