changeset 3406 | e343f37f7013 |
parent 3405 | 9d31c9cb8103 |
child 3451 | 6b46d73823f5 |
3405:9d31c9cb8103 | 3406:e343f37f7013 |
---|---|
48 # infinite loop safety belt |
48 # infinite loop safety belt |
49 if _done is None: |
49 if _done is None: |
50 _done = set() |
50 _done = set() |
51 yield self |
51 yield self |
52 _done.add(self.eid) |
52 _done.add(self.eid) |
53 for tr in self.req.execute('Any T WHERE T is WorkflowTransition, ' |
53 for tr in self._cw.execute('Any T WHERE T is WorkflowTransition, ' |
54 'T transition_of WF, WF eid %(wf)s', |
54 'T transition_of WF, WF eid %(wf)s', |
55 {'wf': self.eid}).entities(): |
55 {'wf': self.eid}).entities(): |
56 if tr.subwf.eid in _done: |
56 if tr.subwf.eid in _done: |
57 continue |
57 continue |
58 for subwf in tr.subwf.iter_workflows(_done): |
58 for subwf in tr.subwf.iter_workflows(_done): |
59 yield subwf |
59 yield subwf |
60 |
60 |
61 # state / transitions accessors ############################################ |
61 # state / transitions accessors ############################################ |
62 |
62 |
63 def state_by_name(self, statename): |
63 def state_by_name(self, statename): |
64 rset = self.req.execute('Any S, SN WHERE S name SN, S name %(n)s, ' |
64 rset = self._cw.execute('Any S, SN WHERE S name SN, S name %(n)s, ' |
65 'S state_of WF, WF eid %(wf)s', |
65 'S state_of WF, WF eid %(wf)s', |
66 {'n': statename, 'wf': self.eid}, 'wf') |
66 {'n': statename, 'wf': self.eid}, 'wf') |
67 if rset: |
67 if rset: |
68 return rset.get_entity(0, 0) |
68 return rset.get_entity(0, 0) |
69 return None |
69 return None |
70 |
70 |
71 def state_by_eid(self, eid): |
71 def state_by_eid(self, eid): |
72 rset = self.req.execute('Any S, SN WHERE S name SN, S eid %(s)s, ' |
72 rset = self._cw.execute('Any S, SN WHERE S name SN, S eid %(s)s, ' |
73 'S state_of WF, WF eid %(wf)s', |
73 'S state_of WF, WF eid %(wf)s', |
74 {'s': eid, 'wf': self.eid}, ('wf', 's')) |
74 {'s': eid, 'wf': self.eid}, ('wf', 's')) |
75 if rset: |
75 if rset: |
76 return rset.get_entity(0, 0) |
76 return rset.get_entity(0, 0) |
77 return None |
77 return None |
78 |
78 |
79 def transition_by_name(self, trname): |
79 def transition_by_name(self, trname): |
80 rset = self.req.execute('Any T, TN WHERE T name TN, T name %(n)s, ' |
80 rset = self._cw.execute('Any T, TN WHERE T name TN, T name %(n)s, ' |
81 'T transition_of WF, WF eid %(wf)s', |
81 'T transition_of WF, WF eid %(wf)s', |
82 {'n': trname, 'wf': self.eid}, 'wf') |
82 {'n': trname, 'wf': self.eid}, 'wf') |
83 if rset: |
83 if rset: |
84 return rset.get_entity(0, 0) |
84 return rset.get_entity(0, 0) |
85 return None |
85 return None |
86 |
86 |
87 def transition_by_eid(self, eid): |
87 def transition_by_eid(self, eid): |
88 rset = self.req.execute('Any T, TN WHERE T name TN, T eid %(t)s, ' |
88 rset = self._cw.execute('Any T, TN WHERE T name TN, T eid %(t)s, ' |
89 'T transition_of WF, WF eid %(wf)s', |
89 'T transition_of WF, WF eid %(wf)s', |
90 {'t': eid, 'wf': self.eid}, ('wf', 't')) |
90 {'t': eid, 'wf': self.eid}, ('wf', 't')) |
91 if rset: |
91 if rset: |
92 return rset.get_entity(0, 0) |
92 return rset.get_entity(0, 0) |
93 return None |
93 return None |
94 |
94 |
95 # wf construction methods ################################################## |
95 # wf construction methods ################################################## |
96 |
96 |
97 def add_state(self, name, initial=False, **kwargs): |
97 def add_state(self, name, initial=False, **kwargs): |
98 """add a state to this workflow""" |
98 """add a state to this workflow""" |
99 state = self.req.create_entity('State', name=unicode(name), **kwargs) |
99 state = self._cw.create_entity('State', name=unicode(name), **kwargs) |
100 self.req.execute('SET S state_of WF WHERE S eid %(s)s, WF eid %(wf)s', |
100 self._cw.execute('SET S state_of WF WHERE S eid %(s)s, WF eid %(wf)s', |
101 {'s': state.eid, 'wf': self.eid}, ('s', 'wf')) |
101 {'s': state.eid, 'wf': self.eid}, ('s', 'wf')) |
102 if initial: |
102 if initial: |
103 assert not self.initial |
103 assert not self.initial |
104 self.req.execute('SET WF initial_state S ' |
104 self._cw.execute('SET WF initial_state S ' |
105 'WHERE S eid %(s)s, WF eid %(wf)s', |
105 'WHERE S eid %(s)s, WF eid %(wf)s', |
106 {'s': state.eid, 'wf': self.eid}, ('s', 'wf')) |
106 {'s': state.eid, 'wf': self.eid}, ('s', 'wf')) |
107 return state |
107 return state |
108 |
108 |
109 def _add_transition(self, trtype, name, fromstates, |
109 def _add_transition(self, trtype, name, fromstates, |
110 requiredgroups=(), conditions=(), **kwargs): |
110 requiredgroups=(), conditions=(), **kwargs): |
111 tr = self.req.create_entity(trtype, name=unicode(name), **kwargs) |
111 tr = self._cw.create_entity(trtype, name=unicode(name), **kwargs) |
112 self.req.execute('SET T transition_of WF ' |
112 self._cw.execute('SET T transition_of WF ' |
113 'WHERE T eid %(t)s, WF eid %(wf)s', |
113 'WHERE T eid %(t)s, WF eid %(wf)s', |
114 {'t': tr.eid, 'wf': self.eid}, ('t', 'wf')) |
114 {'t': tr.eid, 'wf': self.eid}, ('t', 'wf')) |
115 assert fromstates, fromstates |
115 assert fromstates, fromstates |
116 if not isinstance(fromstates, (tuple, list)): |
116 if not isinstance(fromstates, (tuple, list)): |
117 fromstates = (fromstates,) |
117 fromstates = (fromstates,) |
118 for state in fromstates: |
118 for state in fromstates: |
119 if hasattr(state, 'eid'): |
119 if hasattr(state, 'eid'): |
120 state = state.eid |
120 state = state.eid |
121 self.req.execute('SET S allowed_transition T ' |
121 self._cw.execute('SET S allowed_transition T ' |
122 'WHERE S eid %(s)s, T eid %(t)s', |
122 'WHERE S eid %(s)s, T eid %(t)s', |
123 {'s': state, 't': tr.eid}, ('s', 't')) |
123 {'s': state, 't': tr.eid}, ('s', 't')) |
124 tr.set_transition_permissions(requiredgroups, conditions, reset=False) |
124 tr.set_transition_permissions(requiredgroups, conditions, reset=False) |
125 return tr |
125 return tr |
126 |
126 |
129 """add a transition to this workflow from some state(s) to another""" |
129 """add a transition to this workflow from some state(s) to another""" |
130 tr = self._add_transition('Transition', name, fromstates, |
130 tr = self._add_transition('Transition', name, fromstates, |
131 requiredgroups, conditions, **kwargs) |
131 requiredgroups, conditions, **kwargs) |
132 if hasattr(tostate, 'eid'): |
132 if hasattr(tostate, 'eid'): |
133 tostate = tostate.eid |
133 tostate = tostate.eid |
134 self.req.execute('SET T destination_state S ' |
134 self._cw.execute('SET T destination_state S ' |
135 'WHERE S eid %(s)s, T eid %(t)s', |
135 'WHERE S eid %(s)s, T eid %(t)s', |
136 {'t': tr.eid, 's': tostate}, ('s', 't')) |
136 {'t': tr.eid, 's': tostate}, ('s', 't')) |
137 return tr |
137 return tr |
138 |
138 |
139 def add_wftransition(self, name, subworkflow, fromstates, exitpoints, |
139 def add_wftransition(self, name, subworkflow, fromstates, exitpoints, |
141 """add a workflow transition to this workflow""" |
141 """add a workflow transition to this workflow""" |
142 tr = self._add_transition('WorkflowTransition', name, fromstates, |
142 tr = self._add_transition('WorkflowTransition', name, fromstates, |
143 requiredgroups, conditions, **kwargs) |
143 requiredgroups, conditions, **kwargs) |
144 if hasattr(subworkflow, 'eid'): |
144 if hasattr(subworkflow, 'eid'): |
145 subworkflow = subworkflow.eid |
145 subworkflow = subworkflow.eid |
146 self.req.execute('SET T subworkflow WF WHERE WF eid %(wf)s,T eid %(t)s', |
146 self._cw.execute('SET T subworkflow WF WHERE WF eid %(wf)s,T eid %(t)s', |
147 {'t': tr.eid, 'wf': subworkflow}, ('wf', 't')) |
147 {'t': tr.eid, 'wf': subworkflow}, ('wf', 't')) |
148 for fromstate, tostate in exitpoints: |
148 for fromstate, tostate in exitpoints: |
149 tr.add_exit_point(fromstate, tostate) |
149 tr.add_exit_point(fromstate, tostate) |
150 return tr |
150 return tr |
151 |
151 |
176 def may_be_fired(self, eid): |
176 def may_be_fired(self, eid): |
177 """return true if the logged user may fire this transition |
177 """return true if the logged user may fire this transition |
178 |
178 |
179 `eid` is the eid of the object on which we may fire the transition |
179 `eid` is the eid of the object on which we may fire the transition |
180 """ |
180 """ |
181 user = self.req.user |
181 user = self._cw.user |
182 # check user is at least in one of the required groups if any |
182 # check user is at least in one of the required groups if any |
183 groups = frozenset(g.name for g in self.require_group) |
183 groups = frozenset(g.name for g in self.require_group) |
184 if groups: |
184 if groups: |
185 matches = user.matching_groups(groups) |
185 matches = user.matching_groups(groups) |
186 if matches: |
186 if matches: |
188 if 'owners' in groups and user.owns(eid): |
188 if 'owners' in groups and user.owns(eid): |
189 return True |
189 return True |
190 # check one of the rql expression conditions matches if any |
190 # check one of the rql expression conditions matches if any |
191 if self.condition: |
191 if self.condition: |
192 for rqlexpr in self.condition: |
192 for rqlexpr in self.condition: |
193 if rqlexpr.check_expression(self.req, eid): |
193 if rqlexpr.check_expression(self._cw, eid): |
194 return True |
194 return True |
195 if self.condition or groups: |
195 if self.condition or groups: |
196 return False |
196 return False |
197 return True |
197 return True |
198 |
198 |
208 reset=True): |
208 reset=True): |
209 """set or add (if `reset` is False) groups and conditions for this |
209 """set or add (if `reset` is False) groups and conditions for this |
210 transition |
210 transition |
211 """ |
211 """ |
212 if reset: |
212 if reset: |
213 self.req.execute('DELETE T require_group G WHERE T eid %(x)s', |
213 self._cw.execute('DELETE T require_group G WHERE T eid %(x)s', |
214 {'x': self.eid}, 'x') |
214 {'x': self.eid}, 'x') |
215 self.req.execute('DELETE T condition R WHERE T eid %(x)s', |
215 self._cw.execute('DELETE T condition R WHERE T eid %(x)s', |
216 {'x': self.eid}, 'x') |
216 {'x': self.eid}, 'x') |
217 for gname in requiredgroups: |
217 for gname in requiredgroups: |
218 rset = self.req.execute('SET T require_group G ' |
218 rset = self._cw.execute('SET T require_group G ' |
219 'WHERE T eid %(x)s, G name %(gn)s', |
219 'WHERE T eid %(x)s, G name %(gn)s', |
220 {'x': self.eid, 'gn': gname}, 'x') |
220 {'x': self.eid, 'gn': gname}, 'x') |
221 assert rset, '%s is not a known group' % gname |
221 assert rset, '%s is not a known group' % gname |
222 if isinstance(conditions, basestring): |
222 if isinstance(conditions, basestring): |
223 conditions = (conditions,) |
223 conditions = (conditions,) |
224 for expr in conditions: |
224 for expr in conditions: |
225 if isinstance(expr, str): |
225 if isinstance(expr, str): |
226 expr = unicode(expr) |
226 expr = unicode(expr) |
227 self.req.execute('INSERT RQLExpression X: X exprtype "ERQLExpression", ' |
227 self._cw.execute('INSERT RQLExpression X: X exprtype "ERQLExpression", ' |
228 'X expression %(expr)s, T condition X ' |
228 'X expression %(expr)s, T condition X ' |
229 'WHERE T eid %(x)s', |
229 'WHERE T eid %(x)s', |
230 {'x': self.eid, 'expr': expr}, 'x') |
230 {'x': self.eid, 'expr': expr}, 'x') |
231 # XXX clear caches? |
231 # XXX clear caches? |
232 |
232 |
253 def add_exit_point(self, fromstate, tostate): |
253 def add_exit_point(self, fromstate, tostate): |
254 if hasattr(fromstate, 'eid'): |
254 if hasattr(fromstate, 'eid'): |
255 fromstate = fromstate.eid |
255 fromstate = fromstate.eid |
256 if hasattr(tostate, 'eid'): |
256 if hasattr(tostate, 'eid'): |
257 tostate = tostate.eid |
257 tostate = tostate.eid |
258 self.req.execute('INSERT SubWorkflowExitPoint X: T subworkflow_exit X, ' |
258 self._cw.execute('INSERT SubWorkflowExitPoint X: T subworkflow_exit X, ' |
259 'X subworkflow_state FS, X destination_state TS ' |
259 'X subworkflow_state FS, X destination_state TS ' |
260 'WHERE T eid %(t)s, FS eid %(fs)s, TS eid %(ts)s', |
260 'WHERE T eid %(t)s, FS eid %(fs)s, TS eid %(ts)s', |
261 {'t': self.eid, 'fs': fromstate, 'ts': tostate}, |
261 {'t': self.eid, 'fs': fromstate, 'ts': tostate}, |
262 ('t', 'fs', 'ts')) |
262 ('t', 'fs', 'ts')) |
263 |
263 |
265 """if state is an exit point, return its associated destination state""" |
265 """if state is an exit point, return its associated destination state""" |
266 if hasattr(state, 'eid'): |
266 if hasattr(state, 'eid'): |
267 state = state.eid |
267 state = state.eid |
268 stateeid = self.exit_points().get(state) |
268 stateeid = self.exit_points().get(state) |
269 if stateeid is not None: |
269 if stateeid is not None: |
270 return self.req.entity_from_eid(stateeid) |
270 return self._cw.entity_from_eid(stateeid) |
271 return None |
271 return None |
272 |
272 |
273 @cached |
273 @cached |
274 def exit_points(self): |
274 def exit_points(self): |
275 result = {} |
275 result = {} |
382 @property |
382 @property |
383 def printable_state(self): |
383 def printable_state(self): |
384 """return current state name translated to context's language""" |
384 """return current state name translated to context's language""" |
385 state = self.current_state |
385 state = self.current_state |
386 if state: |
386 if state: |
387 return self.req._(state.name) |
387 return self._cw._(state.name) |
388 return u'' |
388 return u'' |
389 |
389 |
390 @property |
390 @property |
391 def workflow_history(self): |
391 def workflow_history(self): |
392 """return the workflow history for this entity (eg ordered list of |
392 """return the workflow history for this entity (eg ordered list of |
400 |
400 |
401 @cached |
401 @cached |
402 def cwetype_workflow(self): |
402 def cwetype_workflow(self): |
403 """return the default workflow for entities of this type""" |
403 """return the default workflow for entities of this type""" |
404 # XXX CWEType method |
404 # XXX CWEType method |
405 wfrset = self.req.execute('Any WF WHERE X is ET, X eid %(x)s, ' |
405 wfrset = self._cw.execute('Any WF WHERE X is ET, X eid %(x)s, ' |
406 'WF workflow_of ET', {'x': self.eid}, 'x') |
406 'WF workflow_of ET', {'x': self.eid}, 'x') |
407 if len(wfrset) == 1: |
407 if len(wfrset) == 1: |
408 return wfrset.get_entity(0, 0) |
408 return wfrset.get_entity(0, 0) |
409 if len(wfrset) > 1: |
409 if len(wfrset) > 1: |
410 for wf in wfrset.entities(): |
410 for wf in wfrset.entities(): |
419 """generates transition that MAY be fired for the given entity, |
419 """generates transition that MAY be fired for the given entity, |
420 expected to be in this state |
420 expected to be in this state |
421 """ |
421 """ |
422 if self.current_state is None or self.current_workflow is None: |
422 if self.current_state is None or self.current_workflow is None: |
423 return |
423 return |
424 rset = self.req.execute( |
424 rset = self._cw.execute( |
425 'Any T,N WHERE S allowed_transition T, S eid %(x)s, ' |
425 'Any T,N WHERE S allowed_transition T, S eid %(x)s, ' |
426 'T name N, T transition_of WF, WF eid %(wfeid)s', |
426 'T name N, T transition_of WF, WF eid %(wfeid)s', |
427 {'x': self.current_state.eid, |
427 {'x': self.current_state.eid, |
428 'wfeid': self.current_workflow.eid}, 'x') |
428 'wfeid': self.current_workflow.eid}, 'x') |
429 for tr in rset.entities(): |
429 for tr in rset.entities(): |
442 args.append( ('by_transition', 'T') ) |
442 args.append( ('by_transition', 'T') ) |
443 kwargs['T'] = treid |
443 kwargs['T'] = treid |
444 if tseid is not None: |
444 if tseid is not None: |
445 args.append( ('to_state', 'S') ) |
445 args.append( ('to_state', 'S') ) |
446 kwargs['S'] = tseid |
446 kwargs['S'] = tseid |
447 return self.req.create_entity('TrInfo', *args, **kwargs) |
447 return self._cw.create_entity('TrInfo', *args, **kwargs) |
448 |
448 |
449 def fire_transition(self, trname, comment=None, commentformat=None): |
449 def fire_transition(self, trname, comment=None, commentformat=None): |
450 """change the entity's state by firing transition of the given name in |
450 """change the entity's state by firing transition of the given name in |
451 entity's workflow |
451 entity's workflow |
452 """ |
452 """ |
512 return tr |
512 return tr |
513 |
513 |
514 @property |
514 @property |
515 @deprecated('[3.5] use printable_state') |
515 @deprecated('[3.5] use printable_state') |
516 def displayable_state(self): |
516 def displayable_state(self): |
517 return self.req._(self.state) |
517 return self._cw._(self.state) |
518 |
518 |
519 MI_REL_TRIGGERS[('in_state', 'subject')] = WorkflowableMixIn |
519 MI_REL_TRIGGERS[('in_state', 'subject')] = WorkflowableMixIn |