19 |
19 |
20 def relation_deleted(session, eidfrom, rtype, eidto): |
20 def relation_deleted(session, eidfrom, rtype, eidto): |
21 session.transaction_data.setdefault('pendingrelations', []).append( |
21 session.transaction_data.setdefault('pendingrelations', []).append( |
22 (eidfrom, rtype, eidto)) |
22 (eidfrom, rtype, eidto)) |
23 |
23 |
24 |
24 def eschema_type_eid(session, etype): |
25 # base meta-data handling ##################################################### |
25 """get eid of the CWEType entity for the given yams type""" |
|
26 eschema = session.repo.schema.eschema(etype) |
|
27 # eschema.eid is None if schema has been readen from the filesystem, not |
|
28 # from the database (eg during tests) |
|
29 if eschema.eid is None: |
|
30 eschema.eid = session.unsafe_execute( |
|
31 'Any X WHERE X is CWEType, X name %(name)s', {'name': etype})[0][0] |
|
32 return eschema.eid |
|
33 |
|
34 |
|
35 # base meta-data handling ###################################################### |
26 |
36 |
27 def setctime_before_add_entity(session, entity): |
37 def setctime_before_add_entity(session, entity): |
28 """before create a new entity -> set creation and modification date |
38 """before create a new entity -> set creation and modification date |
29 |
39 |
30 this is a conveniency hook, you shouldn't have to disable it |
40 this is a conveniency hook, you shouldn't have to disable it |
31 """ |
41 """ |
32 if not 'creation_date' in entity: |
42 timestamp = datetime.now() |
33 entity['creation_date'] = datetime.now() |
43 entity.setdefault('creation_date', timestamp) |
34 if not 'modification_date' in entity: |
44 entity.setdefault('modification_date', timestamp) |
35 entity['modification_date'] = datetime.now() |
45 if not session.get_shared_data('do-not-insert-cwuri'): |
36 if not 'cwuri' in entity: |
46 entity.setdefault('cwuri', u'%seid/%s' % (session.base_url(), entity.eid)) |
37 if not session.get_shared_data('do-not-insert-cwuri'): |
47 |
38 entity['cwuri'] = session.base_url() + u'eid/%s' % entity.eid |
|
39 |
48 |
40 def setmtime_before_update_entity(session, entity): |
49 def setmtime_before_update_entity(session, entity): |
41 """update an entity -> set modification date""" |
50 """update an entity -> set modification date""" |
42 if not 'modification_date' in entity: |
51 entity.setdefault('modification_date', datetime.now()) |
43 entity['modification_date'] = datetime.now() |
52 |
44 |
53 |
45 class SetCreatorOp(PreCommitOperation): |
54 class SetCreatorOp(PreCommitOperation): |
46 |
55 |
47 def precommit_event(self): |
56 def precommit_event(self): |
48 if self.eid in self.session.transaction_data.get('pendingeids', ()): |
57 session = self.session |
|
58 if self.entity.eid in session.transaction_data.get('pendingeids', ()): |
49 # entity have been created and deleted in the same transaction |
59 # entity have been created and deleted in the same transaction |
50 return |
60 return |
51 ueid = self.session.user.eid |
61 if not self.entity.created_by: |
52 execute = self.session.unsafe_execute |
62 session.add_relation(self.entity.eid, 'created_by', session.user.eid) |
53 if not execute('Any X WHERE X created_by U, X eid %(x)s', |
63 |
54 {'x': self.eid}, 'x'): |
|
55 execute('SET X created_by U WHERE X eid %(x)s, U eid %(u)s', |
|
56 {'x': self.eid, 'u': ueid}, 'x') |
|
57 |
64 |
58 def setowner_after_add_entity(session, entity): |
65 def setowner_after_add_entity(session, entity): |
59 """create a new entity -> set owner and creator metadata""" |
66 """create a new entity -> set owner and creator metadata""" |
60 asession = session.actual_session() |
67 asession = session.actual_session() |
61 if not asession.is_internal_session: |
68 if not asession.is_internal_session: |
62 session.unsafe_execute('SET X owned_by U WHERE X eid %(x)s, U eid %(u)s', |
69 session.add_relation(entity.eid, 'owned_by', asession.user.eid) |
63 {'x': entity.eid, 'u': asession.user.eid}, 'x') |
70 SetCreatorOp(asession, entity=entity) |
64 SetCreatorOp(asession, eid=entity.eid) |
71 |
65 |
72 |
66 def setis_after_add_entity(session, entity): |
73 def setis_after_add_entity(session, entity): |
67 """create a new entity -> set is relation""" |
74 """create a new entity -> set is relation""" |
68 if hasattr(entity, '_cw_recreating'): |
75 if hasattr(entity, '_cw_recreating'): |
69 return |
76 return |
70 session.unsafe_execute('SET X is E WHERE X eid %(x)s, E name %(name)s', |
77 try: |
71 {'x': entity.eid, 'name': entity.id}, 'x') |
78 session.add_relation(entity.eid, 'is', |
|
79 eschema_type_eid(session, entity.id)) |
|
80 except IndexError: |
|
81 # during schema serialization, skip |
|
82 return |
72 # XXX < 2.50 bw compat |
83 # XXX < 2.50 bw compat |
73 if not session.get_shared_data('do-not-insert-is_instance_of'): |
84 if not session.get_shared_data('do-not-insert-is_instance_of'): |
74 basetypes = entity.e_schema.ancestors() + [entity.e_schema] |
85 for etype in entity.e_schema.ancestors() + [entity.e_schema]: |
75 session.unsafe_execute('SET X is_instance_of E WHERE X eid %%(x)s, E name IN (%s)' % |
86 session.add_relation(entity.eid, 'is_instance_of', |
76 ','.join("'%s'" % str(etype) for etype in basetypes), |
87 eschema_type_eid(session, etype)) |
77 {'x': entity.eid}, 'x') |
88 |
78 |
89 |
79 def setowner_after_add_user(session, entity): |
90 def setowner_after_add_user(session, entity): |
80 """when a user has been created, add owned_by relation on itself""" |
91 """when a user has been created, add owned_by relation on itself""" |
81 session.unsafe_execute('SET X owned_by X WHERE X eid %(x)s', |
92 session.add_relation(entity.eid, 'owned_by', entity.eid) |
82 {'x': entity.eid}, 'x') |
93 |
83 |
94 |
84 def fti_update_after_add_relation(session, eidfrom, rtype, eidto): |
95 def fti_update_after_add_relation(session, eidfrom, rtype, eidto): |
85 """sync fulltext index when relevant relation is added. Reindexing the |
96 """sync fulltext index when relevant relation is added. Reindexing the |
86 contained entity is enough since it will implicitly reindex the container |
97 contained entity is enough since it will implicitly reindex the container |
87 entity. |
98 entity. |
89 ftcontainer = session.repo.schema.rschema(rtype).fulltext_container |
100 ftcontainer = session.repo.schema.rschema(rtype).fulltext_container |
90 if ftcontainer == 'subject': |
101 if ftcontainer == 'subject': |
91 FTIndexEntityOp(session, entity=session.entity_from_eid(eidto)) |
102 FTIndexEntityOp(session, entity=session.entity_from_eid(eidto)) |
92 elif ftcontainer == 'object': |
103 elif ftcontainer == 'object': |
93 FTIndexEntityOp(session, entity=session.entity_from_eid(eidfrom)) |
104 FTIndexEntityOp(session, entity=session.entity_from_eid(eidfrom)) |
|
105 |
|
106 |
94 def fti_update_after_delete_relation(session, eidfrom, rtype, eidto): |
107 def fti_update_after_delete_relation(session, eidfrom, rtype, eidto): |
95 """sync fulltext index when relevant relation is deleted. Reindexing both |
108 """sync fulltext index when relevant relation is deleted. Reindexing both |
96 entities is necessary. |
109 entities is necessary. |
97 """ |
110 """ |
98 if session.repo.schema.rschema(rtype).fulltext_container: |
111 if session.repo.schema.rschema(rtype).fulltext_container: |
99 FTIndexEntityOp(session, entity=session.entity_from_eid(eidto)) |
112 FTIndexEntityOp(session, entity=session.entity_from_eid(eidto)) |
100 FTIndexEntityOp(session, entity=session.entity_from_eid(eidfrom)) |
113 FTIndexEntityOp(session, entity=session.entity_from_eid(eidfrom)) |
|
114 |
101 |
115 |
102 class SyncOwnersOp(PreCommitOperation): |
116 class SyncOwnersOp(PreCommitOperation): |
103 |
117 |
104 def precommit_event(self): |
118 def precommit_event(self): |
105 self.session.unsafe_execute('SET X owned_by U WHERE C owned_by U, C eid %(c)s,' |
119 self.session.unsafe_execute('SET X owned_by U WHERE C owned_by U, C eid %(c)s,' |
106 'NOT EXISTS(X owned_by U, X eid %(x)s)', |
120 'NOT EXISTS(X owned_by U, X eid %(x)s)', |
107 {'c': self.compositeeid, 'x': self.composedeid}, |
121 {'c': self.compositeeid, 'x': self.composedeid}, |
108 ('c', 'x')) |
122 ('c', 'x')) |
109 |
123 |
|
124 |
110 def sync_owner_after_add_composite_relation(session, eidfrom, rtype, eidto): |
125 def sync_owner_after_add_composite_relation(session, eidfrom, rtype, eidto): |
111 """when adding composite relation, the composed should have the same owners |
126 """when adding composite relation, the composed should have the same owners |
112 has the composite |
127 has the composite |
113 """ |
128 """ |
114 if rtype == 'wf_info_for': |
129 if rtype == 'wf_info_for': |
115 # skip this special composite relation |
130 # skip this special composite relation # XXX (syt) why? |
116 return |
131 return |
117 composite = rproperty(session, rtype, eidfrom, eidto, 'composite') |
132 composite = rproperty(session, rtype, eidfrom, eidto, 'composite') |
118 if composite == 'subject': |
133 if composite == 'subject': |
119 SyncOwnersOp(session, compositeeid=eidfrom, composedeid=eidto) |
134 SyncOwnersOp(session, compositeeid=eidfrom, composedeid=eidto) |
120 elif composite == 'object': |
135 elif composite == 'object': |
121 SyncOwnersOp(session, compositeeid=eidto, composedeid=eidfrom) |
136 SyncOwnersOp(session, compositeeid=eidto, composedeid=eidfrom) |
|
137 |
122 |
138 |
123 def _register_metadata_hooks(hm): |
139 def _register_metadata_hooks(hm): |
124 """register meta-data related hooks on the hooks manager""" |
140 """register meta-data related hooks on the hooks manager""" |
125 hm.register_hook(setctime_before_add_entity, 'before_add_entity', '') |
141 hm.register_hook(setctime_before_add_entity, 'before_add_entity', '') |
126 hm.register_hook(setmtime_before_update_entity, 'before_update_entity', '') |
142 hm.register_hook(setmtime_before_update_entity, 'before_update_entity', '') |
146 etype = session.describe(self.eid)[0] |
163 etype = session.describe(self.eid)[0] |
147 session.unsafe_execute('DELETE %s X WHERE X eid %%(x)s, NOT %s' |
164 session.unsafe_execute('DELETE %s X WHERE X eid %%(x)s, NOT %s' |
148 % (etype, self.relation), |
165 % (etype, self.relation), |
149 {'x': self.eid}, 'x') |
166 {'x': self.eid}, 'x') |
150 |
167 |
|
168 |
151 def handle_composite_before_del_relation(session, eidfrom, rtype, eidto): |
169 def handle_composite_before_del_relation(session, eidfrom, rtype, eidto): |
152 """delete the object of composite relation""" |
170 """delete the object of composite relation""" |
153 composite = rproperty(session, rtype, eidfrom, eidto, 'composite') |
171 composite = rproperty(session, rtype, eidfrom, eidto, 'composite') |
154 if composite == 'subject': |
172 if composite == 'subject': |
155 DelayedDeleteOp(session, eid=eidto, relation='Y %s X' % rtype) |
173 DelayedDeleteOp(session, eid=eidto, relation='Y %s X' % rtype) |
156 elif composite == 'object': |
174 elif composite == 'object': |
157 DelayedDeleteOp(session, eid=eidfrom, relation='X %s Y' % rtype) |
175 DelayedDeleteOp(session, eid=eidfrom, relation='X %s Y' % rtype) |
|
176 |
158 |
177 |
159 def before_del_group(session, eid): |
178 def before_del_group(session, eid): |
160 """check that we don't remove the owners group""" |
179 """check that we don't remove the owners group""" |
161 check_internal_entity(session, eid, ('owners',)) |
180 check_internal_entity(session, eid, ('owners',)) |
162 |
181 |
225 return |
242 return |
226 if self.session.unsafe_execute(*self._rql()).rowcount < 1: |
243 if self.session.unsafe_execute(*self._rql()).rowcount < 1: |
227 etype = self.session.describe(self.eid)[0] |
244 etype = self.session.describe(self.eid)[0] |
228 _ = self.session._ |
245 _ = self.session._ |
229 msg = _('at least one relation %(rtype)s is required on %(etype)s (%(eid)s)') |
246 msg = _('at least one relation %(rtype)s is required on %(etype)s (%(eid)s)') |
230 raise ValidationError(self.eid, {self.rtype: msg % {'rtype': _(self.rtype), |
247 msg %= {'rtype': _(self.rtype), 'etype': _(etype), 'eid': self.eid} |
231 'etype': _(etype), |
248 raise ValidationError(self.eid, {self.rtype: msg}) |
232 'eid': self.eid}}) |
|
233 |
249 |
234 def commit_event(self): |
250 def commit_event(self): |
235 pass |
251 pass |
236 |
252 |
237 def _rql(self): |
253 def _rql(self): |
238 raise NotImplementedError() |
254 raise NotImplementedError() |
|
255 |
239 |
256 |
240 class CheckSRelationOp(CheckRequiredRelationOperation): |
257 class CheckSRelationOp(CheckRequiredRelationOperation): |
241 """check required subject relation""" |
258 """check required subject relation""" |
242 def _rql(self): |
259 def _rql(self): |
243 return 'Any O WHERE S eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x' |
260 return 'Any O WHERE S eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x' |
244 |
261 |
|
262 |
245 class CheckORelationOp(CheckRequiredRelationOperation): |
263 class CheckORelationOp(CheckRequiredRelationOperation): |
246 """check required object relation""" |
264 """check required object relation""" |
247 def _rql(self): |
265 def _rql(self): |
248 return 'Any S WHERE O eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x' |
266 return 'Any S WHERE O eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x' |
|
267 |
249 |
268 |
250 def checkrel_if_necessary(session, opcls, rtype, eid): |
269 def checkrel_if_necessary(session, opcls, rtype, eid): |
251 """check an equivalent operation has not already been added""" |
270 """check an equivalent operation has not already been added""" |
252 for op in session.pending_operations: |
271 for op in session.pending_operations: |
253 if isinstance(op, opcls) and op.rtype == rtype and op.eid == eid: |
272 if isinstance(op, opcls) and op.rtype == rtype and op.eid == eid: |
254 break |
273 break |
255 else: |
274 else: |
256 opcls(session, rtype=rtype, eid=eid) |
275 opcls(session, rtype=rtype, eid=eid) |
|
276 |
257 |
277 |
258 def cardinalitycheck_after_add_entity(session, entity): |
278 def cardinalitycheck_after_add_entity(session, entity): |
259 """check cardinalities are satisfied""" |
279 """check cardinalities are satisfied""" |
260 eid = entity.eid |
280 eid = entity.eid |
261 for rschema, targetschemas, x in entity.e_schema.relation_definitions(): |
281 for rschema, targetschemas, x in entity.e_schema.relation_definitions(): |
274 opcls = CheckORelationOp |
294 opcls = CheckORelationOp |
275 card = rschema.rproperty(subjtype, objtype, 'cardinality') |
295 card = rschema.rproperty(subjtype, objtype, 'cardinality') |
276 if card[cardindex] in '1+': |
296 if card[cardindex] in '1+': |
277 checkrel_if_necessary(session, opcls, rschema.type, eid) |
297 checkrel_if_necessary(session, opcls, rschema.type, eid) |
278 |
298 |
|
299 |
279 def cardinalitycheck_before_del_relation(session, eidfrom, rtype, eidto): |
300 def cardinalitycheck_before_del_relation(session, eidfrom, rtype, eidto): |
280 """check cardinalities are satisfied""" |
301 """check cardinalities are satisfied""" |
281 card = rproperty(session, rtype, eidfrom, eidto, 'cardinality') |
302 card = rproperty(session, rtype, eidfrom, eidto, 'cardinality') |
282 pendingeids = session.transaction_data.get('pendingeids', ()) |
303 pendingeids = session.transaction_data.get('pendingeids', ()) |
283 if card[0] in '1+' and not eidfrom in pendingeids: |
304 if card[0] in '1+' and not eidfrom in pendingeids: |
459 try: |
486 try: |
460 del self.epropdict[self.key] |
487 del self.epropdict[self.key] |
461 except KeyError: |
488 except KeyError: |
462 self.error('%s has no associated value', self.key) |
489 self.error('%s has no associated value', self.key) |
463 |
490 |
|
491 |
464 class ChangeCWPropertyOp(Operation): |
492 class ChangeCWPropertyOp(Operation): |
465 """a user's custom properties has been added/changed""" |
493 """a user's custom properties has been added/changed""" |
466 |
494 |
467 def commit_event(self): |
495 def commit_event(self): |
468 """the observed connections pool has been commited""" |
496 """the observed connections pool has been commited""" |
469 self.epropdict[self.key] = self.value |
497 self.epropdict[self.key] = self.value |
|
498 |
470 |
499 |
471 class AddCWPropertyOp(Operation): |
500 class AddCWPropertyOp(Operation): |
472 """a user's custom properties has been added/changed""" |
501 """a user's custom properties has been added/changed""" |
473 |
502 |
474 def commit_event(self): |
503 def commit_event(self): |
475 """the observed connections pool has been commited""" |
504 """the observed connections pool has been commited""" |
476 eprop = self.eprop |
505 eprop = self.eprop |
477 if not eprop.for_user: |
506 if not eprop.for_user: |
478 self.repo.vreg.eprop_values[eprop.pkey] = eprop.value |
507 self.repo.vreg.eprop_values[eprop.pkey] = eprop.value |
479 # if for_user is set, update is handled by a ChangeCWPropertyOp operation |
508 # if for_user is set, update is handled by a ChangeCWPropertyOp operation |
|
509 |
480 |
510 |
481 def after_add_eproperty(session, entity): |
511 def after_add_eproperty(session, entity): |
482 key, value = entity.pkey, entity.value |
512 key, value = entity.pkey, entity.value |
483 try: |
513 try: |
484 value = session.vreg.typed_value(key, value) |
514 value = session.vreg.typed_value(key, value) |
485 except UnknownProperty: |
515 except UnknownProperty: |
486 raise ValidationError(entity.eid, {'pkey': session._('unknown property key')}) |
516 raise ValidationError(entity.eid, {'pkey': session._('unknown property key')}) |
487 except ValueError, ex: |
517 except ValueError, ex: |
488 raise ValidationError(entity.eid, {'value': session._(str(ex))}) |
518 raise ValidationError(entity.eid, {'value': session._(str(ex))}) |
489 if not session.user.matching_groups('managers'): |
519 if not session.user.matching_groups('managers'): |
490 session.unsafe_execute('SET P for_user U WHERE P eid %(x)s,U eid %(u)s', |
520 session.add_relation(entity.eid, 'for_user', session.user.eid) |
491 {'x': entity.eid, 'u': session.user.eid}, 'x') |
|
492 else: |
521 else: |
493 AddCWPropertyOp(session, eprop=entity) |
522 AddCWPropertyOp(session, eprop=entity) |
|
523 |
494 |
524 |
495 def after_update_eproperty(session, entity): |
525 def after_update_eproperty(session, entity): |
496 key, value = entity.pkey, entity.value |
526 key, value = entity.pkey, entity.value |
497 try: |
527 try: |
498 value = session.vreg.typed_value(key, value) |
528 value = session.vreg.typed_value(key, value) |
507 else: |
537 else: |
508 # site wide properties |
538 # site wide properties |
509 ChangeCWPropertyOp(session, epropdict=session.vreg.eprop_values, |
539 ChangeCWPropertyOp(session, epropdict=session.vreg.eprop_values, |
510 key=key, value=value) |
540 key=key, value=value) |
511 |
541 |
|
542 |
512 def before_del_eproperty(session, eid): |
543 def before_del_eproperty(session, eid): |
513 for eidfrom, rtype, eidto in session.transaction_data.get('pendingrelations', ()): |
544 for eidfrom, rtype, eidto in session.transaction_data.get('pendingrelations', ()): |
514 if rtype == 'for_user' and eidfrom == eid: |
545 if rtype == 'for_user' and eidfrom == eid: |
515 # if for_user was set, delete has already been handled |
546 # if for_user was set, delete has already been handled |
516 break |
547 break |
517 else: |
548 else: |
518 key = session.execute('Any K WHERE P eid %(x)s, P pkey K', |
549 key = session.execute('Any K WHERE P eid %(x)s, P pkey K', |
519 {'x': eid}, 'x')[0][0] |
550 {'x': eid}, 'x')[0][0] |
520 DelCWPropertyOp(session, epropdict=session.vreg.eprop_values, key=key) |
551 DelCWPropertyOp(session, epropdict=session.vreg.eprop_values, key=key) |
|
552 |
521 |
553 |
522 def after_add_for_user(session, fromeid, rtype, toeid): |
554 def after_add_for_user(session, fromeid, rtype, toeid): |
523 if not session.describe(fromeid)[0] == 'CWProperty': |
555 if not session.describe(fromeid)[0] == 'CWProperty': |
524 return |
556 return |
525 key, value = session.execute('Any K,V WHERE P eid %(x)s,P pkey K,P value V', |
557 key, value = session.execute('Any K,V WHERE P eid %(x)s,P pkey K,P value V', |
529 {'for_user': session._("site-wide property can't be set for user")}) |
561 {'for_user': session._("site-wide property can't be set for user")}) |
530 for session_ in get_user_sessions(session.repo, toeid): |
562 for session_ in get_user_sessions(session.repo, toeid): |
531 ChangeCWPropertyOp(session, epropdict=session_.user.properties, |
563 ChangeCWPropertyOp(session, epropdict=session_.user.properties, |
532 key=key, value=value) |
564 key=key, value=value) |
533 |
565 |
|
566 |
534 def before_del_for_user(session, fromeid, rtype, toeid): |
567 def before_del_for_user(session, fromeid, rtype, toeid): |
535 key = session.execute('Any K WHERE P eid %(x)s, P pkey K', |
568 key = session.execute('Any K WHERE P eid %(x)s, P pkey K', |
536 {'x': fromeid}, 'x')[0][0] |
569 {'x': fromeid}, 'x')[0][0] |
537 relation_deleted(session, fromeid, rtype, toeid) |
570 relation_deleted(session, fromeid, rtype, toeid) |
538 for session_ in get_user_sessions(session.repo, toeid): |
571 for session_ in get_user_sessions(session.repo, toeid): |
539 DelCWPropertyOp(session, epropdict=session_.user.properties, key=key) |
572 DelCWPropertyOp(session, epropdict=session_.user.properties, key=key) |
|
573 |
540 |
574 |
541 def _register_eproperty_hooks(hm): |
575 def _register_eproperty_hooks(hm): |
542 """register workflow related hooks on the hooks manager""" |
576 """register workflow related hooks on the hooks manager""" |
543 hm.register_hook(after_add_eproperty, 'after_add_entity', 'CWProperty') |
577 hm.register_hook(after_add_eproperty, 'after_add_entity', 'CWProperty') |
544 hm.register_hook(after_update_eproperty, 'after_update_entity', 'CWProperty') |
578 hm.register_hook(after_update_eproperty, 'after_update_entity', 'CWProperty') |