Thibault Delavallée (OpenERP) has proposed merging 
lp:~openerp-dev/openobject-addons/trunk-mail-invite-tde into 
lp:openobject-addons.

Requested reviews:
  OpenERP Core Team (openerp)

For more details, see:
https://code.launchpad.net/~openerp-dev/openobject-addons/trunk-mail-invite-tde/+merge/123973

OpenChatter "Invite Yur Mummy" Invite branch

Featuring :
- Invite wizard, to add followers to a document, and to send them an email
- Invite button in Chatter, that calls the Invite wizard
- each send notification is a distinct email, one for each partner to have to 
receive a notification. Changes in mail_followers and mail_mail to handle this. 
If send receives a notifier_ids parameter, it uses it instead of the 
mail_mail.email_to field, and send an email to each partner. Overriding some of 
the methods allow to somewhat personnalize the email
- Portal: add the signin url in notifications send to portal users
-- 
https://code.launchpad.net/~openerp-dev/openobject-addons/trunk-mail-invite-tde/+merge/123973
Your team OpenERP R&D Team is subscribed to branch 
lp:~openerp-dev/openobject-addons/trunk-mail-invite-tde.
=== modified file 'mail/__openerp__.py'
--- mail/__openerp__.py	2012-09-06 13:42:01 +0000
+++ mail/__openerp__.py	2012-09-12 14:13:35 +0000
@@ -61,6 +61,7 @@
     'website': 'http://www.openerp.com',
     'depends': ['base', 'base_tools', 'base_setup'],
     'data': [
+        'wizard/invite_view.xml',
         'wizard/mail_compose_message_view.xml',
         'res_config_view.xml',
         'mail_message_view.xml',

=== modified file 'mail/mail_followers.py'
--- mail/mail_followers.py	2012-09-05 15:19:50 +0000
+++ mail/mail_followers.py	2012-09-12 14:13:35 +0000
@@ -84,16 +84,44 @@
         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
+        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')
-        msg = self.pool.get('mail.message').browse(cr, uid, msg_id, context=context)
-
         # add signature
         body_html = msg.body
         signature = msg.author_id and msg.author_id.user_ids[0].signature or ''
@@ -107,27 +135,6 @@
             '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)

=== modified file 'mail/mail_mail.py'
--- mail/mail_mail.py	2012-09-05 15:19:50 +0000
+++ mail/mail_mail.py	2012-09-12 14:13:35 +0000
@@ -30,6 +30,7 @@
 
 _logger = logging.getLogger(__name__)
 
+
 class mail_mail(osv.Model):
     """ Model holding RFC2822 email messages to send. This model also provides
         facilities to queue and send new email messages.  """
@@ -58,8 +59,8 @@
         'body_html': fields.text('Rich-text Contents', help="Rich-text/HTML message"),
 
         # Auto-detected based on create() - if 'mail_message_id' was passed then this mail is a notification
-        # and during unlink() we will cascade delete the parent and its attachments 
-        'notification': fields.boolean('Is Notification') 
+        # and during unlink() we will not cascade delete the parent and its attachments
+        'notification': fields.boolean('Is Notification')
     }
 
     def _get_default_from(self, cr, uid, context=None):
@@ -76,21 +77,21 @@
     def create(self, cr, uid, values, context=None):
         if 'notification' not in values and values.get('mail_message_id'):
             values['notification'] = True
-        return super(mail_mail,self).create(cr, uid, values, context=context)
+        return super(mail_mail, self).create(cr, uid, values, context=context)
 
     def unlink(self, cr, uid, ids, context=None):
         # cascade-delete the parent message for all mails that are not created for a notification
-        ids_to_cascade = self.search(cr, uid, [('notification','=',False),('id','in',ids)])
+        ids_to_cascade = self.search(cr, uid, [('notification', '=', False), ('id', 'in', ids)])
         parent_msg_ids = [m.mail_message_id.id for m in self.browse(cr, uid, ids_to_cascade, context=context)]
-        res = super(mail_mail,self).unlink(cr, uid, ids, context=context)
+        res = super(mail_mail, self).unlink(cr, uid, ids, context=context)
         self.pool.get('mail.message').unlink(cr, uid, parent_msg_ids, context=context)
         return res
 
     def mark_outgoing(self, cr, uid, ids, context=None):
-        return self.write(cr, uid, ids, {'state':'outgoing'}, context=context)
+        return self.write(cr, uid, ids, {'state': 'outgoing'}, context=context)
 
     def cancel(self, cr, uid, ids, context=None):
-        return self.write(cr, uid, ids, {'state':'cancel'}, context=context)
+        return self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
 
     def process_email_queue(self, cr, uid, ids=None, context=None):
         """Send immediately queued messages, committing after each
@@ -127,7 +128,7 @@
         """Perform any post-processing necessary after sending ``mail``
         successfully, including deleting it completely along with its
         attachment if the ``auto_delete`` flag of the mail was set.
-        Overridden by subclasses for extra post-processing behaviors. 
+        Overridden by subclasses for extra post-processing behaviors.
 
         :param browse_record mail: the mail that was just sent
         :return: True
@@ -136,14 +137,46 @@
             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).
@@ -159,45 +192,46 @@
         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})
+                    mail.write({'state': 'sent', 'message_id': res})
                 else:
-                    mail.write({'state':'exception'})
+                    mail.write({'state': 'exception'})
                 mail.refresh()
                 if mail.state == 'sent':
                     self._postprocess_sent_message(cr, uid, mail, context=context)
             except Exception:
                 _logger.exception('failed sending mail.mail %s', mail.id)
-                mail.write({'state':'exception'})
+                mail.write({'state': 'exception'})
 
             if auto_commit == True:
                 cr.commit()

=== modified file 'mail/mail_message.py'
--- mail/mail_message.py	2012-09-07 11:54:22 +0000
+++ mail/mail_message.py	2012-09-12 14:13:35 +0000
@@ -25,6 +25,7 @@
 from email.header import decode_header
 from operator import itemgetter
 from osv import osv, fields
+from tools.translate import _
 
 _logger = logging.getLogger(__name__)
 
@@ -341,3 +342,26 @@
             default = {}
         default.update(message_id=False, headers=False)
         return super(mail_message, self).copy(cr, uid, id, default=default, context=context)
+
+    #------------------------------------------------------
+    # Tools
+    #------------------------------------------------------
+
+    def verify_partner_email(self, cr, uid, partner_ids, context=None):
+        """ Verify that selected partner_ids have an email_address defined.
+            Otherwise throw a warning. """
+        partner_wo_email_lst = []
+        for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context):
+            if not partner.email:
+                partner_wo_email_lst.append(partner)
+        if not partner_wo_email_lst:
+            return {}
+        warning_msg = _('The following partners chosen as recipients for the email have no email address linked :')
+        for partner in partner_wo_email_lst:
+            warning_msg += '\n- %s' % (partner.name)
+        return {'warning': {
+                    'title': _('Partners email addresses not found'),
+                    'message': warning_msg,
+                    }
+                }
+

=== modified file 'mail/mail_thread.py'
--- mail/mail_thread.py	2012-09-05 16:01:45 +0000
+++ mail/mail_thread.py	2012-09-12 14:13:35 +0000
@@ -623,14 +623,8 @@
         return self.message_subscribe(cr, uid, ids, partner_ids, context=context)
 
     def message_subscribe(self, cr, uid, ids, partner_ids, context=None):
-        """ Add partners to the records followers.
-            :param partner_ids: a list of partner_ids to subscribe
-            :param return: new value of followers if read_back key in context
-        """
-        self.write(cr, uid, ids, {'message_follower_ids': [(4, pid) for pid in partner_ids]}, context=context)
-        if context and context.get('read_back'):
-            return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_follower_ids]
-        return []
+        """ Add partners to the records followers. """
+        return self.write(cr, uid, ids, {'message_follower_ids': [(4, pid) for pid in partner_ids]}, context=context)
 
     def message_unsubscribe_users(self, cr, uid, ids, user_ids=None, context=None):
         """ Wrapper on message_subscribe, using users. If user_ids is not
@@ -640,14 +634,8 @@
         return self.message_unsubscribe(cr, uid, ids, partner_ids, context=context)
 
     def message_unsubscribe(self, cr, uid, ids, partner_ids, context=None):
-        """ Remove partners from the records followers.
-            :param partner_ids: a list of partner_ids to unsubscribe
-            :param return: new value of followers if read_back key in context
-        """
-        self.write(cr, uid, ids, {'message_follower_ids': [(3, pid) for pid in partner_ids]}, context=context)
-        if context and context.get('read_back'):
-            return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_follower_ids]
-        return []
+        """ Remove partners from the records followers. """
+        return self.write(cr, uid, ids, {'message_follower_ids': [(3, pid) for pid in partner_ids]}, context=context)
 
     #------------------------------------------------------
     # Thread state

=== modified file 'mail/static/src/css/mail.css'
--- mail/static/src/css/mail.css	2012-09-12 14:02:24 +0000
+++ mail/static/src/css/mail.css	2012-09-12 14:13:35 +0000
@@ -96,10 +96,6 @@
     width: 120px;
 }
 
-.openerp button.oe_mail_button_followers {
-    display: inline;
-}
-
 .openerp button.oe_mail_button_mouseout {
   color: white;
   background-color: #8a89ba;

=== modified file 'mail/static/src/js/mail_followers.js'
--- mail/static/src/js/mail_followers.js	2012-09-06 11:14:44 +0000
+++ mail/static/src/js/mail_followers.js	2012-09-12 14:13:35 +0000
@@ -31,17 +31,9 @@
         },
 
         start: function() {
-            var self = this;
-            // NB: all the widget should be modified to check the actual_mode property on view, not use
-            // any other method to know if the view is in create mode anymore
+            // use actual_mode property on view to know if the view is in create mode anymore
             this.view.on("change:actual_mode", this, this._check_visibility);
             this._check_visibility();
-            this.$el.find('button.oe_mail_button_follow').click(function () { self.do_follow(); })
-                .mouseover(function () { $(this).html('Follow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); })
-                .mouseleave(function () { $(this).html('Not following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); });
-            this.$el.find('button.oe_mail_button_unfollow').click(function () { self.do_unfollow(); })
-                .mouseover(function () { $(this).html('Unfollow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); })
-                .mouseleave(function () { $(this).html('Following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); });
             this.reinit();
         },
 
@@ -49,15 +41,41 @@
             this.$el.toggle(this.view.get("actual_mode") !== "create");
         },
 
-        destroy: function () {
-            this._super.apply(this, arguments);
-        },
-
         reinit: function() {
             this.$el.find('button.oe_mail_button_follow').hide();
             this.$el.find('button.oe_mail_button_unfollow').hide();
         },
 
+        bind_events: function() {
+            var self = this;
+            this.$el.find('button.oe_mail_button_follow').on('click', function () { self.do_follow(); })
+            this.$el.find('button.oe_mail_button_unfollow').on('click', function () { self.do_unfollow(); })
+                .mouseover(function () { $(this).html('Unfollow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); })
+                .mouseleave(function () { $(this).html('Following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); });
+            this.$el.find('button.oe_mail_button_invite').on('click', function(event) {
+                action = {
+                    type: 'ir.actions.act_window',
+                    res_model: 'mail.wizard.invite',
+                    view_mode: 'form',
+                    view_type: 'form',
+                    views: [[false, 'form']],
+                    target: 'new',
+                    context: {
+                        'default_res_model': self.view.dataset.model,
+                        'default_res_id': self.view.datarecord.id
+                    },
+                }
+                self.do_action(action, function() { self.read_value(); });
+            });
+        },
+
+        read_value: function() {
+            var self = this;
+            return this.ds_model.read_ids([this.view.datarecord.id], ['message_follower_ids']).pipe(function (results) {
+                return results[0].message_follower_ids;
+            }).pipe(this.proxy('set_value'));
+        },
+
         set_value: function(value_) {
             this.reinit();
             if (! this.view.datarecord.id ||
@@ -65,11 +83,12 @@
                 this.$el.find('div.oe_mail_recthread_aside').hide();
                 return;
             }
-            return this.fetch_followers(value_);
+            this.bind_events();
+            return this.fetch_followers(value_  || this.get_value());
         },
 
         fetch_followers: function (value_) {
-            return this.ds_follow.call('read', [value_ || this.get_value(), ['name', 'user_ids']]).then(this.proxy('display_followers'));
+            return this.ds_follow.call('read', [value_, ['name', 'user_ids']]).pipe(this.proxy('display_followers'));
         },
 
         /** Display the followers, evaluate is_follower directly */
@@ -91,13 +110,13 @@
         },
 
         do_follow: function () {
-            var context = new session.web.CompoundContext(this.build_context(), {'read_back': true});
-            return this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id], undefined, context]).pipe(this.proxy('set_value'));
+            var context = new session.web.CompoundContext(this.build_context(), {});
+            return this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id], undefined, context]).pipe(this.proxy('read_value'));
         },
 
         do_unfollow: function () {
-            var context = new session.web.CompoundContext(this.build_context(), {'read_back': true});
-            return this.ds_model.call('message_unsubscribe_users', [[this.view.datarecord.id], undefined, context]).pipe(this.proxy('set_value'));
+            var context = new session.web.CompoundContext(this.build_context(), {});
+            return this.ds_model.call('message_unsubscribe_users', [[this.view.datarecord.id], undefined, context]).pipe(this.proxy('read_value'));
         },
     });
 };

=== modified file 'mail/static/src/xml/mail_followers.xml'
--- mail/static/src/xml/mail_followers.xml	2012-09-05 16:31:35 +0000
+++ mail/static/src/xml/mail_followers.xml	2012-09-12 14:13:35 +0000
@@ -7,7 +7,8 @@
         -->
     <div t-name="mail.followers" class="oe_mail_recthread_aside oe_semantic_html_override">
         <div class="oe_mail_recthread_actions">
-            <button type="button" class="oe_mail_button_follow oe_mail_button_mouseout">Not following</button>
+            <button type="button" class="oe_mail_button_invite">Invite</button>
+            <button type="button" class="oe_mail_button_follow">Follow</button>
             <button type="button" class="oe_mail_button_unfollow oe_mail_button_mouseout">Following</button>
         </div>
         <div class="oe_mail_recthread_followers">

=== modified file 'mail/tests/test_mail.py'
--- mail/tests/test_mail.py	2012-09-12 10:21:11 +0000
+++ mail/tests/test_mail.py	2012-09-12 14:13:35 +0000
@@ -19,6 +19,8 @@
 #
 ##############################################################################
 
+import tools
+
 from openerp.tests import common
 from openerp.tools.html_sanitize import html_sanitize
 
@@ -88,10 +90,19 @@
         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 @@
         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',
@@ -274,18 +289,20 @@
         _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 @@
         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 @@
         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 @@
         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')

=== modified file 'mail/wizard/__init__.py'
--- mail/wizard/__init__.py	2012-07-03 17:11:30 +0000
+++ mail/wizard/__init__.py	2012-09-12 14:13:35 +0000
@@ -19,6 +19,7 @@
 #
 ##############################################################################
 
+import invite
 import mail_compose_message
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

=== added file 'mail/wizard/invite.py'
--- mail/wizard/invite.py	1970-01-01 00:00:00 +0000
+++ mail/wizard/invite.py	2012-09-12 14:13:35 +0000
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2012-Today OpenERP SA (<http://www.openerp.com>)
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>
+#
+##############################################################################
+
+from osv import osv
+from osv import fields
+
+
+class invite_wizard(osv.osv_memory):
+    """ Wizard to invite partners and make them followers. """
+    _name = 'mail.wizard.invite'
+    _description = 'Invite wizard'
+
+    _columns = {
+        'res_model': fields.char('Related Document Model', size=128,
+                        required=True, select=1,
+                        help='Model of the followed resource'),
+        'res_id': fields.integer('Related Document ID', select=1,
+                        help='Id of the followed resource'),
+        'partner_ids': fields.many2many('res.partner', string='Partners'),
+        'message': fields.text('Message'),
+    }
+
+    def onchange_partner_ids(self, cr, uid, ids, value, context=None):
+        """ onchange_partner_ids (value format: [[6, 0, [3, 4]]]). The
+            basic purpose of this method is to check that destination partners
+            effectively have email addresses. Otherwise a warning is thrown.
+        """
+        res = {'value': {}}
+        if not value or not value[0] or not value[0][0] == 6:
+            return
+        res.update(self.pool.get('mail.message').verify_partner_email(cr, uid, value[0][2], context=context))
+        return res
+
+    def add_followers(self, cr, uid, ids, context=None):
+        for wizard in self.browse(cr, uid, ids, context=context):
+            model_obj = self.pool.get(wizard.res_model)
+            model_obj.message_subscribe(cr, uid, [wizard.res_id], [p.id for p in wizard.partner_ids], context=context)
+        return {'type': 'ir.actions.act_window_close'}

=== added file 'mail/wizard/invite_view.xml'
--- mail/wizard/invite_view.xml	1970-01-01 00:00:00 +0000
+++ mail/wizard/invite_view.xml	2012-09-12 14:13:35 +0000
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+
+        <!-- wizard view -->
+        <record model="ir.ui.view" id="mail_wizard_invite_form">
+            <field name="name">Add Followers</field>
+            <field name="model">mail.wizard.invite</field>
+            <field name="arch" type="xml">
+                <form string="Add Followers" version="7.0">
+                    <group>
+                        <field name="res_model" invisible="1"/>
+                        <field name="res_id" invisible="1"/>
+                        <field name="partner_ids" widget="many2many_tags"
+                            on_change="onchange_partner_ids(partner_ids)" />
+                        <field name="message"/>
+                    </group>
+                    <footer>
+                        <button string="Add Followers"
+                            name="add_followers" type="object" class="oe_highlight"  />
+                        or
+                        <button string="Cancel" class="oe_link" special="cancel" />    
+                    </footer>
+                </form>
+            </field>
+        </record>
+
+    </data>
+</openerp>

=== modified file 'mail/wizard/mail_compose_message.py'
--- mail/wizard/mail_compose_message.py	2012-09-12 10:21:11 +0000
+++ mail/wizard/mail_compose_message.py	2012-09-12 14:13:35 +0000
@@ -193,24 +193,6 @@
         """
         return {'value': {'content_subtype': value}}
 
-    def _verify_partner_email(self, cr, uid, partner_ids, context=None):
-        """ Verify that selected partner_ids have an email_address defined.
-            Otherwise throw a warning. """
-        partner_wo_email_lst = []
-        for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context):
-            if not partner.email:
-                partner_wo_email_lst.append(partner)
-        if not partner_wo_email_lst:
-            return {}
-        warning_msg = _('The following partners chosen as recipients for the email have no email address linked :')
-        for partner in partner_wo_email_lst:
-            warning_msg += '\n- %s' % (partner.name)
-        return {'warning': {
-                    'title': _('Partners email addresses not found'),
-                    'message': warning_msg,
-                    }
-                }
-
     def onchange_partner_ids(self, cr, uid, ids, value, context=None):
         """ The basic purpose of this method is to check that destination partners
             effectively have email addresses. Otherwise a warning is thrown.
@@ -219,7 +201,7 @@
         res = {'value': {}}
         if not value or not value[0] or not value[0][0] == 6:
             return
-        res.update(self._verify_partner_email(cr, uid, value[0][2], context=context))
+        res.update(self.verify_partner_email(cr, uid, value[0][2], context=context))
         return res
 
     def dummy(self, cr, uid, ids, context=None):

_______________________________________________
Mailing list: https://launchpad.net/~openerp-dev-gtk
Post to     : [email protected]
Unsubscribe : https://launchpad.net/~openerp-dev-gtk
More help   : https://help.launchpad.net/ListHelp

Reply via email to