|
1 """Core hooks: check schema validity, unsure we are not deleting necessary |
|
2 entities... |
|
3 |
|
4 :organization: Logilab |
|
5 :copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
|
6 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
7 """ |
|
8 __docformat__ = "restructuredtext en" |
|
9 |
|
10 from mx.DateTime import now |
|
11 |
|
12 from cubicweb import UnknownProperty, ValidationError, BadConnectionId |
|
13 |
|
14 from cubicweb.common.uilib import soup2xhtml |
|
15 |
|
16 from cubicweb.server.pool import Operation, LateOperation, PreCommitOperation |
|
17 from cubicweb.server.hookhelper import (check_internal_entity, previous_state, |
|
18 get_user_sessions, rproperty) |
|
19 from cubicweb.server.repository import FTIndexEntityOp |
|
20 |
|
21 def relation_deleted(session, eidfrom, rtype, eidto): |
|
22 session.add_query_data('pendingrelations', (eidfrom, rtype, eidto)) |
|
23 |
|
24 |
|
25 # base meta-data handling ##################################################### |
|
26 |
|
27 def setctime_before_add_entity(session, entity): |
|
28 """before create a new entity -> set creation and modification date |
|
29 |
|
30 this is a conveniency hook, you shouldn't have to disable it |
|
31 """ |
|
32 if not 'creation_date' in entity: |
|
33 entity['creation_date'] = now() |
|
34 if not 'modification_date' in entity: |
|
35 entity['modification_date'] = now() |
|
36 |
|
37 def setmtime_before_update_entity(session, entity): |
|
38 """update an entity -> set modification date""" |
|
39 if not 'modification_date' in entity: |
|
40 entity['modification_date'] = now() |
|
41 |
|
42 class SetCreatorOp(PreCommitOperation): |
|
43 |
|
44 def precommit_event(self): |
|
45 if self.eid in self.session.query_data('pendingeids', ()): |
|
46 # entity have been created and deleted in the same transaction |
|
47 return |
|
48 ueid = self.session.user.eid |
|
49 execute = self.session.unsafe_execute |
|
50 if not execute('Any X WHERE X created_by U, X eid %(x)s', |
|
51 {'x': self.eid}, 'x'): |
|
52 execute('SET X created_by U WHERE X eid %(x)s, U eid %(u)s', |
|
53 {'x': self.eid, 'u': ueid}, 'x') |
|
54 |
|
55 def setowner_after_add_entity(session, entity): |
|
56 """create a new entity -> set owner and creator metadata""" |
|
57 asession = session.actual_session() |
|
58 if not asession.is_internal_session: |
|
59 session.unsafe_execute('SET X owned_by U WHERE X eid %(x)s, U eid %(u)s', |
|
60 {'x': entity.eid, 'u': asession.user.eid}, 'x') |
|
61 SetCreatorOp(asession, eid=entity.eid) |
|
62 |
|
63 def setis_after_add_entity(session, entity): |
|
64 """create a new entity -> set is relation""" |
|
65 session.unsafe_execute('SET X is E WHERE X eid %(x)s, E name %(name)s', |
|
66 {'x': entity.eid, 'name': entity.id}, 'x') |
|
67 # XXX < 2.50 bw compat |
|
68 if not session.get_shared_data('do-not-insert-is_instance_of'): |
|
69 basetypes = entity.e_schema.ancestors() + [entity.e_schema] |
|
70 session.unsafe_execute('SET X is_instance_of E WHERE X eid %%(x)s, E name IN (%s)' % |
|
71 ','.join("'%s'" % str(etype) for etype in basetypes), |
|
72 {'x': entity.eid}, 'x') |
|
73 |
|
74 def setowner_after_add_user(session, entity): |
|
75 """when a user has been created, add owned_by relation on itself""" |
|
76 session.unsafe_execute('SET X owned_by X WHERE X eid %(x)s', |
|
77 {'x': entity.eid}, 'x') |
|
78 |
|
79 def fti_update_after_add_relation(session, eidfrom, rtype, eidto): |
|
80 """sync fulltext index when relevant relation is added. Reindexing the |
|
81 contained entity is enough since it will implicitly reindex the container |
|
82 entity. |
|
83 """ |
|
84 ftcontainer = session.repo.schema.rschema(rtype).fulltext_container |
|
85 if ftcontainer == 'subject': |
|
86 FTIndexEntityOp(session, entity=session.entity(eidto)) |
|
87 elif ftcontainer == 'object': |
|
88 FTIndexEntityOp(session, entity=session.entity(eidfrom)) |
|
89 |
|
90 def fti_update_after_delete_relation(session, eidfrom, rtype, eidto): |
|
91 """sync fulltext index when relevant relation is deleted. Reindexing both |
|
92 entities is necessary. |
|
93 """ |
|
94 if session.repo.schema.rschema(rtype).fulltext_container: |
|
95 FTIndexEntityOp(session, entity=session.entity(eidto)) |
|
96 FTIndexEntityOp(session, entity=session.entity(eidfrom)) |
|
97 |
|
98 class SyncOwnersOp(PreCommitOperation): |
|
99 |
|
100 def precommit_event(self): |
|
101 self.session.unsafe_execute('SET X owned_by U WHERE C owned_by U, C eid %(c)s,' |
|
102 'NOT EXISTS(X owned_by U, X eid %(x)s)', |
|
103 {'c': self.compositeeid, 'x': self.composedeid}, |
|
104 ('c', 'x')) |
|
105 |
|
106 def sync_owner_after_add_composite_relation(session, eidfrom, rtype, eidto): |
|
107 """when adding composite relation, the composed should have the same owners |
|
108 has the composite |
|
109 """ |
|
110 if rtype == 'wf_info_for': |
|
111 # skip this special composite relation |
|
112 return |
|
113 composite = rproperty(session, rtype, eidfrom, eidto, 'composite') |
|
114 if composite == 'subject': |
|
115 SyncOwnersOp(session, compositeeid=eidfrom, composedeid=eidto) |
|
116 elif composite == 'object': |
|
117 SyncOwnersOp(session, compositeeid=eidto, composedeid=eidfrom) |
|
118 |
|
119 def _register_metadata_hooks(hm): |
|
120 """register meta-data related hooks on the hooks manager""" |
|
121 hm.register_hook(setctime_before_add_entity, 'before_add_entity', '') |
|
122 hm.register_hook(setmtime_before_update_entity, 'before_update_entity', '') |
|
123 hm.register_hook(setowner_after_add_entity, 'after_add_entity', '') |
|
124 hm.register_hook(sync_owner_after_add_composite_relation, 'after_add_relation', '') |
|
125 hm.register_hook(fti_update_after_add_relation, 'after_add_relation', '') |
|
126 hm.register_hook(fti_update_after_delete_relation, 'after_delete_relation', '') |
|
127 if 'is' in hm.schema: |
|
128 hm.register_hook(setis_after_add_entity, 'after_add_entity', '') |
|
129 if 'EUser' in hm.schema: |
|
130 hm.register_hook(setowner_after_add_user, 'after_add_entity', 'EUser') |
|
131 |
|
132 # core hooks ################################################################## |
|
133 |
|
134 class DelayedDeleteOp(PreCommitOperation): |
|
135 """delete the object of composite relation except if the relation |
|
136 has actually been redirected to another composite |
|
137 """ |
|
138 |
|
139 def precommit_event(self): |
|
140 session = self.session |
|
141 if not self.eid in session.query_data('pendingeids', ()): |
|
142 etype = session.describe(self.eid)[0] |
|
143 session.unsafe_execute('DELETE %s X WHERE X eid %%(x)s, NOT %s' |
|
144 % (etype, self.relation), |
|
145 {'x': self.eid}, 'x') |
|
146 |
|
147 def handle_composite_before_del_relation(session, eidfrom, rtype, eidto): |
|
148 """delete the object of composite relation""" |
|
149 composite = rproperty(session, rtype, eidfrom, eidto, 'composite') |
|
150 if composite == 'subject': |
|
151 DelayedDeleteOp(session, eid=eidto, relation='Y %s X' % rtype) |
|
152 elif composite == 'object': |
|
153 DelayedDeleteOp(session, eid=eidfrom, relation='X %s Y' % rtype) |
|
154 |
|
155 def before_del_group(session, eid): |
|
156 """check that we don't remove the owners group""" |
|
157 check_internal_entity(session, eid, ('owners',)) |
|
158 |
|
159 |
|
160 # schema validation hooks ##################################################### |
|
161 |
|
162 class CheckConstraintsOperation(LateOperation): |
|
163 """check a new relation satisfy its constraints |
|
164 """ |
|
165 def precommit_event(self): |
|
166 eidfrom, rtype, eidto = self.rdef |
|
167 # first check related entities have not been deleted in the same |
|
168 # transaction |
|
169 pending = self.session.query_data('pendingeids', ()) |
|
170 if eidfrom in pending: |
|
171 return |
|
172 if eidto in pending: |
|
173 return |
|
174 for constraint in self.constraints: |
|
175 try: |
|
176 constraint.repo_check(self.session, eidfrom, rtype, eidto) |
|
177 except NotImplementedError: |
|
178 self.critical('can\'t check constraint %s, not supported', |
|
179 constraint) |
|
180 |
|
181 def commit_event(self): |
|
182 pass |
|
183 |
|
184 def cstrcheck_after_add_relation(session, eidfrom, rtype, eidto): |
|
185 """check the relation satisfy its constraints |
|
186 |
|
187 this is delayed to a precommit time operation since other relation which |
|
188 will make constraint satisfied may be added later. |
|
189 """ |
|
190 constraints = rproperty(session, rtype, eidfrom, eidto, 'constraints') |
|
191 if constraints: |
|
192 CheckConstraintsOperation(session, constraints=constraints, |
|
193 rdef=(eidfrom, rtype, eidto)) |
|
194 |
|
195 def uniquecstrcheck_before_modification(session, entity): |
|
196 eschema = entity.e_schema |
|
197 for attr, val in entity.items(): |
|
198 if val is None: |
|
199 continue |
|
200 if eschema.subject_relation(attr).is_final() and \ |
|
201 eschema.has_unique_values(attr): |
|
202 rql = '%s X WHERE X %s %%(val)s' % (entity.e_schema, attr) |
|
203 rset = session.unsafe_execute(rql, {'val': val}) |
|
204 if rset and rset[0][0] != entity.eid: |
|
205 msg = session._('the value "%s" is already used, use another one') |
|
206 raise ValidationError(entity.eid, {attr: msg % val}) |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 class tidy_html_fields(object): |
|
212 """tidy HTML in rich text strings |
|
213 |
|
214 FIXME: (adim) the whole idea of having a class is to store the |
|
215 event type. There might be another way to get dynamically the |
|
216 event inside the hook function. |
|
217 """ |
|
218 # FIXME hooks manager use func_name to register |
|
219 func_name = 'tidy_html_field' |
|
220 |
|
221 def __init__(self, event): |
|
222 self.event = event |
|
223 |
|
224 def __call__(self, session, entity): |
|
225 for attr in entity.formatted_attrs(): |
|
226 value = entity.get(attr) |
|
227 # text was not changed |
|
228 if self.event == 'before_add_entity': |
|
229 fmt = entity.get('%s_format' % attr) |
|
230 else: |
|
231 fmt = entity.get_value('%s_format' % attr) |
|
232 if value and fmt == 'text/html': |
|
233 entity[attr] = soup2xhtml(value, session.encoding) |
|
234 |
|
235 |
|
236 class CheckRequiredRelationOperation(LateOperation): |
|
237 """checking relation cardinality has to be done after commit in |
|
238 case the relation is being replaced |
|
239 """ |
|
240 eid, rtype = None, None |
|
241 |
|
242 def precommit_event(self): |
|
243 # recheck pending eids |
|
244 if self.eid in self.session.query_data('pendingeids', ()): |
|
245 return |
|
246 if self.session.unsafe_execute(*self._rql()).rowcount < 1: |
|
247 etype = self.session.describe(self.eid)[0] |
|
248 msg = self.session._('at least one relation %s is required on %s(%s)') |
|
249 raise ValidationError(self.eid, {self.rtype: msg % (self.rtype, |
|
250 etype, self.eid)}) |
|
251 |
|
252 def commit_event(self): |
|
253 pass |
|
254 |
|
255 def _rql(self): |
|
256 raise NotImplementedError() |
|
257 |
|
258 class CheckSRelationOp(CheckRequiredRelationOperation): |
|
259 """check required subject relation""" |
|
260 def _rql(self): |
|
261 return 'Any O WHERE S eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x' |
|
262 |
|
263 class CheckORelationOp(CheckRequiredRelationOperation): |
|
264 """check required object relation""" |
|
265 def _rql(self): |
|
266 return 'Any S WHERE O eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x' |
|
267 |
|
268 def checkrel_if_necessary(session, opcls, rtype, eid): |
|
269 """check an equivalent operation has not already been added""" |
|
270 for op in session.pending_operations: |
|
271 if isinstance(op, opcls) and op.rtype == rtype and op.eid == eid: |
|
272 break |
|
273 else: |
|
274 opcls(session, rtype=rtype, eid=eid) |
|
275 |
|
276 def cardinalitycheck_after_add_entity(session, entity): |
|
277 """check cardinalities are satisfied""" |
|
278 eid = entity.eid |
|
279 for rschema, targetschemas, x in entity.e_schema.relation_definitions(): |
|
280 # skip automatically handled relations |
|
281 if rschema.type in ('owned_by', 'created_by', 'is', 'is_instance_of'): |
|
282 continue |
|
283 if x == 'subject': |
|
284 subjtype = entity.e_schema |
|
285 objtype = targetschemas[0].type |
|
286 cardindex = 0 |
|
287 opcls = CheckSRelationOp |
|
288 else: |
|
289 subjtype = targetschemas[0].type |
|
290 objtype = entity.e_schema |
|
291 cardindex = 1 |
|
292 opcls = CheckORelationOp |
|
293 card = rschema.rproperty(subjtype, objtype, 'cardinality') |
|
294 if card[cardindex] in '1+': |
|
295 checkrel_if_necessary(session, opcls, rschema.type, eid) |
|
296 |
|
297 def cardinalitycheck_before_del_relation(session, eidfrom, rtype, eidto): |
|
298 """check cardinalities are satisfied""" |
|
299 card = rproperty(session, rtype, eidfrom, eidto, 'cardinality') |
|
300 pendingeids = session.query_data('pendingeids', ()) |
|
301 if card[0] in '1+' and not eidfrom in pendingeids: |
|
302 checkrel_if_necessary(session, CheckSRelationOp, rtype, eidfrom) |
|
303 if card[1] in '1+' and not eidto in pendingeids: |
|
304 checkrel_if_necessary(session, CheckORelationOp, rtype, eidto) |
|
305 |
|
306 |
|
307 def _register_core_hooks(hm): |
|
308 hm.register_hook(handle_composite_before_del_relation, 'before_delete_relation', '') |
|
309 hm.register_hook(before_del_group, 'before_delete_entity', 'EGroup') |
|
310 |
|
311 #hm.register_hook(cstrcheck_before_update_entity, 'before_update_entity', '') |
|
312 hm.register_hook(cardinalitycheck_after_add_entity, 'after_add_entity', '') |
|
313 hm.register_hook(cardinalitycheck_before_del_relation, 'before_delete_relation', '') |
|
314 hm.register_hook(cstrcheck_after_add_relation, 'after_add_relation', '') |
|
315 hm.register_hook(uniquecstrcheck_before_modification, 'before_add_entity', '') |
|
316 hm.register_hook(uniquecstrcheck_before_modification, 'before_update_entity', '') |
|
317 hm.register_hook(tidy_html_fields('before_add_entity'), 'before_add_entity', '') |
|
318 hm.register_hook(tidy_html_fields('before_update_entity'), 'before_update_entity', '') |
|
319 |
|
320 |
|
321 # user/groups synchronisation ################################################# |
|
322 |
|
323 class GroupOperation(Operation): |
|
324 """base class for group operation""" |
|
325 geid = None |
|
326 def __init__(self, session, *args, **kwargs): |
|
327 """override to get the group name before actual groups manipulation: |
|
328 |
|
329 we may temporarily loose right access during a commit event, so |
|
330 no query should be emitted while comitting |
|
331 """ |
|
332 rql = 'Any N WHERE G eid %(x)s, G name N' |
|
333 result = session.execute(rql, {'x': kwargs['geid']}, 'x', build_descr=False) |
|
334 Operation.__init__(self, session, *args, **kwargs) |
|
335 self.group = result[0][0] |
|
336 |
|
337 class DeleteGroupOp(GroupOperation): |
|
338 """synchronize user when a in_group relation has been deleted""" |
|
339 def commit_event(self): |
|
340 """the observed connections pool has been commited""" |
|
341 groups = self.cnxuser.groups |
|
342 try: |
|
343 groups.remove(self.group) |
|
344 except KeyError: |
|
345 self.error('user %s not in group %s', self.cnxuser, self.group) |
|
346 return |
|
347 |
|
348 def after_del_in_group(session, fromeid, rtype, toeid): |
|
349 """modify user permission, need to update users""" |
|
350 for session_ in get_user_sessions(session.repo, fromeid): |
|
351 DeleteGroupOp(session, cnxuser=session_.user, geid=toeid) |
|
352 |
|
353 |
|
354 class AddGroupOp(GroupOperation): |
|
355 """synchronize user when a in_group relation has been added""" |
|
356 def commit_event(self): |
|
357 """the observed connections pool has been commited""" |
|
358 groups = self.cnxuser.groups |
|
359 if self.group in groups: |
|
360 self.warning('user %s already in group %s', self.cnxuser, |
|
361 self.group) |
|
362 return |
|
363 groups.add(self.group) |
|
364 |
|
365 def after_add_in_group(session, fromeid, rtype, toeid): |
|
366 """modify user permission, need to update users""" |
|
367 for session_ in get_user_sessions(session.repo, fromeid): |
|
368 AddGroupOp(session, cnxuser=session_.user, geid=toeid) |
|
369 |
|
370 |
|
371 class DelUserOp(Operation): |
|
372 """synchronize user when a in_group relation has been added""" |
|
373 def __init__(self, session, cnxid): |
|
374 self.cnxid = cnxid |
|
375 Operation.__init__(self, session) |
|
376 |
|
377 def commit_event(self): |
|
378 """the observed connections pool has been commited""" |
|
379 try: |
|
380 self.repo.close(self.cnxid) |
|
381 except BadConnectionId: |
|
382 pass # already closed |
|
383 |
|
384 def after_del_user(session, eid): |
|
385 """modify user permission, need to update users""" |
|
386 for session_ in get_user_sessions(session.repo, eid): |
|
387 DelUserOp(session, session_.id) |
|
388 |
|
389 def _register_usergroup_hooks(hm): |
|
390 """register user/group related hooks on the hooks manager""" |
|
391 hm.register_hook(after_del_user, 'after_delete_entity', 'EUser') |
|
392 hm.register_hook(after_add_in_group, 'after_add_relation', 'in_group') |
|
393 hm.register_hook(after_del_in_group, 'after_delete_relation', 'in_group') |
|
394 |
|
395 |
|
396 # workflow handling ########################################################### |
|
397 |
|
398 def before_add_in_state(session, fromeid, rtype, toeid): |
|
399 """check the transition is allowed and record transition information |
|
400 """ |
|
401 assert rtype == 'in_state' |
|
402 state = previous_state(session, fromeid) |
|
403 etype = session.describe(fromeid)[0] |
|
404 if not (session.is_super_session or 'managers' in session.user.groups): |
|
405 if not state is None: |
|
406 entity = session.entity(fromeid) |
|
407 # we should find at least one transition going to this state |
|
408 try: |
|
409 iter(state.transitions(entity, toeid)).next() |
|
410 except StopIteration: |
|
411 msg = session._('transition is not allowed') |
|
412 raise ValidationError(fromeid, {'in_state': msg}) |
|
413 else: |
|
414 # not a transition |
|
415 # check state is initial state if the workflow defines one |
|
416 isrset = session.unsafe_execute('Any S WHERE ET initial_state S, ET name %(etype)s', |
|
417 {'etype': etype}) |
|
418 if isrset and not toeid == isrset[0][0]: |
|
419 msg = session._('not the initial state for this entity') |
|
420 raise ValidationError(fromeid, {'in_state': msg}) |
|
421 eschema = session.repo.schema[etype] |
|
422 if not 'wf_info_for' in eschema.object_relations(): |
|
423 # workflow history not activated for this entity type |
|
424 return |
|
425 rql = 'INSERT TrInfo T: T wf_info_for E, T to_state DS, T comment %(comment)s' |
|
426 args = {'comment': session.get_shared_data('trcomment', None, pop=True), |
|
427 'e': fromeid, 'ds': toeid} |
|
428 cformat = session.get_shared_data('trcommentformat', None, pop=True) |
|
429 if cformat is not None: |
|
430 args['comment_format'] = cformat |
|
431 rql += ', T comment_format %(comment_format)s' |
|
432 restriction = ['DS eid %(ds)s, E eid %(e)s'] |
|
433 if not state is None: # not a transition |
|
434 rql += ', T from_state FS' |
|
435 restriction.append('FS eid %(fs)s') |
|
436 args['fs'] = state.eid |
|
437 rql = '%s WHERE %s' % (rql, ', '.join(restriction)) |
|
438 session.unsafe_execute(rql, args, 'e') |
|
439 |
|
440 |
|
441 class SetInitialStateOp(PreCommitOperation): |
|
442 """make initial state be a default state""" |
|
443 |
|
444 def precommit_event(self): |
|
445 session = self.session |
|
446 entity = self.entity |
|
447 rset = session.execute('Any S WHERE ET initial_state S, ET name %(name)s', |
|
448 {'name': str(entity.e_schema)}) |
|
449 # if there is an initial state and the entity's state is not set, |
|
450 # use the initial state as a default state |
|
451 pendingeids = session.query_data('pendingeids', ()) |
|
452 if rset and not entity.eid in pendingeids and not entity.in_state: |
|
453 session.unsafe_execute('SET X in_state S WHERE X eid %(x)s, S eid %(s)s', |
|
454 {'x' : entity.eid, 's' : rset[0][0]}, 'x') |
|
455 |
|
456 |
|
457 def set_initial_state_after_add(session, entity): |
|
458 SetInitialStateOp(session, entity=entity) |
|
459 |
|
460 def _register_wf_hooks(hm): |
|
461 """register workflow related hooks on the hooks manager""" |
|
462 if 'in_state' in hm.schema: |
|
463 hm.register_hook(before_add_in_state, 'before_add_relation', 'in_state') |
|
464 hm.register_hook(relation_deleted, 'before_delete_relation', 'in_state') |
|
465 for eschema in hm.schema.entities(): |
|
466 if 'in_state' in eschema.subject_relations(): |
|
467 hm.register_hook(set_initial_state_after_add, 'after_add_entity', |
|
468 str(eschema)) |
|
469 |
|
470 |
|
471 # EProperty hooks ############################################################# |
|
472 |
|
473 |
|
474 class DelEPropertyOp(Operation): |
|
475 """a user's custom properties has been deleted""" |
|
476 |
|
477 def commit_event(self): |
|
478 """the observed connections pool has been commited""" |
|
479 try: |
|
480 del self.epropdict[self.key] |
|
481 except KeyError: |
|
482 self.error('%s has no associated value', self.key) |
|
483 |
|
484 class ChangeEPropertyOp(Operation): |
|
485 """a user's custom properties has been added/changed""" |
|
486 |
|
487 def commit_event(self): |
|
488 """the observed connections pool has been commited""" |
|
489 self.epropdict[self.key] = self.value |
|
490 |
|
491 class AddEPropertyOp(Operation): |
|
492 """a user's custom properties has been added/changed""" |
|
493 |
|
494 def commit_event(self): |
|
495 """the observed connections pool has been commited""" |
|
496 eprop = self.eprop |
|
497 if not eprop.for_user: |
|
498 self.repo.vreg.eprop_values[eprop.pkey] = eprop.value |
|
499 # if for_user is set, update is handled by a ChangeEPropertyOp operation |
|
500 |
|
501 def after_add_eproperty(session, entity): |
|
502 key, value = entity.pkey, entity.value |
|
503 try: |
|
504 value = session.vreg.typed_value(key, value) |
|
505 except UnknownProperty: |
|
506 raise ValidationError(entity.eid, {'pkey': session._('unknown property key')}) |
|
507 except ValueError, ex: |
|
508 raise ValidationError(entity.eid, {'value': session._(str(ex))}) |
|
509 if not session.user.matching_groups('managers'): |
|
510 session.unsafe_execute('SET P for_user U WHERE P eid %(x)s,U eid %(u)s', |
|
511 {'x': entity.eid, 'u': session.user.eid}, 'x') |
|
512 else: |
|
513 AddEPropertyOp(session, eprop=entity) |
|
514 |
|
515 def after_update_eproperty(session, entity): |
|
516 key, value = entity.pkey, entity.value |
|
517 try: |
|
518 value = session.vreg.typed_value(key, value) |
|
519 except UnknownProperty: |
|
520 return |
|
521 except ValueError, ex: |
|
522 raise ValidationError(entity.eid, {'value': session._(str(ex))}) |
|
523 if entity.for_user: |
|
524 for session_ in get_user_sessions(session.repo, entity.for_user[0].eid): |
|
525 ChangeEPropertyOp(session, epropdict=session_.user.properties, |
|
526 key=key, value=value) |
|
527 else: |
|
528 # site wide properties |
|
529 ChangeEPropertyOp(session, epropdict=session.vreg.eprop_values, |
|
530 key=key, value=value) |
|
531 |
|
532 def before_del_eproperty(session, eid): |
|
533 for eidfrom, rtype, eidto in session.query_data('pendingrelations', ()): |
|
534 if rtype == 'for_user' and eidfrom == eid: |
|
535 # if for_user was set, delete has already been handled |
|
536 break |
|
537 else: |
|
538 key = session.execute('Any K WHERE P eid %(x)s, P pkey K', |
|
539 {'x': eid}, 'x')[0][0] |
|
540 DelEPropertyOp(session, epropdict=session.vreg.eprop_values, key=key) |
|
541 |
|
542 def after_add_for_user(session, fromeid, rtype, toeid): |
|
543 if not session.describe(fromeid)[0] == 'EProperty': |
|
544 return |
|
545 key, value = session.execute('Any K,V WHERE P eid %(x)s,P pkey K,P value V', |
|
546 {'x': fromeid}, 'x')[0] |
|
547 if session.vreg.property_info(key)['sitewide']: |
|
548 raise ValidationError(fromeid, |
|
549 {'for_user': session._("site-wide property can't be set for user")}) |
|
550 for session_ in get_user_sessions(session.repo, toeid): |
|
551 ChangeEPropertyOp(session, epropdict=session_.user.properties, |
|
552 key=key, value=value) |
|
553 |
|
554 def before_del_for_user(session, fromeid, rtype, toeid): |
|
555 key = session.execute('Any K WHERE P eid %(x)s, P pkey K', |
|
556 {'x': fromeid}, 'x')[0][0] |
|
557 relation_deleted(session, fromeid, rtype, toeid) |
|
558 for session_ in get_user_sessions(session.repo, toeid): |
|
559 DelEPropertyOp(session, epropdict=session_.user.properties, key=key) |
|
560 |
|
561 def _register_eproperty_hooks(hm): |
|
562 """register workflow related hooks on the hooks manager""" |
|
563 hm.register_hook(after_add_eproperty, 'after_add_entity', 'EProperty') |
|
564 hm.register_hook(after_update_eproperty, 'after_update_entity', 'EProperty') |
|
565 hm.register_hook(before_del_eproperty, 'before_delete_entity', 'EProperty') |
|
566 hm.register_hook(after_add_for_user, 'after_add_relation', 'for_user') |
|
567 hm.register_hook(before_del_for_user, 'before_delete_relation', 'for_user') |