[notification] use new style operation for notification
authorPierre-Yves David <pierre-yves.david@logilab.fr>
Tue, 23 Apr 2013 12:38:27 +0200
changeset 8898 c570d15dce7b
parent 8897 11f7ccd2fa72
child 8899 c7a95ebcc093
[notification] use new style operation for notification This results in far less operations created. Operation creation is expensive. We keep some compatibility with the previous API because some cubes have been reported to use it.
doc/3.17.rst
hooks/notification.py
--- a/doc/3.17.rst	Mon Apr 22 17:16:02 2013 +0200
+++ b/doc/3.17.rst	Tue Apr 23 12:38:27 2013 +0200
@@ -28,6 +28,10 @@
 * The email sending views and controllers have been removed from CubicWeb and
   moved to the `massmailing` cube.
 
+* ``RenderAndSendNotificationView`` is deprecated in favor of
+  ``ActualNotificationOp`` the new operation use the more efficient *data*
+  idiom.
+
 
 Deprecation
 ---------------------
--- a/hooks/notification.py	Mon Apr 22 17:16:02 2013 +0200
+++ b/hooks/notification.py	Tue Apr 23 12:38:27 2013 +0200
@@ -20,23 +20,51 @@
 __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
 
-class RenderAndSendNotificationView(hook.Operation):
-    """delay rendering of notification view until postcommit"""
-    view = None # make pylint happy
+
+@deprecated('[3.17] use ActualNotificationOp instead (using the 3.10 data API)')
+def RenderAndSendNotificationView(session, view, viewargs=None):
+    if viewargs is None:
+        viewargs = {}
+    notif_op = ActualNotificationOp.get_instance(session)
+    notif_op.add_data((view, viewargs))
+    return ActualNotificationOp
+
+
+class ActualNotificationOp(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 rollbacked data.
+    """
+
+    containercls = list
 
     def postcommit_event(self):
-        view = self.view
-        if view.cw_rset is not None and not view.cw_rset:
-            return # entity added and deleted in the same transaction (cache effect)
-        if view.cw_rset and self.session.deleted_in_transaction(view.cw_rset[view.cw_row or 0][view.cw_col or 0]):
-            return # entity added and deleted in the same transaction
-        self.view.render_and_send(**getattr(self, 'viewargs', {}))
+        deleted = self.session.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):
@@ -73,9 +101,11 @@
         # #103822)
         if comment and entity.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})
+        notif_op = ActualNotificationOp.get_instance(self._cw)
+        viewargs = {'comment': comment,
+                    'previous_state': entity.previous_state.name,
+                    'current_state': entity.new_state.name}
+        notif_op.add_data((view, viewargs))
 
 class RelationChangeHook(NotificationHook):
     __regid__ = 'notifyrelationchange'
@@ -91,7 +121,8 @@
                                 rset=rset, row=0)
         if view is None:
             return
-        RenderAndSendNotificationView(self._cw, view=view)
+        notif_op = ActualNotificationOp.get_instance(self._cw)
+        notif_op.add_data((view, {}))
 
 
 class EntityChangeHook(NotificationHook):
@@ -106,7 +137,8 @@
         view = self.select_view('notif_%s' % self.event, rset=rset, row=0)
         if view is None:
             return
-        RenderAndSendNotificationView(self._cw, view=view)
+        notif_op = ActualNotificationOp.get_instance(self._cw)
+        notif_op.add_data((view, {}))
 
 
 class EntityUpdatedNotificationOp(hook.SingleLastOperation):
@@ -119,7 +151,8 @@
             view = session.vreg['views'].select('notif_entity_updated', session,
                                                 rset=session.eid_rset(eid),
                                                 row=0)
-            RenderAndSendNotificationView(session, view=view)
+            notif_op = ActualNotificationOp.get_instance(self._cw)
+            notif_op.add_data((view, {}))
 
 
 class EntityUpdateHook(NotificationHook):