[MERGE] mail, mass_mailing, test_*: improve reply-to management
PURPOSE We receive a lot of tickets from people who set a reply-to on their email templates and are surprised that it doesn't work. Indeed this field is used for mass mailing emails and templates but not in regular Chatter. SPECIFICATIONS Update reply-to field placeholder to "Preferred email address when sending via mass mailing options". Update reply-to field helper message to "Preferred email address when sending via mass mailing options. <br> Only used when the answer is not added into the original discussion."" Update the no_auto_thread field label to "Reply to" in composer and introduce a new radio button replacing the checkbox * The original discussion (update) * Another email address (new) Rename fields on mail_thread and wizard: ``no_auto_thread`` should be replaced to ``reply_to_force_new`` to ease understanding and be prefixed by reply_to. Propagate radio keys (update and new) to mass mailing in order to have a coherent naming (was thread and email). This naming is also coherent with gateway naming (message_update and message_new). LINKS Task ID-2117639 COM PR odoo/odoo#40931 ENT PR odoo/enterprise#17941 UPG PR odoo/upgrade#2419 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
{
|
||||
'name': 'Discuss',
|
||||
'version': '1.3',
|
||||
'version': '1.4',
|
||||
'category': 'Productivity/Discuss',
|
||||
'sequence': 145,
|
||||
'summary': 'Chat, mail gateway and private channels',
|
||||
|
||||
@@ -156,9 +156,9 @@ class Message(models.Model):
|
||||
help='Tracked values are stored in a separate model. This field allow to reconstruct '
|
||||
'the tracking and to generate statistics on the model.')
|
||||
# mail gateway
|
||||
no_auto_thread = fields.Boolean(
|
||||
reply_to_force_new = fields.Boolean(
|
||||
'No threading for answers',
|
||||
help='Answers do not go in the original document discussion thread. This has an impact on the generated message-id.')
|
||||
help='If true, answers do not go in the original document discussion thread. Instead, it will check for the reply_to in tracking message-id and redirected accordingly. This has an impact on the generated message-id.')
|
||||
message_id = fields.Char('Message-Id', help='Message unique identifier', index=True, readonly=1, copy=False)
|
||||
reply_to = fields.Char('Reply-To', help='Reply email address. Setting the reply_to bypasses the automatic thread creation.')
|
||||
mail_server_id = fields.Many2one('ir.mail_server', 'Outgoing mail server')
|
||||
@@ -1207,7 +1207,7 @@ class Message(models.Model):
|
||||
|
||||
@api.model
|
||||
def _get_message_id(self, values):
|
||||
if values.get('no_auto_thread', False) is True:
|
||||
if values.get('reply_to_force_new', False) is True:
|
||||
message_id = tools.generate_tracking_message_id('reply_to')
|
||||
elif self.is_thread_message(values):
|
||||
message_id = tools.generate_tracking_message_id('%(res_id)s-%(model)s' % values)
|
||||
|
||||
@@ -43,7 +43,7 @@ class MailTemplate(models.Model):
|
||||
partner_to = fields.Char('To (Partners)',
|
||||
help="Comma-separated ids of recipient partners (placeholders may be used here)")
|
||||
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)")
|
||||
reply_to = fields.Char('Reply To', help="Email address to which replies will be redirected when sending emails in mass; only used when the reply is not logged in the original discussion thread.")
|
||||
# content
|
||||
body_html = fields.Html('Body', translate=True, sanitize=False)
|
||||
attachment_ids = fields.Many2many('ir.attachment', 'email_template_attachment_rel', 'email_template_id',
|
||||
|
||||
@@ -128,7 +128,8 @@ class MockEmail(common.BaseCase):
|
||||
""" Deprecated, remove in 14.4 """
|
||||
return self.gateway_mail_reply_wrecord(template, record, use_in_reply_to=use_in_reply_to)
|
||||
|
||||
def gateway_mail_reply_wrecord(self, template, record, use_in_reply_to=True):
|
||||
def gateway_mail_reply_wrecord(self, template, record, use_in_reply_to=True,
|
||||
target_model=None, target_field=None):
|
||||
""" Simulate a reply through the mail gateway. Usage: giving a record,
|
||||
find an email sent to him and use its message-ID to simulate a reply.
|
||||
|
||||
@@ -148,8 +149,32 @@ class MockEmail(common.BaseCase):
|
||||
subject='Re: %s' % mail_mail.subject,
|
||||
extra=extra,
|
||||
msg_id='<123456.%s.%d@test.example.com>' % (record._name, record.id),
|
||||
target_model=record._name,
|
||||
target_field=record._rec_name,
|
||||
target_model=target_model or record._name,
|
||||
target_field=target_field or record._rec_name,
|
||||
)
|
||||
|
||||
def gateway_mail_reply_wemail(self, template, email_to, use_in_reply_to=True,
|
||||
target_model=None, target_field=None):
|
||||
""" Simulate a reply through the mail gateway. Usage: giving a record,
|
||||
find an email sent to him and use its message-ID to simulate a reply.
|
||||
|
||||
Some noise is added in References just to test some robustness. """
|
||||
sent_mail = self._find_sent_mail_wemail(email_to)
|
||||
|
||||
if use_in_reply_to:
|
||||
extra = 'In-Reply-To:\r\n\t%s\n' % sent_mail['message_id']
|
||||
else:
|
||||
disturbing_other_msg_id = '<123456.654321@another.host.com>'
|
||||
extra = 'References:\r\n\t%s\n\r%s' % (sent_mail['message_id'], disturbing_other_msg_id)
|
||||
|
||||
return self.format_and_process(
|
||||
template,
|
||||
sent_mail['email_to'],
|
||||
sent_mail['reply_to'],
|
||||
subject='Re: %s' % sent_mail['subject'],
|
||||
extra=extra,
|
||||
target_model=target_model,
|
||||
target_field=target_field or 'name',
|
||||
)
|
||||
|
||||
def from_string(self, text):
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<group>
|
||||
<group>
|
||||
<field name="reply_to"/>
|
||||
<field name="no_auto_thread"/>
|
||||
<field name="reply_to_force_new"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="message_id"/>
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
<field name="email_cc" attrs="{'invisible': [('use_default_to', '=', True)]}"
|
||||
placeholder="Comma-separated carbon copy recipients addresses"/>
|
||||
<field name="reply_to"
|
||||
placeholder="Preferred reply address"/>
|
||||
placeholder="Email address to which replies will be redirected when sending emails in mass"/>
|
||||
<field name="scheduled_date" string="Scheduled Send Date"/>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
'default_composition_mode': 'mass_mail',
|
||||
'default_partner_to': '${object.id or \'\'}',
|
||||
'default_use_template': False,
|
||||
'default_no_auto_thread': True,
|
||||
'default_reply_to_force_new': True,
|
||||
}"/>
|
||||
<field name="binding_model_id" ref="base.model_res_partner"/>
|
||||
<field name="binding_view_types">list</field>
|
||||
|
||||
@@ -67,7 +67,7 @@ class Invite(models.TransientModel):
|
||||
'reply_to': email_from,
|
||||
'model': wizard.res_model,
|
||||
'res_id': wizard.res_id,
|
||||
'no_auto_thread': True,
|
||||
'reply_to_force_new': True,
|
||||
'add_sign': True,
|
||||
})
|
||||
partners_data = []
|
||||
|
||||
@@ -67,10 +67,10 @@ class MailComposer(models.TransientModel):
|
||||
result['model'] = self._context.get('active_model')
|
||||
if 'res_id' in fields and 'res_id' not in result:
|
||||
result['res_id'] = self._context.get('active_id')
|
||||
if 'no_auto_thread' in fields and 'no_auto_thread' not in result and result.get('model'):
|
||||
if 'reply_to_mode' in fields and 'reply_to_mode' not in result and result.get('model'):
|
||||
# doesn't support threading
|
||||
if result['model'] not in self.env or not hasattr(self.env[result['model']], 'message_post'):
|
||||
result['no_auto_thread'] = True
|
||||
result['reply_to_mode'] = 'new'
|
||||
|
||||
if 'active_domain' in self._context: # not context.get() because we want to keep global [] domains
|
||||
result['active_domain'] = '%s' % self._context.get('active_domain')
|
||||
@@ -123,10 +123,15 @@ class MailComposer(models.TransientModel):
|
||||
'mail.activity.type', 'Mail Activity Type',
|
||||
index=True, ondelete='set null')
|
||||
# destination
|
||||
reply_to = fields.Char('Reply-To', help='Reply email address. Setting the reply_to bypasses the automatic thread creation.')
|
||||
no_auto_thread = fields.Boolean(
|
||||
'No threading for answers',
|
||||
help='Answers do not go in the original document discussion thread. This has an impact on the generated message-id.')
|
||||
reply_to = fields.Char('Reply To', help='Reply email address. Setting the reply_to bypasses the automatic thread creation.')
|
||||
reply_to_force_new = fields.Boolean(
|
||||
string='Considers answers as new thread',
|
||||
help='Manage answers as new incoming emails instead of replies going to the same thread.')
|
||||
reply_to_mode = fields.Selection([
|
||||
('update', 'Log in the original discussion thread'),
|
||||
('new', 'Redirect to another email address')],
|
||||
string='Replies', compute='_compute_reply_to_mode', inverse='_inverse_reply_to_mode',
|
||||
help="Original Discussion: Answers go in the original document discussion thread. \n Another Email Address: Answers go to the email address mentioned in the tracking message-id instead of original document discussion thread. \n This has an impact on the generated message-id.")
|
||||
is_log = fields.Boolean('Log an Internal Note',
|
||||
help='Whether the message is an internal note (comment mode only)')
|
||||
partner_ids = fields.Many2many(
|
||||
@@ -140,6 +145,15 @@ class MailComposer(models.TransientModel):
|
||||
auto_delete_message = fields.Boolean('Delete Message Copy', help='Do not keep a copy of the email in the document communication history (mass mailing only)')
|
||||
mail_server_id = fields.Many2one('ir.mail_server', 'Outgoing mail server')
|
||||
|
||||
@api.depends('reply_to_force_new')
|
||||
def _compute_reply_to_mode(self):
|
||||
for composer in self:
|
||||
composer.reply_to_mode = 'new' if composer.reply_to_force_new else 'update'
|
||||
|
||||
def _inverse_reply_to_mode(self):
|
||||
for composer in self:
|
||||
composer.reply_to_force_new = composer.reply_to_mode == 'new'
|
||||
|
||||
@api.model
|
||||
def get_record_data(self, values):
|
||||
""" Returns a defaults-like dict with initial values for the composition
|
||||
@@ -275,7 +289,7 @@ class MailComposer(models.TransientModel):
|
||||
rendered_values = self.render_message(res_ids)
|
||||
# compute alias-based reply-to in batch
|
||||
reply_to_value = dict.fromkeys(res_ids, None)
|
||||
if mass_mail_mode and not self.no_auto_thread:
|
||||
if mass_mail_mode and not self.reply_to_force_new:
|
||||
records = self.env[self.model].browse(res_ids)
|
||||
reply_to_value = records._notify_get_reply_to(default=self.email_from)
|
||||
|
||||
@@ -301,7 +315,7 @@ class MailComposer(models.TransientModel):
|
||||
'author_id': self.author_id.id,
|
||||
'email_from': self.email_from,
|
||||
'record_name': self.record_name,
|
||||
'no_auto_thread': self.no_auto_thread,
|
||||
'reply_to_force_new': self.reply_to_force_new,
|
||||
'mail_server_id': self.mail_server_id.id,
|
||||
'mail_activity_type_id': self.mail_activity_type_id.id,
|
||||
}
|
||||
@@ -319,11 +333,11 @@ class MailComposer(models.TransientModel):
|
||||
email_dict = rendered_values[res_id]
|
||||
mail_values['partner_ids'] += email_dict.pop('partner_ids', [])
|
||||
mail_values.update(email_dict)
|
||||
if not self.no_auto_thread:
|
||||
if not self.reply_to_force_new:
|
||||
mail_values.pop('reply_to')
|
||||
if reply_to_value.get(res_id):
|
||||
mail_values['reply_to'] = reply_to_value[res_id]
|
||||
if self.no_auto_thread and not mail_values.get('reply_to'):
|
||||
if self.reply_to_force_new 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', '')
|
||||
|
||||
@@ -62,10 +62,11 @@
|
||||
<field name="notify"
|
||||
attrs="{'invisible':[('composition_mode', '!=', 'mass_post')]}"/>
|
||||
<!-- mass mailing -->
|
||||
<field name="no_auto_thread" attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}"/>
|
||||
<field name="reply_to" placeholder="Email address to redirect replies..."
|
||||
attrs="{'invisible':['|', ('no_auto_thread', '=', False), ('composition_mode', '!=', 'mass_mail')],
|
||||
'required':[('no_auto_thread', '=', True), ('composition_mode', '=', 'mass_mail')]}"/>
|
||||
<field name="reply_to_force_new" invisible="1"/>
|
||||
<field name="reply_to_mode" attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}" widget="radio"/>
|
||||
<field name="reply_to" placeholder="Email address to which replies will be redirected"
|
||||
attrs="{'invisible':['|', ('reply_to_mode', '=', 'update'), ('composition_mode', '!=', 'mass_mail')],
|
||||
'required':[('reply_to_mode', '!=', 'update'), ('composition_mode', '=', 'mass_mail')]}"/>
|
||||
</group>
|
||||
<field name="body" options="{'style-inline': true}"/>
|
||||
<group col="4">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
'name': 'Email Marketing',
|
||||
'summary': 'Design, send and track emails',
|
||||
'description': "",
|
||||
'version': '2.3',
|
||||
'version': '2.4',
|
||||
'sequence': 60,
|
||||
'website': 'https://www.odoo.com/page/mailing',
|
||||
'category': 'Marketing/Email Marketing',
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
<field name="source_id" ref="mass_mailing.utm_source_0"/>
|
||||
<field name="mailing_model_id" ref="base.model_res_partner"/>
|
||||
<field name="mailing_domain" eval="[('parent_id', '=', ref('base.res_partner_4'))]"/>
|
||||
<field name="reply_to_mode">email</field>
|
||||
<field name="reply_to_mode">new</field>
|
||||
<field name="reply_to">Info <info@yourcompany.example.com></field>
|
||||
<field name="body_html" type="html">
|
||||
<div class="o_layout o_default_theme">
|
||||
|
||||
@@ -113,7 +113,7 @@ class MassMailing(models.Model):
|
||||
# mailing options
|
||||
mailing_type = fields.Selection([('mail', 'Email')], string="Mailing Type", default="mail", required=True)
|
||||
reply_to_mode = fields.Selection([
|
||||
('thread', 'Recipient Followers'), ('email', 'Specified Email Address')],
|
||||
('update', 'Recipient Followers'), ('new', 'Specified Email Address')],
|
||||
string='Reply-To Mode', compute='_compute_reply_to_mode',
|
||||
readonly=False, store=True,
|
||||
help='Thread: replies go to target document. Email: replies are routed to a given email.')
|
||||
@@ -252,16 +252,16 @@ class MassMailing(models.Model):
|
||||
def _compute_reply_to_mode(self):
|
||||
for mailing in self:
|
||||
if mailing.mailing_model_real in ['res.partner', 'mailing.contact']:
|
||||
mailing.reply_to_mode = 'email'
|
||||
mailing.reply_to_mode = 'new'
|
||||
else:
|
||||
mailing.reply_to_mode = 'thread'
|
||||
mailing.reply_to_mode = 'update'
|
||||
|
||||
@api.depends('reply_to_mode')
|
||||
def _compute_reply_to(self):
|
||||
for mailing in self:
|
||||
if mailing.reply_to_mode == 'email' and not mailing.reply_to:
|
||||
if mailing.reply_to_mode == 'new' and not mailing.reply_to:
|
||||
mailing.reply_to = self.env.user.email_formatted
|
||||
elif mailing.reply_to_mode == 'thread':
|
||||
elif mailing.reply_to_mode == 'update':
|
||||
mailing.reply_to = False
|
||||
|
||||
@api.depends('mailing_model_name', 'contact_list_ids')
|
||||
@@ -631,11 +631,11 @@ class MassMailing(models.Model):
|
||||
'composition_mode': 'mass_mail',
|
||||
'mass_mailing_id': mailing.id,
|
||||
'mailing_list_ids': [(4, l.id) for l in mailing.contact_list_ids],
|
||||
'no_auto_thread': mailing.reply_to_mode != 'thread',
|
||||
'reply_to_force_new': mailing.reply_to_mode == 'new',
|
||||
'template_id': None,
|
||||
'mail_server_id': mailing.mail_server_id.id,
|
||||
}
|
||||
if mailing.reply_to_mode == 'email':
|
||||
if mailing.reply_to_mode == 'new':
|
||||
composer_values['reply_to'] = mailing.reply_to
|
||||
|
||||
composer = self.env['mail.compose.message'].with_context(active_ids=res_ids).create(composer_values)
|
||||
|
||||
@@ -72,7 +72,7 @@ class TestMassMailValues(MassMailCommon):
|
||||
self.assertEqual(mailing.medium_id, self.env.ref('utm.utm_medium_email'))
|
||||
self.assertEqual(mailing.mailing_model_name, 'res.partner')
|
||||
self.assertEqual(mailing.mailing_model_real, 'res.partner')
|
||||
self.assertEqual(mailing.reply_to_mode, 'email')
|
||||
self.assertEqual(mailing.reply_to_mode, 'new')
|
||||
self.assertEqual(mailing.reply_to, self.user_marketing.email_formatted)
|
||||
# default for partner: remove blacklisted
|
||||
self.assertEqual(literal_eval(mailing.mailing_domain), [('is_blacklisted', '=', False)])
|
||||
@@ -89,7 +89,7 @@ class TestMassMailValues(MassMailCommon):
|
||||
})
|
||||
self.assertEqual(mailing.mailing_model_name, 'mailing.list')
|
||||
self.assertEqual(mailing.mailing_model_real, 'mailing.contact')
|
||||
self.assertEqual(mailing.reply_to_mode, 'email')
|
||||
self.assertEqual(mailing.reply_to_mode, 'new')
|
||||
self.assertEqual(mailing.reply_to, self.email_reply_to)
|
||||
# default for mailing list: depends upon contact_list_ids
|
||||
self.assertEqual(literal_eval(mailing.mailing_domain), [])
|
||||
@@ -104,7 +104,7 @@ class TestMassMailValues(MassMailCommon):
|
||||
})
|
||||
self.assertEqual(mailing.mailing_model_name, 'mail.channel')
|
||||
self.assertEqual(mailing.mailing_model_real, 'mail.channel')
|
||||
self.assertEqual(mailing.reply_to_mode, 'thread')
|
||||
self.assertEqual(mailing.reply_to_mode, 'update')
|
||||
self.assertFalse(mailing.reply_to)
|
||||
|
||||
@users('user_marketing')
|
||||
@@ -213,6 +213,79 @@ class TestMassMailFeatures(MassMailCommon, CronMixinCase):
|
||||
capt.records.ensure_one()
|
||||
self.assertLessEqual(capt.records.call_at, truth)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mailing_deletion(self):
|
||||
""" Test deletion in various use case, depending on reply-to """
|
||||
# 1- Keep archives and reply-to set to 'answer = new thread'
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'TestSource',
|
||||
'subject': 'TestDeletion',
|
||||
'body_html': "<div>Hello {object.name}</div>",
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.list').id,
|
||||
'contact_list_ids': [(6, 0, self.mailing_list_1.ids)],
|
||||
'keep_archives': True,
|
||||
'reply_to_mode': 'new',
|
||||
'reply_to': self.email_reply_to,
|
||||
})
|
||||
self.assertEqual(self.mailing_list_1.contact_ids.message_ids, self.env['mail.message'])
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=True):
|
||||
mailing.action_send_mail()
|
||||
|
||||
self.assertEqual(len(self._mails), 3)
|
||||
self.assertEqual(len(self._new_mails.exists()), 3)
|
||||
self.assertEqual(len(self.mailing_list_1.contact_ids.message_ids), 3)
|
||||
|
||||
# 2- Keep archives and reply-to set to 'answer = update thread'
|
||||
self.mailing_list_1.contact_ids.message_ids.unlink()
|
||||
mailing = mailing.copy()
|
||||
mailing.write({
|
||||
'reply_to_mode': 'update',
|
||||
})
|
||||
self.assertEqual(self.mailing_list_1.contact_ids.message_ids, self.env['mail.message'])
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=True):
|
||||
mailing.action_send_mail()
|
||||
|
||||
self.assertEqual(len(self._mails), 3)
|
||||
self.assertEqual(len(self._new_mails.exists()), 3)
|
||||
self.assertEqual(len(self.mailing_list_1.contact_ids.message_ids), 3)
|
||||
|
||||
# 3- Remove archives and reply-to set to 'answer = new thread'
|
||||
self.mailing_list_1.contact_ids.message_ids.unlink()
|
||||
mailing = mailing.copy()
|
||||
mailing.write({
|
||||
'keep_archives': False,
|
||||
'reply_to_mode': 'new',
|
||||
'reply_to': self.email_reply_to,
|
||||
})
|
||||
self.assertEqual(self.mailing_list_1.contact_ids.message_ids, self.env['mail.message'])
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=True):
|
||||
mailing.action_send_mail()
|
||||
|
||||
self.assertEqual(len(self._mails), 3)
|
||||
self.assertEqual(len(self._new_mails.exists()), 0)
|
||||
self.assertEqual(self.mailing_list_1.contact_ids.message_ids, self.env['mail.message'])
|
||||
|
||||
# 4- Remove archives and reply-to set to 'answer = update thread'
|
||||
# Imply keeping mail.message for gateway reply)
|
||||
self.mailing_list_1.contact_ids.message_ids.unlink()
|
||||
mailing = mailing.copy()
|
||||
mailing.write({
|
||||
'keep_archives': False,
|
||||
'reply_to_mode': 'update',
|
||||
})
|
||||
self.assertEqual(self.mailing_list_1.contact_ids.message_ids, self.env['mail.message'])
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=True):
|
||||
mailing.action_send_mail()
|
||||
|
||||
self.assertEqual(len(self._mails), 3)
|
||||
self.assertEqual(len(self._new_mails.exists()), 0)
|
||||
self.assertEqual(len(self.mailing_list_1.contact_ids.message_ids), 3)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mailing_on_res_partner(self):
|
||||
@@ -262,7 +335,7 @@ Website3: <a id="url3" href="${httpurl}">${httpurl}</a>
|
||||
External1: <a id="url4" href="https://www.example.com/foo/bar?baz=qux">Youpie</a>
|
||||
Email: <a id="url5" href="mailto:test@odoo.com">test@odoo.com</a></div>""",
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.list').id,
|
||||
'reply_to_mode': 'email',
|
||||
'reply_to_mode': 'new',
|
||||
'reply_to': self.email_reply_to,
|
||||
'contact_list_ids': [(6, 0, self.mailing_list_1.ids)],
|
||||
'keep_archives': True,
|
||||
|
||||
@@ -241,14 +241,14 @@
|
||||
}"/>
|
||||
<field name="reply_to"
|
||||
attrs="{
|
||||
'required': [('reply_to_mode', '=', 'email')],
|
||||
'invisible': [('reply_to_mode', '=', 'thread')],
|
||||
'required': [('reply_to_mode', '=', 'new')],
|
||||
'invisible': [('reply_to_mode', '=', 'update')],
|
||||
'readonly': [('state', 'in', ('sending', 'done'))]
|
||||
}"/>
|
||||
<div style="margin-top:-5px">
|
||||
<small class="oe_edit_only text-muted mb-2"
|
||||
style="font-size:74%"
|
||||
attrs="{'invisible': ['|', ('reply_to_mode', '=', 'thread'), ('mailing_model_name', 'in', ['mailing.contact', 'res.partner', 'mailing.list'])],}">
|
||||
attrs="{'invisible': ['|', ('reply_to_mode', '=', 'update'), ('mailing_model_name', 'in', ['mailing.contact', 'res.partner', 'mailing.list'])],}">
|
||||
To track replies, this address must belong to this database.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
@@ -25,20 +25,18 @@ class MailComposeMessage(models.TransientModel):
|
||||
self.env['ir.model'].sudo().search([('model', '=', self.model), ('is_mail_thread', '=', True)], limit=1):
|
||||
mass_mailing = self.mass_mailing_id
|
||||
if not mass_mailing:
|
||||
reply_to_mode = 'email' if self.no_auto_thread else 'thread'
|
||||
reply_to = self.reply_to if self.no_auto_thread else False
|
||||
mass_mailing = self.env['mailing.mailing'].create({
|
||||
'campaign_id': self.campaign_id.id,
|
||||
'name': self.mass_mailing_name,
|
||||
'subject': self.subject,
|
||||
'state': 'done',
|
||||
'reply_to_mode': reply_to_mode,
|
||||
'reply_to': reply_to,
|
||||
'sent_date': fields.Datetime.now(),
|
||||
'body_html': self.body,
|
||||
'mailing_model_id': self.env['ir.model']._get(self.model).id,
|
||||
'mailing_domain': self.active_domain,
|
||||
'attachment_ids': [(6, 0, self.attachment_ids.ids)],
|
||||
'campaign_id': self.campaign_id.id,
|
||||
'name': self.mass_mailing_name,
|
||||
'subject': self.subject,
|
||||
'state': 'done',
|
||||
'reply_to_mode': self.reply_to_mode,
|
||||
'reply_to': self.reply_to if self.reply_to_mode == 'new' else False,
|
||||
'sent_date': fields.Datetime.now(),
|
||||
'body_html': self.body,
|
||||
'mailing_model_id': self.env['ir.model']._get(self.model).id,
|
||||
'mailing_domain': self.active_domain,
|
||||
'attachment_ids': [(6, 0, self.attachment_ids.ids)],
|
||||
})
|
||||
|
||||
# Preprocess res.partners to batch-fetch from db
|
||||
@@ -87,7 +85,7 @@ class MailComposeMessage(models.TransientModel):
|
||||
'mailing_id': mass_mailing.id,
|
||||
'mailing_trace_ids': [(0, 0, trace_vals)],
|
||||
# email-mode: keep original message for routing
|
||||
'notification': mass_mailing.reply_to_mode == 'thread',
|
||||
'notification': mass_mailing.reply_to_mode == 'update',
|
||||
'auto_delete': not mass_mailing.keep_archives,
|
||||
})
|
||||
return res
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<field name="source_id" ref="utm.utm_source_mailing"/>
|
||||
<field name="mailing_model_id" ref="crm.model_crm_lead"/>
|
||||
<field name="mailing_domain">[]</field>
|
||||
<field name="reply_to_mode">email</field>
|
||||
<field name="reply_to_mode">new</field>
|
||||
<field name="reply_to">${object.company_id.email}</field>
|
||||
<field name="body_html" type="html">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="padding-top: 16px; background-color: #F1F1F1; font-family:Verdana, Arial,sans-serif; color: #454748; width: 100%; border-collapse:separate;"><tr><td align="center">
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<field name="source_id" ref="sale.utm_source_sale_order_0"/>
|
||||
<field name="mailing_model_id" ref="sale.model_sale_order"/>
|
||||
<field name="mailing_domain">[]</field>
|
||||
<field name="reply_to_mode">email</field>
|
||||
<field name="reply_to_mode">new</field>
|
||||
<field name="reply_to">${object.company_id.email}</field>
|
||||
<field name="body_html" type="html">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="padding-top: 16px; background-color: #F1F1F1; font-family:Verdana, Arial,sans-serif; color: #454748; width: 100%; border-collapse:separate;"><tr><td align="center">
|
||||
|
||||
@@ -110,7 +110,7 @@ class TestComposerForm(TestMailComposer):
|
||||
'MailComposer: comment mode should have default subject Re: record_name')
|
||||
# record name not displayed currently in view
|
||||
# self.assertEqual(composer_form.record_name, self.test_record.name, 'MailComposer: comment mode should compute record name')
|
||||
self.assertFalse(composer_form.no_auto_thread)
|
||||
self.assertFalse(composer_form.reply_to_force_new)
|
||||
self.assertEqual(composer_form.composition_mode, 'comment')
|
||||
self.assertEqual(composer_form.model, self.test_record._name)
|
||||
|
||||
@@ -172,7 +172,7 @@ class TestComposerForm(TestMailComposer):
|
||||
self.assertFalse(composer_form.subject, 'MailComposer: mass mode should have void default subject if no template')
|
||||
# record name not displayed currently in view
|
||||
# self.assertFalse(composer_form.record_name, 'MailComposer: mass mode should have void record name')
|
||||
self.assertFalse(composer_form.no_auto_thread)
|
||||
self.assertFalse(composer_form.reply_to_force_new)
|
||||
self.assertEqual(composer_form.composition_mode, 'mass_mail')
|
||||
self.assertEqual(composer_form.model, self.test_records._name)
|
||||
|
||||
@@ -186,7 +186,7 @@ class TestComposerForm(TestMailComposer):
|
||||
'MailComposer: mass mode should have template raw body if template')
|
||||
# record name not displayed currently in view
|
||||
# self.assertFalse(composer_form.record_name, 'MailComposer: mass mode should have void record name')
|
||||
self.assertFalse(composer_form.no_auto_thread)
|
||||
self.assertFalse(composer_form.reply_to_force_new)
|
||||
self.assertEqual(composer_form.composition_mode, 'mass_mail')
|
||||
self.assertEqual(composer_form.model, self.test_records._name)
|
||||
|
||||
|
||||
@@ -1043,14 +1043,14 @@ class TestMailgateway(TestMailCommon):
|
||||
first_record = self.env['mail.test.simple'].with_user(self.user_employee).create({'name': 'Replies to Record'})
|
||||
record_msg = first_record.message_post(
|
||||
subject='Discussion',
|
||||
no_auto_thread=False,
|
||||
reply_to_force_new=False,
|
||||
subtype_xmlid='mail.mt_comment',
|
||||
)
|
||||
self.assertEqual(record_msg.reply_to, formataddr(('%s %s' % (self.user_employee.company_id.name, first_record.name), '%s@%s' % ('catchall.test', 'test.com'))))
|
||||
mail_msg = first_record.message_post(
|
||||
subject='Replies to Record',
|
||||
reply_to='groups@test.com',
|
||||
no_auto_thread=True,
|
||||
reply_to_force_new=True,
|
||||
subtype_xmlid='mail.mt_comment',
|
||||
)
|
||||
self.assertEqual(mail_msg.reply_to, 'groups@test.com')
|
||||
|
||||
@@ -169,11 +169,11 @@ class TestMessageValues(TestMailCommon):
|
||||
self.assertEqual(msg.reply_to, formataddr((reply_to_name, reply_to_email)))
|
||||
self.assertEqual(msg.email_from, formataddr((self.user_employee.name, self.user_employee.email)))
|
||||
|
||||
def test_mail_message_values_no_auto_thread(self):
|
||||
def test_mail_message_values_reply_to_force_new(self):
|
||||
msg = self.Message.create({
|
||||
'model': 'mail.test.container',
|
||||
'res_id': self.alias_record.id,
|
||||
'no_auto_thread': True,
|
||||
'reply_to_force_new': True,
|
||||
})
|
||||
self.assertIn('reply_to', msg.message_id.split('@')[0])
|
||||
self.assertNotIn('mail.test.container', msg.message_id.split('@')[0])
|
||||
|
||||
@@ -5,6 +5,6 @@ from . import test_blacklist
|
||||
from . import test_blacklist_behavior
|
||||
from . import test_blacklist_mixin
|
||||
from . import test_link_tracker
|
||||
from . import test_mailing_internals
|
||||
from . import test_mass_mailing
|
||||
from . import test_mailing_test
|
||||
from . import test_mailing
|
||||
from . import test_performance
|
||||
|
||||
@@ -11,6 +11,13 @@ class TestMassMailCommon(MassMailCommon, TestMailCommon):
|
||||
def setUpClass(cls):
|
||||
super(TestMassMailCommon, cls).setUpClass()
|
||||
|
||||
cls.test_alias = cls.env['mail.alias'].create({
|
||||
'alias_name': 'test.alias',
|
||||
'alias_user_id': False,
|
||||
'alias_model_id': cls.env['ir.model']._get('mailing.test.simple').id,
|
||||
'alias_contact': 'everyone'
|
||||
})
|
||||
|
||||
# enforce last update by user_marketing to match _process_mass_mailing_queue
|
||||
# taking last writer as user running a batch
|
||||
cls.mailing_bl = cls.env['mailing.mailing'].with_user(cls.user_marketing).create({
|
||||
@@ -32,6 +39,7 @@ class TestMassMailCommon(MassMailCommon, TestMailCommon):
|
||||
</div>""",
|
||||
'mailing_type': 'mail',
|
||||
'mailing_model_id': cls.env['ir.model']._get('mailing.test.blacklist').id,
|
||||
'reply_to_mode': 'update',
|
||||
})
|
||||
|
||||
@classmethod
|
||||
|
||||
+157
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.test_mass_mailing.data.mail_test_data import MAIL_TEMPLATE
|
||||
from odoo.addons.test_mass_mailing.tests.common import TestMassMailCommon
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import users
|
||||
@@ -14,6 +15,62 @@ class TestMassMailing(TestMassMailCommon):
|
||||
def setUpClass(cls):
|
||||
super(TestMassMailing, cls).setUpClass()
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_thread')
|
||||
def test_mailing_gateway_reply(self):
|
||||
customers = self.env['res.partner']
|
||||
for x in range(0, 3):
|
||||
customers |= self.env['res.partner'].create({
|
||||
'name': 'Customer_%02d' % x,
|
||||
'email': '"Customer_%02d" <customer_%02d@test.example.com' % (x, x),
|
||||
})
|
||||
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'TestName',
|
||||
'subject': 'TestSubject',
|
||||
'body_html': 'Hello ${object.name}',
|
||||
'reply_to_mode': 'new',
|
||||
'reply_to': '%s@%s' % (self.test_alias.alias_name, self.test_alias.alias_domain),
|
||||
'keep_archives': True,
|
||||
'mailing_model_id': self.env['ir.model']._get('res.partner').id,
|
||||
'mailing_domain': '%s' % [('id', 'in', customers.ids)],
|
||||
})
|
||||
mailing.action_put_in_queue()
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, customers[0], use_in_reply_to=True)
|
||||
self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, customers[1], use_in_reply_to=False)
|
||||
|
||||
# customer2 looses headers
|
||||
mail_mail = self._find_mail_mail_wrecord(customers[2])
|
||||
self.format_and_process(
|
||||
MAIL_TEMPLATE,
|
||||
mail_mail.email_to,
|
||||
mail_mail.reply_to,
|
||||
subject='Re: %s' % mail_mail.subject,
|
||||
extra='',
|
||||
msg_id='<123456.%s.%d@test.example.com>' % (customers[2]._name, customers[2].id),
|
||||
target_model=customers[2]._name, target_field=customers[2]._rec_name,
|
||||
)
|
||||
mailing.flush()
|
||||
|
||||
# check traces status
|
||||
traces = self.env['mailing.trace'].search([('model', '=', customers._name), ('res_id', 'in', customers.ids)])
|
||||
self.assertEqual(len(traces), 3)
|
||||
customer0_trace = traces.filtered(lambda t: t.res_id == customers[0].id)
|
||||
self.assertEqual(customer0_trace.state, 'replied')
|
||||
customer1_trace = traces.filtered(lambda t: t.res_id == customers[1].id)
|
||||
self.assertEqual(customer1_trace.state, 'replied')
|
||||
customer2_trace = traces.filtered(lambda t: t.res_id == customers[2].id)
|
||||
self.assertEqual(customer2_trace.state, 'sent')
|
||||
|
||||
# check mailing statistics
|
||||
self.assertEqual(mailing.sent, 3)
|
||||
self.assertEqual(mailing.delivered, 3)
|
||||
self.assertEqual(mailing.opened, 2)
|
||||
self.assertEqual(mailing.replied, 2)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mailing_gateway_update(self):
|
||||
@@ -62,6 +119,106 @@ class TestMassMailing(TestMassMailCommon):
|
||||
self.assertMailingStatistics(mailing, expected=5, delivered=4, sent=5, opened=1, clicked=1, bounced=1)
|
||||
self.assertEqual(recipients[1].message_bounce, 1)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mailing_reply_to_mode_new(self):
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_bl.ids)
|
||||
recipients = self._create_mailing_test_records(model='mailing.test.blacklist', count=5)
|
||||
self.assertEqual(len(recipients), 5)
|
||||
initial_messages = recipients.message_ids
|
||||
mailing.write({
|
||||
'mailing_domain': [('id', 'in', recipients.ids)],
|
||||
'keep_archives': False,
|
||||
'reply_to_mode': 'new',
|
||||
'reply_to': self.test_alias.display_name,
|
||||
})
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=True):
|
||||
mailing.action_send_mail()
|
||||
|
||||
answer_rec = self.gateway_mail_reply_wemail(MAIL_TEMPLATE, recipients[0].email_normalized, target_model=self.test_alias.alias_model_id.model)
|
||||
self.assertTrue(bool(answer_rec))
|
||||
self.assertEqual(answer_rec.name, 'Re: %s' % mailing.subject)
|
||||
self.assertEqual(
|
||||
answer_rec.message_ids.subject, 'Re: %s' % mailing.subject,
|
||||
'Answer should be logged')
|
||||
self.assertEqual(recipients.message_ids, initial_messages)
|
||||
|
||||
self.assertMailingStatistics(mailing, expected=5, delivered=5, sent=5, opened=1, replied=1)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mailing_reply_to_mode_update(self):
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_bl.ids)
|
||||
recipients = self._create_mailing_test_records(model='mailing.test.blacklist', count=5)
|
||||
self.assertEqual(len(recipients), 5)
|
||||
mailing.write({
|
||||
'mailing_domain': [('id', 'in', recipients.ids)],
|
||||
'keep_archives': False,
|
||||
'reply_to_mode': 'update',
|
||||
'reply_to': self.test_alias.display_name,
|
||||
})
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=True):
|
||||
mailing.action_send_mail()
|
||||
|
||||
answer_rec = self.gateway_mail_reply_wemail(MAIL_TEMPLATE, recipients[0].email_normalized, target_model=self.test_alias.alias_model_id.model)
|
||||
self.assertFalse(bool(answer_rec))
|
||||
self.assertEqual(
|
||||
recipients[0].message_ids[1].subject, mailing.subject,
|
||||
'Should have keep a log (to enable thread-based answer)')
|
||||
self.assertEqual(
|
||||
recipients[0].message_ids[0].subject, 'Re: %s' % mailing.subject,
|
||||
'Answer should be logged')
|
||||
|
||||
self.assertMailingStatistics(mailing, expected=5, delivered=5, sent=5, opened=1, replied=1)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_thread')
|
||||
def test_mailing_trace_utm(self):
|
||||
""" Test mailing UTMs are caught on reply"""
|
||||
self._create_mailing_list()
|
||||
self.test_alias.write({
|
||||
'alias_model_id': self.env['ir.model']._get('mailing.test.utm').id
|
||||
})
|
||||
|
||||
source = self.env['utm.source'].create({'name': 'Source test'})
|
||||
medium = self.env['utm.medium'].create({'name': 'Medium test'})
|
||||
campaign = self.env['utm.campaign'].create({'name': 'Campaign test'})
|
||||
subject = 'MassMailingTestUTM'
|
||||
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'UTMTest',
|
||||
'subject': subject,
|
||||
'body_html': '<p>Hello ${object.name}</p>',
|
||||
'reply_to_mode': 'new',
|
||||
'reply_to': '%s@%s' % (self.test_alias.alias_name, self.test_alias.alias_domain),
|
||||
'keep_archives': True,
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.list').id,
|
||||
'contact_list_ids': [(4, self.mailing_list_1.id)],
|
||||
'source_id': source.id,
|
||||
'medium_id': medium.id,
|
||||
'campaign_id': campaign.id
|
||||
})
|
||||
|
||||
mailing.action_put_in_queue()
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing._process_mass_mailing_queue()
|
||||
|
||||
traces = self.env['mailing.trace'].search([('model', '=', self.mailing_list_1.contact_ids._name), ('res_id', 'in', self.mailing_list_1.contact_ids.ids)])
|
||||
self.assertEqual(len(traces), 3)
|
||||
|
||||
# simulate response to mailing
|
||||
self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, self.mailing_list_1.contact_ids[0], use_in_reply_to=True)
|
||||
self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, self.mailing_list_1.contact_ids[1], use_in_reply_to=False)
|
||||
|
||||
mailing_test_utms = self.env['mailing.test.utm'].search([('name', '=', 'Re: %s' % subject)])
|
||||
self.assertEqual(len(mailing_test_utms), 2)
|
||||
for test_utm in mailing_test_utms:
|
||||
self.assertEqual(test_utm.campaign_id, campaign)
|
||||
self.assertEqual(test_utm.source_id, source)
|
||||
self.assertEqual(test_utm.medium_id, medium)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mailing_w_blacklist(self):
|
||||
@@ -1,163 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.test_mass_mailing.tests.common import TestMassMailCommon
|
||||
from odoo.addons.test_mass_mailing.data.mail_test_data import MAIL_TEMPLATE
|
||||
from odoo.tests.common import users
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
class TestMailingInternals(TestMassMailCommon):
|
||||
|
||||
def setUp(self):
|
||||
super(TestMailingInternals, self).setUp()
|
||||
|
||||
self.env['ir.config_parameter'].set_param('mail.bounce.alias', 'bounce.test')
|
||||
self.env['ir.config_parameter'].set_param('mail.catchall.alias', 'catchall.test')
|
||||
self.env['ir.config_parameter'].set_param('mail.catchall.domain', 'test.example.com')
|
||||
|
||||
self.test_alias = self.env['mail.alias'].create({
|
||||
'alias_name': 'test.alias',
|
||||
'alias_user_id': False,
|
||||
'alias_model_id': self.env['ir.model']._get('mailing.test.simple').id,
|
||||
'alias_contact': 'everyone'
|
||||
})
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_render_mixin')
|
||||
def test_mailing_test_button(self):
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'TestButton',
|
||||
'subject': 'Subject ${object.name}',
|
||||
'preview': 'Preview ${object.name}',
|
||||
'state': 'draft',
|
||||
'mailing_type': 'mail',
|
||||
'body_html': '<p>Hello ${object.name}</p>',
|
||||
'mailing_model_id': self.env['ir.model']._get('res.partner').id,
|
||||
})
|
||||
mailing_test = self.env['mailing.mailing.test'].create({
|
||||
'email_to': 'test@test.com',
|
||||
'mass_mailing_id': mailing.id,
|
||||
})
|
||||
|
||||
with self.mock_mail_gateway():
|
||||
mailing_test.send_mail_test()
|
||||
|
||||
# Test if bad jinja in the subject raises an error
|
||||
mailing.write({'subject': 'Subject ${object.name_id.id}'})
|
||||
with self.mock_mail_gateway(), self.assertRaises(Exception):
|
||||
mailing_test.send_mail_test()
|
||||
|
||||
# Test if bad jinja in the body raises an error
|
||||
mailing.write({
|
||||
'subject': 'Subject ${object.name}',
|
||||
'body_html': '<p>Hello ${object.name_id.id}</p>',
|
||||
})
|
||||
with self.mock_mail_gateway(), self.assertRaises(Exception):
|
||||
mailing_test.send_mail_test()
|
||||
|
||||
# Test if bad jinja in the preview raises an error
|
||||
mailing.write({
|
||||
'body_html': '<p>Hello ${object.name}</p>',
|
||||
'preview': 'Preview ${object.name_id.id}',
|
||||
})
|
||||
with self.mock_mail_gateway(), self.assertRaises(Exception):
|
||||
mailing_test.send_mail_test()
|
||||
|
||||
def test_mailing_trace_update(self):
|
||||
customers = self.env['res.partner']
|
||||
for x in range(0, 3):
|
||||
customers |= self.env['res.partner'].create({
|
||||
'name': 'Customer_%02d' % x,
|
||||
'email': '"Customer_%02d" <customer_%02d@test.example.com' % (x, x),
|
||||
})
|
||||
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'TestName',
|
||||
'subject': 'TestSubject',
|
||||
'body_html': 'Hello ${object.name}',
|
||||
'reply_to_mode': 'email',
|
||||
'reply_to': '%s@%s' % (self.test_alias.alias_name, self.test_alias.alias_domain),
|
||||
'keep_archives': True,
|
||||
'mailing_model_id': self.env['ir.model']._get('res.partner').id,
|
||||
'mailing_domain': '%s' % [('id', 'in', customers.ids)],
|
||||
})
|
||||
mailing.action_put_in_queue()
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, customers[0], use_in_reply_to=True)
|
||||
self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, customers[1], use_in_reply_to=False)
|
||||
|
||||
# customer2 looses headers
|
||||
mail_mail = self._find_mail_mail_wrecord(customers[2])
|
||||
self.format_and_process(
|
||||
MAIL_TEMPLATE,
|
||||
mail_mail.email_to,
|
||||
mail_mail.reply_to,
|
||||
subject='Re: %s' % mail_mail.subject,
|
||||
extra='',
|
||||
msg_id='<123456.%s.%d@test.example.com>' % (customers[2]._name, customers[2].id),
|
||||
target_model=customers[2]._name, target_field=customers[2]._rec_name,
|
||||
)
|
||||
mailing.flush()
|
||||
|
||||
# check traces status
|
||||
traces = self.env['mailing.trace'].search([('model', '=', customers._name), ('res_id', 'in', customers.ids)])
|
||||
self.assertEqual(len(traces), 3)
|
||||
customer0_trace = traces.filtered(lambda t: t.res_id == customers[0].id)
|
||||
self.assertEqual(customer0_trace.state, 'replied')
|
||||
customer1_trace = traces.filtered(lambda t: t.res_id == customers[1].id)
|
||||
self.assertEqual(customer1_trace.state, 'replied')
|
||||
customer2_trace = traces.filtered(lambda t: t.res_id == customers[2].id)
|
||||
self.assertEqual(customer2_trace.state, 'sent')
|
||||
|
||||
# check mailing statistics
|
||||
self.assertEqual(mailing.sent, 3)
|
||||
self.assertEqual(mailing.delivered, 3)
|
||||
self.assertEqual(mailing.opened, 2)
|
||||
self.assertEqual(mailing.replied, 2)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mailing_trace_utm(self):
|
||||
""" Test mailing UTMs are caught on reply"""
|
||||
self._create_mailing_list()
|
||||
self.test_alias.write({
|
||||
'alias_model_id': self.env['ir.model']._get('mailing.test.utm').id
|
||||
})
|
||||
|
||||
source = self.env['utm.source'].create({'name': 'Source test'})
|
||||
medium = self.env['utm.medium'].create({'name': 'Medium test'})
|
||||
campaign = self.env['utm.campaign'].create({'name': 'Campaign test'})
|
||||
subject = 'MassMailingTestUTM'
|
||||
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'UTMTest',
|
||||
'subject': subject,
|
||||
'body_html': '<p>Hello ${object.name}</p>',
|
||||
'reply_to_mode': 'email',
|
||||
'reply_to': '%s@%s' % (self.test_alias.alias_name, self.test_alias.alias_domain),
|
||||
'keep_archives': True,
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.list').id,
|
||||
'contact_list_ids': [(4, self.mailing_list_1.id)],
|
||||
'source_id': source.id,
|
||||
'medium_id': medium.id,
|
||||
'campaign_id': campaign.id
|
||||
})
|
||||
|
||||
mailing.action_put_in_queue()
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing._process_mass_mailing_queue()
|
||||
|
||||
traces = self.env['mailing.trace'].search([('model', '=', self.mailing_list_1.contact_ids._name), ('res_id', 'in', self.mailing_list_1.contact_ids.ids)])
|
||||
self.assertEqual(len(traces), 3)
|
||||
|
||||
# simulate response to mailing
|
||||
self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, self.mailing_list_1.contact_ids[0], use_in_reply_to=True)
|
||||
self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, self.mailing_list_1.contact_ids[1], use_in_reply_to=False)
|
||||
|
||||
mailing_test_utms = self.env['mailing.test.utm'].search([('name', '=', 'Re: %s' % subject)])
|
||||
self.assertEqual(len(mailing_test_utms), 2)
|
||||
for test_utm in mailing_test_utms:
|
||||
self.assertEqual(test_utm.campaign_id, campaign)
|
||||
self.assertEqual(test_utm.source_id, source)
|
||||
self.assertEqual(test_utm.medium_id, medium)
|
||||
@@ -0,0 +1,50 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.test_mass_mailing.tests.common import TestMassMailCommon
|
||||
from odoo.tests.common import users
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
class TestMailingTest(TestMassMailCommon):
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_render_mixin')
|
||||
def test_mailing_test_button(self):
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'TestButton',
|
||||
'subject': 'Subject ${object.name}',
|
||||
'preview': 'Preview ${object.name}',
|
||||
'state': 'draft',
|
||||
'mailing_type': 'mail',
|
||||
'body_html': '<p>Hello ${object.name}</p>',
|
||||
'mailing_model_id': self.env['ir.model']._get('res.partner').id,
|
||||
})
|
||||
mailing_test = self.env['mailing.mailing.test'].create({
|
||||
'email_to': 'test@test.com',
|
||||
'mass_mailing_id': mailing.id,
|
||||
})
|
||||
|
||||
with self.mock_mail_gateway():
|
||||
mailing_test.send_mail_test()
|
||||
|
||||
# Test if bad jinja in the subject raises an error
|
||||
mailing.write({'subject': 'Subject ${object.name_id.id}'})
|
||||
with self.mock_mail_gateway(), self.assertRaises(Exception):
|
||||
mailing_test.send_mail_test()
|
||||
|
||||
# Test if bad jinja in the body raises an error
|
||||
mailing.write({
|
||||
'subject': 'Subject ${object.name}',
|
||||
'body_html': '<p>Hello ${object.name_id.id}</p>',
|
||||
})
|
||||
with self.mock_mail_gateway(), self.assertRaises(Exception):
|
||||
mailing_test.send_mail_test()
|
||||
|
||||
# Test if bad jinja in the preview raises an error
|
||||
mailing.write({
|
||||
'body_html': '<p>Hello ${object.name}</p>',
|
||||
'preview': 'Preview ${object.name_id.id}',
|
||||
})
|
||||
with self.mock_mail_gateway(), self.assertRaises(Exception):
|
||||
mailing_test.send_mail_test()
|
||||
@@ -45,7 +45,7 @@ class TestMassMailPerformance(TestMassMailPerformanceBase):
|
||||
'name': 'Test',
|
||||
'subject': 'Test',
|
||||
'body_html': '<p>Hello <a role="button" href="https://www.example.com/foo/bar?baz=qux">quux</a><a role="button" href="/unsubscribe_from_list">Unsubscribe</a></p>',
|
||||
'reply_to_mode': 'email',
|
||||
'reply_to_mode': 'new',
|
||||
'mailing_model_id': self.ref('test_mass_mailing.model_mailing_performance'),
|
||||
'mailing_domain': [('id', 'in', self.mm_recs.ids)],
|
||||
})
|
||||
@@ -85,7 +85,7 @@ class TestMassMailBlPerformance(TestMassMailPerformanceBase):
|
||||
'name': 'Test',
|
||||
'subject': 'Test',
|
||||
'body_html': '<p>Hello <a role="button" href="https://www.example.com/foo/bar?baz=qux">quux</a><a role="button" href="/unsubscribe_from_list">Unsubscribe</a></p>',
|
||||
'reply_to_mode': 'email',
|
||||
'reply_to_mode': 'new',
|
||||
'mailing_model_id': self.ref('test_mass_mailing.model_mailing_performance_blacklist'),
|
||||
'mailing_domain': [('id', 'in', self.mm_recs.ids)],
|
||||
})
|
||||
|
||||
@@ -223,7 +223,6 @@ class WebsiteForm(http.Controller):
|
||||
'body': nl2br(custom_content),
|
||||
'model': model_name,
|
||||
'message_type': 'comment',
|
||||
'no_auto_thread': False,
|
||||
'res_id': record.id,
|
||||
}
|
||||
mail_id = request.env['mail.message'].with_user(SUPERUSER_ID).create(values)
|
||||
@@ -258,7 +257,6 @@ class WebsiteForm(http.Controller):
|
||||
'body': _('<p>Attached files : </p>'),
|
||||
'model': model_name,
|
||||
'message_type': 'comment',
|
||||
'no_auto_thread': False,
|
||||
'res_id': id_record,
|
||||
'attachment_ids': [(6, 0, orphan_attachment_ids)],
|
||||
'subtype_id': request.env['ir.model.data'].xmlid_to_res_id('mail.mt_comment'),
|
||||
|
||||
@@ -109,7 +109,6 @@ class WebsiteSaleForm(WebsiteForm):
|
||||
'body': nl2br(data['custom']),
|
||||
'model': 'sale.order',
|
||||
'message_type': 'comment',
|
||||
'no_auto_thread': False,
|
||||
'res_id': order.id,
|
||||
}
|
||||
request.env['mail.message'].with_user(SUPERUSER_ID).create(values)
|
||||
|
||||
Reference in New Issue
Block a user