[REF] mail: cleanup mono/multi record composer behavior

RATIONALE

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

SPECIFICATIONS

Support a real res_ids field on mail.compose.message model. Instead of relying
on active_ids from context, store it once for all at composer level and use
it in code. Active_ids usage is still done at default_get level, using it to
populate the field.

Improve usage of domain, renamed to res_domain to match other document related
fields naming. Add support of a res_domain_user_id field allowing to set the
user from which the domain should be evaluated.

Composer now runs on a list of IDs. Mass mail mode and comment mode are now
distinct from running on a singleton or on more records. Rendered or raw
mode is not triggered by

  * mass mailing mode: always display raw mode, whatever the number of records;
  * comment mode: display rendered mode when having a single record (like the
    previous comment mode). Display raw mode when having either no records
    either at least two records.

Task-3035101 (Mail: Support batch-posting from composer)

Part-of: odoo/odoo#99482
This commit is contained in:
Thibault Delavallée
2023-01-17 20:58:39 +01:00
parent 55bcb62131
commit 1a1acabd7b
35 changed files with 559 additions and 310 deletions
+1 -6
View File
@@ -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,
@@ -430,7 +430,6 @@
<field name="context" eval="{
'mail_post_autofollow': True,
'default_composition_mode': 'comment',
'default_use_template': True,
'default_template_id': ref('account.mail_template_data_payment_receipt'),
'default_email_layout_xmlid': 'mail.mail_notification_light',
}"/>
@@ -447,7 +446,6 @@
<field name="context" eval="{
'mail_post_autofollow': True,
'default_composition_mode': 'mass_mail',
'default_use_template': True,
'default_template_id': ref('account.mail_template_data_payment_receipt'),
'default_email_layout_xmlid': 'mail.mail_notification_light',
}"/>
-1
View File
@@ -755,7 +755,6 @@ class Meeting(models.Model):
default_composition_mode=default_composition_mode,
default_model='calendar.event',
default_res_ids=self.ids,
default_use_template=bool(template_id),
default_template_id=template_id,
default_partner_ids=self.partner_ids.ids,
mail_tz=self.env.user.tz,
-2
View File
@@ -735,7 +735,6 @@
<field name="target">new</field>
<field name="context" eval="{
'default_composition_mode': 'comment',
'default_use_template': False,
}"/>
<field name="binding_model_id" ref="model_crm_lead"/>
<field name="binding_view_types">form</field>
@@ -748,7 +747,6 @@
<field name="target">new</field>
<field name="context" eval="{
'default_composition_mode': 'mass_mail',
'default_use_template': False,
}"/>
<field name="binding_model_id" ref="model_crm_lead"/>
<field name="binding_view_types">list</field>
+2 -3
View File
@@ -195,9 +195,8 @@ class EventRegistration(models.Model):
compose_form = self.env.ref('mail.email_compose_message_wizard_form')
ctx = dict(
default_model='event.registration',
default_res_id=self.id,
default_use_template=bool(template),
default_template_id=template and template.id,
default_res_ids=self.ids,
default_template_id=template.id if template else False,
default_composition_mode='comment',
default_email_layout_xmlid="mail.mail_notification_light",
)
+1 -2
View File
@@ -181,8 +181,7 @@ Do not hesitate to contact your manager or the human resource department.""")
compose_form = self.env.ref('mail.email_compose_message_wizard_form', False)
ctx = dict(
default_model="hr.employee",
default_res_id=self.id,
default_use_template=bool(template),
default_res_ids=self.ids,
default_template_id=template.id,
default_composition_mode='comment',
default_is_log=True,
+1 -2
View File
@@ -81,8 +81,7 @@ class LoyaltyCard(models.Model):
compose_form = self.env.ref('mail.email_compose_message_wizard_form', False)
ctx = dict(
default_model='loyalty.card',
default_res_id=self.id,
default_use_template=bool(default_template),
default_res_ids=self.ids,
default_template_id=default_template and default_template.id,
default_composition_mode='comment',
default_email_layout_xmlid='mail.mail_notification_light',
+6 -2
View File
@@ -170,14 +170,18 @@ class MailTemplate(models.Model):
def create_action(self):
ActWindow = self.env['ir.actions.act_window']
view = self.env.ref('mail.email_compose_message_wizard_form')
for template in self:
context = {
'default_composition_mode': 'mass_mail',
'default_model': template.model,
'default_template_id' : template.id,
}
button_name = _('Send Mail (%s)', template.name)
action = ActWindow.create({
'name': button_name,
'type': 'ir.actions.act_window',
'res_model': 'mail.compose.message',
'context': "{'default_composition_mode': 'mass_mail', 'default_template_id' : %d, 'default_use_template': True}" % (template.id),
'context': repr(context),
'view_mode': 'form,tree',
'view_id': view.id,
'target': 'new',
+10 -13
View File
@@ -2170,12 +2170,9 @@ class MailThread(models.AbstractModel):
mails_su = self.env['mail.mail'].sudo()
for subset in [self] if template else self:
composer_ctx['active_ids'] = subset.ids # still required for mass mail mode
if template:
composer_ctx['default_res_id'] = subset.ids[0] if subset.ids else 0
else:
composer_ctx['default_res_ids'] = subset.ids
if not template:
composer_values['body'] = bodies[subset.id]
composer_ctx['default_res_id'] = subset.id
composer = self.env['mail.compose.message'].with_context(
**composer_ctx
@@ -2185,9 +2182,9 @@ class MailThread(models.AbstractModel):
if template:
update_values = composer._onchange_template_id(
template.id,
composer_values['composition_mode'],
'mass_mail',
self._name,
subset.ids[0] if subset.ids else 0
subset.ids,
)['value']
composer.write(update_values)
@@ -2253,10 +2250,9 @@ class MailThread(models.AbstractModel):
for record in self:
if template:
composer = self.env['mail.compose.message'].with_context(
active_ids=record.ids, # still required for mass mail mode
default_composition_mode='comment',
default_model=self._name,
default_res_id=record.id,
default_res_ids=record.ids,
default_template_id=template.id,
).create({
'message_type': message_type,
@@ -2270,7 +2266,7 @@ class MailThread(models.AbstractModel):
template.id,
'comment',
self._name,
record.id,
record.ids,
)['value']
composer.write(update_values)
_mails_as_sudo, messages_as_sudo = composer._action_send_mail()
@@ -3049,11 +3045,12 @@ class MailThread(models.AbstractModel):
lang = False
if force_email_lang:
lang = force_email_lang
elif {'default_template_id', 'default_model', 'default_res_id'} <= self.env.context.keys():
elif {'default_template_id', 'default_model', 'default_res_ids'} <= self.env.context.keys():
# TDE FIXME: this whole brol should be cleaned !
res_ids = self.env['mail.compose.message']._parse_res_ids(self.env.context['default_res_ids'])
template = self.env['mail.template'].browse(self.env.context['default_template_id'])
if template and template.lang:
lang = template._render_lang([self.env.context['default_res_id']])[self.env.context['default_res_id']]
if res_ids and template and template.lang:
lang = template._render_lang(res_ids)[res_ids[0]]
if not lang:
lang = self.env.context.get('lang')
+1 -2
View File
@@ -341,9 +341,8 @@ const KanbanActivity = AbstractField.extend({
views: [[false, "form"]],
target: "new",
context: {
default_res_id: this.res_id,
default_res_ids: [this.res_id],
default_model: this.model,
default_use_template: true,
default_template_id: templateID,
force_email: true,
},
@@ -550,7 +550,7 @@ Model({
default_is_log: this.composer.isLog,
default_model: this.composer.activeThread.model,
default_partner_ids: this.composer.recipients.map((partner) => partner.id),
default_res_id: this.composer.activeThread.id,
default_res_ids: [this.composer.activeThread.id],
default_subtype_xmlid: 'mail.mt_comment',
mail_post_autofollow: this.composer.activeThread.hasWriteAccess,
};
@@ -16,9 +16,8 @@ Model({
views: [[false, "form"]],
target: "new",
context: {
default_res_id: activity.thread.id,
default_res_ids: [activity.thread.id],
default_model: activity.thread.model,
default_use_template: true,
default_subtype_xmlid: 'mail.mt_comment',
default_template_id: this.id,
force_email: true,
@@ -619,7 +619,7 @@ QUnit.module("mail", {}, function () {
});
QUnit.test("activity with mail template: preview mail", async function (assert) {
assert.expect(10);
assert.expect(9);
const pyEnv = await startServer();
const resPartnerId1 = pyEnv["res.partner"].create({});
@@ -642,20 +642,16 @@ QUnit.module("mail", {}, function () {
patchWithCleanup(env.services.action, {
doAction(action) {
assert.step("do_action");
assert.strictEqual(
action.context.default_res_id,
resPartnerId1,
"Action should have the activity res id as default res id in context"
assert.deepEqual(
action.context.default_res_ids,
[resPartnerId1],
"Action should have the activity res id as default res ids in context"
);
assert.strictEqual(
action.context.default_model,
"res.partner",
"Action should have the activity res model as default model in context"
);
assert.ok(
action.context.default_use_template,
"Action should have true as default use_template in context"
);
assert.strictEqual(
action.context.default_template_id,
mailTemplateId1,
+2 -1
View File
@@ -69,10 +69,11 @@ class MockEmail(common.BaseCase, MockSmtplibCase):
with self.mock_smtplib_connection(), \
patch.object(IrMailServer, 'build_email', autospec=True, wraps=IrMailServer, side_effect=_ir_mail_server_build_email) as build_email_mocked, \
patch.object(IrMailServer, 'send_email', autospec=True, wraps=IrMailServer, side_effect=send_email_origin) as send_email_mocked, \
patch.object(MailMail, 'create', autospec=True, wraps=MailMail, side_effect=_mail_mail_create), \
patch.object(MailMail, 'create', autospec=True, wraps=MailMail, side_effect=_mail_mail_create) as mail_mail_create_mocked, \
patch.object(MailMail, 'unlink', autospec=True, wraps=MailMail, side_effect=_mail_mail_unlink):
self.build_email_mocked = build_email_mocked
self.send_email_mocked = send_email_mocked
self.mail_mail_create_mocked = mail_mail_create_mocked
yield
if mail_unlink_sent:
+4 -4
View File
@@ -84,7 +84,7 @@ class TestMailComposerForm(TestMailComposer):
form = Form(self.env['mail.compose.message'].with_context({
'default_partner_ids': partner_classic.ids,
'default_model': test_record._name,
'default_res_id': test_record.id,
'default_res_ids': test_record.ids,
}))
form.body = '<p>Hello</p>'
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 = '<p>Hello</p>'
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,
}))
-1
View File
@@ -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,
}"/>
<field name="binding_model_id" ref="base.model_res_partner"/>
+202 -76
View File
@@ -3,13 +3,15 @@
import ast
import base64
import re
import logging
from odoo import _, api, fields, models, tools, Command
from odoo.exceptions import UserError
from odoo.exceptions import UserError, ValidationError
from odoo.osv import expression
from odoo.tools import email_re
_logger = logging.getLogger(__name__)
def _reopen(self, res_id, model, context=None):
# save original model in context, because selecting the list of available
@@ -41,14 +43,13 @@ class MailComposer(models.TransientModel):
_batch_size = 500
@api.model
def default_get(self, fields):
""" Handle composition mode. Some details about context keys:
- comment: default mode, model and ID of a record the user comments
- default_model or active_model
- default_res_id or active_id
- mass_mail: model and IDs of records the user mass-mails
- active_ids: record IDs
- default_model or active_model
def default_get(self, fields_list):
""" Handle composition mode and contextual computation, until moving
to computed fields. Support active_model / active_id(s) as valid default
values, as this comes from standard web client usage.
Note that supporting active_ids through composer is still done, as we
may have to give a huge list of IDs that won't fit into res_ids field.
"""
# support subtype xmlid, like ``message_post``, when easier than using ``ref``
if self.env.context.get('default_subtype_xmlid'):
@@ -57,12 +58,15 @@ class MailComposer(models.TransientModel):
self.env.context['default_subtype_xmlid']
)
)
# deprecated record context management
if 'default_res_id' in self.env.context:
raise ValueError(_("Deprecated usage of 'default_res_id', should use 'default_res_ids'."))
result = super(MailComposer, self).default_get(fields)
result = super().default_get(fields_list)
# author
missing_author = 'author_id' in fields and 'author_id' not in result
missing_email_from = 'email_from' in fields and 'email_from' not in result
missing_author = 'author_id' in fields_list and 'author_id' not in result
missing_email_from = 'email_from' in fields_list and 'email_from' not in result
if missing_author or missing_email_from:
author_id, email_from = self.env['mail.thread']._message_compute_author(result.get('author_id'), result.get('email_from'), raise_on_email=False)
if missing_email_from:
@@ -70,26 +74,36 @@ class MailComposer(models.TransientModel):
if missing_author:
result['author_id'] = author_id
if 'model' in fields and 'model' not in result:
# record context management
if 'model' in fields_list and 'model' not in result:
result['model'] = self.env.context.get('active_model')
if 'res_id' in fields and 'res_id' not in result:
result['res_id'] = self.env.context.get('active_id')
if 'reply_to_mode' in fields and 'reply_to_mode' not in result and result.get('model'):
# doesn't support threading
if 'res_ids' in fields_list and 'res_ids' not in result:
if self.env.context.get('active_ids'):
active_res_ids = self._parse_res_ids(self.env.context['active_ids'])
# beware, field is limited in storage, usage of active_ids in context still required
if active_res_ids and len(active_res_ids) <= self._batch_size:
result['res_ids'] = self.env.context['active_ids']
elif self.env.context.get('active_id'):
result['res_ids'] = [self.env.context['active_id']]
else:
result['res_ids'] = False
# record / parent based computation
if result.get('composition_mode') == 'comment' and (set(fields_list) & {'model', 'res_ids', 'partner_ids', 'record_name', 'subject'}):
result.update(self.get_record_data(result))
# threading support check: 'update' requires to use message_update/post
if 'reply_to_mode' in fields_list and 'reply_to_mode' not in result and result.get('model'):
if result['model'] not in self.env or not hasattr(self.env[result['model']], 'message_post'):
result['reply_to_mode'] = 'new'
if 'active_domain' in self.env.context: # not context.get() because we want to keep global [] domains
result['active_domain'] = '%r' % self.env.context.get('active_domain')
if result.get('composition_mode') == 'comment' and (set(fields) & set(['model', 'res_id', 'partner_ids', 'record_name', 'subject'])):
result.update(self.get_record_data(result))
# when being in new mode, create_uid is not granted -> ACLs issue may arise
if 'create_uid' in fields and 'create_uid' not in result:
if 'create_uid' in fields_list and 'create_uid' not in result:
result['create_uid'] = self.env.uid
filtered_result = dict((fname, result[fname]) for fname in result if fname in fields)
return filtered_result
return {
fname: result[fname]
for fname in result if fname in fields_list
}
def _partner_ids_domain(self):
return expression.OR([
@@ -120,11 +134,15 @@ class MailComposer(models.TransientModel):
selection=[('comment', 'Post on a document'),
('mass_mail', 'Email Mass Mailing')],
string='Composition mode', default='comment')
composition_batch = fields.Boolean(
'Batch composition', compute='_compute_composition_batch') # more than 1 record (raw source)
model = fields.Char('Related Document Model')
res_id = fields.Integer('Related Document ID')
res_ids = fields.Text('Related Document IDs')
res_domain = fields.Text('Active domain')
res_domain_user_id = fields.Many2one(
'res.users', string='Responsible',
help='Used as context used to evaluate composer domain')
record_name = fields.Char('Message Record Name')
use_active_domain = fields.Boolean('Use active domain')
active_domain = fields.Text('Active domain', readonly=True)
# characteristics
message_type = fields.Selection([
('comment', 'Comment'),
@@ -158,6 +176,35 @@ 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.constrains('res_ids')
def _check_res_ids(self):
""" Check res_ids is a valid list of integers (or Falsy). """
for composer in self:
composer._evaluate_res_ids()
@api.constrains('res_domain')
def _check_res_domain(self):
""" Check domain is a valid domain if set (otherwise it is considered
as a Falsy leaf. """
for composer in self:
composer._evaluate_res_domain()
@api.depends('res_ids')
def _compute_composition_batch(self):
""" Determine if batch mode is activated:
* using res_domain: always batch (even if result is singleton at a
given time, it is user and time dependent, hence batch);
* res_ids: if more than one item in the list (void and singleton are
not batch);
"""
for composer in self:
if composer.res_domain:
composer.composition_batch = True
continue
res_ids = composer._evaluate_res_ids()
composer.composition_batch = len(res_ids) > 1 if res_ids else False
@api.depends('reply_to_force_new')
def _compute_reply_to_mode(self):
for composer in self:
@@ -186,18 +233,19 @@ class MailComposer(models.TransientModel):
@api.onchange('template_id')
def _onchange_template_id_wrapper(self):
self.ensure_one()
values = self._onchange_template_id(self.template_id.id, self.composition_mode, self.model, self.res_id)['value']
values = self._onchange_template_id(self.template_id.id, self.composition_mode, self.model, self._evaluate_res_ids())['value']
for fname, value in values.items():
setattr(self, fname, value)
def _onchange_template_id(self, template_id, composition_mode, model, res_id):
def _onchange_template_id(self, template_id, composition_mode, model, res_ids):
""" Perform the onchange.
* mass_mailing: we cannot render, so return the template values
* mass_mailing or comment in batch: we cannot render, so return the
template values
* normal mode: return rendered values
-> for x2many field, this onchange return command instead of ids
"""
if template_id and composition_mode == 'mass_mail':
if template_id and (composition_mode == 'mass_mail' or len(res_ids) > 1):
# copy raw template values (not rendered due to mass mode)
template = self.env['mail.template'].browse(template_id)
values = dict(
@@ -214,11 +262,13 @@ class MailComposer(models.TransientModel):
values['mail_server_id'] = template.mail_server_id.id
if not tools.is_html_empty(template.body_html):
values['body'] = template.body_html
elif template_id:
elif template_id and len(res_ids) <= 1:
# render template (mono record, comment mode) and set it as composer values
# trick to evaluate qweb even when having no records
template_res_ids = res_ids if res_ids else [0]
values = self._generate_template_for_composer(
self.env['mail.template'].browse(template_id),
[res_id],
template_res_ids,
('attachment_ids',
'body_html',
'email_cc',
@@ -230,7 +280,7 @@ class MailComposer(models.TransientModel):
'report_template',
'subject',
)
)[res_id]
)[template_res_ids[0]]
# transform attachments into attachment_ids; not attached to the document because this will
# be done further in the posting process, allowing to clean database if email not send
attachment_ids = []
@@ -250,7 +300,7 @@ class MailComposer(models.TransientModel):
default_values = self.with_context(
default_composition_mode=composition_mode,
default_model=model,
default_res_id=res_id
default_res_ids=res_ids
).default_get(['attachment_ids',
'body',
'composition_mode',
@@ -260,7 +310,7 @@ class MailComposer(models.TransientModel):
'parent_id',
'partner_ids',
'reply_to',
'res_id',
'res_ids',
'subject',
])
values = dict(
@@ -286,31 +336,33 @@ class MailComposer(models.TransientModel):
a document (model, res_id). This is based on previously computed default
values. """
result, record_name, subject = {}, False, False
model, res_id = values.get('model'), values.get('res_id')
model = values.get('model')
res_ids = self._parse_res_ids(values['res_ids']) if values.get('res_ids') else []
if values.get('parent_id'):
parent = self.env['mail.message'].browse(values.get('parent_id'))
if not model:
model = parent.model
result['model'] = model
if not res_id:
res_id = parent.res_id
result['res_id'] = res_id
if not res_ids:
res_ids = [parent.res_id]
result['res_ids'] = res_ids
result['partner_ids'] = values.get('partner_ids', list()) + parent.partner_ids.ids
record_name = parent.record_name
subject = tools.ustr(parent.subject or '')
if (not subject or not record_name) and model and res_id:
record = self.env[model].browse(res_id)
if (not subject or not record_name) and model and res_ids and len(res_ids) == 1:
record = self.env[model].browse(res_ids[0])
if not record_name:
record_name = record.display_name
if not subject:
subject = record._message_compute_subject()
elif model and res_id:
record = self.env[model].browse(res_id)
elif model and len(res_ids) == 1:
record = self.env[model].browse(res_ids[0])
record_name = record.display_name or ''
subject = record._message_compute_subject()
result['record_name'] = record_name
result['subject'] = subject
if values.get('parent_id') or len(res_ids) == 1: # to be cleanup when moving to computed fields
result['record_name'] = record_name
result['subject'] = subject
return result
@@ -356,18 +408,14 @@ class MailComposer(models.TransientModel):
result_mails_su, result_messages = self.env['mail.mail'].sudo(), self.env['mail.message']
for wizard in self:
# Mass Mailing
mass_mode = wizard.composition_mode == 'mass_mail'
# wizard works in batch mode: [res_id] or active_ids or active_domain
if mass_mode and wizard.use_active_domain and wizard.model:
res_ids = self.env[wizard.model].search(ast.literal_eval(wizard.active_domain)).ids
elif mass_mode and wizard.model and self.env.context.get('active_ids'):
res_ids = self.env.context['active_ids']
if wizard.res_domain:
search_domain = wizard._evaluate_res_domain()
search_user = wizard.res_domain_user_id or self.env.user
res_ids = self.env[wizard.model].with_user(search_user).search(search_domain).ids
else:
res_ids = [wizard.res_id] if wizard.res_id else []
res_ids = wizard._evaluate_res_ids()
# in comment mode: raise here as anyway message_post will raise.
if not res_ids and not mass_mode:
if not res_ids and wizard.composition_mode == 'comment':
raise ValueError(
_('Mail composer in comment mode should run on at least one record. No records found (model %(model_name)s).',
model_name=wizard.model)
@@ -386,7 +434,7 @@ class MailComposer(models.TransientModel):
self.ensure_one()
post_values_all = self._prepare_mail_values(res_ids)
ActiveModel = self.env[self.model] if self.model and hasattr(self.env[self.model], 'message_post') else self.env['mail.thread']
if len(res_ids) > 1:
if self.composition_batch:
# do not send emails directly but use the queue instead
# add context key to avoid subscribing the author
ActiveModel = ActiveModel.with_context(
@@ -509,16 +557,17 @@ class MailComposer(models.TransientModel):
"""
self.ensure_one()
email_mode = self.composition_mode == 'mass_mail'
rendering_mode = email_mode or self.composition_batch
# values that do not depend on rendering mode
base_values = self._prepare_mail_values_static()
additional_values_all = {}
# rendered based on raw content (wizard or template)
if email_mode and self.model:
if rendering_mode and self.model:
additional_values_all = self._prepare_mail_values_dynamic(res_ids)
# wizard content already rendered
elif not email_mode:
elif not rendering_mode:
additional_values_all = self._prepare_mail_values_rendered(res_ids)
mail_values_all = {
@@ -607,17 +656,22 @@ class MailComposer(models.TransientModel):
bodies = self._render_field(
'body', res_ids,
# We want to preserve comments in emails so as to keep mso conditionals
options={'preserve_comments': self.composition_mode == 'mass_mail'},
options={'preserve_comments': email_mode},
)
emails_from = self._render_field('email_from', res_ids)
mail_values_all = {
res_id: {
'body': bodies[res_id], # should be void
'body_html': bodies[res_id] if email_mode else False,
'email_from': emails_from[res_id],
'res_id': res_id,
'subject': subjects[res_id],
# some fields are specific to mail or message
**(
{
'auto_delete': self.auto_delete,
'body_html': bodies[res_id],
'res_id': res_id,
} if email_mode else {}),
}
for res_id in res_ids
}
@@ -639,6 +693,9 @@ class MailComposer(models.TransientModel):
for res_id in res_ids:
# remove attachments from template values as they should not be rendered
template_values[res_id].pop('attachment_ids', None)
# auto_delete is not supported in batch post, only in mass mail
if not email_mode:
template_values[res_id].pop('auto_delete', False)
mail_values_all[res_id].update(template_values[res_id])
# Handle recipients. Without template, if no partner_ids is given, update
@@ -674,25 +731,34 @@ class MailComposer(models.TransientModel):
(name, base64.b64decode(enc_cont))
for name, enc_cont in mail_values.pop('attachments', [])
]
mail_values['attachment_ids'] = record._process_attachments_for_post(
decoded_attachments,
attachment_ids,
{'model': 'mail.message', 'res_id': 0}
)['attachment_ids']
# email_mode: prepare processed attachments as commands for mail.mail
if email_mode:
mail_values['attachment_ids'] = record._process_attachments_for_post(
decoded_attachments,
attachment_ids,
{'model': 'mail.message', 'res_id': 0}
)['attachment_ids']
# comment mode: prepare attachments as a list of IDs, to be processed by MailThread
else:
mail_values['attachments'] = decoded_attachments
mail_values['attachment_ids'] = attachment_ids
# headers
mail_values['headers'] = repr(record._notify_by_email_get_headers())
if email_mode:
mail_values['headers'] = repr(record._notify_by_email_get_headers())
# recipients: transform partner_ids (field used in mail_message) into
# recipient_ids, used by mail_mail
recipient_ids_all = mail_values.pop('partner_ids', []) + self.partner_ids.ids
mail_values['recipient_ids'] = [(4, pid) for pid in recipient_ids_all]
if email_mode:
recipient_ids_all = mail_values.pop('partner_ids', []) + self.partner_ids.ids
mail_values['recipient_ids'] = [(4, pid) for pid in recipient_ids_all]
# when having no specific reply_to -> fetch rendered email_from
reply_to = reply_to_values.get(res_id)
if not reply_to:
reply_to = mail_values.get('email_from', False)
mail_values['reply_to'] = reply_to
if email_mode:
reply_to = reply_to_values.get(res_id)
if not reply_to:
reply_to = mail_values.get('email_from', False)
mail_values['reply_to'] = reply_to
return mail_values_all
@@ -901,3 +967,63 @@ class MailComposer(models.TransientModel):
]
}
return recipients_info
# ----------------------------------------------------------------------
# MISC UTILS
# ----------------------------------------------------------------------
def _evaluate_res_domain(self):
""" Parse composer domain, which can be: an already valid list or
tuple (generally in code), a list or tuple as a string (coming from
actions). Void strings are considered as a falsy domain.
:return: an Odoo domain (list of leaves) """
self.ensure_one()
if isinstance(self.res_domain, (str, bool)) and not self.res_domain:
return expression.FALSE_DOMAIN
try:
domain = self.res_domain
if isinstance(self.res_domain, str):
domain = ast.literal_eval(domain)
expression.expression(
domain,
self.env[self.model],
)
except (ValueError, AssertionError) as e:
raise ValidationError(
_("Invalid domain %(domain)r (type %(domain_type)s)",
domain=self.res_domain,
domain_type=type(self.res_domain))
) from e
return domain
def _evaluate_res_ids(self):
""" Parse composer res_ids, which can be: an already valid list or
tuple (generally in code), a list or tuple as a string (coming from
actions). Void strings / missing values are evaluated as an empty list.
Note that 'active_ids' context key is supported at this point as mailing
on big ID list would create issues if stored in database.
:return: a list of IDs (empty list in case of falsy strings)"""
self.ensure_one()
return self._parse_res_ids(
self.res_ids or self.env.context.get('active_ids')
) or []
@api.model
def _parse_res_ids(self, res_ids):
if tools.is_list_of(res_ids, int) or not res_ids:
return res_ids
error_msg = _("Invalid res_ids %(res_ids_str)s (type %(res_ids_type)s)",
res_ids_str=res_ids,
res_ids_type=type(res_ids))
try:
res_ids = ast.literal_eval(res_ids)
except Exception as e:
raise ValidationError(error_msg) from e
if not tools.is_list_of(res_ids, int):
raise ValidationError(error_msg)
return res_ids
@@ -12,6 +12,7 @@
<field name="author_id" invisible="1"/>
<field name="auto_delete" invisible="1"/>
<field name="auto_delete_message" invisible="1"/>
<field name="composition_batch" invisible="1"/>
<field name="composition_mode" invisible="1"/>
<field name="email_layout_xmlid" invisible="1"/>
<field name="is_log" invisible="1"/>
@@ -21,7 +22,9 @@
<field name="parent_id" invisible="1"/>
<field name="record_name" invisible="1"/>
<field name="render_model" invisible="1"/>
<field name="res_id" invisible="1"/>
<field name="res_domain" invisible="1"/>
<field name="res_domain_user_id" invisible="1"/>
<field name="res_ids" invisible="1"/>
<field name="subtype_id" invisible="1"/>
<!-- visible wizard -->
<field name="email_from"
+10 -6
View File
@@ -1068,17 +1068,21 @@ class MassMailing(models.Model):
'record_name': False,
'reply_to_force_new': mailing.reply_to_mode == 'new',
'subject': mailing.subject,
'template_id': None,
'template_id': False,
}
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)
extra_context = mailing._get_mass_mailing_context()
composer = composer.with_context(active_ids=res_ids, **extra_context)
composer = self.env['mail.compose.message'].with_context(
active_ids=res_ids,
default_composition_mode='mass_mail',
**mailing._get_mass_mailing_context()
).create(composer_values)
# auto-commit except in testing mode
auto_commit = not getattr(threading.current_thread(), 'testing', False)
composer._action_send_mail(auto_commit=auto_commit)
composer._action_send_mail(
auto_commit=not getattr(threading.current_thread(), 'testing', False)
)
mailing.write({
'state': 'done',
'sent_date': fields.Datetime.now(),
@@ -45,7 +45,7 @@ class TestMassMailValues(MassMailCommon):
composer = self.env['mail.compose.message'].with_user(self.user_marketing).with_context({
'default_composition_mode': 'mass_mail',
'default_model': 'res.partner',
'default_res_id': recipient.id,
'default_res_ids': recipient.ids,
}).create({
'subject': 'Mass Mail Responsive',
'body': 'I am Responsive body',
@@ -101,7 +101,7 @@ class MailComposeMessage(models.TransientModel):
'body_html': self.body,
'campaign_id': self.campaign_id.id,
'mailing_model_id': self.env['ir.model']._get(self.model).id,
'mailing_domain': self.active_domain,
'mailing_domain': self.res_domain if self.res_domain else f"[('id', 'in', {self.res_ids})]",
'name': self.mass_mailing_name,
'reply_to': self.reply_to if self.reply_to_mode == 'new' else False,
'reply_to_mode': self.reply_to_mode,
-2
View File
@@ -427,7 +427,6 @@
<field name="target">new</field>
<field name="context" eval="{
'default_composition_mode': 'mass_mail',
'default_use_template': False,
}"/>
<field name="binding_model_id" ref="project.model_project_task"/>
<field name="binding_view_types">list</field>
@@ -440,7 +439,6 @@
<field name="target">new</field>
<field name="context" eval="{
'default_composition_mode': 'mass_mail',
'default_use_template': False,
}"/>
<field name="binding_model_id" ref="project.model_project_project"/>
<field name="binding_view_types">list</field>
+2 -8
View File
@@ -419,10 +419,7 @@ class PurchaseOrder(models.Model):
ctx = dict(self.env.context or {})
ctx.update({
'default_model': 'purchase.order',
'active_model': 'purchase.order',
'active_id': self.ids[0],
'default_res_id': self.ids[0],
'default_use_template': bool(template_id),
'default_res_ids': self.ids,
'default_template_id': template_id,
'default_composition_mode': 'comment',
'default_email_layout_xmlid': "mail.mail_notification_layout_with_responsible_signature",
@@ -781,10 +778,7 @@ class PurchaseOrder(models.Model):
ctx = dict(self.env.context or {})
ctx.update({
'default_model': 'purchase.order',
'active_model': 'purchase.order',
'active_id': self.ids[0],
'default_res_id': self.ids[0],
'default_use_template': bool(template_id),
'default_res_ids': self.ids,
'default_template_id': template_id,
'default_composition_mode': 'comment',
'default_email_layout_xmlid': "mail.mail_notification_layout_with_responsible_signature",
+1 -2
View File
@@ -332,8 +332,7 @@ class Repair(models.Model):
template_id = self.env.ref('repair.mail_template_repair_quotation').id
ctx = {
'default_model': 'repair.order',
'default_res_id': self.id,
'default_use_template': bool(template_id),
'default_res_ids': self.ids,
'default_template_id': template_id,
'default_composition_mode': 'comment',
'default_email_layout_xmlid': 'mail.mail_notification_light',
+5 -6
View File
@@ -111,21 +111,20 @@ class ResCompany(models.Model):
template = self.env.ref('sale.email_template_edi_sale', False)
message_composer = self.env['mail.compose.message'].with_context(
default_use_template=bool(template),
mark_so_as_sent=True,
default_email_layout_xmlid='mail.mail_notification_layout_with_responsible_signature',
proforma=self.env.context.get('proforma', False),
force_email=True,
).create({
'res_id': sample_sales_order.id,
'template_id': template and template.id or False,
'model': 'sale.order',
'composition_mode': 'comment'
'res_ids': sample_sales_order.ids,
'template_id': template.id if template else False,
'model': sample_sales_order._name,
'composition_mode': 'comment',
})
# Simulate the onchange (like trigger in form the view)
update_values = message_composer._onchange_template_id(
template.id, 'comment', 'sale.order', sample_sales_order.id
template.id, 'comment', sample_sales_order._name, sample_sales_order.ids
)['value']
message_composer.write(update_values)
+1 -3
View File
@@ -713,8 +713,7 @@ class SaleOrder(models.Model):
lang = mail_template._render_lang(self.ids)[self.id]
ctx = {
'default_model': 'sale.order',
'default_res_id': self.id,
'default_use_template': bool(mail_template),
'default_res_ids': self.ids,
'default_template_id': mail_template.id if mail_template else None,
'default_composition_mode': 'comment',
'mark_so_as_sent': True,
@@ -877,7 +876,6 @@ class SaleOrder(models.Model):
if template.lang:
lang = template._render_lang(self.ids)[self.id]
ctx = {
'default_use_template': bool(template_id),
'default_template_id': template_id,
'default_order_id': self.id,
'mark_so_as_canceled': True,
@@ -460,7 +460,6 @@
<field name="target">new</field>
<field name="context" eval="{
'default_composition_mode': 'mass_mail',
'default_use_template': False,
}"/>
<field name="binding_model_id" ref="stock.model_stock_picking"/>
<field name="binding_view_types">list</field>
-1
View File
@@ -904,7 +904,6 @@ class Survey(models.Model):
local_context = dict(
self.env.context,
default_survey_id=self.id,
default_use_template=bool(template),
default_template_id=template and template.id or False,
default_email_layout_xmlid='mail.mail_notification_light',
default_send_email=(self.access_mode != 'public'),
@@ -350,10 +350,9 @@ QUnit.module("test_mail", {}, function () {
assert.deepEqual(
{
default_model: "mail.test.activity",
default_res_id: mailTestActivityId2,
default_res_ids: [mailTestActivityId2],
default_subtype_xmlid: 'mail.mt_comment',
default_template_id: mailTemplateId1,
default_use_template: true,
force_email: true,
},
action.context
+283 -129
View File
@@ -1,9 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from unittest.mock import patch
from ast import literal_eval
from unittest.mock import DEFAULT, patch
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.exceptions import AccessError
@@ -84,16 +86,16 @@ class TestMailComposer(TestMailCommon, TestRecipients):
"""
base_context = {
'default_model': records._name,
'default_res_ids': records.ids,
}
if len(records) == 1:
base_context['default_composition_mode'] = 'comment'
base_context['default_res_id'] = records.id
else:
base_context['default_composition_mode'] = 'mass_mail'
base_context['active_ids'] = records.ids
if add_web:
base_context['active_model'] = records._name
base_context['active_id'] = records[0].id
base_context['active_ids'] = records.ids
if values:
base_context.update(**values)
return base_context
@@ -111,6 +113,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.assertFalse(composer_form.composition_batch)
self.assertEqual(composer_form.composition_mode, 'comment')
self.assertEqual(composer_form.email_from, self.env.user.email_formatted)
self.assertFalse(composer_form.mail_server_id)
@@ -119,7 +122,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.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, self.test_record._message_compute_subject())
self.assertIn(f'Ticket for {self.test_record.name}', composer_form.subject,
'Check effective content')
@@ -187,6 +190,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, '<p>TemplateBody %s</p>' % 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, '<p>TemplateBody </p>')
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, '<p>Test Body <t t-out="record.name>/></p>')
self.assertEqual(composer.mail_server_id, self.mail_server_global)
self.assertEqual(composer.subject, 'My amazing subject for {{ record.name }}')
if composition_mode == 'comment':
if composition_mode == 'comment' and not batch:
self.assertEqual(composer.record_name, self.test_record.name)
else:
self.assertFalse(composer.record_name)
self.assertEqual(composer.subject, 'My amazing subject for {{ record.name }}')
# changing template should update its content
composer.write({'template_id': self.template.id})
@@ -569,7 +609,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'<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)
@@ -598,7 +638,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.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')
@@ -618,7 +658,7 @@ class TestComposerInternals(TestMailComposer):
# values are reset with default_get call, if it returns value
# (aka subject for comment mode), and not record_name because
# it was forgotten probably
if composition_mode == 'comment':
if composition_mode == 'comment' and not batch:
self.assertFalse(composer.body)
# self.assertFalse(composer.mail_server_id.id)
self.assertEqual(composer.mail_server_id, self.mail_server_global,
@@ -650,7 +690,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'<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)
@@ -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'<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)
@@ -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):
+10 -10
View File
@@ -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': '<p>Test Body</p>',
'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': '<p>Test Body</p>',
@@ -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 = '<p>Test Body</p>'
@@ -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': '<p>Test Body</p>',
@@ -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()
+1 -2
View File
@@ -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 }}',
})
+1 -2
View File
@@ -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)
+1 -3
View File
@@ -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,
},
}
@@ -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',
)