server/hooks.py
changeset 2603 e47d63351891
parent 2463 5200c0f7d2d5
child 2605 c4f6a53884ec
equal deleted inserted replaced
2602:7bfa83772d90 2603:e47d63351891
    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', '')
   131     if 'is' in hm.schema:
   147     if 'is' in hm.schema:
   132         hm.register_hook(setis_after_add_entity, 'after_add_entity', '')
   148         hm.register_hook(setis_after_add_entity, 'after_add_entity', '')
   133     if 'CWUser' in hm.schema:
   149     if 'CWUser' in hm.schema:
   134         hm.register_hook(setowner_after_add_user, 'after_add_entity', 'CWUser')
   150         hm.register_hook(setowner_after_add_user, 'after_add_entity', 'CWUser')
   135 
   151 
       
   152 
   136 # core hooks ##################################################################
   153 # core hooks ##################################################################
   137 
   154 
   138 class DelayedDeleteOp(PreCommitOperation):
   155 class DelayedDeleteOp(PreCommitOperation):
   139     """delete the object of composite relation except if the relation
   156     """delete the object of composite relation except if the relation
   140     has actually been redirected to another composite
   157     has actually been redirected to another composite
   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 
   183                               constraint)
   202                               constraint)
   184 
   203 
   185     def commit_event(self):
   204     def commit_event(self):
   186         pass
   205         pass
   187 
   206 
       
   207 
   188 def cstrcheck_after_add_relation(session, eidfrom, rtype, eidto):
   208 def cstrcheck_after_add_relation(session, eidfrom, rtype, eidto):
   189     """check the relation satisfy its constraints
   209     """check the relation satisfy its constraints
   190 
   210 
   191     this is delayed to a precommit time operation since other relation which
   211     this is delayed to a precommit time operation since other relation which
   192     will make constraint satisfied may be added later.
   212     will make constraint satisfied may be added later.
   208             if rset and rset[0][0] != entity.eid:
   228             if rset and rset[0][0] != entity.eid:
   209                 msg = session._('the value "%s" is already used, use another one')
   229                 msg = session._('the value "%s" is already used, use another one')
   210                 raise ValidationError(entity.eid, {attr: msg % val})
   230                 raise ValidationError(entity.eid, {attr: msg % val})
   211 
   231 
   212 
   232 
   213 
       
   214 
       
   215 
       
   216 class CheckRequiredRelationOperation(LateOperation):
   233 class CheckRequiredRelationOperation(LateOperation):
   217     """checking relation cardinality has to be done after commit in
   234     """checking relation cardinality has to be done after commit in
   218     case the relation is being replaced
   235     case the relation is being replaced
   219     """
   236     """
   220     eid, rtype = None, None
   237     eid, rtype = None, None
   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:
   312         rql = 'Any N WHERE G eid %(x)s, G name N'
   333         rql = 'Any N WHERE G eid %(x)s, G name N'
   313         result = session.execute(rql, {'x': kwargs['geid']}, 'x', build_descr=False)
   334         result = session.execute(rql, {'x': kwargs['geid']}, 'x', build_descr=False)
   314         Operation.__init__(self, session, *args, **kwargs)
   335         Operation.__init__(self, session, *args, **kwargs)
   315         self.group = result[0][0]
   336         self.group = result[0][0]
   316 
   337 
       
   338 
   317 class DeleteGroupOp(GroupOperation):
   339 class DeleteGroupOp(GroupOperation):
   318     """synchronize user when a in_group relation has been deleted"""
   340     """synchronize user when a in_group relation has been deleted"""
   319     def commit_event(self):
   341     def commit_event(self):
   320         """the observed connections pool has been commited"""
   342         """the observed connections pool has been commited"""
   321         groups = self.cnxuser.groups
   343         groups = self.cnxuser.groups
   323             groups.remove(self.group)
   345             groups.remove(self.group)
   324         except KeyError:
   346         except KeyError:
   325             self.error('user %s not in group %s',  self.cnxuser, self.group)
   347             self.error('user %s not in group %s',  self.cnxuser, self.group)
   326             return
   348             return
   327 
   349 
       
   350 
   328 def after_del_in_group(session, fromeid, rtype, toeid):
   351 def after_del_in_group(session, fromeid, rtype, toeid):
   329     """modify user permission, need to update users"""
   352     """modify user permission, need to update users"""
   330     for session_ in get_user_sessions(session.repo, fromeid):
   353     for session_ in get_user_sessions(session.repo, fromeid):
   331         DeleteGroupOp(session, cnxuser=session_.user, geid=toeid)
   354         DeleteGroupOp(session, cnxuser=session_.user, geid=toeid)
   332 
   355 
   340             self.warning('user %s already in group %s', self.cnxuser,
   363             self.warning('user %s already in group %s', self.cnxuser,
   341                          self.group)
   364                          self.group)
   342             return
   365             return
   343         groups.add(self.group)
   366         groups.add(self.group)
   344 
   367 
       
   368 
   345 def after_add_in_group(session, fromeid, rtype, toeid):
   369 def after_add_in_group(session, fromeid, rtype, toeid):
   346     """modify user permission, need to update users"""
   370     """modify user permission, need to update users"""
   347     for session_ in get_user_sessions(session.repo, fromeid):
   371     for session_ in get_user_sessions(session.repo, fromeid):
   348         AddGroupOp(session, cnxuser=session_.user, geid=toeid)
   372         AddGroupOp(session, cnxuser=session_.user, geid=toeid)
   349 
   373 
   359         try:
   383         try:
   360             self.repo.close(self.cnxid)
   384             self.repo.close(self.cnxid)
   361         except BadConnectionId:
   385         except BadConnectionId:
   362             pass # already closed
   386             pass # already closed
   363 
   387 
       
   388 
   364 def after_del_user(session, eid):
   389 def after_del_user(session, eid):
   365     """modify user permission, need to update users"""
   390     """modify user permission, need to update users"""
   366     for session_ in get_user_sessions(session.repo, eid):
   391     for session_ in get_user_sessions(session.repo, eid):
   367         DelUserOp(session, session_.id)
   392         DelUserOp(session, session_.id)
       
   393 
   368 
   394 
   369 def _register_usergroup_hooks(hm):
   395 def _register_usergroup_hooks(hm):
   370     """register user/group related hooks on the hooks manager"""
   396     """register user/group related hooks on the hooks manager"""
   371     hm.register_hook(after_del_user, 'after_delete_entity', 'CWUser')
   397     hm.register_hook(after_del_user, 'after_delete_entity', 'CWUser')
   372     hm.register_hook(after_add_in_group, 'after_add_relation', 'in_group')
   398     hm.register_hook(after_add_in_group, 'after_add_relation', 'in_group')
   435 
   461 
   436 
   462 
   437 def set_initial_state_after_add(session, entity):
   463 def set_initial_state_after_add(session, entity):
   438     SetInitialStateOp(session, entity=entity)
   464     SetInitialStateOp(session, entity=entity)
   439 
   465 
       
   466 
   440 def _register_wf_hooks(hm):
   467 def _register_wf_hooks(hm):
   441     """register workflow related hooks on the hooks manager"""
   468     """register workflow related hooks on the hooks manager"""
   442     if 'in_state' in hm.schema:
   469     if 'in_state' in hm.schema:
   443         hm.register_hook(before_add_in_state, 'before_add_relation', 'in_state')
   470         hm.register_hook(before_add_in_state, 'before_add_relation', 'in_state')
   444         hm.register_hook(relation_deleted, 'before_delete_relation', 'in_state')
   471         hm.register_hook(relation_deleted, 'before_delete_relation', 'in_state')
   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')