[IMP] mail: added a new behavior when sending notifications. Each notification can...
authorThibault Delavallée <tde@openerp.com>
Wed, 12 Sep 2012 13:38:43 +0000 (15:38 +0200)
committerThibault Delavallée <tde@openerp.com>
Wed, 12 Sep 2012 13:38:43 +0000 (15:38 +0200)
bzr revid: tde@openerp.com-20120912133843-lltf7pzder386e2w

addons/mail/mail_followers.py
addons/mail/mail_mail.py
addons/mail/tests/test_mail.py

index 2aba685..146dc4b 100644 (file)
@@ -84,16 +84,44 @@ class mail_notification(osv.Model):
         notif_ids = self.search(cr, uid, [('partner_id', '=', partner_id), ('message_id', '=', msg_id)], context=context)
         return self.write(cr, uid, notif_ids, {'read': True}, context=context)
 
+    def get_partners_to_notify(self, cr, uid, partner_ids, message, context=None):
+        """ Return the list of partners to notify, based on their preferences.
+
+            :param message: browse_record of a mail.message
+        """
+        notify_pids = []
+        for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context):
+            # Do not send an email to the writer
+            if partner.user_ids and partner.user_ids[0].id == uid:
+                continue
+            # Do not send to partners without email address defined
+            if not partner.email:
+                continue
+            # Partner does not want to receive any emails
+            if partner.notification_email_send == 'none':
+                continue
+            # Partner wants to receive only emails and comments
+            if partner.notification_email_send == 'comment' and message.type not in ('email', 'comment'):
+                continue
+            # Partner wants to receive only emails
+            if partner.notification_email_send == 'email' and message.type != 'email':
+                continue
+            notify_pids.append(partner.id)
+        return notify_pids
+
     def notify(self, cr, uid, partner_ids, msg_id, context=None):
         """ Send by email the notification depending on the user preferences """
         context = context or {}
         # mail_noemail (do not send email) or no partner_ids: do not send, return
         if context.get('mail_noemail') or not partner_ids:
             return True
-
-        mail_mail = self.pool.get('mail.mail')
         msg = self.pool.get('mail.message').browse(cr, uid, msg_id, context=context)
 
+        notify_partner_ids = self.get_partners_to_notify(cr, uid, partner_ids, msg, context=context)
+        if not notify_partner_ids:
+            return True
+
+        mail_mail = self.pool.get('mail.mail')
         # add signature
         body_html = msg.body
         signature = msg.author_id and msg.author_id.user_ids[0].signature or ''
@@ -107,27 +135,6 @@ class mail_notification(osv.Model):
             'body_html': body_html,
             'state': 'outgoing',
         }
-
-        for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context):
-            # Do not send an email to the writer
-            if partner.user_ids and partner.user_ids[0].id == uid:
-                continue
-            # Do not send to partners without email address defined
-            if not partner.email:
-                continue
-            # Partner does not want to receive any emails
-            if partner.notification_email_send == 'none':
-                continue
-            # Partner wants to receive only emails and comments
-            if partner.notification_email_send == 'comment' and msg.type not in ('email', 'comment'):
-                continue
-            # Partner wants to receive only emails
-            if partner.notification_email_send == 'email' and msg.type != 'email':
-                continue
-            if partner.email not in mail_values['email_to']:
-                mail_values['email_to'].append(partner.email)
-        if mail_values['email_to']:
-            mail_values['email_to'] = ', '.join(mail_values['email_to'])
-            email_notif_id = mail_mail.create(cr, uid, mail_values, context=context)
-            mail_mail.send(cr, uid, [email_notif_id], context=context)
-        return True
+        mail_values['email_to'] = ', '.join(mail_values['email_to'])
+        email_notif_id = mail_mail.create(cr, uid, mail_values, context=context)
+        return mail_mail.send(cr, uid, [email_notif_id], notifier_ids=notify_partner_ids, context=context)
index 8d3f3ab..1cb9efa 100644 (file)
@@ -137,14 +137,46 @@ class mail_mail(osv.Model):
             mail.unlink()
         return True
 
-    def _send_get_mail_subject(self, cr, uid, mail, force=False, context=None):
+    def _send_get_mail_subject(self, cr, uid, mail, force=False, partner=None, context=None):
         """ if void and related document: '<Author> posted on <Resource>'
-            :param mail: mail.mail browse_record """
+
+            :param force: force the 'Author posted'... subject
+            :param mail: mail.mail browse_record
+            :param partner: browse_record of the specific recipient partner
+        """
         if force or (not mail.subject and mail.model and mail.res_id):
             return '%s posted on %s' % (mail.author_id.name, mail.record_name)
         return mail.subject
 
-    def send(self, cr, uid, ids, auto_commit=False, context=None):
+    def _send_get_mail_body(self, cr, uid, mail, partner=None, context=None):
+        """ Return a specific ir_email body. The main purpose of this method
+            is to be inherited by Portal, to add a link for signing in, in
+            each notification email a partner receives.
+
+            :param mail: mail.mail browse_record
+            :param partner: browse_record of the specific recipient partner
+        """
+        return mail.body_html
+
+    def _send_get_ir_email_dict(self, cr, uid, mail, partner=None, context=None):
+        """ Return a dictionary for specific ir_email values, depending on a
+            partner, or generic to the whole recipients given by mail.email_to.
+
+            :param mail: mail.mail browse_record
+            :param partner: browse_record of the specific recipient partner
+        """
+        body = self._send_get_mail_body(cr, uid, mail, partner=partner, context=context)
+        subject = self._send_get_mail_subject(cr, uid, mail, partner=partner, context=context)
+        body_alternative = tools.html2plaintext(body)
+        email_to = [partner.email] if partner else tools.email_split(mail.email_to)
+        return {
+            'body': body,
+            'body_alternative': body_alternative,
+            'subject': subject,
+            'email_to': email_to,
+        }
+
+    def send(self, cr, uid, ids, auto_commit=False, notifier_ids=None, context=None):
         """ Sends the selected emails immediately, ignoring their current
             state (mails that have already been sent should not be passed
             unless they should actually be re-sent).
@@ -160,35 +192,36 @@ class mail_mail(osv.Model):
         ir_mail_server = self.pool.get('ir.mail_server')
         for mail in self.browse(cr, uid, ids, context=context):
             try:
-                body = mail.body_html
-                subject = self._send_get_mail_subject(cr, uid, mail, context=context)
-
                 # handle attachments
                 attachments = []
                 for attach in mail.attachment_ids:
                     attachments.append((attach.datas_fname, base64.b64decode(attach.datas)))
-
-                # use only sanitized html and set its plaintexted version as alternative
-                body_alternative = tools.html2plaintext(body)
-                content_subtype_alternative = 'plain'
+                # specific behavior to customize the send email for notified partners
+                ir_email_list = []
+                if notifier_ids:
+                    for partner in self.pool.get('res.partner').browse(cr, uid, notifier_ids, context=context):
+                        ir_email_list.append(self._send_get_ir_email_dict(cr, uid, mail, partner=partner, context=context))
+                else:
+                    ir_email_list.append(self._send_get_ir_email_dict(cr, uid, mail, context=context))
 
                 # build an RFC2822 email.message.Message object and send it without queuing
-                msg = ir_mail_server.build_email(
-                    email_from = mail.email_from,
-                    email_to = tools.email_split(mail.email_to),
-                    subject = subject,
-                    body = body,
-                    body_alternative = body_alternative,
-                    email_cc = tools.email_split(mail.email_cc),
-                    reply_to = mail.reply_to,
-                    attachments = attachments,
-                    message_id = mail.message_id,
-                    references = mail.references,
-                    object_id = mail.res_id and ('%s-%s' % (mail.res_id, mail.model)),
-                    subtype = 'html',
-                    subtype_alternative = content_subtype_alternative)
-                res = ir_mail_server.send_email(cr, uid, msg,
-                    mail_server_id=mail.mail_server_id.id, context=context)
+                for ir_email in ir_email_list:
+                    msg = ir_mail_server.build_email(
+                        email_from = mail.email_from,
+                        email_to = ir_email.get('email_to'),
+                        subject = ir_email.get('subject'),
+                        body = ir_email.get('body'),
+                        body_alternative = ir_email.get('body_alternative'),
+                        email_cc = tools.email_split(mail.email_cc),
+                        reply_to = mail.reply_to,
+                        attachments = attachments,
+                        message_id = mail.message_id,
+                        references = mail.references,
+                        object_id = mail.res_id and ('%s-%s' % (mail.res_id, mail.model)),
+                        subtype = 'html',
+                        subtype_alternative = 'plain')
+                    res = ir_mail_server.send_email(cr, uid, msg,
+                        mail_server_id=mail.mail_server_id.id, context=context)
                 if res:
                     mail.write({'state': 'sent', 'message_id': res})
                 else:
index f45745f..24d7db6 100644 (file)
@@ -19,6 +19,8 @@
 #
 ##############################################################################
 
+import tools
+
 from openerp.tests import common
 from openerp.tools.html_sanitize import html_sanitize
 
@@ -29,7 +31,7 @@ Received: by mail1.openerp.com (Postfix, from userid 10002)
 From: Sylvie Lelitre <sylvie.lelitre@agrolait.com>
 Subject: {subject}
 MIME-Version: 1.0
-Content-Type: multipart/alternative; 
+Content-Type: multipart/alternative;
     boundary="----=_Part_4200734_24778174.1344608186754"
 Date: Fri, 10 Aug 2012 14:16:26 +0000
 Message-ID: <1198923581.41972151344608186760.JavaMail@agrolait.com>
@@ -52,9 +54,9 @@ Content-Transfer-Encoding: quoted-printable
   <meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Dutf-8" />
  </head>=20
  <body style=3D"margin: 0; padding: 0; background: #ffffff;-webkit-text-size-adjust: 100%;">=20
-  
+
   <p>Please call me as soon as possible this afternoon!</p>
-  
+
   <p>--<br/>
      Sylvie
   <p>
@@ -88,10 +90,19 @@ class test_mail(common.TransactionCase):
         return True
 
     def _mock_build_email(self, *args, **kwargs):
-        self._build_email_args = args
-        self._build_email_kwargs = kwargs
+        self._build_email_args_list.append(args)
+        self._build_email_kwargs_list.append(kwargs)
         return self.build_email_real(*args, **kwargs)
 
+    def _init_mock_build_email(self):
+        self._build_email_args_list = []
+        self._build_email_kwargs_list = []
+
+    def _mock_send_get_mail_body(self, *args, **kwargs):
+        # def _send_get_mail_body(self, cr, uid, mail, partner=None, context=None)
+        body = tools.append_content_to_html(args[2].body_html, kwargs.get('partner').name if kwargs.get('partner') else 'No specific partner')
+        return body
+
     def setUp(self):
         super(test_mail, self).setUp()
         self.ir_model = self.registry('ir.model')
@@ -106,10 +117,14 @@ class test_mail(common.TransactionCase):
         self.res_partner = self.registry('res.partner')
 
         # Install mock SMTP gateway
+        self._init_mock_build_email()
         self.build_email_real = self.registry('ir.mail_server').build_email
         self.registry('ir.mail_server').build_email = self._mock_build_email
         self.registry('ir.mail_server').send_email = self._mock_smtp_gateway
 
+        # Mock _send_get_mail_body to test its functionality without other addons override
+        self.registry('mail.mail')._send_get_mail_body = self._mock_send_get_mail_body
+
         # groups@.. will cause the creation of new mail groups
         self.mail_group_model_id = self.ir_model.search(self.cr, self.uid, [('model', '=', 'mail.group')])[0]
         self.mail_alias.create(self.cr, self.uid, {'alias_name': 'groups',
@@ -153,7 +168,7 @@ class test_mail(common.TransactionCase):
         test_msg_id = '<deadcafe.1337@smtp.agrolait.com>'
         mail_text = MAIL_TEMPLATE_PLAINTEXT.format(to='groups@example.com', subject='frogs', extra='', msg_id=test_msg_id)
         self.mail_thread.message_process(cr, uid, None, mail_text)
-        new_mail = self.mail_message.browse(cr, uid, self.mail_message.search(cr, uid, [('message_id','=',test_msg_id)])[0])
+        new_mail = self.mail_message.browse(cr, uid, self.mail_message.search(cr, uid, [('message_id', '=', test_msg_id)])[0])
         self.assertEqual(new_mail.body, '\n<pre>\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n</pre>\n',
                          'plaintext mail incorrectly parsed')
 
@@ -274,18 +289,20 @@ class test_mail(common.TransactionCase):
         _attachments = [('First', 'My first attachment'), ('Second', 'My second attachment')]
 
         # CASE1: post comment, body and subject specified
+        self._init_mock_build_email()
         msg_id = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body1, subject=_subject, type='comment')
         message = self.mail_message.browse(cr, uid, msg_id)
-        sent_email = self._build_email_kwargs
+        sent_emails = self._build_email_kwargs_list
         # Test: notifications have been deleted
         self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg_id)]), 'mail.mail notifications should have been auto-deleted!')
         # Test: mail_message: subject is _subject, body is _body1 (no formatting done)
         self.assertEqual(message.subject, _subject, 'mail.message subject incorrect')
         self.assertEqual(message.body, _body1, 'mail.message body incorrect')
-        # Test: sent_email: email send by server: correct subject, body; body_alternative
-        self.assertEqual(sent_email['subject'], _subject, 'sent_email subject incorrect')
-        self.assertEqual(sent_email['body'], _mail_body1, 'sent_email body incorrect')
-        self.assertEqual(sent_email['body_alternative'], _mail_bodyalt1, 'sent_email body_alternative is incorrect')
+        # Test: sent_email: email send by server: correct subject, body, body_alternative
+        for sent_email in sent_emails:
+            self.assertEqual(sent_email['subject'], _subject, 'sent_email subject incorrect')
+            self.assertEqual(sent_email['body'], _mail_body1 + '\n<pre>Bert Tartopoils</pre>\n', 'sent_email body incorrect')
+            self.assertEqual(sent_email['body_alternative'], _mail_bodyalt1 + '\nBert Tartopoils', 'sent_email body_alternative is incorrect')
         # Test: mail_message: partner_ids = group followers
         message_pids = set([partner.id for partner in message.partner_ids])
         test_pids = set([p_a_id, p_b_id, p_c_id])
@@ -295,14 +312,16 @@ class test_mail(common.TransactionCase):
         notif_pids = set([notif.partner_id.id for notif in self.mail_notification.browse(cr, uid, notif_ids)])
         self.assertEqual(notif_pids, test_pids, 'mail.message notification partners incorrect')
         # Test: sent_email: email_to should contain b@b, not c@c (pref email), not a@a (writer)
-        self.assertEqual(sent_email['email_to'], ['b@b'], 'sent_email email_to is incorrect')
+        for sent_email in sent_emails:
+            self.assertEqual(sent_email['email_to'], ['b@b'], 'sent_email email_to is incorrect')
 
         # CASE2: post an email with attachments, parent_id, partner_ids
         # TESTS: automatic subject, signature in body_html, attachments propagation
+        self._init_mock_build_email()
         msg_id2 = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body2, type='email',
             partner_ids=[(6, 0, [p_d_id])], parent_id=msg_id, attachments=_attachments)
         message = self.mail_message.browse(cr, uid, msg_id2)
-        sent_email = self._build_email_kwargs
+        sent_emails = self._build_email_kwargs_list
         self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg_id2)]), 'mail.mail notifications should have been auto-deleted!')
 
         # Test: mail_message: subject is False, body is _body2 (no formatting done), parent_id is msg_id
@@ -310,9 +329,11 @@ class test_mail(common.TransactionCase):
         self.assertEqual(message.body, html_sanitize(_body2), 'mail.message body incorrect')
         self.assertEqual(message.parent_id.id, msg_id, 'mail.message parent_id incorrect')
         # Test: sent_email: email send by server: correct subject, body, body_alternative
-        self.assertEqual(sent_email['subject'], _mail_subject, 'sent_email subject incorrect')
-        self.assertEqual(sent_email['body'], _mail_body2, 'sent_email body incorrect')
-        self.assertEqual(sent_email['body_alternative'], _mail_bodyalt2, 'sent_email body_alternative incorrect')
+        self.assertEqual(len(sent_emails), 2, 'sent_email number of sent emails incorrect')
+        for sent_email in sent_emails:
+            self.assertEqual(sent_email['subject'], _mail_subject, 'sent_email subject incorrect')
+            self.assertIn(_mail_body2, sent_email['body'], 'sent_email body incorrect')
+            self.assertIn(_mail_bodyalt2, sent_email['body_alternative'], 'sent_email body_alternative incorrect')
         # Test: mail_message: partner_ids = group followers
         message_pids = set([partner.id for partner in message.partner_ids])
         test_pids = set([p_a_id, p_b_id, p_c_id, p_d_id])
@@ -322,7 +343,8 @@ class test_mail(common.TransactionCase):
         notif_pids = set([notif.partner_id.id for notif in self.mail_notification.browse(cr, uid, notif_ids)])
         self.assertEqual(notif_pids, test_pids, 'mail.message notification partners incorrect')
         # Test: sent_email: email_to should contain b@b, c@c, not a@a (writer)
-        self.assertEqual(set(sent_email['email_to']), set(['b@b', 'c@c']), 'sent_email email_to incorrect')
+        for sent_email in sent_emails:
+            self.assertTrue(set(sent_email['email_to']).issubset(set(['b@b', 'c@c'])), 'sent_email email_to incorrect')
         # Test: attachments
         for attach in message.attachment_ids:
             self.assertEqual(attach.res_model, 'mail.group', 'mail.message attachment res_model incorrect')
@@ -458,6 +480,7 @@ class test_mail(common.TransactionCase):
         # It will be updated as soon as we have fixed specs !
         cr, uid = self.cr, self.uid
         group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
+
         def _compare_structures(struct1, struct2, n=0):
             # print '%scompare structure' % ('\t' * n)
             self.assertEqual(len(struct1), len(struct2), 'message_read structure number of childs incorrect')