"""some hooks and views to handle supervising of any data 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"fromcubicwebimportUnknownEidfromcubicweb.selectorsimportnone_rsetfromcubicweb.schemaimportdisplay_namefromcubicweb.viewimportComponentfromcubicweb.common.mailimportformat_mailfromcubicweb.server.hooksmanagerimportHookfromcubicweb.server.hookhelperimportSendMailOpclassSomethingChangedHook(Hook):events=('before_add_relation','before_delete_relation','after_add_entity','before_update_entity')accepts=('Any',)defcall(self,session,*args):ifsession.is_super_sessionorsession.repo.config.repairing:return# ignore changes triggered by hooks or maintainance shelldest=self.config['supervising-addrs']ifnotdest:# no supervisors, don't do this for nothing...returnself.session=sessionifself._call(*args):SupervisionMailOp(session)def_call(self,*args):ifself._event()=='update_entity':ifargs[0].eidinself.session.transaction_data.get('neweids',()):returnFalseifargs[0].e_schema=='CWUser':updated=set(args[0].iterkeys())ifnot(updated-frozenset(('eid','modification_date','last_login_time'))):# don't record last_login_time update which are done# automatically at login timereturnFalseself.session.transaction_data.setdefault('pendingchanges',[]).append((self._event(),args))returnTruedef_event(self):returnself.event.split('_',1)[1]classEntityDeleteHook(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 relationtitle='#%s'%eidself.session.transaction_data.setdefault('pendingchanges',[]).append(('delete_entity',(eid,str(entity.e_schema),title)))returnTruedeffilter_changes(changes):""" * when an entity has been deleted: * don't show deletion of its relations * don't show related TrInfo deletion if any * when an entity has been added don't show owned_by relation addition * don't show new TrInfo entities if any """# first build an index of changesindex={}added,deleted=set(),set()forchangeinchanges[:]:event,changedescr=changeifevent=='add_entity':entity=changedescr[0]added.add(entity.eid)ifentity.e_schema=='TrInfo':changes.remove(change)event='change_state'change=(event,(entity.wf_info_for[0],entity.from_state[0],entity.to_state[0]))changes.append(change)elifevent=='delete_entity':deleted.add(changedescr[0])index.setdefault(event,set()).add(change)forkeyin('delete_relation','add_relation'):forchangeinindex.get(key,{}).copy():ifchange[1][1]=='in_state':index[key].remove(change)# filter changesforeidinadded:try:forchangeinindex['add_relation'].copy():changedescr=change[1]# skip meta-relations which are set automatically# XXX generate list below using rtags (category = 'generated')ifchangedescr[1]in('created_by','owned_by','is','is_instance_of','from_state','to_state','by_transition','wf_info_for') \andchangedescr[0]==eid:index['add_relation'].remove(change)exceptKeyError:breakforeidindeleted:try:forchangeinindex['delete_relation'].copy():fromeid,rtype,toeid=change[1]iffromeid==eid:index['delete_relation'].remove(change)eliftoeid==eid:index['delete_relation'].remove(change)ifrtype=='wf_info_for':forchangeinindex['delete_entity'].copy():ifchange[1][0]==fromeid:index['delete_entity'].remove(change)exceptKeyError:breakforchangeinchanges:event,changedescr=changeifchangeinindex[event]:yieldchangeclassSupervisionEmailView(Component):"""view implementing the email API for data changes supervision notification """__select__=none_rset()id='supervision_notif'defrecipients(self):returnself.config['supervising-addrs']defsubject(self):returnself.req._('[%s supervision] changes summary')%self.config.appiddefcall(self,changes):user=self.req.actual_session().userself.w(self.req._('user %s has made the following change(s):\n\n')%user.login)forevent,changedescrinfilter_changes(changes):self.w(u'* ')getattr(self,event)(*changedescr)self.w(u'\n\n')def_entity_context(self,entity):return{'eid':entity.eid,'etype':entity.dc_type().lower(),'title':entity.dc_title()}defadd_entity(self,entity):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())defupdate_entity(self,entity):msg=self.req._('updated %(etype)s #%(eid)s (%(title)s)')self.w(u'%s\n'%(msg%self._entity_context(entity)))# XXX print changesself.w(u' %s'%entity.absolute_url())defdelete_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())defchange_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):_=self.req._session=self.req.actual_session()defdescribe(eid):try:return_(session.describe(eid)[0]).lower()exceptUnknownEid:# may occurs when an entity has been deleted from an external# source and we're cleaning its relationreturn_('unknown external entity')return{'rtype':_(rtype),'fromeid':fromeid,'frometype':describe(fromeid),'toeid':toeid,'toetype':describe(toeid)}defadd_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))defdelete_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))classSupervisionMailOp(SendMailOp):"""special send email operation which should be done only once for a bunch of changes """def_get_view(self):returnself.session.vreg['components'].select('supervision_notif',self.session)def_prepare_email(self):session=self.sessionconfig=session.vreg.configuinfo={'email':config['sender-addr'],'name':config['sender-name']}view=self._get_view()content=view.render(changes=session.transaction_data.get('pendingchanges'))recipients=view.recipients()msg=format_mail(uinfo,recipients,content,view.subject(),config=config)self.to_send=[(msg,recipients)]defcommit_event(self):self._prepare_email()SendMailOp.commit_event(self)