[web/data] Never hide JS errors in our Deferred implementation
Modern browsers have good debugging tools, let exceptions go all the way
up. This only affects exceptions raised in the js code, if there's an
issue on the server/python side, they'll keep going through our error
callbacks.
Closes #4881300
# copyright 2003-2013 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_textfromlogilab.common.deprecationimportdeprecatedfromcubicwebimportRegistryNotFoundfromcubicweb.predicatesimportis_instancefromcubicweb.serverimporthookfromcubicweb.sobjects.supervisingimportSupervisionMailOp@deprecated('[3.17] use notify_on_commit instead')defRenderAndSendNotificationView(cnx,view,viewargs=None):notify_on_commit(cnx,view,viewargs)defnotify_on_commit(cnx,view,viewargs=None):"""register a notification view (see :class:`~cubicweb.sobjects.notification.NotificationView`) to be sent at post-commit time, ie only if the transaction has succeeded. `viewargs` is an optional dictionary containing extra argument to be given to :meth:`~cubicweb.sobjects.notification.NotificationView.render_and_send` """ifviewargsisNone:viewargs={}notif_op=_RenderAndSendNotificationOp.get_instance(cnx)notif_op.add_data((view,viewargs))class_RenderAndSendNotificationOp(hook.DataOperationMixIn,hook.Operation):"""End of the notification chain. Do render and send views after commit All others Operations end up adding data to this Operation. The notification are done on ``postcommit_event`` to make sure to prevent sending notification about rolled back data. """containercls=listdefpostcommit_event(self):deleted=self.cnx.deleted_in_transactionforview,viewargsinself.get_data():ifview.cw_rsetisnotNone:ifnotview.cw_rset:# entity added and deleted in the same transaction# (cache effect)continueelifdeleted(view.cw_rset[view.cw_rowor0][view.cw_color0]):# entity added and deleted in the same transactioncontinuetry:view.render_and_send(**viewargs)exceptException:# error in post commit are not propagated# We keep this logic here to prevent a small notification error# to prevent them all.self.exception('Notification failed')classNotificationHook(hook.Hook):__abstract__=Truecategory='notification'defselect_view(self,vid,rset,row=0,col=0):try:returnself._cw.vreg['views'].select_or_none(vid,self._cw,rset=rset,row=row,col=col)exceptRegistryNotFound:# can happen in some config# (e.g. repo only config with no# notification views registered by# the instance's cubes)returnNoneclassStatusChangeHook(NotificationHook):"""notify when a workflowable entity has its state modified"""__regid__='notifystatuschange'__select__=NotificationHook.__select__&is_instance('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)viewargs={'comment':comment,'previous_state':entity.previous_state.name,'current_state':entity.new_state.name}notify_on_commit(self._cw,view,viewargs=viewargs)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:returnnotify_on_commit(self._cw,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:returnnotify_on_commit(self._cw,view)classEntityUpdatedNotificationOp(hook.SingleLastOperation):"""scrap all changed entity to prepare a Notification Operation for them"""defprecommit_event(self):# precommit event that creates postcommit operationcnx=self.cnxforeidincnx.transaction_data['changes']:view=cnx.vreg['views'].select('notif_entity_updated',cnx,rset=cnx.eid_rset(eid),row=0)notify_on_commit(self.cnx,view,viewargs={'changes':cnx.transaction_data['changes'][eid]})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):cnx=self._cwifcnx.added_in_transaction(self.entity.eid):return# entity is being created# then compute changesattrs=[kforkinself.entity.cw_editedifnotkinself.skip_attrs]ifnotattrs:returnchanges=cnx.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=cnx.execute(rql,{'x':self.entity.eid})fori,attrinenumerate(attrs):oldvalue=rset[0][i]newvalue=self.entity.cw_edited[attr]ifoldvalue!=newvalue:thisentitychanges.add((attr,oldvalue,newvalue))ifthisentitychanges:EntityUpdatedNotificationOp(cnx)# 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(frozenset(self.entity.cw_edited)-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()exceptException:# 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.cw_etype,title)))returnTrue