When rendering the sale order cancellation message, the rendering should apply the current user record rules, for instance the count of the partner sale orders should match the count of what the salesman can see in his ui. Otherwise he doesn't understand why he has a different count in the UI and in the cancellation message. In case you want the behavior of seeing all records and not just the current salesman records only, then you apply within the template itself the `sudo()`. Applying the `sudo` where you actually need it in the template, and not computing the full subject/body as sudo, offers more granularity. closes odoo/odoo#135233 X-original-commit: 5d49a08f60b1f299784e2e983e87cdec178642c1 Signed-off-by: Denis Ledoux (dle) <dle@odoo.com>
162 lines
7.4 KiB
Python
162 lines
7.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo import api, fields, models, tools, _
|
|
|
|
|
|
class MailComposerMixin(models.AbstractModel):
|
|
""" Mixin used to edit and render some fields used when sending emails or
|
|
notifications based on a mail template.
|
|
|
|
Main current purpose is to hide details related to subject and body computation
|
|
and rendering based on a mail.template. It also give the base tools to control
|
|
who is allowed to edit body, notably when dealing with templating language
|
|
like inline_template or qweb.
|
|
|
|
It is meant to evolve in a near future with upcoming support of qweb and fine
|
|
grain control of rendering access.
|
|
"""
|
|
_name = 'mail.composer.mixin'
|
|
_inherit = 'mail.render.mixin'
|
|
_description = 'Mail Composer Mixin'
|
|
|
|
# Content
|
|
subject = fields.Char('Subject', compute='_compute_subject', readonly=False, store=True, compute_sudo=False)
|
|
body = fields.Html(
|
|
'Contents', compute='_compute_body', readonly=False, store=True, compute_sudo=False,
|
|
render_engine='qweb', render_options={'post_process': True}, sanitize=False)
|
|
body_has_template_value = fields.Boolean(
|
|
'Body content is the same as the template',
|
|
compute='_compute_body_has_template_value',
|
|
)
|
|
template_id = fields.Many2one('mail.template', 'Mail Template', domain="[('model', '=', render_model)]")
|
|
# Language: override mail.render.mixin field, copy template value
|
|
lang = fields.Char(compute='_compute_lang', precompute=True, readonly=False, store=True)
|
|
# Access
|
|
is_mail_template_editor = fields.Boolean('Is Editor', compute='_compute_is_mail_template_editor')
|
|
can_edit_body = fields.Boolean('Can Edit Body', compute='_compute_can_edit_body')
|
|
|
|
@api.depends('template_id')
|
|
def _compute_subject(self):
|
|
""" Computation is coming either from template, either reset. When
|
|
having a template with a value set, copy it. When removing the
|
|
template, reset it. """
|
|
for composer_mixin in self:
|
|
if composer_mixin.template_id.subject:
|
|
composer_mixin.subject = composer_mixin.template_id.subject
|
|
elif not composer_mixin.template_id:
|
|
composer_mixin.subject = False
|
|
|
|
@api.depends('template_id')
|
|
def _compute_body(self):
|
|
""" Computation is coming either from template, either reset. When
|
|
having a template with a value set, copy it. When removing the
|
|
template, reset it. """
|
|
for composer_mixin in self:
|
|
if not tools.is_html_empty(composer_mixin.template_id.body_html):
|
|
composer_mixin.body = composer_mixin.template_id.body_html
|
|
elif not composer_mixin.template_id:
|
|
composer_mixin.body = False
|
|
|
|
@api.depends('body', 'template_id')
|
|
def _compute_body_has_template_value(self):
|
|
""" Computes if the current body is the same as the one from template.
|
|
Both real and sanitized values are considered, to avoid editor issues
|
|
as much as possible. """
|
|
for composer_mixin in self:
|
|
if not tools.is_html_empty(composer_mixin.body) and composer_mixin.template_id:
|
|
template_value = composer_mixin.template_id.body_html
|
|
sanitized_template_value = tools.html_sanitize(template_value)
|
|
composer_mixin.body_has_template_value = composer_mixin.body in (template_value, sanitized_template_value)
|
|
else:
|
|
composer_mixin.body_has_template_value = False
|
|
|
|
@api.depends('template_id')
|
|
def _compute_lang(self):
|
|
""" Computation is coming either from template, either reset. When
|
|
having a template with a value set, copy it. When removing the
|
|
template, reset it. """
|
|
for composer_mixin in self:
|
|
if composer_mixin.template_id.lang:
|
|
composer_mixin.lang = composer_mixin.template_id.lang
|
|
elif not composer_mixin.template_id:
|
|
composer_mixin.lang = False
|
|
|
|
@api.depends_context('uid')
|
|
def _compute_is_mail_template_editor(self):
|
|
is_mail_template_editor = self.env.is_admin() or self.env.user.has_group('mail.group_mail_template_editor')
|
|
for record in self:
|
|
record.is_mail_template_editor = is_mail_template_editor
|
|
|
|
@api.depends('template_id', 'is_mail_template_editor')
|
|
def _compute_can_edit_body(self):
|
|
for record in self:
|
|
record.can_edit_body = (
|
|
record.is_mail_template_editor
|
|
or not record.template_id
|
|
)
|
|
|
|
def _render_field(self, field, *args, **kwargs):
|
|
""" Render the given field on the given records. This method enters
|
|
sudo mode to allow qweb rendering (which is otherwise reserved for
|
|
the 'mail template editor' group') if we consider it safe. Safe
|
|
means content comes from the template which is a validated master
|
|
data. As a summary the heuristic is :
|
|
|
|
* if no template, do not bypass the check;
|
|
* if current user is a template editor, do not bypass the check;
|
|
* if record value and template value are the same (or equals the
|
|
sanitized value in case of an HTML field), bypass the check;
|
|
* for body: if current user cannot edit it, force template value back
|
|
then bypass the check;
|
|
|
|
Also provide support to fetch translations on the remote template.
|
|
Indeed translations are often done on the master template, not on the
|
|
specific composer itself. In that case we need to work on template
|
|
value when it has not been modified in the composer. """
|
|
if field not in self:
|
|
raise ValueError(
|
|
_('Rendering of %(field_name)s is not possible as not defined on template.',
|
|
field_name=field
|
|
)
|
|
)
|
|
|
|
if not self.template_id:
|
|
# Do not need to bypass the verification
|
|
return super()._render_field(field, *args, **kwargs)
|
|
|
|
# template-based access check + translation check
|
|
template_field = {
|
|
'body': 'body_html',
|
|
}.get(field, field)
|
|
if template_field not in self.template_id:
|
|
raise ValueError(
|
|
_('Rendering of %(field_name)s is not possible as no counterpart on template.',
|
|
field_name=field
|
|
)
|
|
)
|
|
|
|
composer_value = self[field]
|
|
template_value = self.template_id[template_field]
|
|
translation_asked = kwargs.get('compute_lang') or kwargs.get('set_lang')
|
|
equality = self.body_has_template_value if field == 'body' else composer_value == template_value
|
|
|
|
call_sudo = False
|
|
if (not self.is_mail_template_editor and field == 'body' and
|
|
(not self.can_edit_body or self.body_has_template_value)):
|
|
call_sudo = True
|
|
# take the previous body which we can trust without HTML editor reformatting
|
|
self.body = self.template_id.body_html
|
|
if (not self.is_mail_template_editor and field != 'body' and
|
|
composer_value == template_value):
|
|
call_sudo = True
|
|
|
|
if translation_asked and equality:
|
|
template = self.template_id.sudo() if call_sudo else self.template_id
|
|
return template._render_field(
|
|
template_field, *args, **kwargs,
|
|
)
|
|
|
|
record = self.sudo() if call_sudo else self
|
|
return super(MailComposerMixin, record)._render_field(field, *args, **kwargs)
|