From 22afd0cefdc3f5547757ce5ae79c3839cee61e47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 25 Feb 2013 11:19:18 +0100 Subject: [PATCH 01/28] [IMP] email_template: renamed email_recipients field to partner_to, in order to have a name that means something. bzr revid: tde@openerp.com-20130225101918-khyd6qkvtsyr2wi3 --- addons/account/edi/invoice_action_data.xml | 2 +- addons/crm/crm_data.xml | 5 ++--- addons/email_template/email_template.py | 8 +++++--- addons/email_template/email_template_view.xml | 4 ++-- addons/email_template/tests/test_mail.py | 6 +++--- .../wizard/email_template_preview_view.xml | 2 +- addons/email_template/wizard/mail_compose_message.py | 10 +++++----- addons/portal_sale/portal_sale_data.xml | 4 ++-- addons/purchase/edi/purchase_order_action_data.xml | 2 +- addons/sale/edi/sale_order_action_data.xml | 2 +- 10 files changed, 23 insertions(+), 22 deletions(-) diff --git a/addons/account/edi/invoice_action_data.xml b/addons/account/edi/invoice_action_data.xml index 18ac71f55ce..b3fe8362b8b 100644 --- a/addons/account/edi/invoice_action_data.xml +++ b/addons/account/edi/invoice_action_data.xml @@ -24,7 +24,7 @@ Invoice - Send by Email ${object.user_id.email or object.company_id.email or 'noreply@localhost'} ${object.company_id.name} Invoice (Ref ${object.number or 'n/a'}) - ${object.partner_id.id} + ${object.partner_id.id} diff --git a/addons/crm/crm_data.xml b/addons/crm/crm_data.xml index 3726c744b2e..e53d8e73599 100644 --- a/addons/crm/crm_data.xml +++ b/addons/crm/crm_data.xml @@ -80,11 +80,10 @@ basis for email recipients, name and to ease the definition of a further elaborated template. --> Opportunity - Send Emails - ${object.user_id.email or ''} - Opportunity ${object.name | h}) + ${object.name} - ${object.partner_id.id} + ${object.partner_id.id} diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py index 743384059b4..6778f8cf752 100644 --- a/addons/email_template/email_template.py +++ b/addons/email_template/email_template.py @@ -143,7 +143,9 @@ class email_template(osv.osv): 'subject': fields.char('Subject', translate=True, help="Subject (placeholders may be used here)",), 'email_from': fields.char('From', help="Sender address (placeholders may be used here)"), 'email_to': fields.char('To (Emails)', help="Comma-separated recipient addresses (placeholders may be used here)"), - 'email_recipients': fields.char('To (Partners)', help="Comma-separated ids of recipient partners (placeholders may be used here)"), + 'partner_to': fields.char('To (Partners)', + help="Comma-separated ids of recipient partners (placeholders may be used here)", + oldname='email_recipients'), 'email_cc': fields.char('Cc', help="Carbon copy recipients (placeholders may be used here)"), 'reply_to': fields.char('Reply-To', help="Preferred response address (placeholders may be used here)"), 'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing Mail Server', readonly=False, @@ -311,7 +313,7 @@ class email_template(osv.osv): template = self.get_email_template(cr, uid, template_id, res_id, context) values = {} for field in ['subject', 'body_html', 'email_from', - 'email_to', 'email_recipients', 'email_cc', 'reply_to']: + 'email_to', 'partner_to', 'email_cc', 'reply_to']: values[field] = self.render_template(cr, uid, getattr(template, field), template.model, res_id, context=context) \ or False @@ -371,7 +373,7 @@ class email_template(osv.osv): values = self.generate_email(cr, uid, template_id, res_id, context=context) assert 'email_from' in values, 'email_from is missing or empty after template rendering, send_mail() cannot proceed' attachments = values.pop('attachments') or {} - del values['email_recipients'] # TODO Properly use them. + del values['partner_to'] # TODO Properly use them. msg_id = mail_mail.create(cr, uid, values, context=context) # link attachments attachment_ids = [] diff --git a/addons/email_template/email_template_view.xml b/addons/email_template/email_template_view.xml index 4cddb31c8e1..5e1d7b931da 100644 --- a/addons/email_template/email_template_view.xml +++ b/addons/email_template/email_template_view.xml @@ -30,7 +30,7 @@ - + @@ -78,7 +78,7 @@ - + diff --git a/addons/email_template/tests/test_mail.py b/addons/email_template/tests/test_mail.py index b87b6d63000..c3ee385cf11 100644 --- a/addons/email_template/tests/test_mail.py +++ b/addons/email_template/tests/test_mail.py @@ -170,20 +170,20 @@ class test_message_compose(TestMailBase): self.assertEqual(set(message_bird_pids), set(partner_ids), 'mail.message on bird notified_partner_ids incorrect') # ---------------------------------------- - # CASE4: test newly introduced email_recipients field + # CASE4: test newly introduced partner_to field # ---------------------------------------- # get already-created partners back p_b_id = self.res_partner.search(cr, uid, [('email', '=', 'b@b.b')])[0] p_c_id = self.res_partner.search(cr, uid, [('email', '=', 'c@c.c')])[0] p_d_id = self.res_partner.search(cr, uid, [('email', '=', 'd@d.d')])[0] - # modify template: use email_recipients, use template and email address in email_to to test all features together + # modify template: use partner_to, use template and email address in email_to to test all features together user_model_id = self.registry('ir.model').search(cr, uid, [('model', '=', 'res.users')])[0] email_template.write(cr, uid, [email_template_id], { 'model_id': user_model_id, 'body_html': '${object.login}', 'email_to': '${object.email} c@c', - 'email_recipients': '%i,%i' % (p_b_id, p_c_id), + 'partner_to': '%i,%i' % (p_b_id, p_c_id), 'email_cc': 'd@d', }) # patner by email + partner by id (no double) diff --git a/addons/email_template/wizard/email_template_preview_view.xml b/addons/email_template/wizard/email_template_preview_view.xml index 8dd4bc92ba7..2d7abcb4eef 100644 --- a/addons/email_template/wizard/email_template_preview_view.xml +++ b/addons/email_template/wizard/email_template_preview_view.xml @@ -17,7 +17,7 @@ - + diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py index b39ed4e5639..bc97383bc19 100644 --- a/addons/email_template/wizard/mail_compose_message.py +++ b/addons/email_template/wizard/mail_compose_message.py @@ -65,7 +65,7 @@ class mail_compose_message(osv.TransientModel): """ - 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, ['subject', 'body_html'], context) + values = self.pool.get('email.template').read(cr, uid, template_id, ['email_from', 'partner_to', 'reply_to', 'subject', 'body_html'], context) values.pop('id') elif template_id: # FIXME odo: change the mail generation to avoid attachment duplication @@ -122,7 +122,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', 'email_recipients', 'email_cc', 'attachments'] + fields = ['body_html', 'subject', 'email_to', 'partner_to', 'email_cc', '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 @@ -132,9 +132,9 @@ class mail_compose_message(osv.TransientModel): for mail in mails: partner_id = self.pool.get('res.partner').find_or_create(cr, uid, mail, context=context) values['partner_ids'].append(partner_id) - email_recipients = values.pop('email_recipients', '') - if email_recipients: - for partner_id in email_recipients.split(','): + partner_to = values.pop('partner_to', '') + if partner_to: + for partner_id in partner_to.split(','): values['partner_ids'].append(int(partner_id)) values['partner_ids'] = list(set(values['partner_ids'])) diff --git a/addons/portal_sale/portal_sale_data.xml b/addons/portal_sale/portal_sale_data.xml index 31a2f19ef28..2cd7ca8b501 100644 --- a/addons/portal_sale/portal_sale_data.xml +++ b/addons/portal_sale/portal_sale_data.xml @@ -8,7 +8,7 @@ Sales Order - Send by Email (Portal) ${object.user_id.email or ''} ${object.company_id.name} ${object.state in ('draft', 'sent') and 'Quotation' or 'Order'} (Ref ${object.name or 'n/a' }) - ${object.partner_invoice_id.id} + ${object.partner_invoice_id.id} @@ -97,7 +97,7 @@ Invoice - Send by Email (Portal) ${object.user_id.email or object.company_id.email or 'noreply@localhost'} ${object.company_id.name} Invoice (Ref ${object.number or 'n/a' }) - ${object.partner_id.id} + ${object.partner_id.id} diff --git a/addons/purchase/edi/purchase_order_action_data.xml b/addons/purchase/edi/purchase_order_action_data.xml index d616ff1d116..819f7e26b55 100644 --- a/addons/purchase/edi/purchase_order_action_data.xml +++ b/addons/purchase/edi/purchase_order_action_data.xml @@ -21,7 +21,7 @@ Purchase Order - Send by mail ${object.validator.email or ''} ${object.company_id.name} Order (Ref ${object.name or 'n/a' }) - ${object.partner_id.id} + ${object.partner_id.id} diff --git a/addons/sale/edi/sale_order_action_data.xml b/addons/sale/edi/sale_order_action_data.xml index 16712532285..a7f597f4339 100644 --- a/addons/sale/edi/sale_order_action_data.xml +++ b/addons/sale/edi/sale_order_action_data.xml @@ -22,7 +22,7 @@ Sales Order - Send by Email ${object.user_id.email or ''} ${object.company_id.name} ${object.state in ('draft', 'sent') and 'Quotation' or 'Order'} (Ref ${object.name or 'n/a' }) - ${object.partner_invoice_id.id} + ${object.partner_invoice_id.id} From 0aaca4daa92bcaba2a981de11c136689fe80209c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 25 Feb 2013 17:48:57 +0100 Subject: [PATCH 02/28] [IMP] mail: improved mass mailing. Correctly take into account email_to, email_cc, partner_to even if no template. Mass mailing is a no-subtype post with additional recipients manually notified. Added option (in technical features) to send to followers, or to directly create a mail_mail. Added some default values in mail_message. Added explicit management of email_from and reply_to. Having a reply_to bypasses the 'model-res_id' in message_id behavior allowing to construct threads. Added explicit recipient_ids field on mail_mail that replaces the recipient_ids parameter in mail_mail.send(). Updated tests accordingly. bzr revid: tde@openerp.com-20130225164857-i635atksj7riq9nd --- addons/crm/crm_lead_view.xml | 16 ++++++ addons/email_template/email_template_view.xml | 2 +- addons/email_template/tests/test_mail.py | 1 + .../wizard/mail_compose_message.py | 36 +++++++++++-- .../wizard/mail_compose_message_view.xml | 5 ++ addons/mail/mail_followers.py | 36 ++++++------- addons/mail/mail_mail.py | 52 +++++++------------ addons/mail/mail_mail_view.xml | 6 ++- addons/mail/mail_message.py | 16 +++--- addons/mail/mail_thread.py | 3 +- addons/mail/res_partner_view.xml | 3 ++ addons/mail/tests/test_mail_features.py | 4 +- addons/mail/wizard/invite.py | 23 ++++---- addons/mail/wizard/mail_compose_message.py | 35 ++++++++++--- .../mail/wizard/mail_compose_message_view.xml | 33 +++++++----- 15 files changed, 173 insertions(+), 98 deletions(-) diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index 87c130547cd..1b961e5a3e0 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -577,5 +577,21 @@ + + + diff --git a/addons/email_template/email_template_view.xml b/addons/email_template/email_template_view.xml index 5e1d7b931da..51e6b53f622 100644 --- a/addons/email_template/email_template_view.xml +++ b/addons/email_template/email_template_view.xml @@ -28,7 +28,7 @@ - + diff --git a/addons/email_template/tests/test_mail.py b/addons/email_template/tests/test_mail.py index c3ee385cf11..56abe6a0fef 100644 --- a/addons/email_template/tests/test_mail.py +++ b/addons/email_template/tests/test_mail.py @@ -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, diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py index bc97383bc19..b7da6ccc236 100644 --- a/addons/email_template/wizard/mail_compose_message.py +++ b/addons/email_template/wizard/mail_compose_message.py @@ -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 diff --git a/addons/email_template/wizard/mail_compose_message_view.xml b/addons/email_template/wizard/mail_compose_message_view.xml index 34a2112e1ac..5b820e5e1ec 100644 --- a/addons/email_template/wizard/mail_compose_message_view.xml +++ b/addons/email_template/wizard/mail_compose_message_view.xml @@ -7,6 +7,11 @@ mail.compose.message + + + + +
Use template diff --git a/addons/mail/mail_followers.py b/addons/mail/mail_followers.py index a9d33f4585d..8b0174137b8 100644 --- a/addons/mail/mail_followers.py +++ b/addons/mail/mail_followers.py @@ -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 - diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index 1b3ca152a9a..b8509885f19 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -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: diff --git a/addons/mail/mail_mail_view.xml b/addons/mail/mail_mail_view.xml index 72d41e33a84..c44b5da5080 100644 --- a/addons/mail/mail_mail_view.xml +++ b/addons/mail/mail_mail_view.xml @@ -25,6 +25,8 @@ + + @@ -67,7 +69,7 @@ - + @@ -97,7 +99,7 @@ - + diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py index 77573f0d2f8..758203a5794 100644 --- a/addons/mail/mail_message.py +++ b/addons/mail/mail_message.py @@ -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 diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index d53536da8f7..c096293c5b3 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -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] diff --git a/addons/mail/res_partner_view.xml b/addons/mail/res_partner_view.xml index a4aa56ae971..7df5d27c9d0 100644 --- a/addons/mail/res_partner_view.xml +++ b/addons/mail/res_partner_view.xml @@ -7,6 +7,9 @@ res.partner + + +
diff --git a/addons/mail/tests/test_mail_features.py b/addons/mail/tests/test_mail_features.py index 4302dab8669..a4bcb60c5a6 100644 --- a/addons/mail/tests/test_mail_features.py +++ b/addons/mail/tests/test_mail_features.py @@ -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 = '' @@ -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) diff --git a/addons/mail/wizard/invite.py b/addons/mail/wizard/invite.py index 72678d468ff..687249a6445 100644 --- a/addons/mail/wizard/invite.py +++ b/addons/mail/wizard/invite.py @@ -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'} diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index ec0c2a8de06..f646b884ad8 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -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) diff --git a/addons/mail/wizard/mail_compose_message_view.xml b/addons/mail/wizard/mail_compose_message_view.xml index fca5ec65ca0..29a305a0462 100644 --- a/addons/mail/wizard/mail_compose_message_view.xml +++ b/addons/mail/wizard/mail_compose_message_view.xml @@ -12,25 +12,31 @@ + + - - - + Partner Mass Mail @@ -48,10 +48,10 @@ 'default_composition_mode': 'mass_mail', 'default_partner_to': '${object.id or \'\'}', 'default_use_template': True, - 'default_template_id': ref('partner_template'), + 'default_template_id': ref('email_template_partner'), }"/> - + diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py index 06dd2919672..2215d0705f1 100644 --- a/addons/email_template/wizard/mail_compose_message.py +++ b/addons/email_template/wizard/mail_compose_message.py @@ -22,7 +22,6 @@ 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', @@ -37,7 +36,6 @@ def _reopen(self, res_id, model): }, } - class mail_compose_message(osv.TransientModel): _inherit = 'mail.compose.message' @@ -69,12 +67,6 @@ class mail_compose_message(osv.TransientModel): 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 """ diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index 29d71ecbf2e..43d0d707699 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -59,13 +59,13 @@ class mail_mail(osv.Model): 'recipient_ids': fields.many2many('res.partner', string='To (Partners)'), 'email_cc': fields.char('Cc', help='Carbon copy message recipients'), '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'), - + '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') + 'notification': fields.boolean('Is Notification', + help='Mail has been created to notify people of an existing mail.message') } def _get_default_from(self, cr, uid, context=None): From 68282c60644eaa7f7689041bb5ea4ef8856de2c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 19 Mar 2013 10:38:44 +0100 Subject: [PATCH 28/28] [IMP] mail_compose_message: some fields back to visible. bzr revid: tde@openerp.com-20130319093844-82c95hmwznaprp6t --- addons/mail/wizard/mail_compose_message_view.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/mail/wizard/mail_compose_message_view.xml b/addons/mail/wizard/mail_compose_message_view.xml index 01cecd7d52c..1ca07b6a94c 100644 --- a/addons/mail/wizard/mail_compose_message_view.xml +++ b/addons/mail/wizard/mail_compose_message_view.xml @@ -16,9 +16,9 @@ - -