[notification] add operation responsible for sending email notification when an entity is updated stable
authorSandrine Ribeau <sandrine.ribeau@logilab.fr>
Tue, 29 Sep 2009 15:10:12 -0700
branchstable
changeset 3525 2dc3908f667f
parent 3523 16880e7ee3fa
child 3526 dfb2ebb765e2
[notification] add operation responsible for sending email notification when an entity is updated
sobjects/notification.py
--- a/sobjects/notification.py	Tue Sep 29 15:10:56 2009 +0200
+++ b/sobjects/notification.py	Tue Sep 29 15:10:12 2009 -0700
@@ -17,7 +17,7 @@
 from cubicweb.selectors import implements, yes
 from cubicweb.view import Component
 from cubicweb.common.mail import NotificationView, parse_message_id
-from cubicweb.server.pool import PreCommitOperation
+from cubicweb.server.pool import PreCommitOperation, SingleLastOperation
 from cubicweb.server.hookhelper import SendMailOp
 from cubicweb.server.hooksmanager import Hook
 
@@ -53,6 +53,20 @@
 
 # hooks #######################################################################
 
+class EntityUpdatedNotificationOp(SingleLastOperation):
+
+    def precommit_event(self):
+        session = self.session
+        for eid in session.transaction_data['changes']:
+            view = session.vreg['views'].select('notif_entity_updated', session,
+                                                rset=session.eid_rset(eid),
+                                                row=0)
+            RenderAndSendNotificationView(session, view=view)
+
+    def commit_event(self):
+        pass
+
+
 class RenderAndSendNotificationView(PreCommitOperation):
     """delay rendering of notification view until precommit"""
     def precommit_event(self):
@@ -121,6 +135,36 @@
             return
         RenderAndSendNotificationView(session, view=view)
 
+class EntityUpdateHook(Hook):
+    events = ('before_update_entity',)
+    accepts = ()
+    skip_attrs = set()
+
+    def call(self, session, entity):
+        if entity.eid in session.transaction_data.get('neweids', ()):
+            return # entity is being created
+        if session.is_super_session:
+            return # ignore changes triggered by hooks
+        # then compute changes
+        changes = session.transaction_data.setdefault('changes', {})
+        thisentitychanges = changes.setdefault(entity.eid, set())
+        attrs = [k for k in entity.edited_attributes if not k in self.skip_attrs]
+        if not attrs:
+            return
+        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 = session.execute(rql, {'x': entity.eid}, 'x')
+        for i, attr in enumerate(attrs):
+            oldvalue = rset[0][i]
+            newvalue = entity[attr]
+            if oldvalue != newvalue:
+                thisentitychanges.add((attr, oldvalue, newvalue))
+        if thisentitychanges:
+            EntityUpdatedNotificationOp(session)
 
 # abstract or deactivated notification views and mixin ########################
 
@@ -193,3 +237,65 @@
                                   entity.eid, self.user_data['login'])
 
 NormalizedTextView = class_renamed('NormalizedTextView', ContentAddedView)
+
+def format_value(value):
+    if isinstance(value, unicode):
+        return u'"%s"' % value
+    return value
+
+class EntityUpdatedNotificationView(NotificationView):
+    """abstract class for notification on entity/relation
+
+    all you have to do by default is :
+    * set id and __select__ attributes to match desired events and entity types
+    * set a content attribute to define the content of the email (unless you
+      override call)
+    """
+    __abstract__ = True
+    id = 'notif_entity_updated'
+    msgid_timestamp = False
+    message = _('updated')
+    no_detailed_change_attrs = ()
+    content = """
+Properties have been updated by %(user)s:
+
+%(changes)s
+
+url: %(url)s
+"""
+
+    def context(self, **kwargs):
+        context = super(EntityUpdatedNotificationView, self).context(**kwargs)
+        changes = self.req.transaction_data['changes'][self.rset[0][0]]
+        _ = self.req._
+        formatted_changes = []
+        for attr, oldvalue, newvalue in sorted(changes):
+            # check current user has permission to see the attribute
+            rschema = self.vreg.schema[attr]
+            if rschema.is_final():
+                if not rschema.has_perm(self.req, 'read', eid=self.rset[0][0]):
+                    continue
+            # XXX suppose it's a subject relation...
+            elif not rschema.has_perm(self.req, 'read', fromeid=self.rset[0][0]):
+                continue
+            if attr in self.no_detailed_change_attrs:
+                msg = _('%s updated') % _(attr)
+            elif oldvalue not in (None, ''):
+                msg = _('%(attr)s updated from %(oldvalue)s to %(newvalue)s') % {
+                    'attr': _(attr),
+                    'oldvalue': format_value(oldvalue),
+                    'newvalue': format_value(newvalue)}
+            else:
+                msg = _('%(attr)s set to %(newvalue)s') % {
+                    'attr': _(attr), 'newvalue': format_value(newvalue)}
+            formatted_changes.append('* ' + msg)
+        if not formatted_changes:
+            # current user isn't allowed to see changes, skip this notification
+            raise SkipEmail()
+        context['changes'] = '\n'.join(formatted_changes)
+        return context
+
+    def subject(self):
+        entity = self.entity(self.row or 0, self.col or 0)
+        return  u'%s #%s (%s)' % (self.req.__('Updated %s' % entity.e_schema),
+                                  entity.eid, self.user_data['login'])