hooks/workflow.py
changeset 2841 107ba1c45227
parent 2835 04034421b072
child 2847 c2ee28f4d4b1
equal deleted inserted replaced
2840:06daf13195d4 2841:107ba1c45227
     5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
     5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
     6 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
     6 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
     7 """
     7 """
     8 __docformat__ = "restructuredtext en"
     8 __docformat__ = "restructuredtext en"
     9 
     9 
    10 from cubicweb import ValidationError
    10 from datetime import datetime
       
    11 
       
    12 from cubicweb import RepositoryError, ValidationError
    11 from cubicweb.interfaces import IWorkflowable
    13 from cubicweb.interfaces import IWorkflowable
    12 from cubicweb.selectors import entity_implements
    14 from cubicweb.selectors import entity_implements
    13 from cubicweb.server.hook import Hook, match_rtype
    15 from cubicweb.server import hook
    14 from cubicweb.server.pool import PreCommitOperation
    16 
    15 from cubicweb.server.hookhelper import previous_state
    17 
       
    18 def previous_state(session, eid):
       
    19     """return the state of the entity with the given eid,
       
    20     usually since it's changing in the current transaction. Due to internal
       
    21     relation hooks, the relation may has been deleted at this point, so
       
    22     we have handle that
       
    23     """
       
    24     if session.added_in_transaction(eid):
       
    25         return
       
    26     pending = session.transaction_data.get('pendingrelations', ())
       
    27     for eidfrom, rtype, eidto in reversed(pending):
       
    28         if rtype == 'in_state' and eidfrom == eid:
       
    29             rset = session.execute('Any S,N WHERE S eid %(x)s, S name N',
       
    30                                    {'x': eidto}, 'x')
       
    31             return rset.get_entity(0, 0)
       
    32     rset = session.execute('Any S,N WHERE X eid %(x)s, X in_state S, S name N',
       
    33                            {'x': eid}, 'x')
       
    34     if rset:
       
    35         return rset.get_entity(0, 0)
    16 
    36 
    17 
    37 
    18 def relation_deleted(session, eidfrom, rtype, eidto):
    38 def relation_deleted(session, eidfrom, rtype, eidto):
    19     session.transaction_data.setdefault('pendingrelations', []).append(
    39     session.transaction_data.setdefault('pendingrelations', []).append(
    20         (eidfrom, rtype, eidto))
    40         (eidfrom, rtype, eidto))
    21 
    41 
    22 
    42 
    23 class _SetInitialStateOp(PreCommitOperation):
    43 class _SetInitialStateOp(hook.Operation):
    24     """make initial state be a default state"""
    44     """make initial state be a default state"""
    25 
    45 
    26     def precommit_event(self):
    46     def precommit_event(self):
    27         session = self.session
    47         session = self.session
    28         entity = self.entity
    48         entity = self.entity
    29         # if there is an initial state and the entity's state is not set,
    49         # if there is an initial state and the entity's state is not set,
    30         # use the initial state as a default state
    50         # use the initial state as a default state
    31         pendingeids = session.transaction_data.get('pendingeids', ())
    51         if not session.deleted_in_transaction(entity.eid) and not entity.in_state:
    32         if not entity.eid in pendingeids and not entity.in_state:
       
    33             rset = session.execute('Any S WHERE ET initial_state S, ET name %(name)s',
    52             rset = session.execute('Any S WHERE ET initial_state S, ET name %(name)s',
    34                                    {'name': entity.id})
    53                                    {'name': entity.id})
    35             if rset:
    54             if rset:
    36                 session.add_relation(entity.eid, 'in_state', rset[0][0])
    55                 session.add_relation(entity.eid, 'in_state', rset[0][0])
    37 
    56 
       
    57 class WorkflowHook(hook.Hook):
       
    58     __abstract__ = True
       
    59     category = 'worfklow'
    38 
    60 
    39 class SetInitialStateHook(Hook):
    61 
       
    62 class SetInitialStateHook(WorkflowHook):
    40     __id__ = 'wfsetinitial'
    63     __id__ = 'wfsetinitial'
    41     __select__ = Hook.__select__ & entity_implements(IWorkflowable)
    64     __select__ = WorkflowHook.__select__ & entity_implements(IWorkflowable)
    42     category = 'worfklow'
       
    43     events = ('after_add_entity',)
    65     events = ('after_add_entity',)
    44 
    66 
    45     def __call__(self):
    67     def __call__(self):
    46         _SetInitialStateOp(self.cw_req, entity=self.entity)
    68         _SetInitialStateOp(self.cw_req, entity=self.entity)
    47 
    69 
    48 
    70 
    49 class PrepareStateChangeHook(Hook):
    71 class PrepareStateChangeHook(WorkflowHook):
    50     """record previous state information"""
    72     """record previous state information"""
    51     __id__ = 'cwdelstate'
    73     __id__ = 'cwdelstate'
    52     __select__ = Hook.__select__ & match_rtype('in_state')
    74     __select__ = WorkflowHook.__select__ & hook.match_rtype('in_state')
    53     category = 'worfklow'
       
    54     events = ('before_delete_relation',)
    75     events = ('before_delete_relation',)
    55 
    76 
    56     def __call__(self):
    77     def __call__(self):
    57         self.cw_req.transaction_data.setdefault('pendingrelations', []).append(
    78         self.cw_req.transaction_data.setdefault('pendingrelations', []).append(
    58             (self.eidfrom, self.rtype, self.eidto))
    79             (self.eidfrom, self.rtype, self.eidto))
   102             rql += ', T from_state FS'
   123             rql += ', T from_state FS'
   103             restriction.append('FS eid %(fs)s')
   124             restriction.append('FS eid %(fs)s')
   104             args['fs'] = state.eid
   125             args['fs'] = state.eid
   105         rql = '%s WHERE %s' % (rql, ', '.join(restriction))
   126         rql = '%s WHERE %s' % (rql, ', '.join(restriction))
   106         session.unsafe_execute(rql, args, 'e')
   127         session.unsafe_execute(rql, args, 'e')
       
   128 
       
   129 
       
   130 class SetModificationDateOnStateChange(WorkflowHook):
       
   131     """update entity's modification date after changing its state"""
       
   132     __id__ = 'wfsyncmdate'
       
   133     __select__ = WorkflowHook.__select__ & hook.match_rtype('in_state')
       
   134     events = ('after_add_relation',)
       
   135 
       
   136     def __call__(self):
       
   137         if self.cw_req.added_in_transaction(self.eidfrom):
       
   138             # new entity, not needed
       
   139             return
       
   140         entity = self.cw_req.entity_from_eid(self.eidfrom)
       
   141         try:
       
   142             entity.set_attributes(modification_date=datetime.now())
       
   143         except RepositoryError, ex:
       
   144             # usually occurs if entity is coming from a read-only source
       
   145             # (eg ldap user)
       
   146             self.warning('cant change modification date for %s: %s', entity, ex)