--- a/hooks/notification.py Mon Jan 04 18:40:30 2016 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,244 +0,0 @@
-# copyright 2003-2015 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"
-
-from logilab.common.textutils import normalize_text
-from logilab.common.deprecation import deprecated
-
-from cubicweb import RegistryNotFound
-from cubicweb.predicates import is_instance
-from cubicweb.server import hook
-from cubicweb.sobjects.supervising import SupervisionMailOp
-
-
-@deprecated('[3.17] use notify_on_commit instead')
-def RenderAndSendNotificationView(cnx, view, viewargs=None):
- notify_on_commit(cnx, view, viewargs)
-
-
-def notify_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`
- """
- if viewargs is None:
- 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 = list
-
- def postcommit_event(self):
- deleted = self.cnx.deleted_in_transaction
- for view, viewargs in self.get_data():
- if view.cw_rset is not None:
- if not view.cw_rset:
- # entity added and deleted in the same transaction
- # (cache effect)
- continue
- elif deleted(view.cw_rset[view.cw_row or 0][view.cw_col or 0]):
- # entity added and deleted in the same transaction
- continue
- try:
- view.render_and_send(**viewargs)
- except Exception:
- # 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')
-
-
-class NotificationHook(hook.Hook):
- __abstract__ = True
- category = 'notification'
-
- def select_view(self, vid, rset, row=0, col=0):
- try:
- return self._cw.vreg['views'].select_or_none(vid, self._cw, rset=rset,
- row=row, col=col)
- except RegistryNotFound: # can happen in some config
- # (e.g. repo only config with no
- # notification views registered by
- # the instance's cubes)
- return None
-
-
-class StatusChangeHook(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.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')
- # XXX don't try to wrap rest until we've a proper transformation (see
- # #103822)
- if comment and entity.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)
-
-class RelationChangeHook(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)
- if view is None:
- return
- notify_on_commit(self._cw, view)
-
-
-class EntityChangeHook(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)
- if view is None:
- return
- notify_on_commit(self._cw, view)
-
-
-class EntityUpdatedNotificationOp(hook.SingleLastOperation):
- """scrap all changed entity to prepare a Notification Operation for them"""
-
- def precommit_event(self):
- # precommit event that creates postcommit operation
- cnx = self.cnx
- for eid in cnx.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]})
-
-
-class EntityUpdateHook(NotificationHook):
- __regid__ = 'notifentityupdated'
- __abstract__ = True # do not register by default
- __select__ = NotificationHook.__select__ & hook.issued_from_user_query()
- events = ('before_update_entity',)
- skip_attrs = set()
-
- def __call__(self):
- cnx = self._cw
- if cnx.added_in_transaction(self.entity.eid):
- return # entity is being created
- # then compute changes
- attrs = [k for k in self.entity.cw_edited
- if not k in self.skip_attrs]
- if not attrs:
- return
- changes = cnx.transaction_data.setdefault('changes', {})
- thisentitychanges = changes.setdefault(self.entity.eid, set())
- rqlsel, rqlrestr = [], ['X eid %(x)s']
- for i, attr in enumerate(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})
- for i, attr in enumerate(attrs):
- oldvalue = rset[0][i]
- newvalue = self.entity.cw_edited[attr]
- if oldvalue != newvalue:
- thisentitychanges.add((attr, oldvalue, newvalue))
- if thisentitychanges:
- EntityUpdatedNotificationOp(cnx)
-
-
-# supervising ##################################################################
-
-class SomethingChangedHook(NotificationHook):
- __regid__ = 'supervising'
- __select__ = NotificationHook.__select__ & hook.issued_from_user_query()
- events = ('before_add_relation', 'before_delete_relation',
- 'after_add_entity', 'before_update_entity')
-
- def __call__(self):
- dest = self._cw.vreg.config['supervising-addrs']
- if not dest: # no supervisors, don't do this for nothing...
- return
- if self._call():
- SupervisionMailOp(self._cw)
-
- def _call(self):
- event = self.event.split('_', 1)[1]
- if event == 'update_entity':
- if self._cw.added_in_transaction(self.entity.eid):
- return False
- if self.entity.e_schema == 'CWUser':
- if not (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 time
- return False
- self._cw.transaction_data.setdefault('pendingchanges', []).append(
- (event, self))
- return True
-
-
-class EntityDeleteHook(SomethingChangedHook):
- __regid__ = 'supervisingentitydel'
- events = ('before_delete_entity',)
-
- def _call(self):
- try:
- title = self.entity.dc_title()
- except Exception:
- # may raise an error during deletion process, for instance due to
- # missing required relation
- title = '#%s' % self.entity.eid
- self._cw.transaction_data.setdefault('pendingchanges', []).append(
- ('delete_entity', (self.entity.eid, self.entity.cw_etype, title)))
- return True