[IMP] mail, various: allow to link multiple reports to templates

RATIONALE

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

SPECIFICATIONS

Update report_template field on template model to be a many2many field instead
of a many2one. It allows to attach multiple dynamic reports to a given template
instead of being limited to a single one.

Name should now come from the report itself, which should be considered as
complete by itself. Template cannot override report naming anymore.

Task-2868153 (Mail: Allow multi reports in mail templates)

Part-of: odoo/odoo#99482
This commit is contained in:
Thibault Delavallée
2023-01-17 20:58:40 +01:00
parent fbcc1bf4c0
commit 28b4ba4049
17 changed files with 149 additions and 112 deletions
+3 -6
View File
@@ -54,8 +54,7 @@
</p>
</div>
</field>
<field name="report_template" ref="account_invoices"/>
<field name="report_name">Invoice_{{ (object.name or '').replace('/','_') }}{{ object.state == 'draft' and '_draft' or '' }}</field>
<field name="report_template_ids" eval="[(4, ref('account.account_invoices'))]"/>
<field name="lang">{{ object.partner_id.lang }}</field>
<field name="auto_delete" eval="True"/>
</record>
@@ -84,8 +83,7 @@
</p>
</div>
</field>
<field name="report_template" ref="account.action_report_payment_receipt"/>
<field name="report_name">{{ (object.name or '').replace('/','-') }}</field>
<field name="report_template_ids" eval="[(4, ref('account.action_report_payment_receipt'))]"/>
<field name="lang">{{ object.partner_id.lang }}</field>
<field name="auto_delete" eval="True"/>
</record>
@@ -129,8 +127,7 @@
</p>
</div>
</field>
<field name="report_template" ref="account_invoices"/>
<field name="report_name">Credit_note_{{ (object.name or '').replace('/','_') }}{{ object.state == 'draft' and '_draft' or '' }}</field>
<field name="report_template_ids" eval="[(4, ref('account.account_invoices'))]"/>
<field name="lang">{{ object.partner_id.lang }}</field>
<field name="auto_delete" eval="True"/>
</record>
+2 -4
View File
@@ -22,8 +22,7 @@
<t t-out="object.event_id.user_id.signature or ''">--<br/>Mitchell Admin</t>
</t>
</div></field>
<field name="report_template" ref="action_report_event_registration_foldable_badge"/>
<field name="report_name">Foldable Badge - {{ (object.event_id.name or 'Event').replace('/','_') }}</field>
<field name="report_template_ids" eval="[(4, ref('event.action_report_event_registration_foldable_badge'))]"/>
<field name="lang">{{ object.partner_id.lang }}</field>
<field name="auto_delete" eval="True"/>
</record>
@@ -254,8 +253,7 @@
</td></tr>
</table>
</field>
<field name="report_template" ref="action_report_event_registration_full_page_ticket"/>
<field name="report_name">Full Page Ticket - {{ (object.event_id.name or 'Event').replace('/','') }}</field>
<field name="report_template_ids" eval="[(4, ref('event.action_report_event_registration_full_page_ticket'))]"/>
<field name="lang">{{ object.partner_id.lang }}</field>
</record>
+2 -4
View File
@@ -34,8 +34,7 @@
</div>
</div>
</field>
<field name="report_template" ref="loyalty.report_gift_card"/>
<field name="report_name">Your Gift Card</field>
<field name="report_template_ids" eval="[(4, ref('loyalty.report_gift_card'))]"/>
<field name="auto_delete" eval="True"/>
</record>
@@ -96,8 +95,7 @@
</tr>
</tbody></table>
</field>
<field name="report_template" ref="loyalty.report_loyalty_card"/>
<field name="report_name">Your Coupon Code</field>
<field name="report_template_ids" eval="[(4, ref('loyalty.report_loyalty_card'))]"/>
<field name="auto_delete" eval="True"/>
</record>
</odoo>
+44 -30
View File
@@ -7,6 +7,7 @@ import logging
from odoo import _, api, fields, models, tools, Command
from odoo.exceptions import UserError
from odoo.tools import is_html_empty
from odoo.tools.safe_eval import safe_eval, time
_logger = logging.getLogger(__name__)
@@ -63,10 +64,12 @@ class MailTemplate(models.Model):
'attachment_id', 'Attachments',
help="You may attach files to this template, to be added to all "
"emails created from this template")
report_name = fields.Char('Report Filename', translate=True, prefetch=True,
help="Name to use for the generated report file (may contain placeholders)\n"
"The extension can be omitted and will then come from the report type.")
report_template = fields.Many2one('ir.actions.report', 'Optional report to print and attach')
report_template_ids = fields.Many2many(
'ir.actions.report', relation='mail_template_ir_actions_report_rel',
column1='mail_template_id',
column2='ir_actions_report_id',
string='Reports to print and attach',
domain="[('model', '=', model)]")
# options
mail_server_id = fields.Many2one('ir.mail_server', 'Outgoing Mail Server', readonly=False,
help="Optional preferred server for outgoing mails. If not set, the highest "
@@ -198,14 +201,14 @@ class MailTemplate(models.Model):
def _generate_template_attachments(self, res_ids, render_fields,
render_results=None):
""" Render attachments of template 'self', returning values for records
given by 'res_ids'. Note that ``report_template`` returns values for
given by 'res_ids'. Note that ``report_template_ids`` returns values for
'attachments', as we have a list of tuple (report_name, base64 value)
for those reports. It is considered as being the job of callers to
transform those attachments into valid ``ir.attachment`` records.
:param list res_ids: list of record IDs on which template is rendered;
:param list render_fields: list of fields to render on template which
are specific to attachments, e.g. attachment_ids or report_template;
are specific to attachments, e.g. attachment_ids or report_template_ids;
:param dict render_results: res_ids-based dictionary of render values.
For each res_id, a dict of values based on render_fields is given
@@ -215,6 +218,11 @@ class MailTemplate(models.Model):
if render_results is None:
render_results = {}
# generating reports is done on a per-record basis, better ensure cache
# is filled up to avoid rendering and browsing in a loop
if res_ids and 'report_template_ids' in render_fields and self.report_template_ids:
self.env[self.model].browse(res_ids)
for res_id in res_ids:
values = render_results.setdefault(res_id, {})
@@ -223,27 +231,33 @@ class MailTemplate(models.Model):
values['attachment_ids'] = self.attachment_ids.ids
# generate attachments (reports)
if 'report_template' in render_fields and self.report_template:
report = self.report_template
if report.report_type in ['qweb-html', 'qweb-pdf']:
report_content, report_format = self.env['ir.actions.report']._render_qweb_pdf(report, [res_id])
else:
render_res = self.env['ir.actions.report']._render(report, [res_id])
if not render_res:
raise UserError(_('Unsupported report type %s found.', report.report_type))
report_content, report_format = render_res
report_content = base64.b64encode(report_content)
report_name = self._render_field('report_name', [res_id])[res_id]
if not report_name:
report_name = 'report.' + report.report_name
ext = "." + report_format
if not report_name.endswith(ext):
report_name += ext
values['attachments'] = [(report_name, report_content)]
elif 'report_template' in render_fields:
if 'report_template_ids' in render_fields and self.report_template_ids:
for report in self.report_template_ids:
# generate content
if report.report_type in ['qweb-html', 'qweb-pdf']:
report_content, report_format = self.env['ir.actions.report']._render_qweb_pdf(report, [res_id])
else:
render_res = self.env['ir.actions.report']._render(report, [res_id])
if not render_res:
raise UserError(_('Unsupported report type %s found.', report.report_type))
report_content, report_format = render_res
report_content = base64.b64encode(report_content)
# generate name
if report.print_report_name:
report_name = safe_eval(
report.print_report_name,
{
'object': self.env[self.model].browse(res_id),
'time': time,
}
)
else:
report_name = _('Report')
extension = "." + report_format
if not report_name.endswith(extension):
report_name += extension
values.setdefault('attachments', []).append((report_name, report_content))
elif 'report_template_ids' in render_fields:
values['attachments'] = []
# hook for attachments-specific computation, used currently only for accounting
@@ -436,7 +450,7 @@ class MailTemplate(models.Model):
'email_cc', # recipients
'email_to', # recipients
'partner_to', # recipients
'report_template', # attachments
'report_template_ids', # attachments
'scheduled_date', # specific
# not rendered (static)
'auto_delete',
@@ -482,7 +496,7 @@ class MailTemplate(models.Model):
)
# generate attachments if requested
if render_fields_set & {'attachment_ids', 'report_template'}:
if render_fields_set & {'attachment_ids', 'report_template_ids'}:
template._generate_template_attachments(
template_res_ids,
render_fields_set,
@@ -533,7 +547,7 @@ class MailTemplate(models.Model):
'model',
'partner_to',
'reply_to',
'report_template',
'report_template_ids',
'res_id',
'scheduled_date',
'subject',
+4 -4
View File
@@ -79,8 +79,9 @@
<field name="lang" placeholder="{{ object.partner_id.lang }}"/>
<field name="mail_server_id"/>
<field name="auto_delete"/>
<field name="report_template" domain="[('model','=',model)]"/>
<field name="report_name" attrs="{'invisible':[('report_template','=',False)]}"/>
<field name="report_template_ids" domain="[('model','=',model)]"
widget="many2many_tags"
options="{'no_create': True}"/>
</group>
</page>
</notebook>
@@ -102,7 +103,6 @@
<field name="email_from" optional="hidden"/>
<field name="email_to" optional="hidden"/>
<field name="partner_to" optional="hidden"/>
<field name="report_name" optional="hidden"/>
</tree>
</field>
</record>
@@ -112,7 +112,7 @@
<field name="model">mail.template</field>
<field name="arch" type="xml">
<search string="Templates">
<field name="name" filter_domain="['|', '|', '|',('name','ilike',self), ('report_name','ilike',self), ('subject','ilike',self), ('email_to','ilike',self)]" string="Templates"/>
<field name="name" filter_domain="['|', '|', ('name','ilike',self), ('subject','ilike',self), ('email_to','ilike',self)]" string="Templates"/>
<field name="lang"/>
<field name="model_id"/>
<filter name="base_templates" string="Base Templates" domain="[('template_category', '=', 'base_template')]"/>
+3 -3
View File
@@ -284,7 +284,7 @@ class MailComposer(models.TransientModel):
'mail_server_id',
'partner_ids',
'reply_to',
'report_template',
'report_template_ids',
'scheduled_date',
'subject',
)
@@ -709,7 +709,7 @@ class MailComposer(models.TransientModel):
'email_cc',
'mail_server_id',
'partner_ids',
'report_template',
'report_template_ids',
'scheduled_date',
)
)
@@ -906,7 +906,7 @@ class MailComposer(models.TransientModel):
# some fields behave / are named differently on template model
mapping = {
'attachments': 'report_template',
'attachments': 'report_template_ids',
'body': 'body_html',
'partner_ids': 'partner_to',
}
+3 -3
View File
@@ -15,7 +15,7 @@ class MailTemplatePreview(models.TransientModel):
'email_from',
'email_to',
'partner_to',
'report_template',
'report_template_ids',
'reply_to',
'scheduled_date',
]
@@ -94,9 +94,9 @@ class MailTemplatePreview(models.TransientModel):
def _set_mail_attributes(self, values=None):
for field in self._MAIL_TEMPLATE_FIELDS:
if field in ('partner_to', 'report_template'):
if field in ('partner_to', 'report_template_ids'):
# partner_to is used to generate partner_ids, handled here below
# report_template generates attachments, no usage here
# report_template_ids generates attachments, no usage here
continue
field_value = values.get(field, False) if values else self.mail_template_id[field]
self[field] = field_value
+3 -6
View File
@@ -30,8 +30,7 @@
</t>
</p>
</div></field>
<field name="report_template" ref="report_purchase_quotation"/>
<field name="report_name">RFQ_{{ (object.name or '').replace('/','_') }}</field>
<field name="report_template_ids" eval="[(4, ref('purchase.report_purchase_quotation'))]"/>
<field name="lang">{{ object.partner_id.lang }}</field>
<field name="auto_delete" eval="True"/>
</record>
@@ -69,8 +68,7 @@
<br/><br/>
</p>
</div></field>
<field name="report_template" ref="action_report_purchase_order"/>
<field name="report_name">PO_{{ (object.name or '').replace('/','_') }}</field>
<field name="report_template_ids" eval="[(4, ref('purchase.action_report_purchase_order'))]"/>
<field name="lang">{{ object.partner_id.lang }}</field>
<field name="auto_delete" eval="True"/>
</record>
@@ -109,8 +107,7 @@
<br/><br/>
</p>
</div></field>
<field name="report_template" ref="action_report_purchase_order"/>
<field name="report_name">PO_{{ (object.name or '').replace('/','_') }}</field>
<field name="report_template_ids" eval="[(4, ref('purchase.action_report_purchase_order'))]"/>
<field name="lang">{{ object.partner_id.lang }}</field>
<field name="auto_delete" eval="True"/>
</record>
+1 -2
View File
@@ -28,8 +28,7 @@
</t>
</p>
</div></field>
<field name="report_template" ref="action_report_repair_order"/>
<field name="report_name">{{ (object.name or '').replace('/','_') }}</field>
<field name="report_template_ids" eval="[(4, ref('repair.action_report_repair_order'))]"/>
<field name="lang">{{ object.partner_id.lang }}</field>
<field name="auto_delete" eval="True"/>
</record>
+2 -4
View File
@@ -39,8 +39,7 @@
</p>
</div>
</field>
<field name="report_template" ref="action_report_saleorder"/>
<field name="report_name">{{ (object.name or '').replace('/','_') }}</field>
<field name="report_template_ids" eval="[(4, ref('sale.action_report_saleorder'))]"/>
<field name="lang">{{ object.partner_id.lang }}</field>
<field name="auto_delete" eval="True"/>
</record>
@@ -234,8 +233,7 @@
</div>
</t>
</div></field>
<field name="report_template" ref="action_report_saleorder"/>
<field name="report_name">{{ (object.name or '').replace('/','_') }}</field>
<field name="report_template_ids" eval="[(4, ref('sale.action_report_saleorder'))]"/>
<field name="lang">{{ object.partner_id.lang }}</field>
<field name="auto_delete" eval="True"/>
</record>
+1 -2
View File
@@ -40,8 +40,7 @@
</p>
</div>
</field>
<field name="report_template" ref="stock.action_report_delivery"/>
<field name="report_name">{{ (object.name or '').replace('/','_') }}</field>
<field name="report_template_ids" eval="[(4, ref('stock.action_report_delivery'))]"/>
<field name="lang">{{ object.partner_id.lang }}</field>
<field name="auto_delete" eval="True"/>
</record>
+1 -2
View File
@@ -79,8 +79,7 @@
</table>
</div>
</field>
<field name="report_template" ref="certification_report"/>
<field name="report_name">Certification Document</field>
<field name="report_template_ids" eval="[(4, ref('survey.certification_report'))]"/>
<field name="lang">{{ object.partner_id.lang }}</field>
<field name="auto_delete" eval="True"/>
</record>
@@ -128,7 +128,8 @@ class TestCertificationFlow(common.TestSurveyCommon, HttpCase):
self.assertIn("User Certification for SO lines", certification_email.subject)
self.assertIn("employee@example.com", certification_email.email_to)
self.assertEqual(len(certification_email.attachment_ids), 1)
self.assertEqual(certification_email.attachment_ids[0].name, 'Certification Document.html')
self.assertEqual(certification_email.attachment_ids[0].name, f'Certification - {certification.title}.html',
'Default certification report print_report_name is "Certification - %s" % (object.survey_id.display_name)')
def test_randomized_certification(self):
# Step: survey user creates the randomized certification
+1 -1
View File
@@ -120,7 +120,7 @@ class TestEventFullCommon(EventCrmCase, TestSalesCommon, MockVisitor):
# ------------------------------------------------------------
test_registration_report = cls.env.ref('test_event_full.event_registration_report_test')
subscription_template = cls.env.ref('event.event_subscription')
subscription_template.write({'report_template': test_registration_report.id})
subscription_template.write({'report_template_ids': [(6, 0, test_registration_report.ids)]})
cls.test_event_type = cls.env['event.type'].create({
'auto_confirm': True,
'default_timezone': 'Europe/Paris',
@@ -33,6 +33,17 @@
</t>
</template>
<template id="mail_test_ticket_test_template_2">
<t t-call="web.html_container">
<t t-set="o" t-value="res_company"/>
<t t-call="web.external_layout">
<div class="page">
<p>This is another sample of an external report.</p>
</div>
</t>
</t>
</template>
<template id="mail_template_simple_test">
<p>Hello <t t-out="partner.name"/>, this comes from <t t-out="object.name"/>.</p>
</template>
+65 -39
View File
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
from ast import literal_eval
from datetime import timedelta
from freezegun import freeze_time
@@ -61,13 +63,21 @@ class TestMailComposer(TestMailCommon, TestRecipients):
additional_values={'user_id': cls.user_employee_2.id},
)
cls.test_report = cls.env['ir.actions.report'].create({
'name': 'Test Report on mail test ticket',
'model': 'mail.test.ticket',
'report_type': 'qweb-pdf',
'report_name': 'test_mail.mail_test_ticket_test_template',
})
cls.test_record_report = cls.env['ir.actions.report']._render_qweb_pdf(cls.test_report, cls.test_record.ids)
cls.test_report, cls.test_report_2 = cls.env['ir.actions.report'].create([
{
'name': 'Test Report on Mail Test Ticket',
'model': 'mail.test.ticket',
'print_report_name': "'TestReport for %s' % object.name",
'report_type': 'qweb-pdf',
'report_name': 'test_mail.mail_test_ticket_test_template',
}, {
'name': 'Test Report 2 on Mail Test Ticket',
'model': 'mail.test.ticket',
'print_report_name': "'TestReport2 for %s' % object.name",
'report_type': 'qweb-pdf',
'report_name': 'test_mail.mail_test_ticket_test_template_2',
}
])
cls.test_from = '"John Doe" <john@example.com>'
@@ -145,14 +155,13 @@ class TestComposerForm(TestMailComposer):
attachment_data = self._generate_attachments_data(2, self.template._name, self.template.id)
template_1 = self.template.copy({
'attachment_ids': [(0, 0, a) for a in attachment_data],
'report_name': 'TestReport for {{ object.name }}.html', # test cursor forces html
'report_template': self.test_report.id,
'report_template_ids': [(6, 0, (self.test_report + self.test_report_2).ids)],
})
template_1_attachments = template_1.attachment_ids
self.assertEqual(len(template_1_attachments), 2)
template_2 = self.template.copy({
'attachment_ids': False,
'report_template': self.test_report.id,
'report_template_ids': [(6, 0, self.test_report.ids)],
})
# begins without attachments
@@ -161,12 +170,12 @@ class TestComposerForm(TestMailComposer):
))
self.assertEqual(len(composer_form.attachment_ids), 0)
# change template: 2 static (attachment_ids) and 1 dynamic (report)
# change template: 2 static (attachment_ids) and 2 dynamic (reports)
composer_form.template_id = template_1
self.assertEqual(len(composer_form.attachment_ids), 3)
self.assertEqual(len(composer_form.attachment_ids), 4)
report_attachments = [att for att in composer_form.attachment_ids if att not in template_1_attachments]
self.assertEqual(len(report_attachments), 1)
tpl_attachments = composer_form.attachment_ids[:] - report_attachments[0]
self.assertEqual(len(report_attachments), 2)
tpl_attachments = composer_form.attachment_ids[:] - self.env['ir.attachment'].concat(*report_attachments)
self.assertEqual(tpl_attachments, template_1_attachments)
# change template: 0 static (attachment_ids) and 1 dynamic (report)
@@ -174,15 +183,15 @@ class TestComposerForm(TestMailComposer):
self.assertEqual(len(composer_form.attachment_ids), 1)
report_attachments = [att for att in composer_form.attachment_ids if att not in template_1_attachments]
self.assertEqual(len(report_attachments), 1)
tpl_attachments = composer_form.attachment_ids[:] - report_attachments[0]
tpl_attachments = composer_form.attachment_ids[:] - self.env['ir.attachment'].concat(*report_attachments)
self.assertFalse(tpl_attachments)
# change back to template 1
composer_form.template_id = template_1
self.assertEqual(len(composer_form.attachment_ids), 3)
self.assertEqual(len(composer_form.attachment_ids), 4)
report_attachments = [att for att in composer_form.attachment_ids if att not in template_1_attachments]
self.assertEqual(len(report_attachments), 1)
tpl_attachments = composer_form.attachment_ids[:] - report_attachments[0]
self.assertEqual(len(report_attachments), 2)
tpl_attachments = composer_form.attachment_ids[:] - self.env['ir.attachment'].concat(*report_attachments)
self.assertEqual(tpl_attachments, template_1_attachments)
# reset template
@@ -362,16 +371,21 @@ class TestComposerInternals(TestMailComposer):
attachment_data = self._generate_attachments_data(3, self.template._name, self.template.id)
self.template.write({
'attachment_ids': [(0, 0, a) for a in attachment_data],
'report_name': 'TestReport for {{ object.name }}.html', # test cursor forces html
'report_template': self.test_report.id,
'report_template_ids': [(6, 0, (self.test_report + self.test_report_2).ids)],
})
template_void = self.template.copy(default={
'attachment_ids': False,
'report_name': False,
'report_template': False,
'report_template_ids': False,
})
attachs = self.env['ir.attachment'].search([('name', 'in', [a['name'] for a in attachment_data])])
self.assertEqual(len(attachs), 3)
extra_attach = self.env['ir.attachment'].create({
'datas': base64.b64encode(b'ExtraData'),
'mimetype': 'text/plain',
'name': 'ExtraAttFileName.txt',
'res_model': False,
'res_id': False,
})
for composition_mode, batch in (('comment', False), ('comment', True),
('mass_mail', False), ('mass_mail', True)):
@@ -391,14 +405,16 @@ class TestComposerInternals(TestMailComposer):
# values coming from template: attachment_ids + report in comment
if composition_mode == 'comment' and not batch:
self.assertEqual(len(composer.attachment_ids), 4)
self.assertEqual(len(composer.attachment_ids), 5)
for attach in attachs:
self.assertIn(attach, composer.attachment_ids)
generated = composer.attachment_ids - attachs
self.assertEqual(len(generated), 1, 'MailComposer: should have 1 additional attachment for report')
self.assertEqual(generated.name, f'TestReport for {self.test_record.name}.html')
self.assertEqual(generated.res_model, 'mail.compose.message')
self.assertEqual(generated.res_id, 0)
self.assertEqual(len(generated), 2, 'MailComposer: should have 2 additional attachments for reports')
self.assertEqual(
sorted(generated.mapped('name')),
sorted([f'TestReport for {self.test_record.name}.html', f'TestReport2 for {self.test_record.name}.html']))
self.assertEqual(generated.mapped('res_model'), ['mail.compose.message'] * 2)
self.assertEqual(generated.mapped('res_id'), [0] * 2)
# values coming from template: attachment_ids only (report is dynamic)
else:
self.assertEqual(
@@ -406,16 +422,25 @@ class TestComposerInternals(TestMailComposer):
sorted(attachs.ids)
)
# manual update
composer.write({
'attachment_ids': [(4, extra_attach.id)],
})
if composition_mode == 'comment' and not batch:
self.assertEqual(composer.attachment_ids, attachs + extra_attach + generated)
else:
self.assertEqual(composer.attachment_ids, attachs + extra_attach)
# update with template with void values: values are kept
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 + generated,
self.assertEqual(composer.attachment_ids, attachs + extra_attach + generated,
'TODO: Values are kept (should be reset ?)')
else:
self.assertEqual(composer.attachment_ids, attachs,
self.assertEqual(composer.attachment_ids, attachs + extra_attach,
'TODO: Values are kept (should be reset ?)')
# reset template: values are kept
@@ -424,10 +449,10 @@ class TestComposerInternals(TestMailComposer):
composer._onchange_template_id_wrapper()
if composition_mode == 'comment' and not batch:
self.assertEqual(composer.attachment_ids, attachs + generated,
self.assertEqual(composer.attachment_ids, attachs + extra_attach + generated,
'TODO: Values are kept (should be reset ?)')
else:
self.assertEqual(composer.attachment_ids, attachs,
self.assertEqual(composer.attachment_ids, attachs + extra_attach,
'TODO: Values are kept (should be reset ?)')
@users('employee')
@@ -973,8 +998,7 @@ class TestComposerInternals(TestMailComposer):
def test_mail_composer_rights_attachments(self):
""" Ensure a user without write access to a template can send an email"""
template_1 = self.template.copy({
'report_name': 'TestReport for {{ object.name }} (thanks TDE).html', # test cursor forces html
'report_template': self.test_report.id,
'report_template_ids': [(6, 0, self.test_report.ids)],
})
attachment_data = self._generate_attachments_data(2, self.template._name, self.template.id)
template_1.write({
@@ -986,7 +1010,7 @@ class TestComposerInternals(TestMailComposer):
template_1_attachments = template_1.attachment_ids
self.assertEqual(len(template_1_attachments), 2)
template_1_attachment_name = list(template_1_attachments.mapped('name')) + ["TestReport for TestRecord (thanks TDE).html"]
template_1_attachment_name = list(template_1_attachments.mapped('name')) + [f"TestReport for {self.test_record.name}.html"]
composer = self.env['mail.compose.message'].with_context(
self._get_web_context(self.test_record)
@@ -1243,8 +1267,7 @@ class TestComposerResultsComment(TestMailComposer, CronMixinCase):
'email_to': '%s, %s, %s' % (email_to_1, email_to_2, email_to_3),
'email_cc': email_cc_1,
'partner_to': '%s, {{ object.customer_id.id if object.customer_id else "" }}' % self.partner_admin.id,
'report_name': 'TestReport for {{ object.name }}', # test cursor forces html
'report_template': self.test_report.id,
'report_template_ids': [(6, 0, (self.test_report + self.test_report_2).ids)],
})
attachs = self.env['ir.attachment'].search([('name', 'in', [a['name'] for a in attachment_data])])
self.assertEqual(len(attachs), 2)
@@ -1346,6 +1369,7 @@ class TestComposerResultsComment(TestMailComposer, CronMixinCase):
{'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'},
{'name': f'TestReport2 for {test_record.name}.html', 'type': 'text/plain'},
]
},
fields_values={
@@ -1363,6 +1387,7 @@ class TestComposerResultsComment(TestMailComposer, CronMixinCase):
{'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'},
{'name': f'TestReport2 for {test_record.name}.html', 'type': 'text/plain'},
]
},
fields_values={
@@ -1376,7 +1401,9 @@ class TestComposerResultsComment(TestMailComposer, CronMixinCase):
# 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'])
set(['AttFileName_00.txt', 'AttFileName_01.txt',
f'TestReport for {test_record.name}.html',
f'TestReport2 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))
@@ -1611,8 +1638,7 @@ class TestComposerResultsMass(TestMailComposer):
'email_to': '%s, %s, %s' % (email_to_1, email_to_2, email_to_3),
'email_cc': email_cc_1,
'partner_to': '%s, {{ object.customer_id.id if object.customer_id else "" }}' % self.partner_admin.id,
'report_name': 'TestReport for {{ object.name }}', # test cursor forces html
'report_template': self.test_report.id,
'report_template_ids': [(6, 0, self.test_report.ids)],
})
attachs = self.env['ir.attachment'].search([('name', 'in', [a['name'] for a in attachment_data])])
self.assertEqual(len(attachs), 2)
@@ -145,7 +145,7 @@ class ChannelUsersRelation(models.Model):
'model',
'partner_to',
'reply_to',
'report_template',
'report_template_ids',
'res_id',
'scheduled_date',
'subject',