# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr## This file is part of CubicWeb.## CubicWeb is free software: you can redistribute it and/or modify it under the# terms of the GNU Lesser General Public License as published by the Free# Software Foundation, either version 2.1 of the License, or (at your option)# any later version.## CubicWeb is distributed in the hope that it will be useful, but WITHOUT# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more# details.## You should have received a copy of the GNU Lesser General Public License along# with CubicWeb. If not, see <http://www.gnu.org/licenses/>."""some hooks to handle notification on entity's changes"""__docformat__="restructuredtext en"fromlogilab.common.textutilsimportnormalize_textfromcubicweb.selectorsimportimplementsfromcubicweb.serverimporthookfromcubicweb.sobjects.supervisingimportSupervisionMailOpclassRenderAndSendNotificationView(hook.Operation):"""delay rendering of notification view until precommit"""defprecommit_event(self):view=self.viewifview.cw_rsetisnotNoneandnotview.cw_rset:return# entity added and deleted in the same transaction (cache effect)ifview.cw_rsetandself.session.deleted_in_transaction(view.cw_rset[view.cw_rowor0][view.cw_color0]):return# entity added and deleted in the same transactionself.view.render_and_send(**getattr(self,'viewargs',{}))classNotificationHook(hook.Hook):__abstract__=Truecategory='notification'defselect_view(self,vid,rset,row=0,col=0):returnself._cw.vreg['views'].select_or_none(vid,self._cw,rset=rset,row=row,col=col)classStatusChangeHook(NotificationHook):"""notify when a workflowable entity has its state modified"""__regid__='notifystatuschange'__select__=NotificationHook.__select__&implements('TrInfo')events=('after_add_entity',)def__call__(self):entity=self.entityifnotentity.from_state:# not a transitionreturnrset=entity.related('wf_info_for')view=self.select_view('notif_status_change',rset=rset,row=0)ifviewisNone:returncomment=entity.printable_value('comment',format='text/plain')# XXX don't try to wrap rest until we've a proper transformation (see# #103822)ifcommentandentity.comment_format!='text/rest':comment=normalize_text(comment,80)RenderAndSendNotificationView(self._cw,view=view,viewargs={'comment':comment,'previous_state':entity.previous_state.name,'current_state':entity.new_state.name})classRelationChangeHook(NotificationHook):__regid__='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.eid_rset(self.eidfrom)view=self.select_view('notif_%s_%s'%(self.event,self.rtype),rset=rset,row=0)ifviewisNone:returnRenderAndSendNotificationView(self._cw,view=view)classEntityChangeHook(NotificationHook):"""if a notification view is defined for the event, send notification email defined by the view """__regid__='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)ifviewisNone:returnRenderAndSendNotificationView(self._cw,view=view)classEntityUpdatedNotificationOp(hook.SingleLastOperation):defprecommit_event(self):session=self.sessionforeidinsession.transaction_data['changes']:view=session.vreg['views'].select('notif_entity_updated',session,rset=session.eid_rset(eid),row=0)RenderAndSendNotificationView(session,view=view)classEntityUpdateHook(NotificationHook):__regid__='notifentityupdated'__abstract__=True# do not register by default__select__=NotificationHook.__select__&hook.from_dbapi_query()events=('before_update_entity',)skip_attrs=set()def__call__(self):session=self._cwifsession.added_in_transaction(self.entity.eid):return# entity is being created# then compute changesattrs=[kforkinself.entity.edited_attributesifnotkinself.skip_attrs]ifnotattrs:returnchanges=session.transaction_data.setdefault('changes',{})thisentitychanges=changes.setdefault(self.entity.eid,set())rqlsel,rqlrestr=[],['X eid %(x)s']fori,attrinenumerate(attrs):var=chr(65+i)rqlsel.append(var)rqlrestr.append('X %s%s'%(attr,var))rql='Any %s WHERE %s'%(','.join(rqlsel),','.join(rqlrestr))rset=session.execute(rql,{'x':self.entity.eid})fori,attrinenumerate(attrs):oldvalue=rset[0][i]newvalue=self.entity[attr]ifoldvalue!=newvalue:thisentitychanges.add((attr,oldvalue,newvalue))ifthisentitychanges:EntityUpdatedNotificationOp(session)# supervising ##################################################################classSomethingChangedHook(NotificationHook):__regid__='supervising'__select__=NotificationHook.__select__&hook.from_dbapi_query()events=('before_add_relation','before_delete_relation','after_add_entity','before_update_entity')def__call__(self):dest=self._cw.vreg.config['supervising-addrs']ifnotdest:# no supervisors, don't do this for nothing...returnifself._call():SupervisionMailOp(self._cw)def_call(self):event=self.event.split('_',1)[1]ifevent=='update_entity':ifself._cw.added_in_transaction(self.entity.eid):returnFalseifself.entity.e_schema=='CWUser':ifnot(self.entity.edited_attributes-frozenset(('eid','modification_date','last_login_time'))):# don't record last_login_time update which are done# automatically at login timereturnFalseself._cw.transaction_data.setdefault('pendingchanges',[]).append((event,self))returnTrueclassEntityDeleteHook(SomethingChangedHook):__regid__='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 relationtitle='#%s'%self.entity.eidself._cw.transaction_data.setdefault('pendingchanges',[]).append(('delete_entity',(self.entity.eid,self.entity.__regid__,title)))returnTrue