mail/models/mail_composer_mixin.py

162 lines
7.5 KiB
Python
Raw Permalink Normal View History

2024-05-03 12:40:35 +03:00
# -*- 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, compute_sudo=False)
# 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)