162 lines
7.5 KiB
Python
162 lines
7.5 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, 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)
|