50 # infinite loop safety belt |
50 # infinite loop safety belt |
51 if _done is None: |
51 if _done is None: |
52 _done = set() |
52 _done = set() |
53 yield self |
53 yield self |
54 _done.add(self.eid) |
54 _done.add(self.eid) |
55 for tr in self.req.execute('Any T WHERE T is WorkflowTransition, ' |
55 for tr in self._cw.execute('Any T WHERE T is WorkflowTransition, ' |
56 'T transition_of WF, WF eid %(wf)s', |
56 'T transition_of WF, WF eid %(wf)s', |
57 {'wf': self.eid}).entities(): |
57 {'wf': self.eid}).entities(): |
58 if tr.subwf.eid in _done: |
58 if tr.subwf.eid in _done: |
59 continue |
59 continue |
60 for subwf in tr.subwf.iter_workflows(_done): |
60 for subwf in tr.subwf.iter_workflows(_done): |
61 yield subwf |
61 yield subwf |
62 |
62 |
63 # state / transitions accessors ############################################ |
63 # state / transitions accessors ############################################ |
64 |
64 |
65 def state_by_name(self, statename): |
65 def state_by_name(self, statename): |
66 rset = self.req.execute('Any S, SN WHERE S name SN, S name %(n)s, ' |
66 rset = self._cw.execute('Any S, SN WHERE S name SN, S name %(n)s, ' |
67 'S state_of WF, WF eid %(wf)s', |
67 'S state_of WF, WF eid %(wf)s', |
68 {'n': statename, 'wf': self.eid}, 'wf') |
68 {'n': statename, 'wf': self.eid}, 'wf') |
69 if rset: |
69 if rset: |
70 return rset.get_entity(0, 0) |
70 return rset.get_entity(0, 0) |
71 return None |
71 return None |
72 |
72 |
73 def state_by_eid(self, eid): |
73 def state_by_eid(self, eid): |
74 rset = self.req.execute('Any S, SN WHERE S name SN, S eid %(s)s, ' |
74 rset = self._cw.execute('Any S, SN WHERE S name SN, S eid %(s)s, ' |
75 'S state_of WF, WF eid %(wf)s', |
75 'S state_of WF, WF eid %(wf)s', |
76 {'s': eid, 'wf': self.eid}, ('wf', 's')) |
76 {'s': eid, 'wf': self.eid}, ('wf', 's')) |
77 if rset: |
77 if rset: |
78 return rset.get_entity(0, 0) |
78 return rset.get_entity(0, 0) |
79 return None |
79 return None |
80 |
80 |
81 def transition_by_name(self, trname): |
81 def transition_by_name(self, trname): |
82 rset = self.req.execute('Any T, TN WHERE T name TN, T name %(n)s, ' |
82 rset = self._cw.execute('Any T, TN WHERE T name TN, T name %(n)s, ' |
83 'T transition_of WF, WF eid %(wf)s', |
83 'T transition_of WF, WF eid %(wf)s', |
84 {'n': trname, 'wf': self.eid}, 'wf') |
84 {'n': trname, 'wf': self.eid}, 'wf') |
85 if rset: |
85 if rset: |
86 return rset.get_entity(0, 0) |
86 return rset.get_entity(0, 0) |
87 return None |
87 return None |
88 |
88 |
89 def transition_by_eid(self, eid): |
89 def transition_by_eid(self, eid): |
90 rset = self.req.execute('Any T, TN WHERE T name TN, T eid %(t)s, ' |
90 rset = self._cw.execute('Any T, TN WHERE T name TN, T eid %(t)s, ' |
91 'T transition_of WF, WF eid %(wf)s', |
91 'T transition_of WF, WF eid %(wf)s', |
92 {'t': eid, 'wf': self.eid}, ('wf', 't')) |
92 {'t': eid, 'wf': self.eid}, ('wf', 't')) |
93 if rset: |
93 if rset: |
94 return rset.get_entity(0, 0) |
94 return rset.get_entity(0, 0) |
95 return None |
95 return None |
96 |
96 |
97 # wf construction methods ################################################## |
97 # wf construction methods ################################################## |
98 |
98 |
99 def add_state(self, name, initial=False, **kwargs): |
99 def add_state(self, name, initial=False, **kwargs): |
100 """add a state to this workflow""" |
100 """add a state to this workflow""" |
101 state = self.req.create_entity('State', name=unicode(name), **kwargs) |
101 state = self._cw.create_entity('State', name=unicode(name), **kwargs) |
102 self.req.execute('SET S state_of WF WHERE S eid %(s)s, WF eid %(wf)s', |
102 self._cw.execute('SET S state_of WF WHERE S eid %(s)s, WF eid %(wf)s', |
103 {'s': state.eid, 'wf': self.eid}, ('s', 'wf')) |
103 {'s': state.eid, 'wf': self.eid}, ('s', 'wf')) |
104 if initial: |
104 if initial: |
105 assert not self.initial, "Initial state already defined as %s" % self.initial |
105 assert not self.initial, "Initial state already defined as %s" % self.initial |
106 self.req.execute('SET WF initial_state S ' |
106 self._cw.execute('SET WF initial_state S ' |
107 'WHERE S eid %(s)s, WF eid %(wf)s', |
107 'WHERE S eid %(s)s, WF eid %(wf)s', |
108 {'s': state.eid, 'wf': self.eid}, ('s', 'wf')) |
108 {'s': state.eid, 'wf': self.eid}, ('s', 'wf')) |
109 return state |
109 return state |
110 |
110 |
111 def _add_transition(self, trtype, name, fromstates, |
111 def _add_transition(self, trtype, name, fromstates, |
112 requiredgroups=(), conditions=(), **kwargs): |
112 requiredgroups=(), conditions=(), **kwargs): |
113 tr = self.req.create_entity(trtype, name=unicode(name), **kwargs) |
113 tr = self._cw.create_entity(trtype, name=unicode(name), **kwargs) |
114 self.req.execute('SET T transition_of WF ' |
114 self._cw.execute('SET T transition_of WF ' |
115 'WHERE T eid %(t)s, WF eid %(wf)s', |
115 'WHERE T eid %(t)s, WF eid %(wf)s', |
116 {'t': tr.eid, 'wf': self.eid}, ('t', 'wf')) |
116 {'t': tr.eid, 'wf': self.eid}, ('t', 'wf')) |
117 assert fromstates, fromstates |
117 assert fromstates, fromstates |
118 if not isinstance(fromstates, (tuple, list)): |
118 if not isinstance(fromstates, (tuple, list)): |
119 fromstates = (fromstates,) |
119 fromstates = (fromstates,) |
120 for state in fromstates: |
120 for state in fromstates: |
121 if hasattr(state, 'eid'): |
121 if hasattr(state, 'eid'): |
122 state = state.eid |
122 state = state.eid |
123 self.req.execute('SET S allowed_transition T ' |
123 self._cw.execute('SET S allowed_transition T ' |
124 'WHERE S eid %(s)s, T eid %(t)s', |
124 'WHERE S eid %(s)s, T eid %(t)s', |
125 {'s': state, 't': tr.eid}, ('s', 't')) |
125 {'s': state, 't': tr.eid}, ('s', 't')) |
126 tr.set_transition_permissions(requiredgroups, conditions, reset=False) |
126 tr.set_permissions(requiredgroups, conditions, reset=False) |
127 return tr |
127 return tr |
128 |
128 |
129 def add_transition(self, name, fromstates, tostate=None, |
129 def add_transition(self, name, fromstates, tostate=None, |
130 requiredgroups=(), conditions=(), **kwargs): |
130 requiredgroups=(), conditions=(), **kwargs): |
131 """add a transition to this workflow from some state(s) to another""" |
131 """add a transition to this workflow from some state(s) to another""" |
132 tr = self._add_transition('Transition', name, fromstates, |
132 tr = self._add_transition('Transition', name, fromstates, |
133 requiredgroups, conditions, **kwargs) |
133 requiredgroups, conditions, **kwargs) |
134 if tostate is not None: |
134 if tostate is not None: |
135 if hasattr(tostate, 'eid'): |
135 if hasattr(tostate, 'eid'): |
136 tostate = tostate.eid |
136 tostate = tostate.eid |
137 self.req.execute('SET T destination_state S ' |
137 self._cw.execute('SET T destination_state S ' |
138 'WHERE S eid %(s)s, T eid %(t)s', |
138 'WHERE S eid %(s)s, T eid %(t)s', |
139 {'t': tr.eid, 's': tostate}, ('s', 't')) |
139 {'t': tr.eid, 's': tostate}, ('s', 't')) |
140 return tr |
140 return tr |
141 |
141 |
142 def add_wftransition(self, name, subworkflow, fromstates, exitpoints=(), |
142 def add_wftransition(self, name, subworkflow, fromstates, exitpoints=(), |
159 if not hasattr(replacement, 'eid'): |
159 if not hasattr(replacement, 'eid'): |
160 replacement = self.state_by_name(replacement) |
160 replacement = self.state_by_name(replacement) |
161 execute = self._cw.unsafe_execute |
161 execute = self._cw.unsafe_execute |
162 execute('SET X in_state S WHERE S eid %(s)s', {'s': todelstate.eid}, 's') |
162 execute('SET X in_state S WHERE S eid %(s)s', {'s': todelstate.eid}, 's') |
163 execute('SET X from_state NS WHERE X to_state OS, OS eid %(os)s, NS eid %(ns)s', |
163 execute('SET X from_state NS WHERE X to_state OS, OS eid %(os)s, NS eid %(ns)s', |
164 {'os': todelstate.eid, 'ns': newstate.eid}, 's') |
164 {'os': todelstate.eid, 'ns': replacement.eid}, 's') |
165 execute('SET X to_state NS WHERE X to_state OS, OS eid %(os)s, NS eid %(ns)s', |
165 execute('SET X to_state NS WHERE X to_state OS, OS eid %(os)s, NS eid %(ns)s', |
166 {'os': todelstate.eid, 'ns': newstate.eid}, 's') |
166 {'os': todelstate.eid, 'ns': replacement.eid}, 's') |
167 todelstate.delete() |
167 todelstate.delete() |
168 |
168 |
169 |
169 |
170 class BaseTransition(AnyEntity): |
170 class BaseTransition(AnyEntity): |
171 """customized class for abstract transition |
171 """customized class for abstract transition |
172 |
172 |
173 provides a specific may_be_fired method to check if the relation may be |
173 provides a specific may_be_fired method to check if the relation may be |
174 fired by the logged user |
174 fired by the logged user |
175 """ |
175 """ |
176 id = 'BaseTransition' |
176 __regid__ = 'BaseTransition' |
177 fetch_attrs, fetch_order = fetch_config(['name']) |
177 fetch_attrs, fetch_order = fetch_config(['name']) |
178 |
178 |
179 def __init__(self, *args, **kwargs): |
179 def __init__(self, *args, **kwargs): |
180 if self.id == 'BaseTransition': |
180 if self.__regid__ == 'BaseTransition': |
181 raise WorkflowException('should not be instantiated') |
181 raise WorkflowException('should not be instantiated') |
182 super(BaseTransition, self).__init__(*args, **kwargs) |
182 super(BaseTransition, self).__init__(*args, **kwargs) |
183 |
183 |
184 @property |
184 @property |
185 def workflow(self): |
185 def workflow(self): |
217 """return (path, parameters) which should be used as redirect |
217 """return (path, parameters) which should be used as redirect |
218 information when this entity is being deleted |
218 information when this entity is being deleted |
219 """ |
219 """ |
220 if self.transition_of: |
220 if self.transition_of: |
221 return self.transition_of[0].rest_path(), {} |
221 return self.transition_of[0].rest_path(), {} |
222 return super(Transition, self).after_deletion_path() |
222 return super(BaseTransition, self).after_deletion_path() |
223 |
223 |
224 def set_transition_permissions(self, requiredgroups=(), conditions=(), |
224 def set_permissions(self, requiredgroups=(), conditions=(), reset=True): |
225 reset=True): |
|
226 """set or add (if `reset` is False) groups and conditions for this |
225 """set or add (if `reset` is False) groups and conditions for this |
227 transition |
226 transition |
228 """ |
227 """ |
229 if reset: |
228 if reset: |
230 self.req.execute('DELETE T require_group G WHERE T eid %(x)s', |
229 self._cw.execute('DELETE T require_group G WHERE T eid %(x)s', |
231 {'x': self.eid}, 'x') |
230 {'x': self.eid}, 'x') |
232 self.req.execute('DELETE T condition R WHERE T eid %(x)s', |
231 self._cw.execute('DELETE T condition R WHERE T eid %(x)s', |
233 {'x': self.eid}, 'x') |
232 {'x': self.eid}, 'x') |
234 for gname in requiredgroups: |
233 for gname in requiredgroups: |
235 rset = self.req.execute('SET T require_group G ' |
234 rset = self._cw.execute('SET T require_group G ' |
236 'WHERE T eid %(x)s, G name %(gn)s', |
235 'WHERE T eid %(x)s, G name %(gn)s', |
237 {'x': self.eid, 'gn': gname}, 'x') |
236 {'x': self.eid, 'gn': gname}, 'x') |
238 assert rset, '%s is not a known group' % gname |
237 assert rset, '%s is not a known group' % gname |
239 if isinstance(conditions, basestring): |
238 if isinstance(conditions, basestring): |
240 conditions = (conditions,) |
239 conditions = (conditions,) |
244 else: |
243 else: |
245 assert isinstance(expr, dict) |
244 assert isinstance(expr, dict) |
246 kwargs = expr |
245 kwargs = expr |
247 kwargs['x'] = self.eid |
246 kwargs['x'] = self.eid |
248 kwargs.setdefault('mainvars', u'X') |
247 kwargs.setdefault('mainvars', u'X') |
249 self.req.execute('INSERT RQLExpression X: X exprtype "ERQLExpression", ' |
248 self._cw.execute('INSERT RQLExpression X: X exprtype "ERQLExpression", ' |
250 'X expression %(expr)s, X mainvars %(mainvars)s, ' |
249 'X expression %(expr)s, X mainvars %(mainvars)s, ' |
251 'T condition X WHERE T eid %(x)s', kwargs, 'x') |
250 'T condition X WHERE T eid %(x)s',kwargs, 'x') |
252 # XXX clear caches? |
251 # XXX clear caches? |
|
252 |
|
253 @deprecated('[3.6.1] use set_permission') |
|
254 def set_transition_permissions(self, requiredgroups=(), conditions=(), |
|
255 reset=True): |
|
256 return self.set_permissions(requiredgroups, conditions, reset) |
253 |
257 |
254 |
258 |
255 class Transition(BaseTransition): |
259 class Transition(BaseTransition): |
256 """customized class for Transition entities""" |
260 """customized class for Transition entities""" |
257 id = 'Transition' |
261 __regid__ = 'Transition' |
258 |
262 |
259 def destination(self): |
263 def destination(self, entity): |
260 return self.destination_state[0] |
264 try: |
|
265 return self.destination_state[0] |
|
266 except IndexError: |
|
267 return entity.latest_trinfo().previous_state |
|
268 |
|
269 def potential_destinations(self): |
|
270 try: |
|
271 yield self.destination_state[0] |
|
272 except IndexError: |
|
273 for incomingstate in self.reverse_allowed_transition: |
|
274 for tr in incomingstate.reverse_destination_state: |
|
275 for previousstate in tr.reverse_allowed_transition: |
|
276 yield previousstate |
261 |
277 |
262 def parent(self): |
278 def parent(self): |
263 return self.workflow |
279 return self.workflow |
264 |
280 |
265 |
281 |
266 class WorkflowTransition(BaseTransition): |
282 class WorkflowTransition(BaseTransition): |
267 """customized class for WorkflowTransition entities""" |
283 """customized class for WorkflowTransition entities""" |
268 id = 'WorkflowTransition' |
284 __regid__ = 'WorkflowTransition' |
269 |
285 |
270 @property |
286 @property |
271 def subwf(self): |
287 def subwf(self): |
272 return self.subworkflow[0] |
288 return self.subworkflow[0] |
273 |
289 |
274 def destination(self): |
290 def destination(self, entity): |
275 return self.subwf.initial |
291 return self.subwf.initial |
|
292 |
|
293 def potential_destinations(self): |
|
294 yield self.subwf.initial |
276 |
295 |
277 def add_exit_point(self, fromstate, tostate): |
296 def add_exit_point(self, fromstate, tostate): |
278 if hasattr(fromstate, 'eid'): |
297 if hasattr(fromstate, 'eid'): |
279 fromstate = fromstate.eid |
298 fromstate = fromstate.eid |
280 if tostate is None: |
299 if tostate is None: |
281 self.req.execute('INSERT SubWorkflowExitPoint X: T subworkflow_exit X, ' |
300 self._cw.execute('INSERT SubWorkflowExitPoint X: T subworkflow_exit X, ' |
282 'X subworkflow_state FS WHERE T eid %(t)s, FS eid %(fs)s', |
301 'X subworkflow_state FS WHERE T eid %(t)s, FS eid %(fs)s', |
283 {'t': self.eid, 'fs': fromstate}, ('t', 'fs')) |
302 {'t': self.eid, 'fs': fromstate}, ('t', 'fs')) |
284 else: |
303 else: |
285 if hasattr(tostate, 'eid'): |
304 if hasattr(tostate, 'eid'): |
286 tostate = tostate.eid |
305 tostate = tostate.eid |
287 self.req.execute('INSERT SubWorkflowExitPoint X: T subworkflow_exit X, ' |
306 self._cw.execute('INSERT SubWorkflowExitPoint X: T subworkflow_exit X, ' |
288 'X subworkflow_state FS, X destination_state TS ' |
307 'X subworkflow_state FS, X destination_state TS ' |
289 'WHERE T eid %(t)s, FS eid %(fs)s, TS eid %(ts)s', |
308 'WHERE T eid %(t)s, FS eid %(fs)s, TS eid %(ts)s', |
290 {'t': self.eid, 'fs': fromstate, 'ts': tostate}, |
309 {'t': self.eid, 'fs': fromstate, 'ts': tostate}, |
291 ('t', 'fs', 'ts')) |
310 ('t', 'fs', 'ts')) |
292 |
311 |
428 return None |
447 return None |
429 |
448 |
430 @cached |
449 @cached |
431 def cwetype_workflow(self): |
450 def cwetype_workflow(self): |
432 """return the default workflow for entities of this type""" |
451 """return the default workflow for entities of this type""" |
433 wfrset = self.req.execute('Any WF WHERE ET default_workflow WF, ' |
452 # XXX CWEType method |
434 'ET name %(et)s', {'et': self.id}) |
453 wfrset = self._cw.execute('Any WF WHERE ET default_workflow WF, ' |
|
454 'ET name %(et)s', {'et': self.__regid__}) |
435 if wfrset: |
455 if wfrset: |
436 return wfrset.get_entity(0, 0) |
456 return wfrset.get_entity(0, 0) |
437 self.warning("can't find any workflow for %s", self.id) |
457 self.warning("can't find any workflow for %s", self.__regid__) |
438 return None |
458 return None |
439 |
459 |
440 def possible_transitions(self, type='normal'): |
460 def possible_transitions(self, type='normal'): |
441 """generates transition that MAY be fired for the given entity, |
461 """generates transition that MAY be fired for the given entity, |
442 expected to be in this state |
462 expected to be in this state |
443 used only by the UI |
463 used only by the UI |
444 """ |
464 """ |
445 if self.current_state is None or self.current_workflow is None: |
465 if self.current_state is None or self.current_workflow is None: |
446 return |
466 return |
447 rset = self.req.execute( |
467 rset = self._cw.execute( |
448 'Any T,TT, TN WHERE S allowed_transition T, S eid %(x)s, ' |
468 'Any T,TT, TN WHERE S allowed_transition T, S eid %(x)s, ' |
449 'T type TT, T type %(type)s, ' |
469 'T type TT, T type %(type)s, ' |
450 'T name TN, T transition_of WF, WF eid %(wfeid)s', |
470 'T name TN, T transition_of WF, WF eid %(wfeid)s', |
451 {'x': self.current_state.eid, 'type': type, |
471 {'x': self.current_state.eid, 'type': type, |
452 'wfeid': self.current_workflow.eid}, 'x') |
472 'wfeid': self.current_workflow.eid}, 'x') |
460 kwargs['comment'] = comment |
480 kwargs['comment'] = comment |
461 if commentformat is not None: |
481 if commentformat is not None: |
462 kwargs['comment_format'] = commentformat |
482 kwargs['comment_format'] = commentformat |
463 kwargs['wf_info_for'] = self |
483 kwargs['wf_info_for'] = self |
464 if treid is not None: |
484 if treid is not None: |
465 kwargs['by_transition'] = self.req.entity_from_eid(treid) |
485 kwargs['by_transition'] = self._cw.entity_from_eid(treid) |
466 if tseid is not None: |
486 if tseid is not None: |
467 kwargs['to_state'] = self.req.entity_from_eid(tseid) |
487 kwargs['to_state'] = self._cw.entity_from_eid(tseid) |
468 return self.req.create_entity('TrInfo', **kwargs) |
488 return self._cw.create_entity('TrInfo', **kwargs) |
469 |
489 |
470 def fire_transition(self, tr, comment=None, commentformat=None): |
490 def fire_transition(self, tr, comment=None, commentformat=None): |
471 """change the entity's state by firing transition of the given name in |
491 """change the entity's state by firing transition of the given name in |
472 entity's workflow |
492 entity's workflow |
473 """ |
493 """ |
474 assert self.current_workflow |
494 assert self.current_workflow |
475 if isinstance(tr, basestring): |
495 if isinstance(tr, basestring): |
476 _tr = self.current_workflow.transition_by_name(tr) |
496 _tr = self.current_workflow.transition_by_name(tr) |
477 assert _tr is not None, 'not a %s transition: %s' % (self.id, tr) |
497 assert _tr is not None, 'not a %s transition: %s' % ( |
|
498 self.__regid__, tr) |
478 tr = _tr |
499 tr = _tr |
479 return self._add_trinfo(comment, commentformat, tr.eid) |
500 return self._add_trinfo(comment, commentformat, tr.eid) |
480 |
501 |
481 def change_state(self, statename, comment=None, commentformat=None, tr=None): |
502 def change_state(self, statename, comment=None, commentformat=None, tr=None): |
482 """change the entity's state to the given state (name or entity) in |
503 """change the entity's state to the given state (name or entity) in |