hooks/workflow.py
author Sylvain Thénault <sylvain.thenault@logilab.fr>
Fri, 14 Aug 2009 09:26:41 +0200
changeset 2835 04034421b072
child 2841 107ba1c45227
permissions -rw-r--r--
[hooks] major refactoring: * they are no "proper" appobject, selected when an event is fired according to its context * new module cubicweb.server.hook containing the Hook class and Operation class * deprecated SystemHook and PreCommitOperation classes * rewrite core server hooks as appobjects in the cubicweb/hooks directory -> deprecates hooksmanager, remove hooks, schemahooks, securityhooks cubicweb.server sub-modules -> new cubicweb.hooks sub-package -> get back to a (somewhat) working state
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
2835
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
     1
"""Core hooks: workflow related hooks
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
     2
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
     3
:organization: Logilab
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
     4
:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
     5
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
     6
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
     7
"""
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
     8
__docformat__ = "restructuredtext en"
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
     9
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    10
from cubicweb import ValidationError
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    11
from cubicweb.interfaces import IWorkflowable
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    12
from cubicweb.selectors import entity_implements
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    13
from cubicweb.server.hook import Hook, match_rtype
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    14
from cubicweb.server.pool import PreCommitOperation
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    15
from cubicweb.server.hookhelper import previous_state
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    16
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    17
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    18
def relation_deleted(session, eidfrom, rtype, eidto):
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    19
    session.transaction_data.setdefault('pendingrelations', []).append(
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    20
        (eidfrom, rtype, eidto))
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    21
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    22
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    23
class _SetInitialStateOp(PreCommitOperation):
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    24
    """make initial state be a default state"""
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    25
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    26
    def precommit_event(self):
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    27
        session = self.session
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    28
        entity = self.entity
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    29
        # if there is an initial state and the entity's state is not set,
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    30
        # use the initial state as a default state
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    31
        pendingeids = session.transaction_data.get('pendingeids', ())
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    32
        if not entity.eid in pendingeids and not entity.in_state:
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    33
            rset = session.execute('Any S WHERE ET initial_state S, ET name %(name)s',
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    34
                                   {'name': entity.id})
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    35
            if rset:
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    36
                session.add_relation(entity.eid, 'in_state', rset[0][0])
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    37
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    38
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    39
class SetInitialStateHook(Hook):
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    40
    __id__ = 'wfsetinitial'
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    41
    __select__ = Hook.__select__ & entity_implements(IWorkflowable)
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    42
    category = 'worfklow'
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    43
    events = ('after_add_entity',)
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    44
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    45
    def __call__(self):
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    46
        _SetInitialStateOp(self.cw_req, entity=self.entity)
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    47
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    48
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    49
class PrepareStateChangeHook(Hook):
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    50
    """record previous state information"""
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    51
    __id__ = 'cwdelstate'
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    52
    __select__ = Hook.__select__ & match_rtype('in_state')
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    53
    category = 'worfklow'
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    54
    events = ('before_delete_relation',)
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    55
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    56
    def __call__(self):
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    57
        self.cw_req.transaction_data.setdefault('pendingrelations', []).append(
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    58
            (self.eidfrom, self.rtype, self.eidto))
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    59
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    60
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    61
class FireTransitionHook(PrepareStateChangeHook):
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    62
    """check the transition is allowed and record transition information"""
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    63
    __id__ = 'wffiretransition'
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    64
    events = ('before_add_relation',)
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    65
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    66
    def __call__(self):
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    67
        session = self.cw_req
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    68
        eidfrom = self.eidfrom
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    69
        eidto = self.eidto
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    70
        state = previous_state(session, eidfrom)
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    71
        etype = session.describe(eidfrom)[0]
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    72
        if not (session.is_super_session or 'managers' in session.user.groups):
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    73
            if not state is None:
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    74
                entity = session.entity_from_eid(eidfrom)
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    75
                # we should find at least one transition going to this state
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    76
                try:
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    77
                    iter(state.transitions(entity, eidto)).next()
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    78
                except StopIteration:
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    79
                    msg = session._('transition is not allowed')
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    80
                    raise ValidationError(eidfrom, {'in_state': msg})
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    81
            else:
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    82
                # not a transition
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    83
                # check state is initial state if the workflow defines one
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    84
                isrset = session.unsafe_execute('Any S WHERE ET initial_state S, ET name %(etype)s',
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    85
                                                {'etype': etype})
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    86
                if isrset and not eidto == isrset[0][0]:
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    87
                    msg = session._('not the initial state for this entity')
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    88
                    raise ValidationError(eidfrom, {'in_state': msg})
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    89
        eschema = session.repo.schema[etype]
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    90
        if not 'wf_info_for' in eschema.object_relations():
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    91
            # workflow history not activated for this entity type
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    92
            return
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    93
        rql = 'INSERT TrInfo T: T wf_info_for E, T to_state DS, T comment %(comment)s'
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    94
        args = {'comment': session.get_shared_data('trcomment', None, pop=True),
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    95
                'e': eidfrom, 'ds': eidto}
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    96
        cformat = session.get_shared_data('trcommentformat', None, pop=True)
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    97
        if cformat is not None:
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    98
            args['comment_format'] = cformat
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
    99
            rql += ', T comment_format %(comment_format)s'
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
   100
        restriction = ['DS eid %(ds)s, E eid %(e)s']
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
   101
        if not state is None: # not a transition
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
   102
            rql += ', T from_state FS'
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
   103
            restriction.append('FS eid %(fs)s')
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
   104
            args['fs'] = state.eid
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
   105
        rql = '%s WHERE %s' % (rql, ', '.join(restriction))
04034421b072 [hooks] major refactoring:
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
diff changeset
   106
        session.unsafe_execute(rql, args, 'e')