[notification] move notification view in ``sobject.notification``
authorPierre-Yves David <pierre-yves.david@logilab.fr>
Thu, 25 Apr 2013 13:34:48 +0200
changeset 8931 4b195bd82e8b
parent 8930 6a02be304486
child 8932 4ae4242bceb1
[notification] move notification view in ``sobject.notification`` It has no user outside this module. This enforce serversideness of notification and allow future cleanup. No backward compat is set up to prevent circular import. The class has no other user anyway. (closes 2845144)
mail.py
sobjects/notification.py
sobjects/test/unittest_notification.py
--- a/mail.py	Thu Apr 25 14:10:55 2013 +0200
+++ b/mail.py	Thu Apr 25 13:34:48 2013 +0200
@@ -27,9 +27,6 @@
 from email.header import Header
 from socket import gethostname
 
-from cubicweb.view import EntityView
-from cubicweb.entity import Entity
-
 def header(ustring):
     return Header(ustring.encode('UTF-8'), 'UTF-8')
 
@@ -142,140 +139,3 @@
         image = MIMEImage(data)
         image.add_header('Content-ID', '<%s>' % htmlId)
         self.attach(image)
-
-
-class NotificationView(EntityView):
-    """abstract view implementing the "email" API (eg to simplify sending
-    notification)
-    """
-    # 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 SkipEmail(Exception):
-    """raise this if you decide to skip an email during its generation"""
--- 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
--- a/sobjects/test/unittest_notification.py	Thu Apr 25 14:10:55 2013 +0200
+++ b/sobjects/test/unittest_notification.py	Thu Apr 25 13:34:48 2013 +0200
@@ -85,7 +85,8 @@
     def test_status_change_view(self):
         req = self.request()
         u = self.create_user(req, 'toto')
-        u.cw_adapt_to('IWorkflowable').fire_transition('deactivate', comment=u'yeah')
+        iwfable = u.cw_adapt_to('IWorkflowable')
+        iwfable.fire_transition('deactivate', comment=u'yeah')
         self.assertFalse(MAILBOX)
         self.commit()
         self.assertEqual(len(MAILBOX), 1)
@@ -99,7 +100,8 @@
 
 url: http://testing.fr/cubicweb/cwuser/toto
 ''')
-        self.assertEqual(email.subject, 'status changed CWUser #%s (admin)' % u.eid)
+        self.assertEqual(email.subject,
+                         'status changed CWUser #%s (admin)' % u.eid)
 
 if __name__ == '__main__':
     unittest_main()