[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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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')]"/>
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user