[migration] always rebuild infered relation
This was skipped for some bad reason (see 12ad88615a12 which
introduced the change).
Fix for #231956 in Yams is necessary to allow this cset: during a
migration, we want to always reinfer relations while allowing explicit
redefinition of an infered relation later. Else, we may run the
migration with missing parts of the schema (the one that should have
been infered).
Closes #3685463.
# 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(session,view,viewargs=None):notify_on_commit(session,view,viewargs)defnotify_on_commit(session,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(session)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.session.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 operationsession=self.sessionforeidinsession.transaction_data['changes']:view=session.vreg['views'].select('notif_entity_updated',session,rset=session.eid_rset(eid),row=0)notify_on_commit(self.session,view,viewargs={'changes':session.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):session=self._cwifsession.added_in_transaction(self.entity.eid):return# entity is being created# then compute changesattrs=[kforkinself.entity.cw_editedifnotkinself.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.cw_edited[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(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