diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 0e0b734f72dc3f6871ca80c68fbb47d7cf5bcb37..305ad549c9fe84d3da65115677509dc74fcbdfcb 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -935,8 +935,10 @@ class crm_lead(format_address, osv.osv): def message_get_reply_to(self, cr, uid, ids, context=None): """ Override to get the reply_to of the parent project. """ - return [lead.section_id.message_get_reply_to()[0] if lead.section_id else False - for lead in self.browse(cr, SUPERUSER_ID, ids, context=context)] + leads = self.browse(cr, SUPERUSER_ID, ids, context=context) + section_ids = set([lead.section_id.id for lead in leads if lead.section_id]) + aliases = self.pool['crm.case.section'].message_get_reply_to(cr, uid, list(section_ids), context=context) + return dict((lead.id, aliases.get(lead.section_id and lead.section_id.id or 0, False)) for lead in leads) def get_formview_id(self, cr, uid, id, context=None): obj = self.browse(cr, uid, id, context=context) diff --git a/addons/email_template/tests/test_mail.py b/addons/email_template/tests/test_mail.py index beda74d085354949adb4127355dac8519a5043c2..f88d11ec71da8259296dd39facea372d5ccfd3ba 100644 --- a/addons/email_template/tests/test_mail.py +++ b/addons/email_template/tests/test_mail.py @@ -237,8 +237,8 @@ class test_message_compose(TestMail): email_template.send_mail(cr, uid, email_template_id, self.group_pigs_id, force_send=True, context=context) sent_emails = self._build_email_kwargs_list email_to_lst = [ - ['b@b.b', 'c@c.c'], ['Administrator <admin@yourcompany.example.com>'], - ['Raoul Grosbedon <raoul@raoul.fr>'], ['Bert Tartignole <bert@bert.fr>']] + ['b@b.b', 'c@c.c'], ['"Administrator" <admin@yourcompany.example.com>'], + ['"Raoul Grosbedon" <raoul@raoul.fr>'], ['"Bert Tartignole" <bert@bert.fr>']] self.assertEqual(len(sent_emails), 4, 'email_template: send_mail: 3 valid email recipients + email_to -> should send 4 emails') for email in sent_emails: self.assertIn(email['email_to'], email_to_lst, 'email_template: send_mail: wrong email_recipients') diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py index 775b84756ce63b82a3944b81fd77b8d613924f07..e0c3abffaa085c5e26ce55ede261b1c6dd965c76 100644 --- a/addons/hr_recruitment/hr_recruitment.py +++ b/addons/hr_recruitment/hr_recruitment.py @@ -20,6 +20,8 @@ ############################################################################## from datetime import datetime + +from openerp import SUPERUSER_ID from openerp.osv import fields, osv from openerp.tools.translate import _ @@ -357,6 +359,13 @@ class hr_applicant(osv.Model): action['domain'] = str(['&', ('res_model', '=', self._name), ('res_id', 'in', ids)]) return action + def message_get_reply_to(self, cr, uid, ids, context=None): + """ Override to get the reply_to of the parent project. """ + applicants = self.browse(cr, SUPERUSER_ID, ids, context=context) + job_ids = set([applicant.job_id.id for applicant in applicants if applicant.job_id]) + aliases = self.pool['project.project'].message_get_reply_to(cr, uid, list(job_ids), context=context) + return dict((applicant.id, aliases.get(applicant.job_id and applicant.job_id.id or 0, False)) for applicant in applicants) + def message_get_suggested_recipients(self, cr, uid, ids, context=None): recipients = super(hr_applicant, self).message_get_suggested_recipients(cr, uid, ids, context=context) for applicant in self.browse(cr, uid, ids, context=context): diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index fc320e03d50faa8e92f346ba08b03c74f8c48d13..0a6a250bcaaca6ed5d50818d598125f4675d3a77 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -188,11 +188,8 @@ class mail_mail(osv.Model): - if 'partner' and mail is a notification on a document: followers (Followers of 'Doc' <email>) - elif 'partner', no notificatoin or no doc: recipient specific (Partner Name <email>) - else fallback on mail.email_to splitting """ - if partner and mail.notification and mail.record_name: - sanitized_record_name = re.sub(r'[^\w+.]+', '-', mail.record_name) - email_to = [_('"Followers of %s" <%s>') % (sanitized_record_name, partner.email)] - elif partner: - email_to = ['%s <%s>' % (partner.name, partner.email)] + if partner: + email_to = ['"%s" <%s>' % (partner.name, partner.email)] else: email_to = tools.email_split(mail.email_to) return email_to diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py index 5b716a14ef2d2cccbeece111a44c7bb02713edc4..b924bb68e8c58e71bc65aa1795ce02938160d3d6 100644 --- a/addons/mail/mail_message.py +++ b/addons/mail/mail_message.py @@ -20,7 +20,6 @@ ############################################################################## import logging -import re from openerp import tools @@ -131,6 +130,8 @@ class mail_message(osv.Model): 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.'), + 'same_thread': fields.boolean('Same thread', + help='Redirect answers to the same discussion thread.'), '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."), @@ -188,6 +189,7 @@ class mail_message(osv.Model): '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._get_default_from(cr, uid, ctx), + 'same_thread': True, } #------------------------------------------------------ @@ -766,42 +768,12 @@ class mail_message(osv.Model): """ Return a specific reply_to: alias of the document through message_get_reply_to or take the email_from """ - email_reply_to = None - - ir_config_parameter = self.pool.get("ir.config_parameter") - catchall_domain = ir_config_parameter.get_param(cr, uid, "mail.catchall.domain", context=context) - - # model, res_id, email_from: comes from values OR related message model, res_id, email_from = values.get('model'), values.get('res_id'), values.get('email_from') - - # if model and res_id: try to use ``message_get_reply_to`` that returns the document alias - if not email_reply_to and model and res_id and catchall_domain and hasattr(self.pool[model], 'message_get_reply_to'): - email_reply_to = self.pool[model].message_get_reply_to(cr, uid, [res_id], context=context)[0] - # no alias reply_to -> catchall alias - if not email_reply_to and catchall_domain: - catchall_alias = ir_config_parameter.get_param(cr, uid, "mail.catchall.alias", context=context) - if catchall_alias: - email_reply_to = '%s@%s' % (catchall_alias, catchall_domain) - # still no reply_to -> reply_to will be the email_from - if not email_reply_to and email_from: - email_reply_to = email_from - - # format 'Document name <email_address>' - if email_reply_to and model and res_id: - emails = tools.email_split(email_reply_to) - if emails: - email_reply_to = emails[0] - document_name = self.pool[model].name_get(cr, SUPERUSER_ID, [res_id], context=context)[0] - if document_name: - # sanitize document name - sanitized_doc_name = re.sub(r'[^\w+.]+', '-', document_name[1]) - # generate reply to - email_reply_to = _('"Followers of %s" <%s>') % (sanitized_doc_name, email_reply_to) - - return email_reply_to + ctx = dict(context, thread_model=model) + return self.pool['mail.thread'].message_get_reply_to(cr, uid, [res_id], default=email_from, context=ctx)[res_id] def _get_message_id(self, cr, uid, values, context=None): - if values.get('reply_to'): + if values.get('same_thread', True) is False: message_id = tools.generate_tracking_message_id('reply_to') elif values.get('res_id') and values.get('model'): message_id = tools.generate_tracking_message_id('%(res_id)s-%(model)s' % values) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 7c3fb84dde27c92ed7adaeeb304fc1633b709323..54f2b8d8f8ba509f6f98ecec6193e11978c5c9aa 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -31,6 +31,7 @@ except ImportError: from lxml import etree import logging import pytz +import re import socket import time import xmlrpclib @@ -688,14 +689,50 @@ class mail_thread(osv.AbstractModel): res[record.id] = {'partner_ids': list(recipient_ids), 'email_to': email_to, 'email_cc': email_cc} return res - def message_get_reply_to(self, cr, uid, ids, context=None): + def message_get_reply_to(self, cr, uid, ids, default=None, context=None): """ Returns the preferred reply-to email address that is basically the alias of the document, if it exists. """ - if not self._inherits.get('mail.alias'): - return [False for id in ids] - return ["%s@%s" % (record.alias_name, record.alias_domain) - if record.alias_domain and record.alias_name else False - for record in self.browse(cr, SUPERUSER_ID, ids, context=context)] + if context is None: + context = {} + model_name = context.get('thread_model') or self._name + alias_domain = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.catchall.domain", context=context) + res = dict.fromkeys(ids, False) + + # alias domain: check for aliases and catchall + aliases = {} + doc_names = {} + if alias_domain: + if model_name and model_name != 'mail.thread': + alias_ids = self.pool['mail.alias'].search( + cr, SUPERUSER_ID, [ + ('alias_parent_model_id.model', '=', model_name), + ('alias_parent_thread_id', 'in', ids), + ('alias_name', '!=', False) + ], context=context) + aliases.update( + dict((alias.alias_parent_thread_id, '%s@%s' % (alias.alias_name, alias_domain)) + for alias in self.pool['mail.alias'].browse(cr, SUPERUSER_ID, alias_ids, context=context))) + doc_names.update( + dict((ng_res[0], ng_res[1]) + for ng_res in self.pool[model_name].name_get(cr, SUPERUSER_ID, aliases.keys(), context=context))) + # left ids: use catchall + left_ids = set(ids).difference(set(aliases.keys())) + if left_ids: + catchall_alias = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.catchall.alias", context=context) + if catchall_alias: + aliases.update(dict((res_id, '%s@%s' % (catchall_alias, alias_domain)) for res_id in left_ids)) + # compute name of reply-to + company_name = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).company_id.name + res.update( + dict((res_id, '"%(company_name)s%(document_name)s" <%(email)s>' % + {'company_name': company_name, + 'document_name': doc_names.get(res_id) and ' ' + re.sub(r'[^\w+.]+', '-', doc_names[res_id]) or '', + 'email': aliases[res_id] + } or False) for res_id in aliases.keys())) + left_ids = set(ids).difference(set(aliases.keys())) + if left_ids and default: + res.update(dict((res_id, default) for res_id in left_ids)) + return res def message_get_email_values(self, cr, uid, id, notif_mail=None, context=None): """ Get specific notification email values to store on the notification diff --git a/addons/mail/tests/test_mail_features.py b/addons/mail/tests/test_mail_features.py index 0a481c78ecaebd15810e282e29df4f4026019f77..17836ea186cf631acd6e2216b4d7736fb39ebda7 100644 --- a/addons/mail/tests/test_mail_features.py +++ b/addons/mail/tests/test_mail_features.py @@ -449,8 +449,8 @@ class test_mail(TestMail): 'message_post: mail.mail notifications should have been auto-deleted!') # Test: notifications emails: to a and b, c is email only, r is author - # test_emailto = ['Administrator <a@a>', 'Bert Tartopoils <b@b>'] - test_emailto = ['"Followers of -Pigs-" <a@a>', '"Followers of -Pigs-" <b@b>'] + test_emailto = ['"Administrator" <a@a>', '"Bert Tartopoils" <b@b>'] + # test_emailto = ['"Followers of -Pigs-" <a@a>', '"Followers of -Pigs-" <b@b>'] self.assertEqual(len(sent_emails), 2, 'message_post: notification emails wrong number of send emails') self.assertEqual(set([m['email_to'][0] for m in sent_emails]), set(test_emailto), @@ -462,7 +462,7 @@ class test_mail(TestMail): 'message_post: notification email sent to more than one email address instead of a precise partner') self.assertIn(sent_email['email_to'][0], test_emailto, 'message_post: notification email email_to incorrect') - self.assertEqual(sent_email['reply_to'], '"Followers of -Pigs-" <group+pigs@schlouby.fr>', + self.assertEqual(sent_email['reply_to'], '"YourCompany -Pigs-" <group+pigs@schlouby.fr>', 'message_post: notification email reply_to incorrect') self.assertEqual(_subject, sent_email['subject'], 'message_post: notification email subject incorrect') @@ -523,8 +523,8 @@ class test_mail(TestMail): self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg2_id)]), 'mail.mail notifications should have been auto-deleted!') # Test: emails send by server (to a, b, c, d) - # test_emailto = [u'Administrator <a@a>', u'Bert Tartopoils <b@b>', u'Carine Poilvache <c@c>', u'D\xe9d\xe9 Grosbedon <d@d>'] - test_emailto = [u'"Followers of Pigs" <a@a>', u'"Followers of Pigs" <b@b>', u'"Followers of Pigs" <c@c>', u'"Followers of Pigs" <d@d>'] + test_emailto = [u'"Administrator" <a@a>', u'"Bert Tartopoils" <b@b>', u'"Carine Poilvache" <c@c>', u'"D\xe9d\xe9 Grosbedon" <d@d>'] + # test_emailto = [u'"Followers of Pigs" <a@a>', u'"Followers of Pigs" <b@b>', u'"Followers of Pigs" <c@c>', u'"Followers of Pigs" <d@d>'] # self.assertEqual(len(sent_emails), 3, 'sent_email number of sent emails incorrect') for sent_email in sent_emails: self.assertEqual(sent_email['email_from'], 'Raoul Grosbedon <r@r>', diff --git a/addons/mail/tests/test_mail_message.py b/addons/mail/tests/test_mail_message.py index 180661a0707725e1ed057efd4e212c515479166c..d4a7ae75df37a104e7d751653ed0bcc5e03fa77b 100644 --- a/addons/mail/tests/test_mail_message.py +++ b/addons/mail/tests/test_mail_message.py @@ -81,8 +81,7 @@ class TestMailMessage(TestMail): alias_domain = 'schlouby.fr' raoul_from = 'Raoul Grosbedon <raoul@raoul.fr>' raoul_from_alias = 'Raoul Grosbedon <raoul@schlouby.fr>' - raoul_reply = '"Followers of Pigs" <raoul@raoul.fr>' - raoul_reply_alias = '"Followers of Pigs" <group+pigs@schlouby.fr>' + raoul_reply_alias = '"YourCompany Pigs" <group+pigs@schlouby.fr>' # -------------------------------------------------- # Case1: without alias_domain @@ -91,7 +90,7 @@ class TestMailMessage(TestMail): self.registry('ir.config_parameter').unlink(cr, uid, param_ids) # Do: free message; specified values > default values - msg_id = self.mail_message.create(cr, user_raoul_id, {'reply_to': reply_to1, 'email_from': email_from1}) + msg_id = self.mail_message.create(cr, user_raoul_id, {'same_thread': False, 'reply_to': reply_to1, 'email_from': email_from1}) msg = self.mail_message.browse(cr, user_raoul_id, msg_id) # Test: message content self.assertIn('reply_to', msg.message_id, @@ -118,7 +117,7 @@ class TestMailMessage(TestMail): 'mail_message: message_id should contain model') self.assertIn('%s' % self.group_pigs_id, msg.message_id, 'mail_message: message_id should contain res_id') - self.assertEqual(msg.reply_to, raoul_reply, + self.assertEqual(msg.reply_to, raoul_from, 'mail_message: incorrect reply_to: should be Raoul') self.assertEqual(msg.email_from, raoul_from, 'mail_message: incorrect email_from: should be Raoul') @@ -152,7 +151,7 @@ class TestMailMessage(TestMail): msg_id = self.mail_message.create(cr, user_raoul_id, {}) msg = self.mail_message.browse(cr, user_raoul_id, msg_id) # Test: generated reply_to - self.assertEqual(msg.reply_to, 'gateway@schlouby.fr', + self.assertEqual(msg.reply_to, '"YourCompany" <gateway@schlouby.fr>', 'mail_mail: reply_to should equal the catchall email alias') # Do: create a mail_mail diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index e14a8f3dfe5b59578b99e880d06baf61c7cd09b2..0425cd1ee0e93598a76c9072f125ef3a389cc077 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -121,16 +121,12 @@ class mail_compose_message(osv.TransientModel): # mass mode options 'notify': fields.boolean('Notify followers', help='Notify followers of the document (mass post only)'), - 'same_thread': fields.boolean('Replies in the document', - help='Replies to the messages will go into the selected document (mass mail only)'), } - #TODO change same_thread to False in trunk (Require view update) _defaults = { 'composition_mode': 'comment', 'body': lambda self, cr, uid, ctx={}: '', 'subject': lambda self, cr, uid, ctx={}: False, 'partner_ids': lambda self, cr, uid, ctx={}: [], - 'same_thread': True, } def check_access_rule(self, cr, uid, ids, operation, context=None): @@ -251,6 +247,10 @@ class mail_compose_message(osv.TransientModel): # render all template-based value at once if mass_mail_mode and wizard.model: rendered_values = self.render_message_batch(cr, uid, wizard, res_ids, context=context) + # compute alias-based reply-to in batch + reply_to_value = dict.fromkeys(res_ids, None) + if mass_mail_mode and wizard.same_thread: + reply_to_value = self.pool['mail.thread'].message_get_reply_to(cr, uid, res_ids, default=wizard.email_from, context=dict(context, thread_model=wizard.model)) for res_id in res_ids: # static wizard (mail.message) values @@ -277,7 +277,9 @@ class mail_compose_message(osv.TransientModel): mail_values.update(email_dict) if wizard.same_thread: mail_values.pop('reply_to') - elif not mail_values.get('reply_to'): + if reply_to_value.get(res_id): + mail_values['reply_to'] = reply_to_value[res_id] + if not wizard.same_thread and not mail_values.get('reply_to'): mail_values['reply_to'] = mail_values['email_from'] # mail_mail values: body -> body_html, partner_ids -> recipient_ids mail_values['body_html'] = mail_values.get('body', '') diff --git a/addons/project/project.py b/addons/project/project.py index a9c487be244027527926e019cdc15f57078dd251..8f665353325de21c5a48fe09f21e43b889475249 100644 --- a/addons/project/project.py +++ b/addons/project/project.py @@ -1099,8 +1099,10 @@ class task(osv.osv): def message_get_reply_to(self, cr, uid, ids, context=None): """ Override to get the reply_to of the parent project. """ - return [task.project_id.message_get_reply_to()[0] if task.project_id else False - for task in self.browse(cr, uid, ids, context=context)] + tasks = self.browse(cr, SUPERUSER_ID, ids, context=context) + project_ids = set([task.project_id.id for task in tasks if task.project_id]) + aliases = self.pool['project.project'].message_get_reply_to(cr, uid, list(project_ids), context=context) + return dict((task.id, aliases.get(task.project_id and task.project_id.id or 0, False)) for task in tasks) def message_new(self, cr, uid, msg, custom_values=None, context=None): """ Override to updates the document according to the email. """ diff --git a/addons/project_issue/project_issue.py b/addons/project_issue/project_issue.py index 6c06f14d5fa8f3d513f10886c66182f62553f819..9bbcd43ae19b8047becf157c694f9de1d8300b45 100644 --- a/addons/project_issue/project_issue.py +++ b/addons/project_issue/project_issue.py @@ -414,8 +414,10 @@ class project_issue(osv.Model): def message_get_reply_to(self, cr, uid, ids, context=None): """ Override to get the reply_to of the parent project. """ - return [issue.project_id.message_get_reply_to()[0] if issue.project_id else False - for issue in self.browse(cr, uid, ids, context=context)] + issues = self.browse(cr, SUPERUSER_ID, ids, context=context) + project_ids = set([issue.project_id.id for issue in issues if issue.project_id]) + aliases = self.pool['project.project'].message_get_reply_to(cr, uid, list(project_ids), context=context) + return dict((issue.id, aliases.get(issue.project_id and issue.project_id.id or 0, False)) for issue in issues) def message_get_suggested_recipients(self, cr, uid, ids, context=None): recipients = super(project_issue, self).message_get_suggested_recipients(cr, uid, ids, context=context)