Merge pull request #683 from odoo-dev/master-ref-reply_to-tde

[REF] mail, crm, hr_recruitment, project, project_issue: simplified repl.y-to of notification emails (now displaying 'YourCompany -docname- ' instead of 'Followers of ...'; added same_thread directly on the mail.message model to ease the reply_to and message-id computation; factorized a bit the reply-to generation for a batch of records; udpated tests accordingly.
This commit is contained in:
Thibault Delavallée 2014-06-23 12:11:07 +02:00
commit 5739f3a2f9
11 changed files with 90 additions and 68 deletions

View File

@ -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)

View File

@ -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')

View File

@ -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):

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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>',

View File

@ -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

View File

@ -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', '')

View File

@ -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. """

View File

@ -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)