sobjects/notification.py
changeset 8931 4b195bd82e8b
parent 8897 11f7ccd2fa72
child 8932 4ae4242bceb1
--- a/sobjects/notification.py	Thu Apr 25 14:10:55 2013 +0200
+++ b/sobjects/notification.py	Thu Apr 25 13:34:48 2013 +0200
@@ -26,9 +26,10 @@
 from logilab.common.deprecation import class_renamed, class_moved, deprecated
 from logilab.common.registry import yes
 
-from cubicweb.view import Component
-from cubicweb.mail import NotificationView as BaseNotificationView, SkipEmail
+from cubicweb.entity import Entity
+from cubicweb.view import Component, EntityView
 from cubicweb.server.hook import SendMailOp
+from cubicweb.mail import construct_message_id, format_mail
 
 
 class RecipientsFinder(Component):
@@ -59,6 +60,146 @@
 
 # abstract or deactivated notification views and mixin ########################
 
+
+class SkipEmail(Exception):
+    """raise this if you decide to skip an email during its generation"""
+
+
+class BaseNotificationView(EntityView):
+    """abstract view implementing the "email" API (eg to simplify sending
+    notification)
+    """
+    __abstract__ = True
+
+    # XXX refactor this class to work with len(rset) > 1
+
+    msgid_timestamp = True
+
+    # to be defined on concrete sub-classes
+    content = None # body of the mail
+    message = None # action verb of the subject
+
+    # this is usually the method to call
+    def render_and_send(self, **kwargs):
+        """generate and send an email message for this view"""
+        delayed = kwargs.pop('delay_to_commit', None)
+        for recipients, msg in self.render_emails(**kwargs):
+            if delayed is None:
+                self.send(recipients, msg)
+            elif delayed:
+                self.send_on_commit(recipients, msg)
+            else:
+                self.send_now(recipients, msg)
+
+    def cell_call(self, row, col=0, **kwargs):
+        self.w(self._cw._(self.content) % self.context(**kwargs))
+
+    def render_emails(self, **kwargs):
+        """generate and send emails for this view (one per recipient)"""
+        self._kwargs = kwargs
+        recipients = self.recipients()
+        if not recipients:
+            self.info('skipping %s notification, no recipients', self.__regid__)
+            return
+        if self.cw_rset is not None:
+            entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+            # if the view is using timestamp in message ids, no way to reference
+            # previous email
+            if not self.msgid_timestamp:
+                refs = [self.construct_message_id(eid)
+                        for eid in entity.cw_adapt_to('INotifiable').notification_references(self)]
+            else:
+                refs = ()
+            msgid = self.construct_message_id(entity.eid)
+        else:
+            refs = ()
+            msgid = None
+        req = self._cw
+        self.user_data = req.user_data()
+        origlang = req.lang
+        for something in recipients:
+            if isinstance(something, Entity):
+                # hi-jack self._cw to get a session for the returned user
+                self._cw = self._cw.hijack_user(something)
+                emailaddr = something.cw_adapt_to('IEmailable').get_email()
+            else:
+                emailaddr, lang = something
+                self._cw.set_language(lang)
+            # since the same view (eg self) may be called multiple time and we
+            # need a fresh stream at each iteration, reset it explicitly
+            self.w = None
+            # XXX call render before subject to set .row/.col attributes on the
+            #     view
+            try:
+                content = self.render(row=0, col=0, **kwargs)
+                subject = self.subject()
+            except SkipEmail:
+                continue
+            except Exception as ex:
+                # shouldn't make the whole transaction fail because of rendering
+                # error (unauthorized or such) XXX check it doesn't actually
+                # occurs due to rollback on such error
+                self.exception(str(ex))
+                continue
+            msg = format_mail(self.user_data, [emailaddr], content, subject,
+                              config=self._cw.vreg.config, msgid=msgid, references=refs)
+            yield [emailaddr], msg
+        # restore language
+        req.set_language(origlang)
+
+    # recipients / email sending ###############################################
+
+    def recipients(self):
+        """return a list of either 2-uple (email, language) or user entity to
+        who this email should be sent
+        """
+        finder = self._cw.vreg['components'].select(
+            'recipients_finder', self._cw, rset=self.cw_rset,
+            row=self.cw_row or 0, col=self.cw_col or 0)
+        return finder.recipients()
+
+    def send_now(self, recipients, msg):
+        self._cw.vreg.config.sendmails([(msg, recipients)])
+
+    def send_on_commit(self, recipients, msg):
+        raise NotImplementedError
+
+    send = send_now
+
+    # email generation helpers #################################################
+
+    def construct_message_id(self, eid):
+        return construct_message_id(self._cw.vreg.config.appid, eid,
+                                    self.msgid_timestamp)
+
+    def format_field(self, attr, value):
+        return ':%(attr)s: %(value)s' % {'attr': attr, 'value': value}
+
+    def format_section(self, attr, value):
+        return '%(attr)s\n%(ul)s\n%(value)s\n' % {
+            'attr': attr, 'ul': '-'*len(attr), 'value': value}
+
+    def subject(self):
+        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+        subject = self._cw._(self.message)
+        etype = entity.dc_type()
+        eid = entity.eid
+        login = self.user_data['login']
+        return self._cw._('%(subject)s %(etype)s #%(eid)s (%(login)s)') % locals()
+
+    def context(self, **kwargs):
+        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+        for key, val in kwargs.iteritems():
+            if val and isinstance(val, unicode) and val.strip():
+               kwargs[key] = self._cw._(val)
+        kwargs.update({'user': self.user_data['login'],
+                       'eid': entity.eid,
+                       'etype': entity.dc_type(),
+                       'url': entity.absolute_url(),
+                       'title': entity.dc_long_title(),})
+        return kwargs
+
+
 class NotificationView(BaseNotificationView):
     """overriden to delay actual sending of mails to a commit operation by
     default