# HG changeset patch # User Sylvain Thénault # Date 1250241250 -7200 # Node ID 107ba1c452279e897345215bf057dc35d885bf7f # Parent 06daf13195d4ec2218b4f6bfb37dd1192b8cc72d rewrite hooks in sobjects as new Hook style into hooks sub-package diff -r 06daf13195d4 -r 107ba1c45227 hooks/bookmark.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hooks/bookmark.py Fri Aug 14 11:14:10 2009 +0200 @@ -0,0 +1,31 @@ +"""bookmark related hooks + +:organization: Logilab +:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses +""" +__docformat__ = "restructuredtext en" + +from cubicweb.selectors import entity_implements +from cubicweb.server import hook + + +class AutoDeleteBookmarkOp(hook.Operation): + bookmark = None # make pylint happy + def precommit_event(self): + if not self.session.deleted_in_transaction(self.bookmark.eid): + if not self.bookmark.bookmarked_by: + self.bookmark.delete() + + +class DelBookmarkedByHook(hook.Hook): + """ensure user logins are stripped""" + __id__ = 'autodelbookmark' + __select__ = hook.Hook.__select__ & entity_implements('bookmarked_by',) + category = 'bookmark' + events = ('after_delete_relation',) + + def __call__(self): + AutoDeleteBookmarkOp(self.cw_req, + bookmark=self.cw_req.entity_from_eid(self.eidfrom)) diff -r 06daf13195d4 -r 107ba1c45227 hooks/email.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hooks/email.py Fri Aug 14 11:14:10 2009 +0200 @@ -0,0 +1,62 @@ +"""hooks to ensure use_email / primary_email relations consistency + +:organization: Logilab +:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses +""" +__docformat__ = "restructuredtext en" + +from cubicweb.server import hook + +class SetUseEmailRelationOp(hook.Operation): + """delay this operation to commit to avoid conflict with a late rql query + already setting the relation + """ + rtype = 'use_email' + entity = email = None # make pylint happy + + def condition(self): + """check entity has use_email set for the email address""" + return not any(e for e in self.entity.use_email + if self.email.eid == e.eid) + + def precommit_event(self): + if self.condition(): + self.session.unsafe_execute( + 'SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % self.rtype, + {'x': self.entity.eid, 'y': self.email.eid}, 'x') + +class SetPrimaryEmailRelationOp(SetUseEmailRelationOp): + rtype = 'primary_email' + + def condition(self): + """check entity has no primary_email set""" + return not self.entity.primary_email + + +class SetPrimaryEmailHook(hook.Hook): + """notify when a bug or story or version has its state modified""" + __id__ = 'setprimaryemail' + __select__ = hook.Hook.__select__ & hook.match_rtype('use_email') + category = 'email' + events = ('after_add_relation',) + + def call(self, session, eidfrom, rtype, eidto): + entity = self.cw_req.entity_from_eid(self.eidfrom) + if 'primary_email' in entity.e_schema.subject_relations(): + SetPrimaryEmailRelationOp(self.cw_req, entity=entity, + email=self.cw_req.entity_from_eid(self.eidto)) + +class SetUseEmailHook(hook.Hook): + """notify when a bug or story or version has its state modified""" + __id__ = 'setprimaryemail' + __select__ = hook.Hook.__select__ & hook.match_rtype('primary_email') + category = 'email' + events = ('after_add_relation',) + + def __call__(self): + entity = self.cw_req.entity_from_eid(self.eidfrom) + if 'use_email' in entity.e_schema.subject_relations(): + SetUseEmailRelationOp(self.cw_req, entity=entity, + email=self.cw_req.entity_from_eid(self.eidto)) diff -r 06daf13195d4 -r 107ba1c45227 hooks/integrity.py --- a/hooks/integrity.py Fri Aug 14 11:13:18 2009 +0200 +++ b/hooks/integrity.py Fri Aug 14 11:14:10 2009 +0200 @@ -10,9 +10,9 @@ from cubicweb import ValidationError from cubicweb.selectors import entity_implements -from cubicweb.server.hook import Hook +from cubicweb.common.uilib import soup2xhtml +from cubicweb.server import hook from cubicweb.server.pool import LateOperation, PreCommitOperation -from cubicweb.server.hookhelper import rproperty # special relations that don't have to be checked for integrity, usually # because they are handled internally by hooks (so we trust ourselves) @@ -23,7 +23,7 @@ 'wf_info_for', 'from_state', 'to_state')) -class _CheckRequiredRelationOperation(LateOperation): +class _CheckRequiredRelationOperation(hook.LateOperation): """checking relation cardinality has to be done after commit in case the relation is being replaced """ @@ -31,7 +31,7 @@ def precommit_event(self): # recheck pending eids - if self.eid in self.session.transaction_data.get('pendingeids', ()): + if self.session.deleted_in_transaction(self.eid): return if self.session.unsafe_execute(*self._rql()).rowcount < 1: etype = self.session.describe(self.eid)[0] @@ -59,10 +59,14 @@ return 'Any S WHERE O eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x' -class CheckCardinalityHook(Hook): +class IntegrityHook(hook.Hook): + __abstract__ = True + category = 'integrity' + + +class CheckCardinalityHook(IntegrityHook): """check cardinalities are satisfied""" __id__ = 'checkcard' - category = 'integrity' events = ('after_add_entity', 'before_delete_relation') def __call__(self): @@ -103,28 +107,26 @@ return session = self.cw_req eidfrom, eidto = self.eidfrom, self.eidto - card = rproperty(session, rtype, eidfrom, eidto, 'cardinality') + card = session.schema_rproperty(rtype, eidfrom, eidto, 'cardinality') pendingrdefs = session.transaction_data.get('pendingrdefs', ()) if (session.describe(eidfrom)[0], rtype, session.describe(eidto)[0]) in pendingrdefs: return - pendingeids = session.transaction_data.get('pendingeids', ()) - if card[0] in '1+' and not eidfrom in pendingeids: + if card[0] in '1+' and not session.deleted_in_transaction(eidfrom): self.checkrel_if_necessary(_CheckSRelationOp, rtype, eidfrom) - if card[1] in '1+' and not eidto in pendingeids: + if card[1] in '1+' and not session.deleted_in_transaction(eidto): self.checkrel_if_necessary(_CheckORelationOp, rtype, eidto) -class _CheckConstraintsOp(LateOperation): +class _CheckConstraintsOp(hook.LateOperation): """check a new relation satisfy its constraints """ def precommit_event(self): eidfrom, rtype, eidto = self.rdef # first check related entities have not been deleted in the same # transaction - pending = self.session.transaction_data.get('pendingeids', ()) - if eidfrom in pending: + if self.session.deleted_in_transaction(eidfrom): return - if eidto in pending: + if self.session.deleted_in_transaction(eidto): return for constraint in self.constraints: try: @@ -137,25 +139,25 @@ pass -class CheckConstraintHook(Hook): +class CheckConstraintHook(IntegrityHook): """check the relation satisfy its constraints this is delayed to a precommit time operation since other relation which will make constraint satisfied may be added later. """ __id__ = 'checkconstraint' - category = 'integrity' events = ('after_add_relation',) + def __call__(self): - constraints = rproperty(self.cw_req, self.rtype, self.eidfrom, self.eidto, + constraints = self.cw_req.schema_rproperty(self.rtype, self.eidfrom, self.eidto, 'constraints') if constraints: _CheckConstraintsOp(self.cw_req, constraints=constraints, rdef=(self.eidfrom, self.rtype, self.eidto)) -class CheckUniqueHook(Hook): + +class CheckUniqueHook(IntegrityHook): __id__ = 'checkunique' - category = 'integrity' events = ('before_add_entity', 'before_update_entity') def __call__(self): @@ -174,7 +176,7 @@ raise ValidationError(entity.eid, {attr: msg % val}) -class _DelayedDeleteOp(PreCommitOperation): +class _DelayedDeleteOp(hook.Operation): """delete the object of composite relation except if the relation has actually been redirected to another composite """ @@ -182,23 +184,23 @@ def precommit_event(self): session = self.session # don't do anything if the entity is being created or deleted - if not (self.eid in session.transaction_data.get('pendingeids', ()) or - self.eid in session.transaction_data.get('neweids', ())): + if not (session.deleted_in_transaction(self.eid) or + session.added_in_transaction(self.eid)): etype = session.describe(self.eid)[0] session.unsafe_execute('DELETE %s X WHERE X eid %%(x)s, NOT %s' % (etype, self.relation), {'x': self.eid}, 'x') -class DeleteCompositeOrphanHook(Hook): +class DeleteCompositeOrphanHook(IntegrityHook): """delete the composed of a composite relation when this relation is deleted """ __id__ = 'deletecomposite' - category = 'integrity' events = ('before_delete_relation',) + def __call__(self): - composite = rproperty(self.cw_req, self.rtype, self.eidfrom, self.eidto, - 'composite') + composite = self.cw_req.schema_rproperty(self.rtype, self.eidfrom, self.eidto, + 'composite') if composite == 'subject': _DelayedDeleteOp(self.cw_req, eid=self.eidto, relation='Y %s X' % self.rtype) @@ -207,12 +209,11 @@ relation='X %s Y' % self.rtype) -class DontRemoveOwnersGroupHook(Hook): +class DontRemoveOwnersGroupHook(IntegrityHook): """delete the composed of a composite relation when this relation is deleted """ __id__ = 'checkownersgroup' - __select__ = Hook.__select__ & entity_implements('CWGroup') - category = 'integrity' + __select__ = IntegrityHook.__select__ & entity_implements('CWGroup') events = ('before_delete_entity', 'before_update_entity') def __call__(self): @@ -226,3 +227,31 @@ self.entity['name'] = newname +class TidyHtmlFields(IntegrityHook): + """tidy HTML in rich text strings""" + __id__ = 'htmltidy' + events = ('before_add_entity', 'before_update_entity') + + def __call__(self): + entity = self.entity + metaattrs = entity.e_schema.meta_attributes() + for metaattr, (metadata, attr) in metaattrs.iteritems(): + if metadata == 'format' and attr in entity.edited_attributes: + try: + value = entity[attr] + except KeyError: + continue # no text to tidy + if isinstance(value, unicode): # filter out None and Binary + if getattr(entity, str(metaattr)) == 'text/html': + entity[attr] = soup2xhtml(value, self.cw_req.encoding) + + +class StripCWUserLoginHook(IntegrityHook): + """ensure user logins are stripped""" + __id__ = 'stripuserlogin' + __select__ = IntegrityHook.__select__ & entity_implements('CWUser') + events = ('before_add_entity', 'before_update_entity',) + + def call(self, session, entity): + if 'login' in entity.edited_attributes and entity['login']: + entity['login'] = entity['login'].strip() diff -r 06daf13195d4 -r 107ba1c45227 hooks/metadata.py --- a/hooks/metadata.py Fri Aug 14 11:13:18 2009 +0200 +++ b/hooks/metadata.py Fri Aug 14 11:14:10 2009 +0200 @@ -11,9 +11,7 @@ from datetime import datetime from cubicweb.selectors import entity_implements -from cubicweb.server.hook import Hook -from cubicweb.server.pool import Operation, LateOperation, PreCommitOperation -from cubicweb.server.hookhelper import rproperty +from cubicweb.server import hook from cubicweb.server.repository import FTIndexEntityOp @@ -28,14 +26,18 @@ return eschema.eid -class InitMetaAttrsHook(Hook): +class MetaDataHook(hook.Hook): + __abstract__ = True + category = 'metadata' + + +class InitMetaAttrsHook(MetaDataHook): """before create a new entity -> set creation and modification date this is a conveniency hook, you shouldn't have to disable it """ - id = 'metaattrsinit' + __id__ = 'metaattrsinit' events = ('before_add_entity',) - category = 'metadata' def __call__(self): timestamp = datetime.now() @@ -46,31 +48,31 @@ self.entity.setdefault('cwuri', cwuri) -class UpdateMetaAttrsHook(Hook): +class UpdateMetaAttrsHook(MetaDataHook): """update an entity -> set modification date""" - id = 'metaattrsupdate' + __id__ = 'metaattrsupdate' events = ('before_update_entity',) - category = 'metadata' + def __call__(self): self.entity.setdefault('modification_date', datetime.now()) -class _SetCreatorOp(PreCommitOperation): +class _SetCreatorOp(hook.Operation): def precommit_event(self): session = self.session - if self.entity.eid in session.transaction_data.get('pendingeids', ()): + if session.deleted_in_transaction(self.entity.eid): # entity have been created and deleted in the same transaction return if not self.entity.created_by: session.add_relation(self.entity.eid, 'created_by', session.user.eid) -class SetIsHook(Hook): +class SetIsHook(MetaDataHook): """create a new entity -> set is relation""" - id = 'setis' + __id__ = 'setis' events = ('after_add_entity',) - category = 'metadata' + def __call__(self): if hasattr(self.entity, '_cw_recreating'): return @@ -87,11 +89,11 @@ eschema_type_eid(session, etype)) -class SetOwnershipHook(Hook): +class SetOwnershipHook(MetaDataHook): """create a new entity -> set owner and creator metadata""" - id = 'setowner' + __id__ = 'setowner' events = ('after_add_entity',) - category = 'metadata' + def __call__(self): asession = self.cw_req.actual_session() if not asession.is_internal_session: @@ -99,48 +101,48 @@ _SetCreatorOp(asession, entity=self.entity) -class _SyncOwnersOp(PreCommitOperation): +class _SyncOwnersOp(hook.Operation): def precommit_event(self): self.session.unsafe_execute('SET X owned_by U WHERE C owned_by U, C eid %(c)s,' 'NOT EXISTS(X owned_by U, X eid %(x)s)', {'c': self.compositeeid, 'x': self.composedeid}, ('c', 'x')) -class SyncCompositeOwner(Hook): + +class SyncCompositeOwner(MetaDataHook): """when adding composite relation, the composed should have the same owners has the composite """ - id = 'synccompositeowner' + __id__ = 'synccompositeowner' events = ('after_add_relation',) - category = 'metadata' + def __call__(self): if self.rtype == 'wf_info_for': # skip this special composite relation # XXX (syt) why? return eidfrom, eidto = self.eidfrom, self.eidto - composite = rproperty(self.cw_req, self.rtype, eidfrom, eidto, 'composite') + composite = self.cw_req.schema_rproperty(self.rtype, eidfrom, eidto, 'composite') if composite == 'subject': _SyncOwnersOp(self.cw_req, compositeeid=eidfrom, composedeid=eidto) elif composite == 'object': _SyncOwnersOp(self.cw_req, compositeeid=eidto, composedeid=eidfrom) -class FixUserOwnershipHook(Hook): +class FixUserOwnershipHook(MetaDataHook): """when a user has been created, add owned_by relation on itself""" - id = 'fixuserowner' - __select__ = Hook.__select__ & entity_implements('CWUser') + __id__ = 'fixuserowner' + __select__ = MetaDataHook.__select__ & entity_implements('CWUser') events = ('after_add_entity',) - category = 'metadata' + def __call__(self): self.cw_req.add_relation(self.entity.eid, 'owned_by', self.entity.eid) -class UpdateFTIHook(Hook): +class UpdateFTIHook(MetaDataHook): """sync fulltext index when relevant relation is added / removed """ - id = 'updateftirel' + __id__ = 'updateftirel' events = ('after_add_relation', 'after_delete_relation') - category = 'metadata' def __call__(self): rtype = self.rtype diff -r 06daf13195d4 -r 107ba1c45227 hooks/notification.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hooks/notification.py Fri Aug 14 11:14:10 2009 +0200 @@ -0,0 +1,133 @@ +"""some hooks to handle notification on entity's changes + +:organization: Logilab +:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses +""" +__docformat__ = "restructuredtext en" + +from logilab.common.textutils import normalize_text + +from cubicweb import RegistryException +from cubicweb.selectors import entity_implements +from cubicweb.server import hook + + +class RenderAndSendNotificationView(hook.Operation): + """delay rendering of notification view until precommit""" + def precommit_event(self): + view = self.view + if view.cw_rset and self.session.deleted_in_transaction(view.cw_rset[cw_rset.cw_row or 0][cw_rset.cw_col or 0]): + return # entity added and deleted in the same transaction + self.view.render_and_send(**getattr(self, 'viewargs', {})) + + +class NotificationHook(hook.Hook): + __abstract__ = True + category = 'notification' + + def select_view(self, vid, rset, row=0, col=0): + return self.cw_req.vreg['views'].select_object(vid, self.cw_req, + rset=rset, row=0, col=0) + + +class StatusChangeHook(NotificationHook): + """notify when a workflowable entity has its state modified""" + __id__ = 'notifystatuschange' + __select__ = NotificationHook.__select__ & entity_implements('TrInfo') + events = ('after_add_entity',) + + def __call__(self): + entity = self.entity + if not entity.from_state: # not a transition + return + rset = entity.related('wf_info_for') + view = self.select_view('notif_status_change', rset=rset, row=0) + if view is None: + return + comment = entity.printable_value('comment', format='text/plain') + if comment: + comment = normalize_text(comment, 80, + rest=entity.comment_format=='text/rest') + RenderAndSendNotificationView(self.cw_req, view=view, viewargs={ + 'comment': comment, 'previous_state': entity.previous_state.name, + 'current_state': entity.new_state.name}) + + +class RelationChangeHook(NotificationHook): + __id__ = 'notifyrelationchange' + events = ('before_add_relation', 'after_add_relation', + 'before_delete_relation', 'after_delete_relation') + + def __call__(self): + """if a notification view is defined for the event, send notification + email defined by the view + """ + rset = self.cw_req.eid_rset(self.eidfrom) + view = self.select_view('notif_%s_%s' % (self.event, self.rtype), + rset=rset, row=0) + if view is None: + return + RenderAndSendNotificationView(self.cw_req, view=view) + + +class EntityChangeHook(NotificationHook): + """if a notification view is defined for the event, send notification + email defined by the view + """ + __id__ = 'notifyentitychange' + events = ('after_add_entity', 'after_update_entity') + + def __call__(self): + rset = self.entity.as_rset() + view = self.select_view('notif_%s' % self.event, rset=rset, row=0) + if view is None: + return + RenderAndSendNotificationView(self.cw_req, view=view) + + +# supervising ################################################################## + +class SomethingChangedHook(NotificationHook): + __id__ = 'supervising' + events = ('before_add_relation', 'before_delete_relation', + 'after_add_entity', 'before_update_entity') + + def __call__(self): + dest = self.cw_req.vreg.config['supervising-addrs'] + if not dest: # no supervisors, don't do this for nothing... + return + if self._call(): + SupervisionMailOp(self.cw_req) + + def _call(self): + event = self.event.split('_', 1)[1] + if event == 'update_entity': + if self.cw_req.added_in_transaction(self.entity.eid): + return False + if self.entity.e_schema == 'CWUser': + if not (self.entity.edited_attributes - frozenset(('eid', 'modification_date', + 'last_login_time'))): + # don't record last_login_time update which are done + # automatically at login time + return False + self.cw_req.transaction_data.setdefault('pendingchanges', []).append( + (event, self)) + return True + + +class EntityDeleteHook(SomethingChangedHook): + __id__ = 'supervisingentitydel' + events = ('before_delete_entity',) + + def _call(self): + try: + title = self.entity.dc_title() + except: + # may raise an error during deletion process, for instance due to + # missing required relation + title = '#%s' % eid + self.cw_req.transaction_data.setdefault('pendingchanges', []).append( + ('delete_entity', (self.eid, str(self.entity.e_schema), title))) + return True diff -r 06daf13195d4 -r 107ba1c45227 hooks/syncschema.py --- a/hooks/syncschema.py Fri Aug 14 11:13:18 2009 +0200 +++ b/hooks/syncschema.py Fri Aug 14 11:14:10 2009 +0200 @@ -453,7 +453,7 @@ # when the relation is added in the same transaction, the constraint # object is created by the operation adding the attribute or relation, # so there is nothing to do here - if rdef.eid in session.transaction_data.get('neweids', ()): + if session.added_in_transaction(rdef.eid): return subjtype, rtype, objtype = session.schema.schema_by_eid(rdef.eid) cstrtype = self.entity.type @@ -602,7 +602,7 @@ # when the relation is added in the same transaction, the constraint # object is created by the operation adding the attribute or relation, # so there is nothing to do here - if rdef.eid in self.session.transaction_data.get('neweids', ()): + if session.added_in_transaction(rdef.eid): self.cancelled = True return subjtype, rtype, objtype = self.session.schema.schema_by_eid(rdef.eid) @@ -729,17 +729,20 @@ erschema.set_rqlexprs(self.perm, rqlexprs) -# deletion hooks ############################################################### +class SyncSchemaHook(hook.Hook): + __abstract__ = True + category = 'syncschema' -class DelCWETypeHook(hook.Hook): +# CWEType hooks ################################################################ + +class DelCWETypeHook(SyncSchemaHook): """before deleting a CWEType entity: * check that we don't remove a core entity type * cascade to delete related CWAttribute and CWRelation entities * instantiate an operation to delete the entity type on commit """ __id__ = 'syncdelcwetype' - __select__ = hook.Hook.__select__ & entity_implements('CWEType') - category = 'syncschema' + __select__ = SyncSchemaHook.__select__ & entity_implements('CWEType') events = ('before_delete_entity',) def __call__(self): @@ -831,16 +834,19 @@ MemSchemaCWETypeRename(self.cw_req, oldname=oldname, newname=newname) entity['name'] = newname -class DelCWRTypeHook(hook.Hook): + +# CWRType hooks ################################################################ + +class DelCWRTypeHook(SyncSchemaHook): """before deleting a CWRType entity: * check that we don't remove a core relation type * cascade to delete related CWAttribute and CWRelation entities * instantiate an operation to delete the relation type on commit """ __id__ = 'syncdelcwrtype' - __select__ = hook.Hook.__select__ & entity_implements('CWRType') - category = 'syncschema' + __select__ = SyncSchemaHook.__select__ & entity_implements('CWRType') events = ('before_delete_entity',) + def __call__(self): name = self.entity.name if name in CORE_ETYPES: @@ -900,8 +906,9 @@ entity=entity) +# relation_type hooks ########################################################## -class AfterDelRelationTypeHook(hook.Hook): +class AfterDelRelationTypeHook(SyncSchemaHook): """before deleting a CWAttribute or CWRelation entity: * if this is a final or inlined relation definition, instantiate an operation to drop necessary column, else if this is the last instance @@ -911,8 +918,7 @@ * delete the associated relation type when necessary """ __id__ = 'syncdelrelationtype' - __select__ = hook.Hook.__select__ & hook.match_rtype('relation_type') - category = 'syncschema' + __select__ = SyncSchemaHook.__select__ & hook.match_rtype('relation_type') events = ('after_delete_relation',) def __call__(self): @@ -954,10 +960,11 @@ MemSchemaRDefDel(session, (subjschema, rschema, objschema)) -class AfterAddCWAttributeHook(hook.Hook): +# CWAttribute / CWRelation hooks ############################################### + +class AfterAddCWAttributeHook(SyncSchemaHook): __id__ = 'syncaddcwattribute' - __select__ = hook.Hook.__select__ & entity_implements('CWAttribute') - category = 'syncschema' + __select__ = SyncSchemaHook.__select__ & entity_implements('CWAttribute') events = ('after_add_entity',) def __call__(self): @@ -966,21 +973,21 @@ class AfterAddCWRelationHook(AfterAddCWAttributeHook): __id__ = 'syncaddcwrelation' - __select__ = hook.Hook.__select__ & entity_implements('CWRelation') + __select__ = SyncSchemaHook.__select__ & entity_implements('CWRelation') def __call__(self): SourceDbCWRelationAdd(self.cw_req, entity=self.entity) -class AfterUpdateCWRDefHook(hook.Hook): +class AfterUpdateCWRDefHook(SyncSchemaHook): __id__ = 'syncaddcwattribute' - __select__ = hook.Hook.__select__ & entity_implements('CWAttribute', 'CWRelation') - category = 'syncschema' + __select__ = SyncSchemaHook.__select__ & entity_implements('CWAttribute', + 'CWRelation') events = ('after_update_entity',) def __call__(self): entity = self.entity - if entity.eid in self.cw_req.transaction_data.get('pendingeids', ()): + if self.cw_req.deleted_in_transaction(entity.eid): return desttype = entity.otype.name rschema = self.cw_req.schema[entity.rtype.name] @@ -1002,10 +1009,9 @@ # constraints synchronization hooks ############################################ -class AfterAddCWConstraintHook(hook.Hook): +class AfterAddCWConstraintHook(SyncSchemaHook): __id__ = 'syncaddcwconstraint' - __select__ = hook.Hook.__select__ & entity_implements('CWConstraint') - category = 'syncschema' + __select__ = SyncSchemaHook.__select__ & entity_implements('CWConstraint') events = ('after_add_entity', 'after_update_entity') def __call__(self): @@ -1013,14 +1019,13 @@ SourceDbCWConstraintAdd(self.cw_req, entity=self.entity) -class AfterAddConstrainedByHook(hook.Hook): +class AfterAddConstrainedByHook(SyncSchemaHook): __id__ = 'syncdelconstrainedby' - __select__ = hook.Hook.__select__ & hook.match_rtype('constrainted_by') - category = 'syncschema' + __select__ = SyncSchemaHook.__select__ & hook.match_rtype('constrainted_by') events = ('after_add_relation',) def __call__(self): - if self.eidfrom in self.cw_req.transaction_data.get('neweids', ()): + if self.cw_req.added_in_transaction(self.eidfrom): self.cw_req.transaction_data.setdefault(self.eidfrom, []).append(self.eidto) @@ -1029,7 +1034,7 @@ events = ('before_delete_relation',) def __call__(self): - if self.eidfrom in self.cw_req.transaction_data.get('pendingeids', ()): + if self.cw_req.deleted_in_transaction(self.eidfrom): return schema = self.cw_req.schema entity = self.cw_req.entity_from_eid(self.eidto) @@ -1049,13 +1054,12 @@ # permissions synchronization hooks ############################################ -class AfterAddPermissionHook(hook.Hook): +class AfterAddPermissionHook(SyncSchemaHook): """added entity/relation *_permission, need to update schema""" __id__ = 'syncaddperm' - __select__ = hook.Hook.__select__ & hook.match_rtype( + __select__ = SyncSchemaHook.__select__ & hook.match_rtype( 'read_permission', 'add_permission', 'delete_permission', 'update_permission') - category = 'syncschema' events = ('after_add_relation',) def __call__(self): @@ -1076,7 +1080,7 @@ events = ('before_delete_relation',) def __call__(self): - if self.eidfrom in self.cw_req.transaction_data.get('pendingeids', ()): + if self.cw_req.deleted_in_transaction(self.eidfrom): return perm = self.rtype.split('_', 1)[0] if self.cw_req.describe(self.eidto)[0] == 'CWGroup': @@ -1087,10 +1091,9 @@ -class ModifySpecializesHook(hook.Hook): +class ModifySpecializesHook(SyncSchemaHook): __id__ = 'syncspecializes' - __select__ = hook.Hook.__select__ & hook.match_rtype('specializes') - category = 'syncschema' + __select__ = SyncSchemaHook.__select__ & hook.match_rtype('specializes') events = ('after_add_relation', 'after_delete_relation') def __call__(self): diff -r 06daf13195d4 -r 107ba1c45227 hooks/syncsession.py --- a/hooks/syncsession.py Fri Aug 14 11:13:18 2009 +0200 +++ b/hooks/syncsession.py Fri Aug 14 11:14:10 2009 +0200 @@ -9,14 +9,23 @@ from cubicweb import UnknownProperty, ValidationError, BadConnectionId from cubicweb.selectors import entity_implements -from cubicweb.server.hook import Hook, match_rtype -from cubicweb.server.pool import Operation -from cubicweb.server.hookhelper import get_user_sessions +from cubicweb.server import hook + + +def get_user_sessions(repo, ueid): + for session in repo._sessions.values(): + if ueid == session.user.eid: + yield session + + +class SyncSessionHook(hook.Hook): + __abstract__ = True + category = 'syncsession' # user/groups synchronisation ################################################# -class _GroupOperation(Operation): +class _GroupOperation(hook.Operation): """base class for group operation""" geid = None def __init__(self, session, *args, **kwargs): @@ -27,7 +36,7 @@ """ rql = 'Any N WHERE G eid %(x)s, G name N' result = session.execute(rql, {'x': kwargs['geid']}, 'x', build_descr=False) - Operation.__init__(self, session, *args, **kwargs) + hook.Operation.__init__(self, session, *args, **kwargs) self.group = result[0][0] @@ -55,11 +64,10 @@ groups.add(self.group) -class SyncInGroupHook(Hook): +class SyncInGroupHook(SyncSessionHook): __id__ = 'syncingroup' - __select__ = Hook.__select__ & match_rtype('in_group') + __select__ = SyncSessionHook.__select__ & hook.match_rtype('in_group') events = ('after_delete_relation', 'after_add_relation') - category = 'syncsession' def __call__(self): if self.event == 'after_delete_relation': @@ -70,11 +78,11 @@ opcls(self.cw_req, cnxuser=session.user, geid=self.eidto) -class _DelUserOp(Operation): +class _DelUserOp(hook.Operation): """close associated user's session when it is deleted""" def __init__(self, session, cnxid): self.cnxid = cnxid - Operation.__init__(self, session) + hook.Operation.__init__(self, session) def commit_event(self): """the observed connections pool has been commited""" @@ -84,11 +92,10 @@ pass # already closed -class CloseDeletedUserSessionsHook(Hook): +class CloseDeletedUserSessionsHook(SyncSessionHook): __id__ = 'closession' - __select__ = Hook.__select__ & entity_implements('CWUser') + __select__ = SyncSessionHook.__select__ & entity_implements('CWUser') events = ('after_delete_entity',) - category = 'syncsession' def __call__(self): """modify user permission, need to update users""" @@ -99,7 +106,7 @@ # CWProperty hooks ############################################################# -class _DelCWPropertyOp(Operation): +class _DelCWPropertyOp(hook.Operation): """a user's custom properties has been deleted""" def commit_event(self): @@ -110,7 +117,7 @@ self.error('%s has no associated value', self.key) -class _ChangeCWPropertyOp(Operation): +class _ChangeCWPropertyOp(hook.Operation): """a user's custom properties has been added/changed""" def commit_event(self): @@ -118,7 +125,7 @@ self.epropdict[self.key] = self.value -class _AddCWPropertyOp(Operation): +class _AddCWPropertyOp(hook.Operation): """a user's custom properties has been added/changed""" def commit_event(self): @@ -129,10 +136,9 @@ # if for_user is set, update is handled by a ChangeCWPropertyOp operation -class AddCWPropertyHook(Hook): +class AddCWPropertyHook(SyncSessionHook): __id__ = 'addcwprop' - __select__ = Hook.__select__ & entity_implements('CWProperty') - category = 'syncsession' + __select__ = SyncSessionHook.__select__ & entity_implements('CWProperty') events = ('after_add_entity',) def __call__(self): @@ -194,11 +200,10 @@ _DelCWPropertyOp(session, epropdict=session.vreg.eprop_values, key=entity.pkey) -class AddForUserRelationHook(Hook): +class AddForUserRelationHook(SyncSessionHook): __id__ = 'addcwpropforuser' - __select__ = Hook.__select__ & match_rtype('for_user') + __select__ = SyncSessionHook.__select__ & hook.match_rtype('for_user') events = ('after_add_relation',) - category = 'syncsession' def __call__(self): session = self.cw_req diff -r 06daf13195d4 -r 107ba1c45227 hooks/workflow.py --- a/hooks/workflow.py Fri Aug 14 11:13:18 2009 +0200 +++ b/hooks/workflow.py Fri Aug 14 11:14:10 2009 +0200 @@ -7,12 +7,32 @@ """ __docformat__ = "restructuredtext en" -from cubicweb import ValidationError +from datetime import datetime + +from cubicweb import RepositoryError, ValidationError from cubicweb.interfaces import IWorkflowable from cubicweb.selectors import entity_implements -from cubicweb.server.hook import Hook, match_rtype -from cubicweb.server.pool import PreCommitOperation -from cubicweb.server.hookhelper import previous_state +from cubicweb.server import hook + + +def previous_state(session, eid): + """return the state of the entity with the given eid, + usually since it's changing in the current transaction. Due to internal + relation hooks, the relation may has been deleted at this point, so + we have handle that + """ + if session.added_in_transaction(eid): + return + pending = session.transaction_data.get('pendingrelations', ()) + for eidfrom, rtype, eidto in reversed(pending): + if rtype == 'in_state' and eidfrom == eid: + rset = session.execute('Any S,N WHERE S eid %(x)s, S name N', + {'x': eidto}, 'x') + return rset.get_entity(0, 0) + rset = session.execute('Any S,N WHERE X eid %(x)s, X in_state S, S name N', + {'x': eid}, 'x') + if rset: + return rset.get_entity(0, 0) def relation_deleted(session, eidfrom, rtype, eidto): @@ -20,7 +40,7 @@ (eidfrom, rtype, eidto)) -class _SetInitialStateOp(PreCommitOperation): +class _SetInitialStateOp(hook.Operation): """make initial state be a default state""" def precommit_event(self): @@ -28,29 +48,30 @@ entity = self.entity # if there is an initial state and the entity's state is not set, # use the initial state as a default state - pendingeids = session.transaction_data.get('pendingeids', ()) - if not entity.eid in pendingeids and not entity.in_state: + if not session.deleted_in_transaction(entity.eid) and not entity.in_state: rset = session.execute('Any S WHERE ET initial_state S, ET name %(name)s', {'name': entity.id}) if rset: session.add_relation(entity.eid, 'in_state', rset[0][0]) +class WorkflowHook(hook.Hook): + __abstract__ = True + category = 'worfklow' -class SetInitialStateHook(Hook): + +class SetInitialStateHook(WorkflowHook): __id__ = 'wfsetinitial' - __select__ = Hook.__select__ & entity_implements(IWorkflowable) - category = 'worfklow' + __select__ = WorkflowHook.__select__ & entity_implements(IWorkflowable) events = ('after_add_entity',) def __call__(self): _SetInitialStateOp(self.cw_req, entity=self.entity) -class PrepareStateChangeHook(Hook): +class PrepareStateChangeHook(WorkflowHook): """record previous state information""" __id__ = 'cwdelstate' - __select__ = Hook.__select__ & match_rtype('in_state') - category = 'worfklow' + __select__ = WorkflowHook.__select__ & hook.match_rtype('in_state') events = ('before_delete_relation',) def __call__(self): @@ -104,3 +125,22 @@ args['fs'] = state.eid rql = '%s WHERE %s' % (rql, ', '.join(restriction)) session.unsafe_execute(rql, args, 'e') + + +class SetModificationDateOnStateChange(WorkflowHook): + """update entity's modification date after changing its state""" + __id__ = 'wfsyncmdate' + __select__ = WorkflowHook.__select__ & hook.match_rtype('in_state') + events = ('after_add_relation',) + + def __call__(self): + if self.cw_req.added_in_transaction(self.eidfrom): + # new entity, not needed + return + entity = self.cw_req.entity_from_eid(self.eidfrom) + try: + entity.set_attributes(modification_date=datetime.now()) + except RepositoryError, ex: + # usually occurs if entity is coming from a read-only source + # (eg ldap user) + self.warning('cant change modification date for %s: %s', entity, ex) diff -r 06daf13195d4 -r 107ba1c45227 sobjects/email.py --- a/sobjects/email.py Fri Aug 14 11:13:18 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +0,0 @@ -"""hooks to ensure use_email / primary_email relations consistency - -:organization: Logilab -:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. -:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses -""" -__docformat__ = "restructuredtext en" - -from cubicweb.server.hooksmanager import Hook -from cubicweb.server.pool import PreCommitOperation - -class SetUseEmailRelationOp(PreCommitOperation): - """delay this operation to commit to avoid conflict with a late rql query - already setting the relation - """ - rtype = 'use_email' - fromeid = toeid = None # make pylint happy - - def condition(self): - """check entity has use_email set for the email address""" - return not self.session.unsafe_execute( - 'Any X WHERE X eid %(x)s, X use_email Y, Y eid %(y)s', - {'x': self.fromeid, 'y': self.toeid}, 'x') - - def precommit_event(self): - session = self.session - if self.condition(): - session.unsafe_execute( - 'SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % self.rtype, - {'x': self.fromeid, 'y': self.toeid}, 'x') - -class SetPrimaryEmailRelationOp(SetUseEmailRelationOp): - rtype = 'primary_email' - - def condition(self): - """check entity has no primary_email set""" - return not self.session.unsafe_execute( - 'Any X WHERE X eid %(x)s, X primary_email Y', - {'x': self.fromeid}, 'x') - - -class SetPrimaryEmailHook(Hook): - """notify when a bug or story or version has its state modified""" - events = ('after_add_relation',) - accepts = ('use_email',) - - def call(self, session, fromeid, rtype, toeid): - subjtype = session.describe(fromeid)[0] - eschema = self.vreg.schema[subjtype] - if 'primary_email' in eschema.subject_relations(): - SetPrimaryEmailRelationOp(session, vreg=self.vreg, - fromeid=fromeid, toeid=toeid) - -class SetUseEmailHook(Hook): - """notify when a bug or story or version has its state modified""" - events = ('after_add_relation',) - accepts = ('primary_email',) - - def call(self, session, fromeid, rtype, toeid): - subjtype = session.describe(fromeid)[0] - eschema = self.vreg.schema[subjtype] - if 'use_email' in eschema.subject_relations(): - SetUseEmailRelationOp(session, vreg=self.vreg, - fromeid=fromeid, toeid=toeid) diff -r 06daf13195d4 -r 107ba1c45227 sobjects/hooks.py --- a/sobjects/hooks.py Fri Aug 14 11:13:18 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,84 +0,0 @@ -"""various library content hooks - -:organization: Logilab -:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. -:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses -""" -__docformat__ = "restructuredtext en" - -from datetime import datetime - -from cubicweb import RepositoryError -from cubicweb.common.uilib import soup2xhtml -from cubicweb.server.hooksmanager import Hook -from cubicweb.server.pool import PreCommitOperation - - -class SetModificationDateOnStateChange(Hook): - """update entity's modification date after changing its state""" - events = ('after_add_relation',) - accepts = ('in_state',) - - def call(self, session, fromeid, rtype, toeid): - if fromeid in session.transaction_data.get('neweids', ()): - # new entity, not needed - return - entity = session.entity_from_eid(fromeid) - try: - entity.set_attributes(modification_date=datetime.now()) - except RepositoryError, ex: - # usually occurs if entity is coming from a read-only source - # (eg ldap user) - self.warning('cant change modification date for %s: %s', entity, ex) - - -class AddUpdateCWUserHook(Hook): - """ensure user logins are stripped""" - events = ('before_add_entity', 'before_update_entity',) - accepts = ('CWUser',) - - def call(self, session, entity): - if 'login' in entity and entity['login']: - entity['login'] = entity['login'].strip() - - -class AutoDeleteBookmark(PreCommitOperation): - beid = None # make pylint happy - def precommit_event(self): - session = self.session - if not self.beid in session.transaction_data.get('pendingeids', ()): - if not session.unsafe_execute('Any X WHERE X bookmarked_by U, X eid %(x)s', - {'x': self.beid}, 'x'): - session.unsafe_execute('DELETE Bookmark X WHERE X eid %(x)s', - {'x': self.beid}, 'x') - -class DelBookmarkedByHook(Hook): - """ensure user logins are stripped""" - events = ('after_delete_relation',) - accepts = ('bookmarked_by',) - - def call(self, session, subj, rtype, obj): - AutoDeleteBookmark(session, beid=subj) - - -class TidyHtmlFields(Hook): - """tidy HTML in rich text strings""" - events = ('before_add_entity', 'before_update_entity') - accepts = ('Any',) - - def call(self, session, entity): - metaattrs = entity.e_schema.meta_attributes() - for metaattr, (metadata, attr) in metaattrs.iteritems(): - if metadata == 'format': - try: - value = entity[attr] - except KeyError: - continue # no text to tidy - if isinstance(value, unicode): # filter out None and Binary - if self.event == 'before_add_entity': - fmt = entity.get(metaattr) - else: - fmt = entity.get_value(metaattr) - if fmt == 'text/html': - entity[attr] = soup2xhtml(value, session.encoding) diff -r 06daf13195d4 -r 107ba1c45227 sobjects/notification.py --- a/sobjects/notification.py Fri Aug 14 11:13:18 2009 +0200 +++ b/sobjects/notification.py Fri Aug 14 11:14:10 2009 +0200 @@ -1,4 +1,4 @@ -"""some hooks and views to handle notification on entity's changes +"""some views to handle notification on data changes :organization: Logilab :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. @@ -18,16 +18,12 @@ return 'XXX' from logilab.common.textutils import normalize_text -from logilab.common.deprecation import class_renamed -from cubicweb import RegistryException -from cubicweb.selectors import implements, yes +from cubicweb.selectors import yes from cubicweb.view import EntityView, Component from cubicweb.common.mail import format_mail -from cubicweb.server.pool import PreCommitOperation from cubicweb.server.hookhelper import SendMailOp -from cubicweb.server.hooksmanager import Hook class RecipientsFinder(Component): @@ -57,72 +53,6 @@ return dests -# hooks ####################################################################### - -class RenderAndSendNotificationView(PreCommitOperation): - """delay rendering of notification view until precommit""" - def precommit_event(self): - if self.view.rset and self.view.rset[0][0] in self.session.transaction_data.get('pendingeids', ()): - return # entity added and deleted in the same transaction - self.view.render_and_send(**getattr(self, 'viewargs', {})) - -class StatusChangeHook(Hook): - """notify when a workflowable entity has its state modified""" - events = ('after_add_entity',) - accepts = ('TrInfo',) - - def call(self, session, entity): - if not entity.from_state: # not a transition - return - rset = entity.related('wf_info_for') - try: - view = session.vreg['views'].select('notif_status_change', session, - rset=rset, row=0) - except RegistryException: - return - comment = entity.printable_value('comment', format='text/plain') - if comment: - comment = normalize_text(comment, 80, - rest=entity.comment_format=='text/rest') - RenderAndSendNotificationView(session, view=view, viewargs={ - 'comment': comment, 'previous_state': entity.previous_state.name, - 'current_state': entity.new_state.name}) - - -class RelationChangeHook(Hook): - events = ('before_add_relation', 'after_add_relation', - 'before_delete_relation', 'after_delete_relation') - accepts = ('Any',) - def call(self, session, fromeid, rtype, toeid): - """if a notification view is defined for the event, send notification - email defined by the view - """ - rset = session.eid_rset(fromeid) - vid = 'notif_%s_%s' % (self.event, rtype) - try: - view = session.vreg['views'].select(vid, session, rset=rset, row=0) - except RegistryException: - return - RenderAndSendNotificationView(session, view=view) - - -class EntityChangeHook(Hook): - events = ('after_add_entity', - 'after_update_entity') - accepts = ('Any',) - def call(self, session, entity): - """if a notification view is defined for the event, send notification - email defined by the view - """ - rset = entity.as_rset() - vid = 'notif_%s' % self.event - try: - view = session.vreg['views'].select(vid, session, rset=rset, row=0) - except RegistryException: - return - RenderAndSendNotificationView(session, view=view) - - # abstract or deactivated notification views and mixin ######################## class NotificationView(EntityView): @@ -296,4 +226,7 @@ return u'%s #%s (%s)' % (self.req.__('New %s' % entity.e_schema), entity.eid, self.user_login()) +from logilab.common.deprecation import class_renamed, class_moved NormalizedTextView = class_renamed('NormalizedTextView', ContentAddedView) +from cubicweb.hooks.notification import RenderAndSendNotificationView +RenderAndSendNotificationView = class_moved(RenderAndSendNotificationView) diff -r 06daf13195d4 -r 107ba1c45227 sobjects/supervising.py --- a/sobjects/supervising.py Fri Aug 14 11:13:18 2009 +0200 +++ b/sobjects/supervising.py Fri Aug 14 11:14:10 2009 +0200 @@ -13,58 +13,9 @@ from cubicweb.schema import display_name from cubicweb.view import Component from cubicweb.common.mail import format_mail -from cubicweb.server.hooksmanager import Hook from cubicweb.server.hookhelper import SendMailOp -class SomethingChangedHook(Hook): - events = ('before_add_relation', 'before_delete_relation', - 'after_add_entity', 'before_update_entity') - accepts = ('Any',) - - def call(self, session, *args): - dest = session.vreg.config['supervising-addrs'] - if not dest: # no supervisors, don't do this for nothing... - return - self.session = session - if self._call(*args): - SupervisionMailOp(session) - - def _call(self, *args): - if self._event() == 'update_entity': - if args[0].eid in self.session.transaction_data.get('neweids', ()): - return False - if args[0].e_schema == 'CWUser': - updated = set(args[0].iterkeys()) - if not (updated - frozenset(('eid', 'modification_date', - 'last_login_time'))): - # don't record last_login_time update which are done - # automatically at login time - return False - self.session.transaction_data.setdefault('pendingchanges', []).append( - (self._event(), args)) - return True - - def _event(self): - return self.event.split('_', 1)[1] - - -class EntityDeleteHook(SomethingChangedHook): - events = ('before_delete_entity',) - - def _call(self, eid): - entity = self.session.entity_from_eid(eid) - try: - title = entity.dc_title() - except: - # may raise an error during deletion process, for instance due to - # missing required relation - title = '#%s' % eid - self.session.transaction_data.setdefault('pendingchanges', []).append( - ('delete_entity', (eid, str(entity.e_schema), title))) - return True - - def filter_changes(changes): """ * when an entity has been deleted: @@ -79,7 +30,7 @@ for change in changes[:]: event, changedescr = change if event == 'add_entity': - entity = changedescr[0] + entity = changedescr.entity added.add(entity.eid) if entity.e_schema == 'TrInfo': changes.remove(change) @@ -111,14 +62,14 @@ changedescr = change[1] # skip meta-relations which are set automatically # XXX generate list below using rtags (category = 'generated') - if changedescr[1] in ('created_by', 'owned_by', 'is', 'is_instance_of', + if changedescr.rtype in ('created_by', 'owned_by', 'is', 'is_instance_of', 'from_state', 'to_state', 'wf_info_for',) \ - and changedescr[0] == eid: + and changedescr.eidfrom == eid: index['add_relation'].remove(change) # skip in_state relation if the entity is being created # XXX this may be automatized by skipping all mandatory relation # at entity creation time - elif changedescr[1] == 'in_state' and changedescr[0] in added: + elif changedescr.rtype == 'in_state' and changedescr.eidfrom in added: index['add_relation'].remove(change) except KeyError: @@ -126,15 +77,14 @@ for eid in deleted: try: for change in index['delete_relation'].copy(): - fromeid, rtype, toeid = change[1] - if fromeid == eid: + if change.eidfrom == eid: index['delete_relation'].remove(change) - elif toeid == eid: + elif change.eidto == eid: index['delete_relation'].remove(change) - if rtype == 'wf_info_for': - for change in index['delete_entity'].copy(): - if change[1][0] == fromeid: - index['delete_entity'].remove(change) + if change.rtype == 'wf_info_for': + for change_ in index['delete_entity'].copy(): + if change_[1].eidfrom == change.eidfrom: + index['delete_entity'].remove(change_) except KeyError: break for change in changes: @@ -161,7 +111,7 @@ % user.login) for event, changedescr in filter_changes(changes): self.w(u'* ') - getattr(self, event)(*changedescr) + getattr(self, event)(changedescr) self.w(u'\n\n') def _entity_context(self, entity): @@ -169,30 +119,30 @@ 'etype': entity.dc_type().lower(), 'title': entity.dc_title()} - def add_entity(self, entity): + def add_entity(self, changedescr): msg = self.req._('added %(etype)s #%(eid)s (%(title)s)') - self.w(u'%s\n' % (msg % self._entity_context(entity))) - self.w(u' %s' % entity.absolute_url()) + self.w(u'%s\n' % (msg % self._entity_context(changedescr.entity))) + self.w(u' %s' % changedescr.entity.absolute_url()) - def update_entity(self, entity): + def update_entity(self, changedescr): msg = self.req._('updated %(etype)s #%(eid)s (%(title)s)') - self.w(u'%s\n' % (msg % self._entity_context(entity))) + self.w(u'%s\n' % (msg % self._entity_context(changedescr.entity))) # XXX print changes - self.w(u' %s' % entity.absolute_url()) + self.w(u' %s' % changedescr.entity.absolute_url()) - def delete_entity(self, eid, etype, title): + def delete_entity(self, (eid, etype, title)): msg = self.req._('deleted %(etype)s #%(eid)s (%(title)s)') etype = display_name(self.req, etype).lower() self.w(msg % locals()) - def change_state(self, entity, fromstate, tostate): + def change_state(self, (entity, fromstate, tostate)): msg = self.req._('changed state of %(etype)s #%(eid)s (%(title)s)') self.w(u'%s\n' % (msg % self._entity_context(entity))) self.w(_(' from state %(fromstate)s to state %(tostate)s\n' % {'fromstate': _(fromstate.name), 'tostate': _(tostate.name)})) self.w(u' %s' % entity.absolute_url()) - def _relation_context(self, fromeid, rtype, toeid): + def _relation_context(self, changedescr): _ = self.req._ session = self.req.actual_session() def describe(eid): @@ -202,19 +152,20 @@ # may occurs when an entity has been deleted from an external # source and we're cleaning its relation return _('unknown external entity') + eidfrom, rtype, eidto = changedescr.eidfrom, changedescr.rtype, changedescr.eidto return {'rtype': _(rtype), - 'fromeid': fromeid, - 'frometype': describe(fromeid), - 'toeid': toeid, - 'toetype': describe(toeid)} + 'eidfrom': eidfrom, + 'frometype': describe(eidfrom), + 'eidto': eidto, + 'toetype': describe(eidto)} - def add_relation(self, fromeid, rtype, toeid): - msg = self.req._('added relation %(rtype)s from %(frometype)s #%(fromeid)s to %(toetype)s #%(toeid)s') - self.w(msg % self._relation_context(fromeid, rtype, toeid)) + def add_relation(self, changedescr): + msg = self.req._('added relation %(rtype)s from %(frometype)s #%(eidfrom)s to %(toetype)s #%(eidto)s') + self.w(msg % self._relation_context(changedescr)) - def delete_relation(self, fromeid, rtype, toeid): - msg = self.req._('deleted relation %(rtype)s from %(frometype)s #%(fromeid)s to %(toetype)s #%(toeid)s') - self.w(msg % self._relation_context(fromeid, rtype, toeid)) + def delete_relation(self, eidfrom, rtype, eidto): + msg = self.req._('deleted relation %(rtype)s from %(frometype)s #%(eidfrom)s to %(toetype)s #%(eidto)s') + self.w(msg % self._relation_context(changedescr)) class SupervisionMailOp(SendMailOp):