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) |