diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index 269329d4004..7de4485ba16 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -3509,12 +3509,7 @@ class AccountMove(models.Model):
compose_form = self.env.ref('account.account_invoice_send_wizard_form', raise_if_not_found=False)
ctx = dict(
default_model='account.move',
- default_res_id=self.id,
- # For the sake of consistency we need a default_res_model if
- # default_res_id is set. Not renaming default_model as it can
- # create many side-effects.
- default_res_model='account.move',
- default_use_template=bool(template),
+ default_res_ids=self.ids,
default_template_id=template and template.id or False,
default_composition_mode='comment',
mark_invoice_as_sent=True,
diff --git a/addons/account/views/account_payment_view.xml b/addons/account/views/account_payment_view.xml
index 7d60a621236..38704aadb77 100644
--- a/addons/account/views/account_payment_view.xml
+++ b/addons/account/views/account_payment_view.xml
@@ -430,7 +430,6 @@
Hello
' self.assertEqual( @@ -116,7 +116,7 @@ class TestMailComposerForm(TestMailComposer): form = Form(self.env['mail.compose.message'].with_context({ 'default_partner_ids': (partner_private + partner_classic).ids, 'default_model': test_record._name, - 'default_res_id': test_record.id, + 'default_res_ids': test_record.ids, })) form.body = 'Hello
' self.assertEqual( @@ -156,7 +156,7 @@ class TestMailComposerForm(TestMailComposer): _form = Form(self.env['mail.compose.message'].with_context({ 'default_partner_ids': (self.partner_private + partner_classic).ids, 'default_model': test_record._name, - 'default_res_id': test_record.id, + 'default_res_ids': test_record.ids, })) @mute_logger('odoo.addons.mail.models.mail_mail') @@ -177,7 +177,7 @@ class TestMailComposerForm(TestMailComposer): form = Form(self.env['mail.compose.message'].with_context({ 'default_model': test_record._name, - 'default_res_id': test_record.id, + 'default_res_ids': test_record.ids, 'default_template_id': template.id, })) diff --git a/addons/mail/views/res_partner_views.xml b/addons/mail/views/res_partner_views.xml index a3fae6a1d55..377d3589b84 100644 --- a/addons/mail/views/res_partner_views.xml +++ b/addons/mail/views/res_partner_views.xml @@ -111,7 +111,6 @@ 'default_composition_mode': 'mass_mail', 'default_partner_to': '{{ object.id or \'\' }}', 'default_subtype_xmlid': 'mail.mt_comment', - 'default_use_template': False, 'default_reply_to_force_new': True, }"/>TemplateBody %s
' % self.test_record.name) + self.assertFalse(composer_form.composition_batch) self.assertEqual(composer_form.composition_mode, 'comment') self.assertEqual(composer_form.email_from, self.user_employee_2.email_formatted) self.assertEqual(composer_form.mail_server_id, self.mail_server_domain) @@ -195,10 +199,39 @@ class TestComposerForm(TestMailComposer): self.assertEqual(composer_form.record_name, self.test_record.name, 'MailComposer: comment mode should compute record name') self.assertEqual(composer_form.reply_to, 'info@test.example.com') self.assertFalse(composer_form.reply_to_force_new) - self.assertEqual(composer_form.res_id, self.test_record.id) + self.assertEqual(literal_eval(composer_form.res_ids), self.test_record.ids) self.assertEqual(composer_form.subject, 'TemplateSubject %s' % self.test_record.name) self.assertEqual(composer_form.subtype_id, self.env.ref('mail.mt_comment')) + @users('employee') + def test_mail_composer_comment_wtpl_batch(self): + """ Batch mode of composer in comment mode. """ + composer_form = Form(self.env['mail.compose.message'].with_context( + self._get_web_context( + self.test_records, + add_web=True, + default_composition_mode='comment', + default_template_id=self.template.id), + )) + # self.assertTrue(composer_form.auto_delete) + self.assertFalse(composer_form.auto_delete) # FIXME: currently not taking template value + self.assertFalse(composer_form.auto_delete_message) + self.assertEqual(composer_form.author_id, self.env.user.partner_id) + self.assertEqual(composer_form.body, self.template.body_html, + 'MailComposer: comment in batch mode should have template raw body if template') + self.assertTrue(composer_form.composition_batch) + self.assertEqual(composer_form.composition_mode, 'comment') + self.assertEqual(composer_form.email_from, self.template.email_from, + 'MailComposer: comment in batch mode should have template raw email_from if template') + self.assertEqual(composer_form.mail_server_id, self.mail_server_domain) + self.assertEqual(composer_form.model, self.test_record._name) + self.assertFalse(composer_form.record_name, 'MailComposer: comment in batch mode should have void record name') + 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.subject, self.template.subject, + 'MailComposer: comment in batch mode should have template raw subject if template') + @users('employee') def test_mail_composer_comment_wtpl_norecords(self): """ Test specific case when running without records, to see the rendering @@ -213,6 +246,7 @@ class TestComposerForm(TestMailComposer): self.assertFalse(composer_form.auto_delete_message) self.assertEqual(composer_form.author_id, self.env.user.partner_id) self.assertEqual(composer_form.body, 'TemplateBody
') + self.assertFalse(composer_form.composition_batch) self.assertEqual(composer_form.composition_mode, 'comment') self.assertEqual(composer_form.email_from, self.env.user.partner_id.email_formatted) self.assertEqual(composer_form.mail_server_id, self.mail_server_domain) @@ -221,7 +255,7 @@ class TestComposerForm(TestMailComposer): self.assertFalse(composer_form.record_name) self.assertEqual(composer_form.reply_to, 'info@test.example.com') self.assertFalse(composer_form.reply_to_force_new) - self.assertFalse(composer_form.res_id) + self.assertFalse(composer_form.res_ids) self.assertEqual(composer_form.subject, 'TemplateSubject ') self.assertEqual(composer_form.subtype_id, self.env.ref('mail.mt_comment')) @@ -234,6 +268,7 @@ class TestComposerForm(TestMailComposer): self.assertFalse(composer_form.auto_delete_message) self.assertEqual(composer_form.author_id, self.env.user.partner_id) self.assertFalse(composer_form.body) + self.assertTrue(composer_form.composition_batch) self.assertEqual(composer_form.composition_mode, 'mass_mail') self.assertEqual(composer_form.email_from, self.env.user.email_formatted) self.assertFalse(composer_form.mail_server_id) @@ -241,8 +276,7 @@ class TestComposerForm(TestMailComposer): self.assertFalse(composer_form.record_name, 'MailComposer: mass mode should have void record name') self.assertFalse(composer_form.reply_to) self.assertFalse(composer_form.reply_to_force_new) - self.assertEqual(composer_form.res_id, self.test_records[0].id, - 'MailComposer: even in mass mode web active_id presence may add a res_id') + self.assertEqual(sorted(literal_eval(composer_form.res_ids)), sorted(self.test_records.ids)) 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')) @@ -257,6 +291,7 @@ class TestComposerForm(TestMailComposer): self.assertEqual(composer_form.author_id, self.env.user.partner_id) self.assertEqual(composer_form.body, self.template.body_html, 'MailComposer: mass mode should have template raw body if template') + self.assertTrue(composer_form.composition_batch) self.assertEqual(composer_form.composition_mode, 'mass_mail') self.assertEqual(composer_form.email_from, self.template.email_from, 'MailComposer: mass mode should have template raw email_from if template') @@ -265,8 +300,7 @@ class TestComposerForm(TestMailComposer): self.assertFalse(composer_form.record_name, 'MailComposer: mass mode should have void record name') self.assertEqual(composer_form.reply_to, self.template.reply_to) self.assertFalse(composer_form.reply_to_force_new) - self.assertEqual(composer_form.res_id, self.test_records[0].id, - 'MailComposer: even in mass mode web active_id presence may add a res_id') + self.assertEqual(sorted(literal_eval(composer_form.res_ids)), sorted(self.test_records.ids)) 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')) @@ -286,6 +320,7 @@ class TestComposerForm(TestMailComposer): self.assertEqual(composer_form.author_id, self.env.user.partner_id) self.assertEqual(composer_form.body, self.template.body_html, 'MailComposer: mass mode should have template raw body if template') + self.assertFalse(composer_form.composition_batch) self.assertEqual(composer_form.composition_mode, 'mass_mail') self.assertEqual(composer_form.email_from, self.template.email_from, 'MailComposer: mass mode should have template raw email_from if template') @@ -294,7 +329,7 @@ class TestComposerForm(TestMailComposer): self.assertFalse(composer_form.record_name, 'MailComposer: mass mode should have void record name') self.assertEqual(composer_form.reply_to, self.template.reply_to) self.assertFalse(composer_form.reply_to_force_new) - self.assertEqual(composer_form.res_id, 0) + self.assertFalse(composer_form.res_ids) 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')) @@ -321,7 +356,8 @@ class TestComposerInternals(TestMailComposer): attachs = self.env['ir.attachment'].search([('name', 'in', [a['name'] for a in attachment_data])]) self.assertEqual(len(attachs), 3) - for composition_mode, batch in (('comment', False), ('mass_mail', True)): + for composition_mode, batch in (('comment', False), ('comment', True), + ('mass_mail', False), ('mass_mail', True)): with self.subTest(composition_mode=composition_mode, batch=batch): test_records = self.test_records if batch else self.test_record ctx = self._get_web_context( @@ -337,7 +373,7 @@ class TestComposerInternals(TestMailComposer): composer._onchange_template_id_wrapper() # values coming from template: attachment_ids + report in comment - if composition_mode == 'comment': + if composition_mode == 'comment' and not batch: self.assertEqual(len(composer.attachment_ids), 4) for attach in attachs: self.assertIn(attach, composer.attachment_ids) @@ -358,7 +394,7 @@ class TestComposerInternals(TestMailComposer): # currently onchange necessary composer._onchange_template_id_wrapper() - if composition_mode == 'comment': + if composition_mode == 'comment' and not batch: self.assertEqual(composer.attachment_ids, attachs + generated, 'TODO: Values are kept (should be reset ?)') else: @@ -370,7 +406,7 @@ class TestComposerInternals(TestMailComposer): # currently onchange necessary composer._onchange_template_id_wrapper() - if composition_mode == 'comment': + if composition_mode == 'comment' and not batch: self.assertEqual(composer.attachment_ids, attachs + generated, 'TODO: Values are kept (should be reset ?)') else: @@ -386,7 +422,8 @@ class TestComposerInternals(TestMailComposer): 'email_from': False, }) - for composition_mode, batch in (('comment', False), ('mass_mail', True)): + for composition_mode, batch in (('comment', False), ('comment', True), + ('mass_mail', False), ('mass_mail', True)): with self.subTest(composition_mode=composition_mode, batch=batch): test_records = self.test_records if batch else self.test_record ctx = self._get_web_context( @@ -400,6 +437,7 @@ class TestComposerInternals(TestMailComposer): # default values are current user self.assertEqual(composer.author_id, self.env.user.partner_id) + self.assertEqual(composer.composition_mode, composition_mode) self.assertEqual(composer.email_from, self.env.user.email_formatted) # author values reset email (FIXME: currently not synchronized) @@ -414,12 +452,12 @@ class TestComposerInternals(TestMailComposer): composer._onchange_template_id_wrapper() self.assertEqual(composer.author_id, self.env.user.partner_id, 'MailComposer: should take value given by user') - if composition_mode == 'comment': + if composition_mode == 'comment' and not batch: self.assertEqual(composer.email_from, self.test_record.user_id.email_formatted, - 'MailComposer: should take email_from rendered from template') + f'MailComposer: should take email_from rendered from template ({composition_mode}-{batch})') else: self.assertEqual(composer.email_from, self.template.email_from, - 'MailComposer: should take email_from raw from template') + f'MailComposer: should take email_from raw from template ({composition_mode}-{batch})') # manual values are kept over template values composer.write({'email_from': self.test_from}) @@ -431,7 +469,7 @@ class TestComposerInternals(TestMailComposer): composer.write({'template_id': template_void.id}) # currently onchange necessary composer._onchange_template_id_wrapper() - if composition_mode == 'comment': + if composition_mode == 'comment' and not batch: self.assertEqual(composer.author_id, self.env.user.partner_id) self.assertEqual(composer.email_from, self.test_from) else: @@ -442,7 +480,7 @@ class TestComposerInternals(TestMailComposer): composer.write({'template_id': False}) # currently onchange necessary composer._onchange_template_id_wrapper() - if composition_mode == 'comment': + if composition_mode == 'comment' and not batch: self.assertEqual(composer.author_id, self.env.user.partner_id) self.assertEqual(composer.email_from, self.env.user.email_formatted) else: @@ -458,7 +496,8 @@ class TestComposerInternals(TestMailComposer): 'auto_delete': False, }) - for composition_mode, batch in (('comment', False), ('mass_mail', True)): + for composition_mode, batch in (('comment', False), ('comment', True), + ('mass_mail', False), ('mass_mail', True)): with self.subTest(composition_mode=composition_mode, batch=batch): test_records = self.test_records if batch else self.test_record ctx = self._get_web_context( @@ -539,7 +578,8 @@ class TestComposerInternals(TestMailComposer): 'subject': False, }) - for composition_mode, batch in (('comment', False), ('mass_mail', True)): + for composition_mode, batch in (('comment', False), ('comment', True), + ('mass_mail', False), ('mass_mail', True)): with self.subTest(composition_mode=composition_mode, batch=batch): test_records = self.test_records if batch else self.test_record ctx = self._get_web_context( @@ -557,11 +597,11 @@ class TestComposerInternals(TestMailComposer): # creation values are taken self.assertEqual(composer.body, 'Test Body
TemplateBody {self.test_record.name}
') self.assertEqual(composer.mail_server_id, self.template.mail_server_id) self.assertEqual(composer.record_name, self.test_record.name) @@ -670,7 +710,7 @@ class TestComposerInternals(TestMailComposer): composer._onchange_template_id_wrapper() # values come from template - if composition_mode == 'comment': + if composition_mode == 'comment' and not batch: self.assertEqual(composer.body, f'TemplateBody {self.test_record.name}
') self.assertEqual(composer.mail_server_id, self.template.mail_server_id) self.assertEqual(composer.record_name, self.test_record.name) @@ -713,7 +753,8 @@ class TestComposerInternals(TestMailComposer): 'reply_to': False, }) - for composition_mode, batch in (('comment', False), ('mass_mail', True)): + for composition_mode, batch in (('comment', False), ('comment', True), + ('mass_mail', False), ('mass_mail', True)): with self.subTest(composition_mode=composition_mode, batch=batch): self.assertFalse( self.env['res.partner'].search([ @@ -769,7 +810,7 @@ class TestComposerInternals(TestMailComposer): ) # values come from template - if composition_mode == 'comment': + if composition_mode == 'comment' and not batch: self.assertEqual(len(new_partners), 2) self.assertEqual(composer.partner_ids, self.partner_1 + new_partners, 'Template took customer_id as set on record') self.assertEqual(composer.reply_to, 'info@test.example.com', 'Template was rendered') @@ -790,7 +831,7 @@ class TestComposerInternals(TestMailComposer): composer._onchange_template_id_wrapper() # values are kept, should probably be reset - if composition_mode == 'comment': + if composition_mode == 'comment' and not batch: self.assertEqual(composer.partner_ids, self.partner_admin, 'TODO: Values are kept (should be reset ?)') self.assertEqual(composer.reply_to, 'info@test.example.com', @@ -812,11 +853,11 @@ class TestComposerInternals(TestMailComposer): composer._onchange_template_id_wrapper() # values come from template - if composition_mode == 'comment': + if composition_mode == 'comment' and not batch: self.assertEqual(composer.partner_ids, self.partner_1 + new_partners) else: self.assertFalse(composer.partner_ids) - if composition_mode == 'comment': + if composition_mode == 'comment' and not batch: self.assertEqual(composer.reply_to, "info@test.example.com") else: self.assertEqual(composer.reply_to, self.template.reply_to) @@ -832,11 +873,11 @@ class TestComposerInternals(TestMailComposer): composer._onchange_template_id_wrapper() # values come from template - if composition_mode == 'comment': + if composition_mode == 'comment' and not batch: self.assertEqual(composer.partner_ids, self.partner_1 + new_partners) else: self.assertFalse(composer.partner_ids) - if composition_mode == 'comment': + if composition_mode == 'comment' and not batch: self.assertEqual(composer.reply_to, "info@test.example.com") else: self.assertEqual(composer.reply_to, self.template.reply_to) @@ -1137,10 +1178,11 @@ class TestComposerResultsComment(TestMailComposer): @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') def test_mail_composer_wtpl_complete(self): """ Test a posting process using a complex template, holding several - additional recipients and attachments. + 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_record followers and partner_admin added in partner_to.""" + test_records followers and partner_admin added in partner_to.""" 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' @@ -1158,82 +1200,97 @@ class TestComposerResultsComment(TestMailComposer): attachs = self.env['ir.attachment'].search([('name', 'in', [a['name'] for a in attachment_data])]) self.assertEqual(len(attachs), 2) - # ensure initial data - self.assertEqual(self.test_record.user_id, self.user_employee_2) - self.assertEqual(self.test_record.message_partner_ids, self.partner_employee_2) + for test_records, batch in ((self.test_record, False), (self.test_records, True)): + with self.subTest(test_records=test_records, batch=batch): + # ensure initial data + self.assertEqual(len(test_records.customer_id), len(test_records)) + self.assertEqual(test_records.user_id, self.user_employee_2) + self.assertEqual(test_records.message_partner_ids, self.partner_employee_2) - # open a composer and run it in comment mode - composer_form = Form(self.env['mail.compose.message'].with_context( - self._get_web_context(self.test_record, add_web=True, - default_template_id=self.template.id) - )) - 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(): - composer._action_send_mail() + # open a composer and run it in comment mode + 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) + )) + 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(): + 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']) - ) + # 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, 'Should have created 1 mail.mail') - self.assertEqual(len(self._mails), 5, 'Should have sent 5 emails, one per recipient') + # 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') + # monorecord: force_send notifications + if not batch: + self.assertEqual(len(self._mails), 5, 'Should have sent 5 emails, one per recipient per record') + self.assertEqual(self._new_mails.mapped('state'), ['sent'] * len(self._new_mails)) + # mutlirecord: 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)) + # simulate cron sending emails + self.env['mail.mail'].sudo().process_email_queue() - # template is sent only to partners (email_to are transformed) - message = self.test_record.message_ids[0] - self.assertMailMail(self.partner_employee_2, 'sent', - mail_message=message, - author=self.partner_employee, # author != email_from (template sets only email_from) - email_values={ - 'body_content': 'TemplateBody %s' % self.test_record.name, - 'email_from': self.test_record.user_id.email_formatted, # set by template - 'subject': 'TemplateSubject %s' % self.test_record.name, - 'attachments_info': [ - {'name': 'AttFileName_00.txt', 'raw': b'AttContent_00', 'type': 'text/plain'}, - {'name': 'AttFileName_01.txt', 'raw': b'AttContent_01', 'type': 'text/plain'}, - {'name': 'TestReport for %s.html' % self.test_record.name, 'type': 'text/plain'}, - ] - }, - fields_values={ - 'mail_server_id': self.mail_server_domain, - }, - ) - self.assertMailMail(self.test_record.customer_id + new_partners, 'sent', - mail_message=message, - author=self.partner_employee, # author != email_from (template sets only email_from) - email_values={ - 'body_content': 'TemplateBody %s' % self.test_record.name, - 'email_from': self.test_record.user_id.email_formatted, # set by template - 'subject': 'TemplateSubject %s' % self.test_record.name, - 'attachments_info': [ - {'name': 'AttFileName_00.txt', 'raw': b'AttContent_00', 'type': 'text/plain'}, - {'name': 'AttFileName_01.txt', 'raw': b'AttContent_01', 'type': 'text/plain'}, - {'name': 'TestReport for %s.html' % self.test_record.name, 'type': 'text/plain'}, - ] - }, - fields_values={ - 'mail_server_id': self.mail_server_domain, - }, - ) + # template is sent only to partners (email_to are transformed) + for test_record in test_records: + message = test_record.message_ids[0] + self.assertMailMail(self.partner_employee_2, 'sent', + mail_message=message, + author=self.partner_employee, # author != email_from (template sets only email_from) + email_values={ + 'body_content': f'TemplateBody {test_record.name}', + 'email_from': test_record.user_id.email_formatted, # set by template + 'subject': f'TemplateSubject {test_record.name}', + 'attachments_info': [ + {'name': 'AttFileName_00.txt', 'raw': b'AttContent_00', 'type': 'text/plain'}, + {'name': 'AttFileName_01.txt', 'raw': b'AttContent_01', 'type': 'text/plain'}, + {'name': f'TestReport for {test_record.name}.html', 'type': 'text/plain'}, + ] + }, + fields_values={ + 'mail_server_id': self.mail_server_domain, + }, + ) + self.assertMailMail(test_record.customer_id + new_partners, 'sent', + mail_message=message, + author=self.partner_employee, # author != email_from (template sets only email_from) + email_values={ + 'body_content': f'TemplateBody {test_record.name}', + 'email_from': test_record.user_id.email_formatted, # set by template + 'subject': f'TemplateSubject {test_record.name}', + 'attachments_info': [ + {'name': 'AttFileName_00.txt', 'raw': b'AttContent_00', 'type': 'text/plain'}, + {'name': 'AttFileName_01.txt', 'raw': b'AttContent_01', 'type': 'text/plain'}, + {'name': f'TestReport for {test_record.name}.html', 'type': 'text/plain'}, + ] + }, + fields_values={ + 'mail_server_id': self.mail_server_domain, + }, + ) - # message is posted and notified admin - self.assertEqual(message.subtype_id, self.env.ref('mail.mt_comment')) - self.assertNotified(message, [{'partner': self.partner_admin, 'is_read': False, 'type': 'inbox'}]) - # attachments are copied on message and linked to document - self.assertEqual( - set(message.attachment_ids.mapped('name')), - set(['AttFileName_00.txt', 'AttFileName_01.txt', 'TestReport for %s.html' % self.test_record.name]) - ) - self.assertEqual(set(message.attachment_ids.mapped('res_model')), set([self.test_record._name])) - self.assertEqual(set(message.attachment_ids.mapped('res_id')), set(self.test_record.ids)) - self.assertTrue(all(attach not in message.attachment_ids for attach in attachs), 'Should have copied attachments') + # message is posted and notified admin + self.assertEqual(message.subtype_id, self.env.ref('mail.mt_comment')) + self.assertNotified(message, [{'partner': self.partner_admin, 'is_read': False, 'type': 'inbox'}]) + # attachments are copied on message and linked to document + self.assertEqual( + set(message.attachment_ids.mapped('name')), + set(['AttFileName_00.txt', 'AttFileName_01.txt', f'TestReport for {test_record.name}.html']) + ) + self.assertEqual(set(message.attachment_ids.mapped('res_model')), set([test_record._name])) + self.assertEqual(set(message.attachment_ids.mapped('res_id')), set(test_record.ids)) + self.assertTrue(all(attach not in message.attachment_ids for attach in attachs), 'Should have copied attachments') @@ -1563,51 +1620,66 @@ class TestComposerResultsMass(TestMailComposer): @users('employee') @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') def test_mail_composer_wtpl_recipients(self): - """ Test various combinations of recipients: active_domain, active_id, + """ Test various combinations of recipients: res_domain, active_id, active_ids, ... to ensure fallback behavior are working. """ - # 1: active_domain + # 1: active ids composer_form = Form(self.env['mail.compose.message'].with_context( - self._get_web_context(self.test_records, add_web=True, - default_template_id=self.template.id, - active_ids=[], - default_use_active_domain=True, - default_active_domain=[('id', 'in', self.test_records.ids)]) + active_ids=self.test_records.ids, + default_composition_mode='mass_mail', + default_model=self.test_records._name, + default_template_id=self.template.id, )) composer = composer_form.save() + self.assertEqual(sorted(literal_eval(composer.res_ids)), sorted(self.test_records.ids)) + with self.mock_mail_gateway(mail_unlink_sent=True): composer._action_send_mail() + # should create emails in a single batch + self.assertEqual(self.build_email_mocked.call_count, 2, 'One build email per outgoing email') + self.assertEqual(self.mail_mail_create_mocked.call_count, 2, 'Emails are anyway created in a singleton loop') # global outgoing - self.assertEqual(len(self._new_mails), 2, 'Should have created 1 mail.mail per record') - self.assertEqual(len(self._mails), 2, 'Should have sent 1 email per record') + self.assertEqual(len(self._new_mails), 2, 'Should have created 1 mail.mail per record based on active_ids') + self.assertEqual(len(self._mails), 2, 'Should have sent 1 email per record based on on active_ids') for record in self.test_records: # template is sent directly using customer field, even if author is partner_employee self.assertSentEmail(self.partner_employee_2.email_formatted, record.customer_id) - # 2: active_domain not taken into account if use_active_domain is False + # 2: default_res_ids + active_ids -> res_ids takes lead composer_form = Form(self.env['mail.compose.message'].with_context( - self._get_web_context(self.test_records, add_web=True, + self._get_web_context(self.test_record, add_web=False, + default_composition_mode='mass_mail', + default_res_ids=self.test_record.ids, default_template_id=self.template.id, - default_use_active_domain=False, - default_active_domain=[('id', 'in', -1)]) + active_ids=self.test_records.ids, + ) )) composer = composer_form.save() + self.assertEqual(literal_eval(composer.res_ids), self.test_record.ids) + with self.mock_mail_gateway(mail_unlink_sent=True): composer._action_send_mail() # global outgoing - self.assertEqual(len(self._new_mails), 2, 'Should have created 1 mail.mail per record') - self.assertEqual(len(self._mails), 2, 'Should have sent 1 email per record') + self.assertEqual(len(self._new_mails), 1, 'Should have taken default_res_ids (1 record)') + self.assertEqual(len(self._mails), 1, 'Should have taken default_res_ids (1 record)') + + # template is sent directly using customer field, even if author is partner_employee + self.assertSentEmail(self.partner_employee_2.email_formatted, + self.test_record.customer_id) # 3: fallback on active_id if not active_ids composer_form = Form(self.env['mail.compose.message'].with_context( - self._get_web_context(self.test_records, add_web=True, - default_template_id=self.template.id, - active_ids=[]) + active_id=self.test_record.id, + default_composition_mode='mass_mail', + default_model=self.test_records._name, + default_template_id=self.template.id, )) composer = composer_form.save() + self.assertEqual(literal_eval(composer.res_ids), self.test_record.ids) + with self.mock_mail_gateway(mail_unlink_sent=False): composer._action_send_mail() @@ -1615,7 +1687,54 @@ class TestComposerResultsMass(TestMailComposer): self.assertEqual(len(self._new_mails), 1, 'Should have created 1 mail.mail per record') self.assertEqual(len(self._mails), 1, 'Should have sent 1 email per record') - # 3: void is void: raise in comment mode, just don't send anything in mass mail mode + # 4: _batch_size limit for active_ids + with patch.object(MailComposer, '_batch_size', new=1): + composer_form = Form(self.env['mail.compose.message'].with_context( + active_ids=self.test_records.ids, + default_composition_mode='mass_mail', + default_model=self.test_records._name, + default_template_id=self.template.id, + )) + composer = composer_form.save() + self.assertTrue(composer.composition_batch) + self.assertEqual(composer.composition_mode, 'mass_mail') + self.assertFalse(composer.res_ids) + + with self.mock_mail_gateway(mail_unlink_sent=True): + composer._action_send_mail() + + # should create emails in 2 batches of 1 + self.assertEqual(self.build_email_mocked.call_count, 2) + self.assertEqual(self.mail_mail_create_mocked.call_count, 2) + # global outgoing + self.assertEqual(len(self._new_mails), 2, 'Should have created 1 mail.mail per record based on active_ids') + self.assertEqual(len(self._mails), 2, 'Should have sent 1 email per record based on on active_ids') + + # 5: mail.batch_size config parameter support, for sending only + self.env['ir.config_parameter'].sudo().set_param('mail.batch_size', 1) + with patch.object(MailComposer, '_batch_size', new=50): + composer_form = Form(self.env['mail.compose.message'].with_context( + active_ids=self.test_records.ids, + default_composition_mode='mass_mail', + default_model=self.test_records._name, + default_template_id=self.template.id, + )) + composer = composer_form.save() + self.assertTrue(composer.composition_batch) + self.assertEqual(composer.composition_mode, 'mass_mail') + self.assertEqual(sorted(literal_eval(composer.res_ids)), sorted(self.test_records.ids)) + + with self.mock_mail_gateway(mail_unlink_sent=True): + composer._action_send_mail() + + # should create emails in 2 batches of 1 + self.assertEqual(self.build_email_mocked.call_count, 2) + self.assertEqual(self.mail_mail_create_mocked.call_count, 2) + # global outgoing + self.assertEqual(len(self._new_mails), 2, 'Should have created 1 mail.mail per record based on active_ids') + self.assertEqual(len(self._mails), 2, 'Should have sent 1 email per record based on on active_ids') + + # 6: void is void: raise in comment mode, just don't send anything in mass mail mode composer_form = Form(self.env['mail.compose.message'].with_context( default_model='mail.test.ticket', default_template_id=self.template.id @@ -1637,6 +1756,41 @@ class TestComposerResultsMass(TestMailComposer): composer._action_send_mail() self.assertNotSentEmail() + @users('employee') + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_mail_composer_wtpl_recipients_res_domain(self): + """ Test specific use case of res_domain usage, in combination with + res_domain_user_id allowing to set the evaluation environment for + the domain. """ + def _search_as_employee_2(recordset, *args, **kwargs): + """ Mock to return only one record, simulating a specific ir.rule """ + if recordset.env.uid == self.user_employee_2.id: + return self.test_records[0] + return DEFAULT + + composer_form = Form(self.env['mail.compose.message'].with_context( + default_composition_mode='mass_mail', + default_model=self.test_records._name, + default_res_domain=[('id', 'in', self.test_records.ids)], + default_res_domain_user_id=self.user_employee_2.id, + default_template_id=self.template.id, + )) + composer = composer_form.save() + self.assertEqual(literal_eval(composer.res_domain), [('id', 'in', self.test_records.ids)]) + self.assertEqual(composer.res_domain_user_id, self.user_employee_2) + + with self.mock_mail_gateway(mail_unlink_sent=True), \ + patch.object(MailTestTicket, 'search', autospec=True, side_effect=_search_as_employee_2): + composer._action_send_mail() + + # global outgoing + self.assertEqual(len(self._new_mails), 1, 'Should have created 1 mail.mail, search done by employee 2') + self.assertEqual(len(self._mails), 1, 'Should have sent 1 email, search done by employee 2') + + # template is sent directly using customer field, whatever the author + self.assertSentEmail(self.partner_employee_2.email_formatted, + self.test_records[0].customer_id) + @users('employee') @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') def test_mail_composer_wtpl_reply_to_force_new(self): diff --git a/addons/test_mail/tests/test_performance.py b/addons/test_mail/tests/test_performance.py index c3c98458cd3..a1ebb139e43 100644 --- a/addons/test_mail/tests/test_performance.py +++ b/addons/test_mail/tests/test_performance.py @@ -341,7 +341,7 @@ class TestMailAPIPerformance(BaseMailPerformance): composer = self.env['mail.compose.message'].with_context({ 'default_composition_mode': 'comment', 'default_model': test_record._name, - 'default_res_id': test_record.id, + 'default_res_ids': test_record.ids, }).create({ 'body': 'Test Body
', 'partner_ids': [(4, customer_id)], @@ -361,7 +361,7 @@ class TestMailAPIPerformance(BaseMailPerformance): composer = self.env['mail.compose.message'].with_context({ 'default_composition_mode': 'comment', 'default_model': test_record._name, - 'default_res_id': test_record.id, + 'default_res_ids': test_record.ids, }).create({ 'attachment_ids': attachments.ids, 'body': 'Test Body
', @@ -383,7 +383,7 @@ class TestMailAPIPerformance(BaseMailPerformance): self.env['mail.compose.message'].with_context({ 'default_composition_mode': 'comment', 'default_model': test_record._name, - 'default_res_id': test_record.id, + 'default_res_ids': test_record.ids, }) ) composer_form.body = 'Test Body
' @@ -408,9 +408,9 @@ class TestMailAPIPerformance(BaseMailPerformance): with self.assertQueryCount(__system__=3, employee=3): composer = self.env['mail.compose.message'].with_context({ - 'active_ids': test_records.ids, 'default_composition_mode': 'mass_mail', 'default_model': test_records._name, + 'default_res_ids': test_records.ids, 'default_template_id': test_template.id, }).create({}) composer._onchange_template_id_wrapper() @@ -430,7 +430,7 @@ class TestMailAPIPerformance(BaseMailPerformance): composer = self.env['mail.compose.message'].with_context({ 'default_composition_mode': 'comment', 'default_model': test_record._name, - 'default_res_id': test_record.id, + 'default_res_ids': test_record.ids, 'mail_auto_delete': False, }).create({ 'body': 'Test Body
', @@ -451,7 +451,7 @@ class TestMailAPIPerformance(BaseMailPerformance): composer = self.env['mail.compose.message'].with_context({ 'default_composition_mode': 'comment', 'default_model': test_record._name, - 'default_res_id': test_record.id, + 'default_res_ids': test_record.ids, 'default_template_id': test_template.id, }).create({}) composer._onchange_template_id_wrapper() @@ -476,7 +476,7 @@ class TestMailAPIPerformance(BaseMailPerformance): composer = self.env['mail.compose.message'].with_context({ 'default_composition_mode': 'comment', 'default_model': test_record._name, - 'default_res_id': test_record.id, + 'default_res_ids': test_record.ids, 'default_template_id': test_template.id, }).create({}) composer._onchange_template_id_wrapper() @@ -507,7 +507,7 @@ class TestMailAPIPerformance(BaseMailPerformance): self.env['mail.compose.message'].with_context({ 'default_composition_mode': 'comment', 'default_model': test_record._name, - 'default_res_id': test_record.id, + 'default_res_ids': test_record.ids, 'default_template_id': test_template.id, }) ) @@ -537,7 +537,7 @@ class TestMailAPIPerformance(BaseMailPerformance): self.env['mail.compose.message'].with_context({ 'default_composition_mode': 'comment', 'default_model': test_record._name, - 'default_res_id': test_record.id, + 'default_res_ids': test_record.ids, 'default_template_id': test_template.id, }) ) @@ -883,9 +883,9 @@ class TestMailComplexPerformance(BaseMailPerformance): with self.assertQueryCount(__system__=3, employee=3): composer = self.env['mail.compose.message'].with_context({ - 'active_ids': test_records.ids, 'default_composition_mode': 'mass_mail', 'default_model': test_records._name, + 'default_res_ids': test_records.ids, 'default_template_id': test_template.id, }).create({}) composer._onchange_template_id_wrapper() diff --git a/addons/test_mail_full/tests/test_portal.py b/addons/test_mail_full/tests/test_portal.py index 44a086a9901..a0f72c3d78a 100644 --- a/addons/test_mail_full/tests/test_portal.py +++ b/addons/test_mail_full/tests/test_portal.py @@ -218,9 +218,8 @@ class TestPortalFlow(MailCommon, HttpCase): 'default_composition_mode': 'comment', 'default_email_layout_xmlid': 'mail.mail_notification_layout_with_responsible_signature', 'default_model': self.record_portal._name, - 'default_res_id': self.record_portal.id, + 'default_res_ids': self.record_portal.ids, 'default_template_id': template_id, - 'default_use_template': template_id, 'force_email': True, 'lang': '{{ object.partner_id.lang }}', }) diff --git a/addons/website/models/website_visitor.py b/addons/website/models/website_visitor.py index 48be0b14f4b..27c61d775ac 100644 --- a/addons/website/models/website_visitor.py +++ b/addons/website/models/website_visitor.py @@ -167,7 +167,7 @@ class WebsiteVisitor(models.Model): def _prepare_message_composer_context(self): return { 'default_model': 'res.partner', - 'default_res_id': self.partner_id.id, + 'default_res_ids': self.partner_id.ids, 'default_partner_ids': [self.partner_id.id], } @@ -178,7 +178,6 @@ class WebsiteVisitor(models.Model): visitor_composer_ctx = self._prepare_message_composer_context() compose_form = self.env.ref('mail.email_compose_message_wizard_form', False) compose_ctx = dict( - default_use_template=False, default_composition_mode='comment', ) compose_ctx.update(**visitor_composer_ctx) diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py index e6a805326c1..b70e34e747b 100644 --- a/addons/website_sale/models/sale_order.py +++ b/addons/website_sale/models/sale_order.py @@ -352,12 +352,10 @@ class SaleOrder(models.Model): 'context': { 'default_composition_mode': 'mass_mail' if len(self.ids) > 1 else 'comment', 'default_email_layout_xmlid': 'mail.mail_notification_layout_with_responsible_signature', - 'default_res_id': self.ids[0], + 'default_res_ids': self.ids, 'default_model': 'sale.order', - 'default_use_template': bool(template_id), 'default_template_id': template_id, 'website_sale_send_recovery_email': True, - 'active_ids': self.ids, }, } diff --git a/addons/website_slides/models/slide_channel.py b/addons/website_slides/models/slide_channel.py index 6b3a364d342..1399299fbdb 100644 --- a/addons/website_slides/models/slide_channel.py +++ b/addons/website_slides/models/slide_channel.py @@ -651,7 +651,6 @@ class Channel(models.Model): local_context = dict( self.env.context, default_channel_id=self.id, - default_use_template=bool(template), default_template_id=template and template.id or False, default_email_layout_xmlid='website_slides.mail_notification_channel_invite', )