entities/wfobjs.py
brancholdstable
changeset 4985 02b52bf9f5f8
parent 4716 55b6a3262071
child 4835 13b0b96d7982
equal deleted inserted replaced
4563:c25da7573ebd 4985:02b52bf9f5f8
    13 from logilab.common.deprecation import deprecated
    13 from logilab.common.deprecation import deprecated
    14 from logilab.common.compat import any
    14 from logilab.common.compat import any
    15 
    15 
    16 from cubicweb.entities import AnyEntity, fetch_config
    16 from cubicweb.entities import AnyEntity, fetch_config
    17 from cubicweb.interfaces import IWorkflowable
    17 from cubicweb.interfaces import IWorkflowable
    18 from cubicweb.common.mixins import MI_REL_TRIGGERS
    18 from cubicweb.mixins import MI_REL_TRIGGERS
    19 
    19 
    20 class WorkflowException(Exception): pass
    20 class WorkflowException(Exception): pass
    21 
    21 
    22 class Workflow(AnyEntity):
    22 class Workflow(AnyEntity):
    23     id = 'Workflow'
    23     __regid__ = 'Workflow'
    24 
    24 
    25     @property
    25     @property
    26     def initial(self):
    26     def initial(self):
    27         """return the initial state for this workflow"""
    27         """return the initial state for this workflow"""
    28         return self.initial_state and self.initial_state[0] or None
    28         return self.initial_state and self.initial_state[0] or None
    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=(),
   144         """add a workflow transition to this workflow"""
   144         """add a workflow transition to this workflow"""
   145         tr = self._add_transition('WorkflowTransition', name, fromstates,
   145         tr = self._add_transition('WorkflowTransition', name, fromstates,
   146                                   requiredgroups, conditions, **kwargs)
   146                                   requiredgroups, conditions, **kwargs)
   147         if hasattr(subworkflow, 'eid'):
   147         if hasattr(subworkflow, 'eid'):
   148             subworkflow = subworkflow.eid
   148             subworkflow = subworkflow.eid
   149         assert self.req.execute('SET T subworkflow WF WHERE WF eid %(wf)s,T eid %(t)s',
   149         assert self._cw.execute('SET T subworkflow WF WHERE WF eid %(wf)s,T eid %(t)s',
   150                                 {'t': tr.eid, 'wf': subworkflow}, ('wf', 't'))
   150                                 {'t': tr.eid, 'wf': subworkflow}, ('wf', 't'))
   151         for fromstate, tostate in exitpoints:
   151         for fromstate, tostate in exitpoints:
   152             tr.add_exit_point(fromstate, tostate)
   152             tr.add_exit_point(fromstate, tostate)
   153         return tr
   153         return tr
   154 
   154 
   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):
   193     def may_be_fired(self, eid):
   193     def may_be_fired(self, eid):
   194         """return true if the logged user may fire this transition
   194         """return true if the logged user may fire this transition
   195 
   195 
   196         `eid` is the eid of the object on which we may fire the transition
   196         `eid` is the eid of the object on which we may fire the transition
   197         """
   197         """
   198         user = self.req.user
   198         user = self._cw.user
   199         # check user is at least in one of the required groups if any
   199         # check user is at least in one of the required groups if any
   200         groups = frozenset(g.name for g in self.require_group)
   200         groups = frozenset(g.name for g in self.require_group)
   201         if groups:
   201         if groups:
   202             matches = user.matching_groups(groups)
   202             matches = user.matching_groups(groups)
   203             if matches:
   203             if matches:
   205             if 'owners' in groups and user.owns(eid):
   205             if 'owners' in groups and user.owns(eid):
   206                 return True
   206                 return True
   207         # check one of the rql expression conditions matches if any
   207         # check one of the rql expression conditions matches if any
   208         if self.condition:
   208         if self.condition:
   209             for rqlexpr in self.condition:
   209             for rqlexpr in self.condition:
   210                 if rqlexpr.check_expression(self.req, eid):
   210                 if rqlexpr.check_expression(self._cw, eid):
   211                     return True
   211                     return True
   212         if self.condition or groups:
   212         if self.condition or groups:
   213             return False
   213             return False
   214         return True
   214         return True
   215 
   215 
   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 
   299         except KeyError:
   318         except KeyError:
   300             return None
   319             return None
   301         if tostateeid is None:
   320         if tostateeid is None:
   302             # go back to state from which we've entered the subworkflow
   321             # go back to state from which we've entered the subworkflow
   303             return entity.subworkflow_input_trinfo().previous_state
   322             return entity.subworkflow_input_trinfo().previous_state
   304         return self.req.entity_from_eid(tostateeid)
   323         return self._cw.entity_from_eid(tostateeid)
   305 
   324 
   306     @cached
   325     @cached
   307     def exit_points(self):
   326     def exit_points(self):
   308         result = {}
   327         result = {}
   309         for ep in self.subworkflow_exit:
   328         for ep in self.subworkflow_exit:
   310             result[ep.subwf_state.eid] = ep.destination and ep.destination.eid
   329             result[ep.subwf_state.eid] = ep.destination and ep.destination.eid
   311         return result
   330         return result
   312 
   331 
   313     def clear_all_caches(self):
   332     def clear_all_caches(self):
   314         super(WorkflowableMixIn, self).clear_all_caches()
   333         super(WorkflowTransition, self).clear_all_caches()
   315         clear_cache(self, 'exit_points')
   334         clear_cache(self, 'exit_points')
   316 
   335 
   317 
   336 
   318 class SubWorkflowExitPoint(AnyEntity):
   337 class SubWorkflowExitPoint(AnyEntity):
   319     """customized class for SubWorkflowExitPoint entities"""
   338     """customized class for SubWorkflowExitPoint entities"""
   320     id = 'SubWorkflowExitPoint'
   339     __regid__ = 'SubWorkflowExitPoint'
   321 
   340 
   322     @property
   341     @property
   323     def subwf_state(self):
   342     def subwf_state(self):
   324         return self.subworkflow_state[0]
   343         return self.subworkflow_state[0]
   325 
   344 
   331         return self.reverse_subworkflow_exit[0]
   350         return self.reverse_subworkflow_exit[0]
   332 
   351 
   333 
   352 
   334 class State(AnyEntity):
   353 class State(AnyEntity):
   335     """customized class for State entities"""
   354     """customized class for State entities"""
   336     id = 'State'
   355     __regid__ = 'State'
   337     fetch_attrs, fetch_order = fetch_config(['name'])
   356     fetch_attrs, fetch_order = fetch_config(['name'])
   338     rest_attr = 'eid'
   357     rest_attr = 'eid'
   339 
   358 
   340     @property
   359     @property
   341     def workflow(self):
   360     def workflow(self):
   347 
   366 
   348 
   367 
   349 class TrInfo(AnyEntity):
   368 class TrInfo(AnyEntity):
   350     """customized class for Transition information entities
   369     """customized class for Transition information entities
   351     """
   370     """
   352     id = 'TrInfo'
   371     __regid__ = 'TrInfo'
   353     fetch_attrs, fetch_order = fetch_config(['creation_date', 'comment'],
   372     fetch_attrs, fetch_order = fetch_config(['creation_date', 'comment'],
   354                                             pclass=None) # don't want modification_date
   373                                             pclass=None) # don't want modification_date
   355     @property
   374     @property
   356     def for_entity(self):
   375     def for_entity(self):
   357         return self.wf_info_for[0]
   376         return self.wf_info_for[0]
   408     @property
   427     @property
   409     def printable_state(self):
   428     def printable_state(self):
   410         """return current state name translated to context's language"""
   429         """return current state name translated to context's language"""
   411         state = self.current_state
   430         state = self.current_state
   412         if state:
   431         if state:
   413             return self.req._(state.name)
   432             return self._cw._(state.name)
   414         return u''
   433         return u''
   415 
   434 
   416     @property
   435     @property
   417     def workflow_history(self):
   436     def workflow_history(self):
   418         """return the workflow history for this entity (eg ordered list of
   437         """return the workflow history for this entity (eg ordered list of
   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
   492                 warn('[3.5] give a state name', DeprecationWarning)
   513                 warn('[3.5] give a state name', DeprecationWarning)
   493                 state = self.current_workflow.state_by_eid(statename)
   514                 state = self.current_workflow.state_by_eid(statename)
   494             else:
   515             else:
   495                 state = self.current_workflow.state_by_name(statename)
   516                 state = self.current_workflow.state_by_name(statename)
   496             if state is None:
   517             if state is None:
   497                 raise WorkflowException('not a %s state: %s' % (self.id,
   518                 raise WorkflowException('not a %s state: %s' % (self.__regid__,
   498                                                                 statename))
   519                                                                 statename))
   499             stateeid = state.eid
   520             stateeid = state.eid
   500         # XXX try to find matching transition?
   521         # XXX try to find matching transition?
   501         return self._add_trinfo(comment, commentformat, tr and tr.eid, stateeid)
   522         return self._add_trinfo(comment, commentformat, tr and tr.eid, stateeid)
   502 
   523 
   530 
   551 
   531     def clear_all_caches(self):
   552     def clear_all_caches(self):
   532         super(WorkflowableMixIn, self).clear_all_caches()
   553         super(WorkflowableMixIn, self).clear_all_caches()
   533         clear_cache(self, 'cwetype_workflow')
   554         clear_cache(self, 'cwetype_workflow')
   534 
   555 
   535     @deprecated('get transition from current workflow and use its may_be_fired method')
   556     @deprecated('[3.5] get transition from current workflow and use its may_be_fired method')
   536     def can_pass_transition(self, trname):
   557     def can_pass_transition(self, trname):
   537         """return the Transition instance if the current user can fire the
   558         """return the Transition instance if the current user can fire the
   538         transition with the given name, else None
   559         transition with the given name, else None
   539         """
   560         """
   540         tr = self.current_workflow and self.current_workflow.transition_by_name(trname)
   561         tr = self.current_workflow and self.current_workflow.transition_by_name(trname)
   541         if tr and tr.may_be_fired(self.eid):
   562         if tr and tr.may_be_fired(self.eid):
   542             return tr
   563             return tr
   543 
   564 
   544     @property
   565     @property
   545     @deprecated('use printable_state')
   566     @deprecated('[3.5] use printable_state')
   546     def displayable_state(self):
   567     def displayable_state(self):
   547         return self.req._(self.state)
   568         return self._cw._(self.state)
   548 
   569 
   549 MI_REL_TRIGGERS[('in_state', 'subject')] = WorkflowableMixIn
   570 MI_REL_TRIGGERS[('in_state', 'subject')] = WorkflowableMixIn