[IMP] mail: support 'scheduled_date' from template on composer

RATIONALE

Improve usage of composer in comment or email mode: support batch-posting in
comment, support more configuration from templates, improve global model.

SPECIFICATIONS

Purpose of this task is to correctly support scheduled_date from template on
both comment and mass mode in the composer.

It is currently mainly supported at template level, when using a template
to directly send emails. In this commit we add a field on composer that
takes the value from the template, and propagate it to the mail or messages
created when validating it.

As most template fields it can contain inline template code to be rendered
dynamically on target records, hence using a char field. Its rendering
is done using template that calls _parse_scheduled_datetime. It allows
to have an UTC and timezone agnostic value.

SCHEDULED_DATE SUPPORT

When posting a comment, scheduled posts uses the 'mail.message.schedule'
mechanism that creates the message but send notifications later.

When sending a mailing, emails have a scheduled_date set. As the 'send'
method does not check for scheduled_date (only the cron queue) we have
to filter emails scheduled in the future before calling the 'send'
method.

Task-2993872 (Mail: Support scheduled date in all composer flows)

Part-of: odoo/odoo#99482
This commit is contained in:
Thibault Delavallée
2023-01-17 20:58:40 +01:00
parent f652f6a75c
commit fbcc1bf4c0
4 changed files with 164 additions and 37 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
{
'name': 'Discuss',
'version': '1.11',
'version': '1.12',
'category': 'Productivity/Discuss',
'sequence': 145,
'summary': 'Chat, mail gateway and private channels',
+25 -1
View File
@@ -3,6 +3,7 @@
import ast
import base64
import datetime
import logging
from odoo import _, api, fields, models, tools, Command
@@ -175,6 +176,11 @@ class MailComposer(models.TransientModel):
help='This option permanently removes any track of email after it\'s been sent, including from the Technical menu in the Settings, in order to preserve storage space of your Odoo database.')
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')
scheduled_date = fields.Char(
'Scheduled Date',
help="In comment mode: if set, postpone notifications sending. "
"In mass mail mode: if sent, send emails after that date. "
"This date is considered as being in UTC timezone.")
@api.constrains('res_ids')
def _check_res_ids(self):
@@ -252,6 +258,7 @@ class MailComposer(models.TransientModel):
(field, template[field])
for field in ('email_from',
'reply_to',
'scheduled_date',
'subject',
)
if template[field]
@@ -278,6 +285,7 @@ class MailComposer(models.TransientModel):
'partner_ids',
'reply_to',
'report_template',
'scheduled_date',
'subject',
)
)[template_res_ids[0]]
@@ -311,6 +319,7 @@ class MailComposer(models.TransientModel):
'partner_ids',
'reply_to',
'res_ids',
'scheduled_date',
'subject',
])
values = dict(
@@ -321,6 +330,7 @@ class MailComposer(models.TransientModel):
'mail_server_id',
'partner_ids',
'reply_to',
'scheduled_date',
'subject',
) if key in default_values)
@@ -479,7 +489,17 @@ class MailComposer(models.TransientModel):
records = self.env[self.model].browse(res_ids_iter) if self.model and hasattr(self.env[self.model], 'message_post') else False
if records:
records._message_mail_after_hook(iter_mails_sudo)
iter_mails_sudo.send(auto_commit=auto_commit)
# as 'send' does not filter out scheduled mails (only 'process_email_queue'
# does) we need to do it manually
iter_mails_sudo_tosend = iter_mails_sudo.filtered(
lambda mail: (
not mail.scheduled_date or
mail.scheduled_date <= datetime.datetime.utcnow()
)
)
if iter_mails_sudo_tosend:
iter_mails_sudo_tosend.send(auto_commit=auto_commit)
return mails_sudo
@@ -547,6 +567,7 @@ class MailComposer(models.TransientModel):
STA - 'record_name', (to check, single post or False in email)
DYN - 'reply_to',
STA - 'reply_to_force_new',
DYN - 'scheduled_date',
DYN - 'subject',
STA - 'subtype_id',
@@ -664,6 +685,7 @@ class MailComposer(models.TransientModel):
res_id: {
'body': bodies[res_id], # should be void
'email_from': emails_from[res_id],
'scheduled_date': False,
'subject': subjects[res_id],
# some fields are specific to mail or message
**(
@@ -688,6 +710,7 @@ class MailComposer(models.TransientModel):
'mail_server_id',
'partner_ids',
'report_template',
'scheduled_date',
)
)
for res_id in res_ids:
@@ -797,6 +820,7 @@ class MailComposer(models.TransientModel):
'body': self.body or '',
'email_from': self.email_from,
'partner_ids': self.partner_ids.ids,
'scheduled_date': self.scheduled_date,
'subject': self.subject or '',
}
for res_id in res_ids
@@ -25,6 +25,7 @@
<field name="res_domain" invisible="1"/>
<field name="res_domain_user_id" invisible="1"/>
<field name="res_ids" invisible="1"/>
<field name="scheduled_date" invisible="1"/>
<field name="subtype_id" invisible="1"/>
<!-- visible wizard -->
<field name="email_from"
+137 -35
View File
@@ -2,12 +2,17 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from ast import literal_eval
from datetime import timedelta
from freezegun import freeze_time
from itertools import product
from unittest.mock import DEFAULT, patch
from odoo.addons.base.tests.test_ir_cron import CronMixinCase
from odoo.addons.mail.tests.common import mail_new_test_user
from odoo.addons.mail.wizard.mail_compose_message import MailComposer
from odoo.addons.test_mail.models.test_mail_models import MailTestTicket
from odoo.addons.test_mail.tests.common import TestMailCommon, TestRecipients
from odoo.fields import Datetime as FieldDatetime
from odoo.exceptions import AccessError
from odoo.tests import tagged
from odoo.tests.common import users, Form
@@ -21,6 +26,8 @@ class TestMailComposer(TestMailCommon, TestRecipients):
@classmethod
def setUpClass(cls):
super(TestMailComposer, cls).setUpClass()
cls.reference_now = FieldDatetime.from_string('2022-12-24 12:00:00')
cls.env.cr._now = cls.reference_now # force create_date to check schedulers
# ensure employee can create partners, necessary for templates
cls.user_employee.write({
@@ -75,6 +82,7 @@ class TestMailComposer(TestMailCommon, TestRecipients):
'mail_server_id': cls.mail_server_domain.id,
'model_id': cls.env['ir.model']._get('mail.test.ticket').id,
'reply_to': '{{ ctx.get("custom_reply_to") or "info@test.example.com" }}',
'scheduled_date': '{{ (object.create_date or datetime.datetime(2022, 12, 26, 18, 0, 0)) + datetime.timedelta(days=2) }}',
})
def _get_web_context(self, records, add_web=True, **values):
@@ -122,6 +130,7 @@ class TestComposerForm(TestMailComposer):
self.assertEqual(composer_form.record_name, self.test_record.name, 'MailComposer: comment mode should compute record name')
self.assertFalse(composer_form.reply_to)
self.assertFalse(composer_form.reply_to_force_new)
self.assertFalse(composer_form.scheduled_date)
self.assertEqual(literal_eval(composer_form.res_ids), self.test_record.ids)
self.assertEqual(composer_form.subject, self.test_record._message_compute_subject())
self.assertIn(f'Ticket for {self.test_record.name}', composer_form.subject,
@@ -200,6 +209,7 @@ class TestComposerForm(TestMailComposer):
self.assertEqual(composer_form.reply_to, 'info@test.example.com')
self.assertFalse(composer_form.reply_to_force_new)
self.assertEqual(literal_eval(composer_form.res_ids), self.test_record.ids)
self.assertEqual(composer_form.scheduled_date, FieldDatetime.to_string(self.reference_now + timedelta(days=2)))
self.assertEqual(composer_form.subject, 'TemplateSubject %s' % self.test_record.name)
self.assertEqual(composer_form.subtype_id, self.env.ref('mail.mt_comment'))
@@ -229,6 +239,7 @@ class TestComposerForm(TestMailComposer):
self.assertEqual(composer_form.reply_to, self.template.reply_to)
self.assertFalse(composer_form.reply_to_force_new)
self.assertEqual(literal_eval(composer_form.res_ids), self.test_records.ids)
self.assertEqual(composer_form.scheduled_date, self.template.scheduled_date)
self.assertEqual(composer_form.subject, self.template.subject,
'MailComposer: comment in batch mode should have template raw subject if template')
@@ -256,6 +267,9 @@ class TestComposerForm(TestMailComposer):
self.assertEqual(composer_form.reply_to, 'info@test.example.com')
self.assertFalse(composer_form.reply_to_force_new)
self.assertFalse(composer_form.res_ids)
self.assertEqual(composer_form.scheduled_date,
'2022-12-28 18:00:00',
'No record but rendered, see expression in template')
self.assertEqual(composer_form.subject, 'TemplateSubject ')
self.assertEqual(composer_form.subtype_id, self.env.ref('mail.mt_comment'))
@@ -277,6 +291,7 @@ class TestComposerForm(TestMailComposer):
self.assertFalse(composer_form.reply_to)
self.assertFalse(composer_form.reply_to_force_new)
self.assertEqual(sorted(literal_eval(composer_form.res_ids)), sorted(self.test_records.ids))
self.assertFalse(composer_form.scheduled_date)
self.assertFalse(composer_form.subject, 'MailComposer: mass mode should have void default subject if no template')
self.assertEqual(composer_form.subtype_id, self.env.ref('mail.mt_comment'))
@@ -301,6 +316,7 @@ class TestComposerForm(TestMailComposer):
self.assertEqual(composer_form.reply_to, self.template.reply_to)
self.assertFalse(composer_form.reply_to_force_new)
self.assertEqual(sorted(literal_eval(composer_form.res_ids)), sorted(self.test_records.ids))
self.assertEqual(composer_form.scheduled_date, self.template.scheduled_date)
self.assertEqual(composer_form.subject, self.template.subject,
'MailComposer: mass mode should have template raw subject if template')
self.assertEqual(composer_form.subtype_id, self.env.ref('mail.mt_comment'))
@@ -330,6 +346,7 @@ class TestComposerForm(TestMailComposer):
self.assertEqual(composer_form.reply_to, self.template.reply_to)
self.assertFalse(composer_form.reply_to_force_new)
self.assertFalse(composer_form.res_ids)
self.assertEqual(composer_form.scheduled_date, self.template.scheduled_date)
self.assertEqual(composer_form.subject, self.template.subject,
'MailComposer: mass mode should have template raw subject if template')
self.assertEqual(composer_form.subtype_id, self.env.ref('mail.mt_comment'))
@@ -570,11 +587,13 @@ class TestComposerInternals(TestMailComposer):
@users('employee')
def test_mail_composer_content(self):
""" Test content management (body, mail_server_id, record_name, subject)
in both comment and mass mailing mode. Template update is also tested. """
""" Test content management (body, mail_server_id, record_name, scheduled_date,
subject) in both comment and mass mailing mode. Template update is also
tested. """
template_void = self.template.copy(default={
'body_html': False,
'mail_server_id': False,
'scheduled_date': False,
'subject': False,
})
@@ -591,6 +610,7 @@ class TestComposerInternals(TestMailComposer):
composer = self.env['mail.compose.message'].with_context(ctx).create({
'body': '<p>Test Body <t t-out="record.name>/></p>',
'mail_server_id': self.mail_server_global.id,
'scheduled_date': '{{ datetime.datetime(2023, 1, 10, 10, 0, 0) }}',
'subject': 'My amazing subject for {{ record.name }}',
})
@@ -601,6 +621,7 @@ class TestComposerInternals(TestMailComposer):
self.assertEqual(composer.record_name, self.test_record.name)
else:
self.assertFalse(composer.record_name)
self.assertEqual(composer.scheduled_date, '{{ datetime.datetime(2023, 1, 10, 10, 0, 0) }}')
self.assertEqual(composer.subject, 'My amazing subject for {{ record.name }}')
# changing template should update its content
@@ -613,11 +634,14 @@ class TestComposerInternals(TestMailComposer):
self.assertEqual(composer.body, f'<p>TemplateBody {self.test_record.name}</p>')
self.assertEqual(composer.mail_server_id, self.template.mail_server_id)
self.assertEqual(composer.record_name, self.test_record.name)
self.assertEqual(FieldDatetime.from_string(composer.scheduled_date),
self.reference_now + timedelta(days=2))
self.assertEqual(composer.subject, f'TemplateSubject {self.test_record.name}')
else:
self.assertEqual(composer.body, self.template.body_html)
self.assertEqual(composer.mail_server_id, self.template.mail_server_id)
self.assertFalse(composer.record_name)
self.assertEqual(composer.scheduled_date, self.template.scheduled_date)
self.assertEqual(composer.subject, self.template.subject)
# manual values is kept over template
@@ -625,15 +649,17 @@ class TestComposerInternals(TestMailComposer):
'body': '<p>Back to my amazing body <t t-out="record.name>/></p>',
'mail_server_id': self.mail_server_global.id,
'record_name': 'Manual update',
'scheduled_date': '{{ datetime.datetime(2023, 1, 10, 10, 0, 0) }}',
'subject': 'Back to my amazing subject for {{ record.name }}',
})
self.assertEqual(composer.body, '<p>Back to my amazing body <t t-out="record.name>/></p>')
self.assertEqual(composer.mail_server_id, self.mail_server_global)
self.assertEqual(composer.record_name, 'Manual update')
self.assertEqual(composer.scheduled_date, '{{ datetime.datetime(2023, 1, 10, 10, 0, 0) }}')
self.assertEqual(composer.subject, 'Back to my amazing subject for {{ record.name }}')
# update with template with void values: void value is not forced in
# rendering mode as well as when copying template values
# rendering mode as well as in raw mode
composer.write({'template_id': template_void.id})
# currently onchange necessary
composer._onchange_template_id_wrapper()
@@ -642,15 +668,16 @@ class TestComposerInternals(TestMailComposer):
self.assertEqual(composer.body, '<p>Back to my amazing body <t t-out="record.name>/></p>')
self.assertEqual(composer.mail_server_id, self.mail_server_global)
self.assertEqual(composer.record_name, 'Manual update')
self.assertEqual(composer.scheduled_date, '{{ datetime.datetime(2023, 1, 10, 10, 0, 0) }}')
self.assertEqual(composer.subject, 'Back to my amazing subject for {{ record.name }}')
else:
self.assertEqual(composer.body, '<p>Back to my amazing body <t t-out="record.name>/></p>')
self.assertEqual(composer.mail_server_id, self.mail_server_global)
self.assertEqual(composer.record_name, 'Manual update')
self.assertEqual(composer.scheduled_date, '{{ datetime.datetime(2023, 1, 10, 10, 0, 0) }}')
self.assertEqual(composer.subject, 'Back to my amazing subject for {{ record.name }}')
# reset template should reset values
composer.write({'body': '<p>Back to my amazing body <t t-out="record.name>/></p>'})
composer.write({'template_id': False})
# currently onchange necessary
composer._onchange_template_id_wrapper()
@@ -666,6 +693,9 @@ class TestComposerInternals(TestMailComposer):
# self.assertEqual(composer.record_name, self.test_record.name)
self.assertEqual(composer.record_name, 'Manual update',
'TODO: Reset not called')
# self.assertFalse(composer.scheduled_date)
self.assertEqual(composer.scheduled_date, '{{ datetime.datetime(2023, 1, 10, 10, 0, 0) }}',
'TODO: Values are kept (should be reset ?)')
self.assertEqual(composer.subject, self.test_record._message_compute_subject())
self.assertIn(f'Ticket for {self.test_record.name}', composer.subject,
'Check effective content')
@@ -677,6 +707,9 @@ class TestComposerInternals(TestMailComposer):
# self.assertFalse(composer.record_name)
self.assertEqual(composer.record_name, 'Manual update',
'TODO: Reset not called')
# self.assertFalse(composer.scheduled_date)
self.assertEqual(composer.scheduled_date, '{{ datetime.datetime(2023, 1, 10, 10, 0, 0) }}',
'TODO: Values are kept (should be reset ?)')
# self.assertFalse(composer.subject)
self.assertEqual(composer.subject, 'Back to my amazing subject for {{ record.name }}',
'TODO: Values are kept (should be reset ?)')
@@ -694,11 +727,13 @@ class TestComposerInternals(TestMailComposer):
self.assertEqual(composer.body, f'<p>TemplateBody {self.test_record.name}</p>')
self.assertEqual(composer.mail_server_id, self.template.mail_server_id)
self.assertEqual(composer.record_name, self.test_record.name)
self.assertEqual(FieldDatetime.from_string(composer.scheduled_date), self.reference_now + timedelta(days=2))
self.assertEqual(composer.subject, f'TemplateSubject {self.test_record.name}')
else:
self.assertEqual(composer.body, self.template.body_html)
self.assertEqual(composer.mail_server_id, self.template.mail_server_id)
self.assertFalse(composer.record_name)
self.assertEqual(composer.scheduled_date, self.template.scheduled_date)
self.assertEqual(composer.subject, self.template.subject)
# 3. check at create
@@ -714,11 +749,13 @@ class TestComposerInternals(TestMailComposer):
self.assertEqual(composer.body, f'<p>TemplateBody {self.test_record.name}</p>')
self.assertEqual(composer.mail_server_id, self.template.mail_server_id)
self.assertEqual(composer.record_name, self.test_record.name)
self.assertEqual(FieldDatetime.from_string(composer.scheduled_date), self.reference_now + timedelta(days=2))
self.assertEqual(composer.subject, f'TemplateSubject {self.test_record.name}')
else:
self.assertEqual(composer.body, self.template.body_html)
self.assertEqual(composer.mail_server_id, self.template.mail_server_id)
self.assertFalse(composer.record_name)
self.assertEqual(composer.scheduled_date, self.template.scheduled_date)
self.assertEqual(composer.subject, self.template.subject)
# 4. template + user input
@@ -727,6 +764,7 @@ class TestComposerInternals(TestMailComposer):
'body': '<p>Test Body</p>',
'mail_server_id': False,
'record_name': 'CustomName',
'scheduled_date': '{{ datetime.datetime(2023, 1, 10, 10, 0, 0) }}',
'subject': 'My amazing subject',
})
@@ -734,6 +772,7 @@ class TestComposerInternals(TestMailComposer):
self.assertEqual(composer.body, '<p>Test Body</p>')
self.assertEqual(composer.mail_server_id.id, False)
self.assertEqual(composer.record_name, 'CustomName')
self.assertEqual(composer.scheduled_date, '{{ datetime.datetime(2023, 1, 10, 10, 0, 0) }}')
self.assertEqual(composer.subject, 'My amazing subject')
@users('employee')
@@ -1015,7 +1054,7 @@ class TestComposerInternals(TestMailComposer):
@tagged('mail_composer')
class TestComposerResultsComment(TestMailComposer):
class TestComposerResultsComment(TestMailComposer, CronMixinCase):
""" Test global output of composer used in comment mode. Test notably
notification and emails generated during this process. """
@@ -1175,14 +1214,24 @@ class TestComposerResultsComment(TestMailComposer):
self.assertEqual(message.partner_ids, self.partner_1 | self.partner_2)
@users('employee')
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail', 'odoo.addons.mail.models.mail_message_schedule')
def test_mail_composer_wtpl_complete(self):
""" Test a posting process using a complex template, holding several
additional recipients and attachments. It is done in monorecord and
in batch since this is now supported.
This tests notifies: 2 new email_to (+ 1 duplicated), 1 email_cc,
test_records followers and partner_admin added in partner_to."""
test_records followers and partner_admin added in partner_to.
Global notification
* monorecord: send notifications right away (force_send=True)
* multirecord: delay notification sending (force_send=False)
Use cases
* scheduled_date: creates mail.message.schedule (no email sent), then
scheduling send notifications with notification parameters kept
* otherwise: global behavior
"""
attachment_data = self._generate_attachments_data(2, self.template._name, self.template.id)
email_to_1 = 'test.to.1@test.example.com'
email_to_2 = 'test.to.2@test.example.com'
@@ -1200,8 +1249,14 @@ class TestComposerResultsComment(TestMailComposer):
attachs = self.env['ir.attachment'].search([('name', 'in', [a['name'] for a in attachment_data])])
self.assertEqual(len(attachs), 2)
for test_records, batch in ((self.test_record, False), (self.test_records, True)):
with self.subTest(test_records=test_records, batch=batch):
for batch, scheduled_date in product(
(False, True),
(False, '{{ (object.create_date or datetime.datetime(2022, 12, 26, 18, 0, 0)) + datetime.timedelta(days=2) }}')
):
with self.subTest(batch=batch, scheduled_date=scheduled_date):
self.template.write({'scheduled_date': scheduled_date})
test_records = self.test_records if batch else self.test_record
# ensure initial data
self.assertEqual(len(test_records.customer_id), len(test_records))
self.assertEqual(test_records.user_id, self.user_employee_2)
@@ -1211,37 +1266,72 @@ class TestComposerResultsComment(TestMailComposer):
composer_form = Form(self.env['mail.compose.message'].with_context(
self._get_web_context(test_records, add_web=True,
default_composition_mode='comment',
default_template_id=self.template.id)
default_template_id=self.template.id,
# avoid successive tests issues with followers
mail_create_nosubscribe=True,
)
))
composer = composer_form.save()
self.assertFalse(composer.reply_to_force_new, 'Mail: thread-enabled models should use auto thread by default')
with self.mock_mail_gateway(mail_unlink_sent=False), self.mock_mail_app():
# due to scheduled_date, cron for sending notification will be used
schedule_cron_id = self.env.ref('mail.ir_cron_send_scheduled_message').id
with self.mock_mail_gateway(mail_unlink_sent=False), \
self.mock_mail_app(), \
freeze_time(self.reference_now), \
self.capture_triggers(schedule_cron_id) as capt:
composer._action_send_mail()
# check new partners have been created based on emails given
new_partners = self.env['res.partner'].search([
('email', 'in', [email_to_1, email_to_2, email_to_3, email_cc_1])
])
self.assertEqual(len(new_partners), 3)
self.assertEqual(set(new_partners.mapped('email')),
set(['test.to.1@test.example.com', 'test.to.2@test.example.com', 'test.cc.1@test.example.com'])
)
# global outgoing: one mail.mail (all customer recipients, then all employee recipients)
# and 5 emails, and 1 inbox notification (admin)
self.assertEqual(len(self._new_mails), 2 * len(test_records),
'Should have created 1 mail.mail per group per record')
# notification process should not have been sent
if scheduled_date:
self.assertFalse(self._new_mails)
self.assertFalse(self._mails)
# monorecord: force_send notifications
if not batch:
self.assertEqual(len(self._mails), 5, 'Should have sent 5 emails, one per recipient per record')
elif not batch:
self.assertEqual(self._new_mails.mapped('state'), ['sent'] * len(self._new_mails))
# mutlirecord: use email queue
self.assertEqual(len(self._mails), 5, 'Should have sent 5 emails, one per recipient per record')
# multirecord: use email queue
else:
self.assertEqual(len(self._mails), 0, 'Should have put emails in queue and not sent any emails')
self.assertEqual(self._new_mails.mapped('state'), ['outgoing'] * len(self._new_mails))
self.assertEqual(len(self._mails), 0, 'Should have put emails in queue and not sent any emails')
# simulate cron sending emails
self.env['mail.mail'].sudo().process_email_queue()
# notification process should not have been sent
if scheduled_date:
self.assertEqual(
capt.records.mapped('call_at'), [self.reference_now + timedelta(days=2)] * len(test_records),
msg='Should have created a cron trigger for the scheduled sending'
)
else:
self.assertFalse(capt.records)
# check new partners have been created based on emails given
new_partners = self.env['res.partner'].search([
('email', 'in', [email_to_1, email_to_2, email_to_3, email_cc_1])
])
self.assertEqual(len(new_partners), 3)
self.assertEqual(
set(new_partners.mapped('email')),
set(['test.to.1@test.example.com', 'test.to.2@test.example.com', 'test.cc.1@test.example.com'])
)
# if scheduled_date is set: simulate cron for sending notifications
if scheduled_date:
# Send the scheduled message from the CRON
with self.mock_mail_gateway(mail_unlink_sent=False), \
self.mock_mail_app(), \
freeze_time(self.reference_now + timedelta(days=3)):
self.env['mail.message.schedule'].sudo()._send_notifications_cron()
# global outgoing: one mail.mail (all customer recipients, then all employee recipients)
# and 5 emails, and 1 inbox notification (admin)
self.assertEqual(len(self._new_mails), 2 * len(test_records),
'Should have created 1 mail.mail per group per record')
self.assertEqual(self._new_mails.mapped('state'), ['sent'] * len(self._new_mails))
self.assertEqual(len(self._mails), 5 * len(test_records),
'Should have sent 5 emails, one per recipient per record')
# template is sent only to partners (email_to are transformed)
for test_record in test_records:
message = test_record.message_ids[0]
@@ -1507,6 +1597,9 @@ class TestComposerResultsMass(TestMailComposer):
def test_mail_composer_wtpl_complete(self):
""" Test a composer in mass mode with a quite complete template, containing
notably email-based recipients and attachments. """
# as we use the email queue, don't have failing tests due to other outgoing emails
self.env['mail.mail'].sudo().search([]).unlink()
attachment_data = self._generate_attachments_data(2, self.template._name, self.template.id)
email_to_1 = 'test.to.1@test.example.com'
email_to_2 = 'test.to.2@test.example.com'
@@ -1534,16 +1627,25 @@ class TestComposerResultsMass(TestMailComposer):
default_template_id=self.template.id)
))
composer = composer_form.save()
with self.mock_mail_gateway(mail_unlink_sent=False):
with self.mock_mail_gateway(mail_unlink_sent=False), \
freeze_time(self.reference_now):
composer._action_send_mail()
new_partners = self.env['res.partner'].search([
('email', 'in', [email_to_1, email_to_2, email_to_3, email_cc_1])
])
self.assertEqual(len(new_partners), 3)
new_partners = self.env['res.partner'].search([
('email', 'in', [email_to_1, email_to_2, email_to_3, email_cc_1])
])
self.assertEqual(len(new_partners), 3)
# global outgoing: emails not sent due to scheduled_date
self.assertEqual(len(self._new_mails), 2, 'Should have created 1 mail.mail per record')
self.assertEqual(len(self._mails), 0, 'Should not send emails, scheduled in the future')
self.assertEqual(self._new_mails.mapped('scheduled_date'),
[self.reference_now + timedelta(days=2)] * 2)
# simulate cron queue at right time for sending
with freeze_time(self.reference_now + timedelta(days=2)):
self.env['mail.mail'].sudo().process_email_queue()
# global outgoing
self.assertEqual(len(self._new_mails), 2, 'Should have created 1 mail.mail per record')
self.assertEqual(len(self._mails), 10, 'Should have sent 5 emails per record')
# hack to use assertEmails: filtering on from/to only is not sufficient to distinguish emails