[REF] mail: move composer 'attachment_ids' field from onchange to computed

RATIONALE

Remove _onchange_template_id / defaults usage for composer fields. It
lacks control and make value computation hard to understand and predict.

PURPOSE

Split _onchange_template_id + default_get + get_record_data usage into editable
computed stored fields. Each field should have its own method with its triggers
(template, composition mode, ...).

SPECIFICATIONS

Improve composer to correctly compute fields ``attachment_ids`` and reports
(``report_template_ids`` on template that generates reports) in compute method.

Computation is based on template and composition mode. In monorecord comment
mode, template is used to generate attachments based on both attachment_ids
of template, and reports coming from report_template_ids. Those are generated
based on the current record to display. As template generation returns a list
of tuples, new attachments are created on the fly during the compute.

In batch or email mode, only attachment_ids from template are used on the
composer. Reports will be generated at sending time.

When template is removed, attachments are reset.

Task-2088884 (Mail: Use editable computed stored fields in composer)

Part-of: odoo/odoo#107356
This commit is contained in:
Thibault Delavallée
2023-01-27 19:56:07 +01:00
parent 6a78ed2cad
commit bfc583fcf3
2 changed files with 55 additions and 44 deletions
+47 -25
View File
@@ -98,7 +98,8 @@ class MailComposer(models.TransientModel):
template_id = fields.Many2one('mail.template', 'Use template', domain="[('model', '=', model)]")
attachment_ids = fields.Many2many(
'ir.attachment', 'mail_compose_message_ir_attachments_rel',
'wizard_id', 'attachment_id', 'Attachments')
'wizard_id', 'attachment_id', string='Attachments',
compute='_compute_attachment_ids', readonly=False, store=True)
email_layout_xmlid = fields.Char('Email Notification Layout', copy=False)
email_add_signature = fields.Boolean(default=True)
# origin
@@ -226,6 +227,48 @@ class MailComposer(models.TransientModel):
if not composer.template_id:
composer.body = False
@api.depends('composition_mode', 'model', 'res_domain', 'res_ids', 'template_id')
def _compute_attachment_ids(self):
""" Computation is based on template and composition mode. In monorecord
comment mode, template is used to generate attachments based on both
attachment_ids of template, and reports coming from report_template_ids.
Those are generated based on the current record to display. As template
generation returns a list of tuples, new attachments are created on
the fly during the compute.
In batch or email mode, only attachment_ids from template are used on
the composer. Reports will be generated at sending time.
When template is removed, attachments are reset. """
for composer in self:
res_ids = composer._evaluate_res_ids() or [0]
if (composer.template_id.attachment_ids and
(composer.composition_mode == 'mass_mail' or composer.composition_batch)):
composer.attachment_ids = composer.template_id.attachment_ids
elif composer.template_id and composer.composition_mode == 'comment' and len(res_ids) == 1:
rendered_values = composer._generate_template_for_composer(
composer.template_id,
res_ids,
('attachment_ids', 'attachments'),
)[res_ids[0]]
attachment_ids = rendered_values.get('attachment_ids') or []
# 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
if rendered_values.get('attachments'):
attachment_ids += self.env['ir.attachment'].create([
{'name': attach_fname,
'datas': attach_datas,
'res_model': 'mail.compose.message',
'res_id': 0,
'type': 'binary', # override default_type from context, possibly meant for another model!
} for attach_fname, attach_datas in rendered_values.pop('attachments')
]).ids
if attachment_ids:
composer.attachment_ids = attachment_ids
elif not composer.template_id:
composer.attachment_ids = False
@api.depends('composition_mode', 'email_from', 'model',
'res_domain', 'res_ids', 'template_id')
def _compute_authorship(self):
@@ -510,8 +553,6 @@ class MailComposer(models.TransientModel):
for field in ()
if template[field]
)
if template.attachment_ids:
values['attachment_ids'] = [att.id for att in template.attachment_ids]
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
@@ -519,40 +560,21 @@ class MailComposer(models.TransientModel):
values = self._generate_template_for_composer(
self.env['mail.template'].browse(template_id),
template_res_ids,
('attachment_ids',
'report_template_ids',
)
()
)[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 = []
Attachment = self.env['ir.attachment']
for attach_fname, attach_datas in values.pop('attachments', []):
data_attach = {
'name': attach_fname,
'datas': attach_datas,
'res_model': 'mail.compose.message',
'res_id': 0,
'type': 'binary', # override default_type from context, possibly meant for another model!
}
attachment_ids.append(Attachment.create(data_attach).id)
if values.get('attachment_ids', []) or attachment_ids:
values['attachment_ids'] = [Command.set(values.get('attachment_ids', []) + attachment_ids)]
else:
default_values = self.with_context(
default_composition_mode=composition_mode,
default_model=model,
default_res_ids=res_ids
).default_get(['attachment_ids',
'composition_mode',
).default_get(['composition_mode',
'model',
'parent_id',
'res_ids',
])
values = dict(
(key, default_values[key])
for key in ('attachment_ids',
) if key in default_values)
for key in () if key in default_values)
# This onchange should return command instead of ids for x2many field.
values = self._convert_to_write(values)
+8 -19
View File
@@ -484,8 +484,6 @@ class TestComposerInternals(TestMailComposer):
composer = self.env['mail.compose.message'].with_context(ctx).create({
'body': '<p>Test Body</p>',
})
# currently onchange necessary
composer._onchange_template_id_wrapper()
# values coming from template: attachment_ids + report in comment
if composition_mode == 'comment' and not batch:
@@ -515,29 +513,22 @@ class TestComposerInternals(TestMailComposer):
else:
self.assertEqual(composer.attachment_ids, attachs + extra_attach)
# update with template with void values: values are kept
# update with template with void values: values are kept, void
# value is not forced in rendering mode as well as when copying
# template values
composer.write({'template_id': template_void.id})
# currently onchange necessary
composer._onchange_template_id_wrapper()
if composition_mode == 'comment' and not batch:
self.assertEqual(composer.attachment_ids, attachs + extra_attach + generated,
'TODO: Values are kept (should be reset ?)')
self.assertEqual(composer.attachment_ids, attachs + extra_attach + generated)
else:
self.assertEqual(composer.attachment_ids, attachs + extra_attach,
'TODO: Values are kept (should be reset ?)')
self.assertEqual(composer.attachment_ids, attachs + extra_attach)
# reset template: values are kept
# reset template: values are reset
composer.write({'template_id': False})
# currently onchange necessary
composer._onchange_template_id_wrapper()
if composition_mode == 'comment' and not batch:
self.assertEqual(composer.attachment_ids, attachs + extra_attach + generated,
'TODO: Values are kept (should be reset ?)')
self.assertFalse(composer.attachment_ids)
else:
self.assertEqual(composer.attachment_ids, attachs + extra_attach,
'TODO: Values are kept (should be reset ?)')
self.assertFalse(composer.attachment_ids)
@users('employee')
@mute_logger('odoo.addons.mail.models.mail_mail')
@@ -1098,10 +1089,8 @@ class TestComposerInternals(TestMailComposer):
self._get_web_context(self.test_record)
).create({
'body': '<p>Template Body</p>',
'partner_ids': [self.partner_employee_2.id],
'template_id': template_1.id,
})
composer._onchange_template_id_wrapper()
composer._action_send_mail()
self.assertEqual(