[IMP] mail: improved mass mailing. Correctly take into account email_to, email_cc...
authorThibault Delavallée <tde@openerp.com>
Mon, 25 Feb 2013 16:48:57 +0000 (17:48 +0100)
committerThibault Delavallée <tde@openerp.com>
Mon, 25 Feb 2013 16:48:57 +0000 (17:48 +0100)
bzr revid: tde@openerp.com-20130225164857-i635atksj7riq9nd

15 files changed:
addons/crm/crm_lead_view.xml
addons/email_template/email_template_view.xml
addons/email_template/tests/test_mail.py
addons/email_template/wizard/mail_compose_message.py
addons/email_template/wizard/mail_compose_message_view.xml
addons/mail/mail_followers.py
addons/mail/mail_mail.py
addons/mail/mail_mail_view.xml
addons/mail/mail_message.py
addons/mail/mail_thread.py
addons/mail/res_partner_view.xml
addons/mail/tests/test_mail_features.py
addons/mail/wizard/invite.py
addons/mail/wizard/mail_compose_message.py
addons/mail/wizard/mail_compose_message_view.xml

index 87c1305..1b961e5 100644 (file)
             </field>
         </record>
 
+        <!--
+            MASS MAILING
+        -->
+        <act_window name="Mass Mailing"
+                res_model="mail.compose.message"
+                src_model="crm.lead"
+                view_mode="form"
+                multi="True"
+                target="new"
+                key2="client_action_multi"
+                id="crm.action_lead_mass_mail"
+                context="{
+                            'default_composition_mode': 'mass_mail',
+                            'default_email_to':'{$object.email or \'\'}',
+                        }"/>
+
     </data>
 </openerp>
index 5e1d7b9..51e6b53 100644 (file)
@@ -28,7 +28,7 @@
                             <page string="Email Details">
                                 <group>
                                     <group string="Addressing">
-                                      <field name="email_from" required="1"/>
+                                      <field name="email_from"/>
                                       <field name="email_to"/>
                                       <field name="partner_to"/>
                                       <field name="email_cc"/>
index c3ee385..56abe6a 100644 (file)
@@ -130,6 +130,7 @@ class test_message_compose(TestMailBase):
         # 1. Mass_mail on pigs and bird, with a default_partner_ids set to check he is correctly added
         context = {
             'default_composition_mode': 'mass_mail',
+            'default_notify': True,
             'default_model': 'mail.group',
             'default_res_id': self.group_pigs_id,
             'default_template_id': email_template_id,
index bc97383..b7da6cc 100644 (file)
@@ -22,6 +22,7 @@
 from openerp import tools
 from openerp.osv import osv, fields
 
+
 def _reopen(self, res_id, model):
     return {'type': 'ir.actions.act_window',
             'view_mode': 'form',
@@ -36,6 +37,7 @@ def _reopen(self, res_id, model):
             },
     }
 
+
 class mail_compose_message(osv.TransientModel):
     _inherit = 'mail.compose.message'
 
@@ -59,13 +61,25 @@ class mail_compose_message(osv.TransientModel):
     _columns = {
         # incredible hack of the day: size=-1 means we want an int db column instead of an str one
         'template_id': fields.selection(_get_templates, 'Template', size=-1),
+        'partner_to': fields.char('To (Partner IDs)', readonly=True,
+            help="Comma-separated list of recipient partners ids (placeholders may be used here)"),
+        'email_to': fields.char('To (Emails)', readonly=True,
+            help="Comma-separated recipient addresses (placeholders may be used here)",),
+        'email_cc': fields.char('Cc (Emails)', readonly=True,
+            help="Carbon copy recipients (placeholders may be used here)"),
+    }
+
+    _defaults = {
+        'partner_to': lambda self, cr, uid, ctx={}: '',
+        'email_to': lambda self, cr, uid, ctx={}: '',
+        'email_cc': lambda self, cr, uid, ctx={}: '',
     }
 
     def onchange_template_id(self, cr, uid, ids, template_id, composition_mode, model, res_id, context=None):
         """ - mass_mailing: we cannot render, so return the template values
             - normal mode: return rendered values """
         if template_id and composition_mode == 'mass_mail':
-            values = self.pool.get('email.template').read(cr, uid, template_id, ['email_from', 'partner_to', 'reply_to', 'subject', 'body_html'], context)
+            values = self.pool.get('email.template').read(cr, uid, template_id, ['subject', 'body_html', 'email_from', 'email_to', 'email_cc', 'partner_to', 'reply_to'], context)
             values.pop('id')
         elif template_id:
             # FIXME odo: change the mail generation to avoid attachment duplication
@@ -80,11 +94,11 @@ class mail_compose_message(osv.TransientModel):
                     'datas_fname': attach_fname,
                     'res_model': model,
                     'res_id': res_id,
-                    'type': 'binary', # override default_type from context, possibly meant for another model!
+                    'type': 'binary',  # override default_type from context, possibly meant for another model!
                 }
                 values['attachment_ids'].append(ir_attach_obj.create(cr, uid, data_attach, context=context))
         else:
-            values = self.default_get(cr, uid, ['body', 'subject', 'partner_ids', 'attachment_ids'], context=context)
+            values = self.default_get(cr, uid, ['subject', 'body', 'email_from', 'email_to', 'email_cc', 'partner_to', 'reply_to', 'attachment_ids'], context=context)
 
         if values.get('body_html'):
             values['body'] = values.pop('body_html')
@@ -122,7 +136,7 @@ class mail_compose_message(osv.TransientModel):
             mail.compose.message, transform email_cc and email_to into partner_ids """
         template_values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context)
         # filter template values
-        fields = ['body_html', 'subject', 'email_to', 'partner_to', 'email_cc', 'attachments']
+        fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc',  'reply_to', 'attachments']
         values = dict((field, template_values[field]) for field in fields if template_values.get(field))
         values['body'] = values.pop('body_html', '')
         # transform email_to, email_cc into partner_ids
@@ -150,6 +164,20 @@ class mail_compose_message(osv.TransientModel):
             values = {}
         # get values to return
         email_dict = super(mail_compose_message, self).render_message(cr, uid, wizard, res_id, context)
+
+        email_to = self.render_template(cr, uid, wizard.email_to, wizard.model, res_id, context)
+        email_cc = self.render_template(cr, uid, wizard.email_cc, wizard.model, res_id, context)
+        partner_to = self.render_template(cr, uid, wizard.partner_to, wizard.model, res_id, context)
+        email_dict['partner_ids'] = []
+
+        mails = tools.email_split((email_to or '') + ' ' + (email_cc or ''))
+        for mail in mails:
+            partner_id = self.pool.get('res.partner').find_or_create(cr, uid, mail, context=context)
+            email_dict['partner_ids'].append(partner_id)
+        if partner_to:
+            for partner_id in partner_to.split(','):
+                email_dict['partner_ids'].append(int(partner_id))
+
         values.update(email_dict)
         return values
 
index 34a2112..5b820e5 100644 (file)
@@ -7,6 +7,11 @@
             <field name="model">mail.compose.message</field>
             <field name="inherit_id" ref="mail.email_compose_message_wizard_form"/>
             <field name="arch" type="xml">
+                <xpath expr="//field[@name='parent_id']" position="after">
+                    <field name="partner_to" groups="base.group_no_one"/>
+                    <field name="email_to" groups="base.group_no_one"/>
+                    <field name="email_cc" groups="base.group_no_one"/>
+                </xpath>
                 <xpath expr="//footer" position="inside">
                     <group class="oe_right" col="1">
                         <div>Use template
index a9d33f4..8b01741 100644 (file)
@@ -75,7 +75,7 @@ class mail_notification(osv.Model):
         if not cr.fetchone():
             cr.execute('CREATE INDEX mail_notification_partner_id_read_starred_message_id ON mail_notification (partner_id, read, starred, message_id)')
 
-    def get_partners_to_notify(self, cr, uid, message, context=None):
+    def get_partners_to_notify(self, cr, uid, message, partners_to_notify=[], context=None):
         """ Return the list of partners to notify, based on their preferences.
 
             :param browse_record message: mail.message to notify
@@ -85,6 +85,9 @@ class mail_notification(osv.Model):
             if notification.read:
                 continue
             partner = notification.partner_id
+            # NOtify only required partners
+            if partner.id not in partners_to_notify:
+                continue
             # Do not send to partners without email address defined
             if not partner.email:
                 continue
@@ -100,17 +103,24 @@ class mail_notification(osv.Model):
             notify_pids.append(partner.id)
         return notify_pids
 
-    def _notify(self, cr, uid, msg_id, context=None):
+    def _notify(self, cr, uid, msg_id, partners_to_notify=[], context=None):
         """ Send by email the notification depending on the user preferences """
         if context is None:
             context = {}
+        if not partners_to_notify:
+            return True
+        mail_message_obj = self.pool.get('mail.message')
+
+        # update message
+        mail_message_obj.write(cr, uid, msg_id, {'notified_partner_ids': [(4, id) for id in partners_to_notify]}, context=context)
+
         # mail_notify_noemail (do not send email) or no partner_ids: do not send, return
         if context.get('mail_notify_noemail'):
             return True
         # browse as SUPERUSER_ID because of access to res_partner not necessarily allowed
         msg = self.pool.get('mail.message').browse(cr, SUPERUSER_ID, msg_id, context=context)
-        notify_partner_ids = self.get_partners_to_notify(cr, uid, msg, context=context)
-        if not notify_partner_ids:
+        partners_to_notify = self.get_partners_to_notify(cr, uid, msg, partners_to_notify=partners_to_notify, context=context)
+        if not partners_to_notify:
             return True
 
         # add the context in the email
@@ -126,26 +136,16 @@ class mail_notification(osv.Model):
         if signature:
             body_html = tools.append_content_to_html(body_html, signature, plaintext=True, container_tag='div')
 
-        # email_from: partner-user alias or partner email or mail.message email_from
-        if msg.author_id and msg.author_id.user_ids and msg.author_id.user_ids[0].alias_domain and msg.author_id.user_ids[0].alias_name:
-            email_from = '%s <%s@%s>' % (msg.author_id.name, msg.author_id.user_ids[0].alias_name, msg.author_id.user_ids[0].alias_domain)
-        elif msg.author_id:
-            email_from = '%s <%s>' % (msg.author_id.name, msg.author_id.email)
-        else:
-            email_from = msg.email_from
-
         mail_values = {
             'mail_message_id': msg.id,
-            'email_to': [],
             'auto_delete': True,
             'body_html': body_html,
-            'email_from': email_from,
-            'state': 'outgoing',
+            'recipient_ids': [(4, id) for id in partners_to_notify]
         }
-        mail_values['email_to'] = ', '.join(mail_values['email_to'])
+        if msg.email_from:
+            mail_values['email_from'] = msg.email_from
         email_notif_id = mail_mail.create(cr, uid, mail_values, context=context)
         try:
-            return mail_mail.send(cr, uid, [email_notif_id], recipient_ids=notify_partner_ids, context=context)
+            return mail_mail.send(cr, uid, [email_notif_id], context=context)
         except Exception:
             return False
-
index 1b3ca15..b850988 100644 (file)
@@ -55,22 +55,25 @@ class mail_mail(osv.Model):
             help="Permanently delete this email after sending it, to save space"),
         'references': fields.text('References', help='Message references, such as identifiers of previous messages', readonly=1),
         'email_from': fields.char('From', help='Message sender, taken from user preferences.'),
-        'email_to': fields.text('To', help='Message recipients'),
+        'email_to': fields.text('To', help='Message recipients (emails)'),
+        'recipient_ids': fields.many2many('res.partner', string='Message recipients (partners)'),
         'email_cc': fields.char('Cc', help='Carbon copy message recipients'),
-        'reply_to': fields.char('Reply-To', help='Preferred response address for the message'),
         'body_html': fields.text('Rich-text Contents', help="Rich-text/HTML message"),
 
+        # If not set in create values, auto-detected based on create values (res_id, model, email_from)
+        'reply_to': fields.char('Reply-To', help='Preferred response address for the message'),
+
         # Auto-detected based on create() - if 'mail_message_id' was passed then this mail is a 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):
-        this = self.pool.get('res.users').browse(cr, uid, uid, context=context)
+        this = self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=context)
         if this.alias_domain:
-            return '%s@%s' % (this.alias_name, this.alias_domain)
+            return '%s <%s@%s>' % (this.name, this.alias_name, this.alias_domain)
         elif this.email:
-            return this.email
+            return '%s <%s>' % (this.name, this.email)
         raise osv.except_osv(_('Invalid Action!'), _("Unable to send email, please configure the sender's email address or alias."))
 
     _defaults = {
@@ -82,12 +85,16 @@ class mail_mail(osv.Model):
         # protection for `default_type` values leaking from menu action context (e.g. for invoices)
         # To remove when automatic context propagation is removed in web client
         if context and context.get('default_type') and context.get('default_type') not in self._all_columns['type'].column.selection:
-            context = dict(context, default_type = None)
+            context = dict(context, default_type=None)
         return super(mail_mail, self).default_get(cr, uid, fields, context=context)
 
     def create(self, cr, uid, values, context=None):
         if 'notification' not in values and values.get('mail_message_id'):
             values['notification'] = True
+        if not values.get('reply_to') and values.get('res_id') and values.get('model') and hasattr(self.pool.get(values.get('model')), 'message_get_reply_to'):
+            values['reply_to'] = self.pool.get(values.get('model')).message_get_reply_to(cr, uid, [values.get('res_id')], context=context)[0]
+        elif not values.get('reply_to'):
+            values['reply_to'] = values.get('email_from', False)
         return super(mail_mail, self).create(cr, uid, values, context=context)
 
     def unlink(self, cr, uid, ids, context=None):
@@ -190,22 +197,6 @@ class mail_mail(osv.Model):
                 pass
         return body
 
-    def send_get_mail_reply_to(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 browse_record mail: mail.mail browse_record
-            :param browse_record partner: specific recipient partner
-        """
-        if mail.reply_to:
-            return mail.reply_to
-        if not mail.model or not mail.res_id:
-            return False
-        if not hasattr(self.pool.get(mail.model), 'message_get_reply_to'):
-            return False
-        return self.pool.get(mail.model).message_get_reply_to(cr, uid, [mail.res_id], context=context)[0]
-
     def send_get_email_dict(self, cr, uid, mail, partner=None, context=None):
         """ Return a dictionary for specific email values, depending on a
             partner, or generic to the whole recipients given by mail.email_to.
@@ -215,7 +206,6 @@ class mail_mail(osv.Model):
         """
         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)
-        reply_to = self.send_get_mail_reply_to(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 {
@@ -223,10 +213,9 @@ class mail_mail(osv.Model):
             'body_alternative': body_alternative,
             'subject': subject,
             'email_to': email_to,
-            'reply_to': reply_to,
         }
 
-    def send(self, cr, uid, ids, auto_commit=False, recipient_ids=None, context=None):
+    def send(self, cr, uid, ids, auto_commit=False, 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).
@@ -237,14 +226,10 @@ class mail_mail(osv.Model):
             :param bool auto_commit: whether to force a commit of the mail status
                 after sending each mail (meant only for scheduler processing);
                 should never be True during normal transactions (default: False)
-            :param list recipient_ids: specific list of res.partner recipients.
-                If set, one email is sent to each partner. Its is possible to
-                tune the sent email through ``send_get_mail_body`` and ``send_get_mail_subject``.
-                If not specified, one email is sent to mail_mail.email_to.
             :return: True
         """
         ir_mail_server = self.pool.get('ir.mail_server')
-        for mail in self.browse(cr, uid, ids, context=context):
+        for mail in self.browse(cr, SUPERUSER_ID, ids, context=context):
             try:
                 # handle attachments
                 attachments = []
@@ -252,11 +237,10 @@ class mail_mail(osv.Model):
                     attachments.append((attach.datas_fname, base64.b64decode(attach.datas)))
                 # specific behavior to customize the send email for notified partners
                 email_list = []
-                if recipient_ids:
-                    for partner in self.pool.get('res.partner').browse(cr, SUPERUSER_ID, recipient_ids, context=context):
-                        email_list.append(self.send_get_email_dict(cr, uid, mail, partner=partner, context=context))
-                else:
+                if mail.email_to:
                     email_list.append(self.send_get_email_dict(cr, uid, mail, context=context))
+                for partner in mail.recipient_ids:
+                    email_list.append(self.send_get_email_dict(cr, uid, mail, partner=partner, context=context))
 
                 # build an RFC2822 email.message.Message object and send it without queuing
                 for email in email_list:
index 72d41e3..c44b5da 100644 (file)
@@ -25,6 +25,8 @@
                                     </group>
                                     <group>
                                         <field name="partner_ids" widget="many2many_tags"/>
+                                        <field name="notified_partner_ids" widget="many2many_tags"/>
+                                        <field name="recipient_ids" widget="many2many_tags"/>
                                     </group>
                                 </group>
                                 <notebook>
@@ -67,7 +69,7 @@
                     <field name="subject"/>
                     <field name="author_id" string="User"/>
                     <field name="message_id" invisible="1"/>
-                    <field name="partner_ids" invisible="1"/>
+                    <field name="recipient_ids" invisible="1"/>
                     <field name="model" invisible="1"/>
                     <field name="res_id" invisible="1"/>
                     <field name="email_from" invisible="1"/>
@@ -97,7 +99,7 @@
                     <filter icon="terp-camera_test" name="type_notification" string="Notification" domain="[('type','=','notification')]"/>
                     <group expand="0" string="Extended Filters...">
                         <field name="author_id"/>
-                        <field name="partner_ids"/>
+                        <field name="recipient_ids"/>
                         <field name="model"/>
                         <field name="res_id"/>
                     </group>
index 77573f0..758203a 100644 (file)
@@ -139,6 +139,8 @@ class mail_message(osv.Model):
                  "message, comment for other messages such as user replies"),
         'email_from': fields.char('From',
             help="Email address of the sender. This field is set when no matching partner is found for incoming emails."),
+        'reply_to': fields.char('Reply-To',
+            help='Reply email address. Setting the reply_to bypasses the automatic thread creation.'),
         'author_id': fields.many2one('res.partner', 'Author', select=1,
             ondelete='set null',
             help="Author of the message. If not set, email_from may hold an email address that did not match any partner."),
@@ -185,8 +187,9 @@ class mail_message(osv.Model):
     _defaults = {
         'type': 'email',
         'date': lambda *a: fields.datetime.now(),
-        'author_id': lambda self, cr, uid, ctx={}: self._get_default_author(cr, uid, ctx),
+        'author_id': lambda self, cr, uid, ctx=None: self._get_default_author(cr, uid, ctx),
         'body': '',
+        'email_from': lambda self, cr, uid, ctx=None: self.pool.get('mail.mail')._get_default_from(cr, uid, ctx),
     }
 
     #------------------------------------------------------
@@ -731,7 +734,10 @@ class mail_message(osv.Model):
         if context is None:
             context = {}
         default_starred = context.pop('default_starred', False)
-        if not values.get('message_id') and values.get('res_id') and values.get('model'):
+        # generate message_id, to redirect answers to the right discussion thread
+        if not values.get('message_id') and values.get('reply_to'):
+            values['message_id'] = tools.generate_tracking_message_id('reply_to')
+        elif not values.get('message_id') and values.get('res_id') and values.get('model'):
             values['message_id'] = tools.generate_tracking_message_id('%(res_id)s-%(model)s' % values)
         elif not values.get('message_id'):
             values['message_id'] = tools.generate_tracking_message_id('private')
@@ -861,7 +867,7 @@ class mail_message(osv.Model):
         # message has no subtype_id: pure log message -> no partners, no one notified
         if not message.subtype_id:
             return True
-            
+
         # all followers of the mail.message document have to be added as partners and notified
         if message.model and message.res_id:
             fol_obj = self.pool.get("mail.followers")
@@ -883,9 +889,7 @@ class mail_message(osv.Model):
             partners_to_notify |= set(message.partner_ids)
 
         # notify
-        if partners_to_notify:
-            self.write(cr, SUPERUSER_ID, [newid], {'notified_partner_ids': [(4, p.id) for p in partners_to_notify]}, context=context)
-        notification_obj._notify(cr, uid, newid, context=context)
+        notification_obj._notify(cr, uid, newid, partners_to_notify=[p.id for p in partners_to_notify], context=context)
         message.refresh()
 
         # An error appear when a user receive a notification without notifying
index d53536d..c096293 100644 (file)
@@ -771,8 +771,7 @@ class mail_thread(osv.AbstractModel):
             author_ids = self._message_find_partners(cr, uid, message, ['From'], context=context)
             if author_ids:
                 msg_dict['author_id'] = author_ids[0]
-            else:
-                msg_dict['email_from'] = decode(message.get('from'))
+            msg_dict['email_from'] = decode(message.get('from'))
         partner_ids = self._message_find_partners(cr, uid, message, ['To', 'Cc'], context=context)
         msg_dict['partner_ids'] = [(4, partner_id) for partner_id in partner_ids]
 
index a4aa56a..7df5d27 100644 (file)
@@ -7,6 +7,9 @@
             <field name="model">res.partner</field>
             <field name="inherit_id" ref="base.view_partner_form"/>
             <field name="arch" type="xml">
+                <xpath expr="//field[@name='active']" position="after">
+                    <field name='notification_email_send'/>
+                </xpath>
                 <xpath expr="//sheet" position="after">
                     <div class="oe_chatter">
                         <field name="message_follower_ids" widget="mail_followers"/>
index 4302dab..a4bcb60 100644 (file)
@@ -154,7 +154,7 @@ class test_mail(TestMailBase):
         new_mail = self.mail_message.browse(cr, uid, self.mail_message.search(cr, uid, [('message_id', '=', test_msg_id)])[0])
         # Test: author_id set, not email_from
         self.assertEqual(new_mail.author_id, user_raoul.partner_id, 'message process wrong author found')
-        self.assertFalse(new_mail.email_from, 'message process should not set the email_from when an author is found')
+        self.assertEqual(new_mail.email_from, user_raoul.email, 'message process wrong email_from')
 
         # Do: post a new message, with a unknown partner
         test_msg_id = '<deadcafe.1337-3@smtp.agrolait.com>'
@@ -524,7 +524,7 @@ class test_mail(TestMailBase):
         # 1. mass_mail on pigs and bird
         compose_id = mail_compose.create(cr, uid,
             {'subject': _subject, 'body': '${object.description}'},
-            {'default_composition_mode': 'mass_mail', 'default_model': 'mail.group', 'default_res_id': False,
+            {'default_composition_mode': 'mass_mail', 'default_model': 'mail.group', 'default_res_id': False, 'default_notify': True,
                 'active_ids': [self.group_pigs_id, group_bird_id]})
         compose = mail_compose.browse(cr, uid, compose_id)
 
index 72678d4..687249a 100644 (file)
@@ -66,16 +66,17 @@ class invite_wizard(osv.osv_memory):
                 signature = user_id and user_id["signature"] or ''
                 if signature:
                     wizard.message = tools.append_content_to_html(wizard.message, signature, plaintext=True, container_tag='div')
+
                 # send mail to new followers
-                for follower_id in new_follower_ids:
-                    mail_mail = self.pool.get('mail.mail')
-                    # the invite wizard should create a private message not related to any object -> no model, no res_id
-                    mail_id = mail_mail.create(cr, uid, {
-                        'model': wizard.res_model,
-                        'res_id': wizard.res_id,
-                        'subject': 'Invitation to follow %s' % document.name_get()[0][1],
-                        'body_html': '%s' % wizard.message,
-                        'auto_delete': True,
-                        }, context=context)
-                    mail_mail.send(cr, uid, [mail_id], recipient_ids=[follower_id], context=context)
+                # the invite wizard should create a private message not related to any object -> no model, no res_id
+                mail_mail = self.pool.get('mail.mail')
+                mail_id = mail_mail.create(cr, uid, {
+                    'model': wizard.res_model,
+                    'res_id': wizard.res_id,
+                    'subject': 'Invitation to follow %s' % document.name_get()[0][1],
+                    'body_html': '%s' % wizard.message,
+                    'auto_delete': True,
+                    'recipient_ids': [(4, id) for id in new_follower_ids]
+                    }, context=context)
+                mail_mail.send(cr, uid, [mail_id], context=context)
         return {'type': 'ir.actions.act_window_close'}
index ec0c2a8..f646b88 100644 (file)
@@ -113,6 +113,12 @@ class mail_compose_message(osv.TransientModel):
         'partner_ids': fields.many2many('res.partner',
             'mail_compose_message_res_partner_rel',
             'wizard_id', 'partner_id', 'Additional contacts'),
+        'notify': fields.boolean('Notify followers',
+            help='Notify followers of the documents.'),
+        'post': fields.boolean('Post',
+            help='Post a copy of the message on the document communication history.'),
+        'same_thread': fields.boolean('Reply in the document',
+            help='Replies to the messages will go into the selected document.'),
         'attachment_ids': fields.many2many('ir.attachment',
             'mail_compose_message_ir_attachments_rel',
             'wizard_id', 'attachment_id', 'Attachments'),
@@ -124,6 +130,9 @@ class mail_compose_message(osv.TransientModel):
         'body': lambda self, cr, uid, ctx={}: '',
         'subject': lambda self, cr, uid, ctx={}: False,
         'partner_ids': lambda self, cr, uid, ctx={}: [],
+        'notify': lambda self, cr, uid, ctx={}: False,
+        'post': lambda self, cr, uid, ctx={}: True,
+        'same_thread': lambda self, cr, uid, ctx={}: True,
     }
 
     def _notify(self, cr, uid, newid, context=None):
@@ -215,15 +224,27 @@ class mail_compose_message(osv.TransientModel):
                     new_attachments = email_dict.pop('attachments', [])
                     post_values['attachments'] += new_attachments
                     post_values.update(email_dict)
+                    # email_from: mass mailing only can specify another email_from
+                    if email_dict.get('email_from'):
+                        post_values['email_from'] = email_dict.pop('email_from')
+                    # replies redirection: mass mailing only
+                    if not wizard.same_thread:
+                        post_values['reply_to'] = email_dict.pop('reply_to')
                 # automatically subscribe recipients if asked to
                 if context.get('mail_post_autofollow') and wizard.model and post_values.get('partner_ids'):
                     active_model_pool.message_subscribe(cr, uid, [res_id], [item[1] for item in post_values.get('partner_ids')], context=context)
                 # post the message
-                active_model_pool.message_post(cr, uid, [res_id], type='comment', subtype='mt_comment', context=context, **post_values)
-
-            # post process: update attachments, because id is not necessarily known when adding attachments in Chatter
-            # self.pool.get('ir.attachment').write(cr, uid, [attach.id for attach in wizard.attachment_ids], {
-            #     'res_id': wizard.id, 'res_model': wizard.model or False}, context=context)
+                if mass_mail_mode and not wizard.post:
+                    post_values['recipient_ids'] = post_values.pop('partner_ids')
+                    self.pool.get('mail.mail').create(cr, uid, post_values, context=context)
+                else:
+                    subtype = False
+                    if wizard.notify:
+                        subtype = 'mail.mt_comment'
+                    msg_id = active_model_pool.message_post(cr, uid, [res_id], type='comment', subtype=subtype, context=context, **post_values)
+                    # mass_mailing, post without notify: notify specific partners
+                    if mass_mail_mode and not wizard.notify and post_values['partner_ids']:
+                        self.pool.get('mail.notification')._notify(cr, uid, msg_id, [item[1] for item in post_values['partner_ids']], context=context)
 
         return {'type': 'ir.actions.act_window_close'}
 
@@ -234,6 +255,8 @@ class mail_compose_message(osv.TransientModel):
         return {
             'subject': self.render_template(cr, uid, wizard.subject, wizard.model, res_id, context),
             'body': self.render_template(cr, uid, wizard.body, wizard.model, res_id, context),
+            'email_from': self.render_template(cr, uid, wizard.email_from, wizard.model, res_id, context),
+            'reply_to': self.render_template(cr, uid, wizard.reply_to, wizard.model, res_id, context),
         }
 
     def render_template(self, cr, uid, template, model, res_id, context=None):
@@ -258,7 +281,7 @@ class mail_compose_message(osv.TransientModel):
             result = eval(exp, {
                 'user': self.pool.get('res.users').browse(cr, uid, uid, context=context),
                 'object': self.pool.get(model).browse(cr, uid, res_id, context=context),
-                'context': dict(context), # copy context to prevent side-effects of eval
+                'context': dict(context),  # copy context to prevent side-effects of eval
                 })
             return result and tools.ustr(result) or ''
         return template and EXPRESSION_PATTERN.sub(merge, template)
index fca5ec6..29a305a 100644 (file)
                         <field name="model" invisible="1"/>
                         <field name="res_id" invisible="1"/>
                         <field name="parent_id" invisible="1"/>
+                        <field name="post"  groups="base.group_no_one"/>
+                        <field name="notify"  groups="base.group_no_one"/>
                         <!-- visible wizard -->
-                        <label for="partner_ids" string="Recipients"/>
-                        <div groups="base.group_user">
-                            <span attrs="{'invisible':['|', ('model', '=', False), ('composition_mode', '!=', 'mass_mail')]}">
-                                Followers of selected items and
-                            </span>
-                            <span attrs="{'invisible':['|', ('model', '=', False), ('composition_mode', '=', 'mass_mail')]}">
+                        <field name="email_from"
+                            attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}"/>
+                        <label for="partner_ids" string="Recipients"
+                            attrs="{'invisible':[('composition_mode', '=', 'mass_mail')]}"/>
+                        <div groups="base.group_user"
+                            attrs="{'invisible':[('composition_mode', '=', 'mass_mail')]}">
+                            <span attrs="{'invisible':[('model', '=', False)]}">
                                 Followers of
-                                <field name="record_name" readonly="1" class="oe_inline"
-                                    attrs="{'invisible':[('model', '=', False)]}"/>
-                                and 
+                                <field name="record_name" readonly="1" class="oe_inline"/>
+                                and
                             </span>
                             <field name="partner_ids" widget="many2many_tags_email" placeholder="Add contacts to notify..."
                                 context="{'force_email':True, 'show_email':True}"/>
                         </div>
-                        <field name="subject" placeholder="Subject..."/>
+                        <field name="same_thread"/>
+                        <field name="reply_to" placeholder="Email address te redirect replies..."
+                            attrs="{'invisible':[('same_thread', '=', True)],
+                                    'required':[('same_thread', '!=', True)]}"/>
+                        <field name="subject" placeholder="Subject..." required="True"/>
                     </group>
                     <field name="body"/>
-                    <field name="attachment_ids" widget="many2many_binary"/>
+                    <field name="attachment_ids" widget="many2many_binary" string="Attach a file"/>
                     <footer>
                         <button string="Send" name="send_mail" type="object" class="oe_highlight"/>
                         or
@@ -59,6 +65,9 @@
                 target="new"
                 key2="client_action_multi"
                 id="base.action_partner_mass_mail"
-                context="{'default_composition_mode': 'mass_mail'}"/>
+                context="{
+                            'default_composition_mode': 'mass_mail',
+                            'default_partner_to': '${object.id or \'\'}',
+                        }"/>
     </data>
 </openerp>