[REF] mail, various: cleanup usage of options in render mixin

Due to recent improvements (Jinja -> Qweb, safe rendering) rendering API
supports several ways of giving options to the rendering process. Those
have mainly two usage

  * ``preserve_comments`` : keep comments in rendered HTML, used notably
    in mass mailing or digest to keep browser-specific comments;
  * ``post_process`` : perform a post processing on rendered HTML, used notably
    to process local links and add tracking to shortened links;

All those are now given directly inside an optional ``options`` parameter
given to ``_render_field`` and its sub-method ``_render_template``.

They can also be defined as field level, using ``render_options`` field
parameter. Some fields are updated

  * composer mixin body (which impacts all inheriting models, notably
    mail composer, survey and elearning invite wizards as well as appraisal
    and appraisal feedback): post processing is now always done by default.
    It was already done manually in calls that can be simplified;
  * mail template body_html: post processing is now always done by default. It
    was already done manually in calls that can be simplified;
  * mailing body_html and preview are now post processed by default. It was
    already the case in testing wizard. Preview is updated to avoid post
    processing of links, as it was before. Remaining use case is the sending
    which uses the mail composer, therefore was already post processed;

Finally some 'compute_lang' are explicitly added in _render_field calls in
order to be explicit on what we want, instead of being unsure.

Task-2710804 (Mail: Clean MailThread API)
Prepares Task-2088884 (Mail: Use editable computed stored fields in composer)

Part-of: odoo/odoo#106072
This commit is contained in:
Thibault Delavallée
2022-11-21 19:55:13 +01:00
parent 902cda0a1b
commit 3d5b012501
12 changed files with 74 additions and 36 deletions
+1 -2
View File
@@ -123,8 +123,7 @@ class Attendee(models.Model):
body = mail_template._render_field(
'body_html',
attendee.ids,
compute_lang=True,
post_process=True)[attendee.id]
compute_lang=True)[attendee.id]
subject = mail_template._render_field(
'subject',
attendee.ids,
+13 -3
View File
@@ -165,8 +165,10 @@ class Digest(models.Model):
'tips': self._compute_tips(user.company_id, user, tips_count=tips_count, consumed=consume_tips),
'preferences': self._compute_preferences(user.company_id, user),
},
post_process=True,
options={'preserve_comments': True}
options={
'preserve_comments': True,
'post_process': True,
},
)[self.id]
full_mail = self.env['mail.render.mixin']._render_encapsulate(
'digest.digest_mail_layout',
@@ -288,7 +290,15 @@ class Digest(models.Model):
'|', ('group_id', 'in', user.groups_id.ids), ('group_id', '=', False)
], limit=tips_count)
tip_descriptions = [
tools.html_sanitize(self.env['mail.render.mixin'].sudo()._render_template(tip.tip_description, 'digest.tip', tip.ids, post_process=True, engine="qweb")[tip.id])
tools.html_sanitize(
self.env['mail.render.mixin'].sudo()._render_template(
tip.tip_description,
'digest.tip',
tip.ids,
engine="qweb",
options={'post_process': True},
)[tip.id]
)
for tip in tips
]
if consumed:
+3 -1
View File
@@ -22,7 +22,9 @@ class MailComposerMixin(models.AbstractModel):
# Content
subject = fields.Char('Subject', compute='_compute_subject', readonly=False, store=True)
body = fields.Html('Contents', compute='_compute_body', render_engine='qweb', store=True, readonly=False, sanitize=False)
body = fields.Html(
'Contents', compute='_compute_body', readonly=False, store=True,
render_engine='qweb', render_options={'post_process': True}, sanitize=False)
template_id = fields.Many2one('mail.template', 'Mail Template', domain="[('model', '=', render_model)]")
# Access
is_mail_template_editor = fields.Boolean('Is Editor', compute='_compute_is_mail_template_editor')
+30 -14
View File
@@ -284,7 +284,8 @@ class MailRenderMixin(models.AbstractModel):
:param dict add_context: additional context to give to renderer. It
allows to add or update values to base rendering context generated
by ``MailRenderMixin._render_eval_context()``;
:param dict options: options for rendering (not used currently);
:param dict options: options for rendering propagated to IrQweb render
(see docstring for available options);
:return dict: {res_id: string of rendered template based on record}
"""
@@ -337,7 +338,8 @@ class MailRenderMixin(models.AbstractModel):
:param dict add_context: additional context to give to renderer. It
allows to add or update values to base rendering context generated
by ``MailRenderMixin._render_eval_context()``;
:param dict options: options for rendering (not used currently);
:param dict options: options for rendering propagated to IrQweb render
(see docstring for available options);
:return dict: {res_id: string of rendered template based on record}
"""
@@ -378,7 +380,8 @@ class MailRenderMixin(models.AbstractModel):
:param dict add_context: additional context to give to renderer. It
allows to add or update values to base rendering context generated
by ``MailRenderMixin._render_inline_template_eval_context()``;
:param dict options: options for rendering;
:param dict options: options for rendering (no options available
currently);
:return dict: {res_id: string of rendered template based on record}
"""
@@ -434,7 +437,7 @@ class MailRenderMixin(models.AbstractModel):
@api.model
def _render_template(self, template_src, model, res_ids, engine='inline_template',
add_context=None, options=None, post_process=False):
add_context=None, options=None):
""" Render the given string on records designed by model / res_ids using
the given rendering engine. Possible engine are small_web, qweb, or
qweb_view.
@@ -449,12 +452,20 @@ class MailRenderMixin(models.AbstractModel):
:param dict add_context: additional context to give to renderer. It
allows to add or update values to base rendering context generated
by ``MailRenderMixin._render_<engine>_eval_context()``;
:param dict options: options for rendering;
:param boolean post_process: perform a post processing on rendered result
(notably html links management). See``_render_template_postprocess``;
:param dict options: options for rendering. Use in this method and also
propagated to rendering sub-methods. May contain notably
boolean post_process: perform a post processing on rendered result
(notably html links management). See``_render_template_postprocess``;
boolean preserve_comments: if set, comments are preserved. Default
behavior is to remove them. It is used notably for browser-specific
code implemented like comments;
:return dict: {res_id: string of rendered template based on record}
"""
if options is None:
options = {}
if not isinstance(res_ids, (list, tuple)):
raise ValueError(_('Template rendering should be called only using on a list of IDs.'))
if engine not in ('inline_template', 'qweb', 'qweb_view'):
@@ -470,7 +481,7 @@ class MailRenderMixin(models.AbstractModel):
rendered = self._render_template_inline_template(template_src, model, res_ids,
add_context=add_context, options=options)
if post_process:
if options.get('post_process'):
rendered = self._render_template_postprocess(rendered)
return rendered
@@ -523,7 +534,7 @@ class MailRenderMixin(models.AbstractModel):
def _render_field(self, field, res_ids, engine='inline_template',
compute_lang=False, set_lang=False,
add_context=None, options=None, post_process=False):
add_context=None, options=None):
""" Given some record ids, render a template located on field on all
records. ``field`` should be a field of self (i.e. ``body_html`` on
``mail.template``). res_ids are record IDs linked to ``model`` field
@@ -542,9 +553,16 @@ class MailRenderMixin(models.AbstractModel):
``compute_lang`` is False;
:param dict add_context: additional context to give to renderer;
:param dict options: options for rendering;
:param boolean post_process: perform a post processing on rendered result
(notably html links management). See``_render_template_postprocess``);
:param dict options: options for rendering. Use in this method and also
propagated to rendering sub-methods. Base values come from the field
(coming from ``render_options`` parameter) and are updated by this
optional dictionary. May contain notably
boolean post_process: perform a post processing on rendered result
(notably html links management). See``_render_template_postprocess``;
boolean preserve_comments: if set, comments are preserved. Default
behavior is to remove them. It is used notably for browser-specific
code implemented like comments;
:return dict: {res_id: string of rendered template based on record}
"""
@@ -568,7 +586,6 @@ class MailRenderMixin(models.AbstractModel):
# rendering options
engine = getattr(self._fields[field], 'render_engine', engine)
options.update(**getattr(self._fields[field], 'render_options', {}))
post_process = options.get('post_process') or post_process
return dict(
(res_id, rendered)
@@ -580,6 +597,5 @@ class MailRenderMixin(models.AbstractModel):
engine=engine,
add_context=add_context,
options=options,
post_process=post_process
).items()
)
+4 -3
View File
@@ -56,7 +56,9 @@ class MailTemplate(models.Model):
email_cc = fields.Char('Cc', help="Carbon copy recipients (placeholders may be used here)")
reply_to = fields.Char('Reply To', help="Email address to which replies will be redirected when sending emails in mass; only used when the reply is not logged in the original discussion thread.")
# content
body_html = fields.Html('Body', render_engine='qweb', translate=True, prefetch=True, sanitize=False)
body_html = fields.Html(
'Body', render_engine='qweb', render_options={'post_process': True},
prefetch=True, translate=True, sanitize=False)
attachment_ids = fields.Many2many('ir.attachment', 'email_template_attachment_rel', 'email_template_id',
'attachment_id', 'Attachments',
help="You may attach files to this template, to be added to all "
@@ -246,8 +248,7 @@ class MailTemplate(models.Model):
for lang, (template, template_res_ids) in self._classify_per_lang(res_ids).items():
for field in fields:
generated_field_values = template._render_field(
field, template_res_ids,
post_process=(field == 'body_html')
field, template_res_ids
)
for res_id, field_value in generated_field_values.items():
results.setdefault(res_id, dict())[field] = field_value
+4 -2
View File
@@ -89,7 +89,9 @@ class MailComposer(models.TransientModel):
# content
subject = fields.Char('Subject', compute=False)
body = fields.Html('Contents', render_engine='qweb', compute=False, default='', sanitize_style=True)
body = fields.Html(
'Contents', render_engine='qweb', render_options={'post_process': True},
compute=False, default='', sanitize_style=True)
parent_id = fields.Many2one(
'mail.message', 'Parent Message', ondelete='set null')
template_id = fields.Many2one('mail.template', 'Use template', domain="[('model', '=', model)]")
@@ -658,7 +660,7 @@ class MailComposer(models.TransientModel):
subjects = self._render_field('subject', res_ids)
# We want to preserve comments in emails so as to keep mso conditionals
bodies = self._render_field('body', res_ids, post_process=True, options={'preserve_comments': self.composition_mode == 'mass_mail'})
bodies = self._render_field('body', res_ids, options={'preserve_comments': self.composition_mode == 'mass_mail'})
emails_from = self._render_field('email_from', res_ids)
replies_to = self._render_field('reply_to', res_ids)
default_recipients = {}
+7 -1
View File
@@ -167,7 +167,13 @@ class MassMailController(http.Controller):
if not self._valid_unsubscribe_token(mailing_id, res_id, email, str(token)) and not request.env.user.has_group('mass_mailing.group_mass_mailing_user'):
raise exceptions.AccessDenied()
html_markupsafe = mailing._render_field('body_html', [res_id])[res_id]
# do not force lang, will simply use user context
html_markupsafe = mailing._render_field(
'body_html',
[res_id],
compute_lang=False,
options={'post_process': False}
)[res_id]
# Update generic URLs (without parameters) to final ones
html_markupsafe = html_markupsafe.replace('/unsubscribe_from_list',
mailing._get_unsubscribe_url(email, res_id))
+4 -3
View File
@@ -73,6 +73,7 @@ class MassMailing(models.Model):
'Subject', required=True, translate=False)
preview = fields.Char(
'Preview', translate=False,
render_engine='inline_template', render_options={'post_process': True},
help='Catchy preview sentence that encourages recipients to open this email.\n'
'In most inboxes, this is displayed next to the subject.\n'
'Keep it empty if you prefer the first characters of your email content to appear instead.')
@@ -104,9 +105,9 @@ class MassMailing(models.Model):
help="Date at which the mailing was or will be sent.")
# don't translate 'body_arch', the translations are only on 'body_html'
body_arch = fields.Html(string='Body', translate=False, sanitize=False)
body_html = fields.Html(string='Body converted to be sent by mail', render_engine='qweb', sanitize=False)
# used to determine if the mail body is empty
body_html = fields.Html(
string='Body converted to be sent by mail', sanitize=False,
render_engine='qweb', render_options={'post_process': True})
is_body_empty = fields.Boolean(compute="_compute_is_body_empty")
attachment_ids = fields.Many2many(
'ir.attachment', 'mass_mailing_ir_attachments_rel',
@@ -37,10 +37,11 @@ class TestMassMailing(models.TransientModel):
# Downside: Qweb syntax is only tested when there is atleast one record of the mailing's model
if record:
# Returns a proper error if there is a syntax error with Qweb
body = mailing._render_field('body_html', record.ids, post_process=True)[record.id]
preview = mailing._render_field('preview', record.ids, post_process=True)[record.id]
# do not force lang, will simply use user context
body = mailing._render_field('body_html', record.ids, compute_lang=False)[record.id]
preview = mailing._render_field('preview', record.ids, compute_lang=False)[record.id]
full_body = mailing._prepend_preview(Markup(body), preview)
subject = mailing._render_field('subject', record.ids)[record.id]
subject = mailing._render_field('subject', record.ids, compute_lang=False)[record.id]
else:
full_body = mailing._prepend_preview(mailing.body_html, mailing.preview)
subject = mailing.subject
+2 -2
View File
@@ -66,7 +66,7 @@ class SaleOrderCancel(models.TransientModel):
wizard.template_id.subject,
'sale.order',
[wizard.order_id.id],
post_process=True,
options={'post_process': True},
)[wizard.order_id.id]
@api.depends('order_id')
@@ -77,7 +77,7 @@ class SaleOrderCancel(models.TransientModel):
wizard.template_id.body_html,
'sale.order',
[wizard.order_id.id],
post_process=True,
options={'post_process': True},
engine='qweb',
)[wizard.order_id.id]
+1 -1
View File
@@ -210,7 +210,7 @@ class SurveyInvite(models.TransientModel):
def _send_mail(self, answer):
""" Create mail specific for recipient containing notably its access token """
subject = self._render_field('subject', answer.ids)[answer.id]
body = self._render_field('body', answer.ids, post_process=True)[answer.id]
body = self._render_field('body', answer.ids)[answer.id]
# post the message
mail_values = {
'attachment_ids': [(4, att.id) for att in self.attachment_ids],
@@ -67,7 +67,7 @@ class SlideChannelInvite(models.TransientModel):
def _prepare_mail_values(self, slide_channel_partner):
""" Create mail specific for recipient """
subject = self._render_field('subject', slide_channel_partner.ids)[slide_channel_partner.id]
body = self._render_field('body', slide_channel_partner.ids, post_process=True)[slide_channel_partner.id]
body = self._render_field('body', slide_channel_partner.ids)[slide_channel_partner.id]
# post the message
mail_values = {
'email_from': self.env.user.email_formatted,