entities/wfobjs.py
changeset 3406 e343f37f7013
parent 3405 9d31c9cb8103
child 3451 6b46d73823f5
equal deleted inserted replaced
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