# HG changeset patch # User Sandrine Ribeau # Date 1254262212 25200 # Node ID 2dc3908f667f0ef092fa718c8bfd0064cbe2d909 # Parent 16880e7ee3fa5b9e70018b5cc36c600c2d5382d8 [notification] add operation responsible for sending email notification when an entity is updated diff -r 16880e7ee3fa -r 2dc3908f667f 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'])