[REF] mail: improve propagation and setup of render context when posting messages

PURPOSE

Purpose of this commit is to cleanup flow of values going through message_post
and its sub methods until email generation for notifications.

SPECIFICATIONS

Ensure some values are given directly when creating message linked to post
methods to avoid browsing message when value is not known. Notably make
signature propagation (add_sign) more explicit in message_post and its sub
methods (notify, log, ...).

Prepare future language related improvements by allowing to force company and
lang values for rendering context. Also add ``is_html_empty`` tool method to
use it in notification templates. It will be used notably to check for empty
html blocks, e.g. user signature.

Rename some internal variables to better understand their purpose.

Finally update some outdated and/or badly indented docstrings.

Task-2726501 (Mail: Propagate message values in post methods)

Part-of: odoo/odoo#82167
This commit is contained in:
Thibault Delavallée
2022-01-31 17:47:30 +00:00
parent 517df26613
commit 3eb9680602
2 changed files with 163 additions and 90 deletions
+12 -1
View File
@@ -7,6 +7,7 @@ import logging
from odoo import _, api, fields, models, tools, Command from odoo import _, api, fields, models, tools, Command
from odoo.exceptions import UserError from odoo.exceptions import UserError
from odoo.tools import is_html_empty
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@@ -301,10 +302,20 @@ class MailTemplate(models.Model):
model = model.with_context(lang=lang) model = model.with_context(lang=lang)
template_ctx = { template_ctx = {
# message
'message': self.env['mail.message'].sudo().new(dict(body=values['body_html'], record_name=record.display_name)), 'message': self.env['mail.message'].sudo().new(dict(body=values['body_html'], record_name=record.display_name)),
'subtype': self.env['mail.message.subtype'].sudo(),
# record
'model_description': model.display_name, 'model_description': model.display_name,
'company': 'company_id' in record and record['company_id'] or self.env.company,
'record': record, 'record': record,
'record_name': False,
# user / environment
'company': 'company_id' in record and record['company_id'] or self.env.company,
'email_add_signature': False,
'signature': '',
'website_url': '',
# tools
'is_html_empty': is_html_empty,
} }
body = template._render(template_ctx, engine='ir.qweb', minimal_qcontext=True) body = template._render(template_ctx, engine='ir.qweb', minimal_qcontext=True)
values['body_html'] = self.env['mail.render.mixin']._replace_local_links(body) values['body_html'] = self.env['mail.render.mixin']._replace_local_links(body)
+151 -89
View File
@@ -26,7 +26,7 @@ from xmlrpc import client as xmlrpclib
from odoo import _, api, exceptions, fields, models, tools, registry, SUPERUSER_ID, Command from odoo import _, api, exceptions, fields, models, tools, registry, SUPERUSER_ID, Command
from odoo.exceptions import MissingError from odoo.exceptions import MissingError
from odoo.osv import expression from odoo.osv import expression
from odoo.tools import is_html_empty
from odoo.tools.misc import clean_context, split_every from odoo.tools.misc import clean_context, split_every
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@@ -1644,12 +1644,22 @@ class MailThread(models.AbstractModel):
def _message_post_process_attachments(self, attachments, attachment_ids, message_values): def _message_post_process_attachments(self, attachments, attachment_ids, message_values):
""" Preprocess attachments for mail_thread.message_post() or mail_mail.create(). """ Preprocess attachments for mail_thread.message_post() or mail_mail.create().
Purpose is to
:param list attachments: list of attachment tuples in the form ``(name,content)``, #todo xdo update that * transfer attachments given by ``attachment_ids`` from the composer to
where content is NOT base64 encoded the record (if any);
:param list attachment_ids: a list of attachment ids, not in tomany command form * limit attachments manipulation when being a shared user;
:param dict message_data: model: the model of the attachments parent record, * create attachments from ``attachments``. If those are linked to the
res_id: the id of the attachments parent record content (body) through CIDs body is updated accordingly;
:param list(tuple(str,str), tuple(str,str, dict)) attachments : list of attachment
tuples in the form ``(name,content)`` or ``(name,content, info)`` where content
is NOT base64 encoded;
:param list attachment_ids: list of existing attachments to link to this message;
:param message_values: dictionary of values that will be used to create the
message. It is used to find back record- or content- context;
:return dict: new values for message: 'attachment_ids' and optionally 'body'
""" """
return_values = {} return_values = {}
body = message_values.get('body') body = message_values.get('body')
@@ -1703,7 +1713,7 @@ class MailThread(models.AbstractModel):
content = content.as_bytes() content = content.as_bytes()
elif content is None: elif content is None:
continue continue
attachement_values= { attachement_values = {
'name': name, 'name': name,
'datas': base64.b64encode(content), 'datas': base64.b64encode(content),
'type': 'binary', 'type': 'binary',
@@ -1753,29 +1763,37 @@ class MailThread(models.AbstractModel):
email_from=None, author_id=None, parent_id=False, email_from=None, author_id=None, parent_id=False,
subtype_xmlid=None, subtype_id=False, partner_ids=None, subtype_xmlid=None, subtype_id=False, partner_ids=None,
attachments=None, attachment_ids=None, attachments=None, attachment_ids=None,
add_sign=True, record_name=False,
**kwargs): **kwargs):
""" Post a new message in an existing thread, returning the new """ Post a new message in an existing thread, returning the new mail.message.
mail.message ID.
:param str body: body of the message, usually raw HTML that will :param str body: body of the message, usually raw HTML that will
be sanitized be sanitized
:param str subject: subject of the message :param str subject: subject of the message
:param str message_type: see mail_message.message_type field. Can be anything but :param str message_type: see mail_message.message_type field. Can be anything but
user_notification, reserved for message_notify user_notification, reserved for message_notify
:param int parent_id: handle thread formation :param str email_from: from address of the author. See ``_message_compute_author``
:param int subtype_id: subtype_id of the message, used mainly use for that uses it to make email_from / author_id coherent;
followers notification mechanism; :param int author_id: optional ID of partner record being the author. See
:param list(int) partner_ids: partner_ids to notify in addition to partners ``_message_compute_author`` that uses it to make email_from / author_id coherent;
computed based on subtype / followers matching; :param int parent_id: handle thread formation
:param list(tuple(str,str), tuple(str,str, dict) or int) attachments : list of attachment tuples in the form :param int subtype_id: subtype_id of the message, used mainly for followers
``(name,content)`` or ``(name,content, info)``, where content is NOT base64 encoded notification mechanism;
:param list id attachment_ids: list of existing attachement to link to this message :param list(int) partner_ids: partner_ids to notify in addition to partners
-Should only be setted by chatter computed based on subtype / followers matching;
-Attachement object attached to mail.compose.message(0) will be attached :param list(tuple(str,str), tuple(str,str, dict)) attachments : list of attachment
to the related document. tuples in the form ``(name,content)`` or ``(name,content, info)`` where content
Extra keyword arguments will be used as default column values for the is NOT base64 encoded;
new mail.message record. :param list attachment_ids: list of existing attachments to link to this message
:return int: ID of newly created mail.message -Should only be set by chatter
-Attachment object attached to mail.compose.message(0) will be attached
to the related document.
Extra keyword arguments will be used either
* as default column values for the new mail.message record if they match
mail.message fields;
* propagated to notification methods;
:return record: newly create mail.message
""" """
self.ensure_one() # should always be posted on a record, use message_notify if no record self.ensure_one() # should always be posted on a record, use message_notify if no record
# split message additional values from notify additional values # split message additional values from notify additional values
@@ -1795,12 +1813,11 @@ class MailThread(models.AbstractModel):
if any(not isinstance(pc_id, int) for pc_id in partner_ids): if any(not isinstance(pc_id, int) for pc_id in partner_ids):
raise ValueError(_('message_post partner_ids and must be integer list, not commands.')) raise ValueError(_('message_post partner_ids and must be integer list, not commands.'))
self = self._fallback_lang() # add lang to context imediatly since it will be usefull in various flows latter. self = self._fallback_lang() # add lang to context immediately since it will be useful in various flows latter.
# Explicit access rights check, because display_name is computed as sudo. # Explicit access rights check, because display_name is computed as sudo.
self.check_access_rights('read') self.check_access_rights('read')
self.check_access_rule('read') self.check_access_rule('read')
record_name = record_name or self.display_name
# Find the message's author # Find the message's author
if self.env.user._is_public() and 'guest' in self.env.context: if self.env.user._is_public() and 'guest' in self.env.context:
@@ -1821,38 +1838,43 @@ class MailThread(models.AbstractModel):
parent_id = self._message_compute_parent_id(parent_id) parent_id = self._message_compute_parent_id(parent_id)
values = dict(msg_kwargs) msg_values = dict(msg_kwargs)
values.update({ if 'add_sign' not in msg_values:
msg_values['add_sign'] = True
if not msg_values.get('record_name'):
msg_values['record_name'] = self.display_name
msg_values.update({
'author_id': author_id, 'author_id': author_id,
'author_guest_id': author_guest_id, 'author_guest_id': author_guest_id,
'email_from': email_from, 'email_from': email_from,
'model': self._name, 'model': self._name,
'res_id': self.id, 'res_id': self.id,
# content
'body': body, 'body': body,
'subject': subject or False, 'subject': subject or False,
'message_type': message_type, 'message_type': message_type,
'parent_id': parent_id, 'parent_id': parent_id,
'subtype_id': subtype_id, 'subtype_id': subtype_id,
# recipients
'partner_ids': partner_ids, 'partner_ids': partner_ids,
'add_sign': add_sign,
'record_name': record_name,
}) })
attachments = attachments or [] attachments = attachments or []
attachment_ids = attachment_ids or [] attachment_ids = attachment_ids or []
attachement_values = self._message_post_process_attachments(attachments, attachment_ids, values) attachement_values = self._message_post_process_attachments(attachments, attachment_ids, msg_values)
values.update(attachement_values) # attachement_ids, [body] msg_values.update(attachement_values) # attachement_ids, [body]
new_message = self._message_create(values) new_message = self._message_create(msg_values)
# Set main attachment field if necessary # Set main attachment field if necessary
self._message_set_main_attachment_id(values['attachment_ids']) self._message_set_main_attachment_id(msg_values['attachment_ids'])
if values['author_id'] and values['message_type'] != 'notification' and not self._context.get('mail_create_nosubscribe'): if msg_values['author_id'] and msg_values['message_type'] != 'notification' and not self._context.get('mail_create_nosubscribe'):
if self.env['res.partner'].browse(values['author_id']).active: # we dont want to add odoobot/inactive as a follower if self.env['res.partner'].browse(msg_values['author_id']).active: # we dont want to add odoobot/inactive as a follower
self._message_subscribe(partner_ids=[values['author_id']]) self._message_subscribe(partner_ids=[msg_values['author_id']])
self._message_post_after_hook(new_message, values) self._message_post_after_hook(new_message, msg_values)
self._notify_thread(new_message, values, **notif_kwargs) self._notify_thread(new_message, msg_values, **notif_kwargs)
return new_message return new_message
def _message_set_main_attachment_id(self, attachment_ids): # todo move this out of mail.thread def _message_set_main_attachment_id(self, attachment_ids): # todo move this out of mail.thread
@@ -1957,7 +1979,7 @@ class MailThread(models.AbstractModel):
res_id = False res_id = False
MailThread = self.env['mail.thread'] MailThread = self.env['mail.thread']
values = { msg_values = {
'parent_id': parent_id, 'parent_id': parent_id,
'model': self._name if self else model, 'model': self._name if self else model,
'res_id': self.id if self else res_id, 'res_id': self.id if self else res_id,
@@ -1973,9 +1995,12 @@ class MailThread(models.AbstractModel):
'reply_to': MailThread._notify_get_reply_to(default=email_from)[False], 'reply_to': MailThread._notify_get_reply_to(default=email_from)[False],
'message_id': tools.generate_tracking_message_id('message-notify'), 'message_id': tools.generate_tracking_message_id('message-notify'),
} }
values.update(msg_kwargs) msg_values.update(msg_kwargs)
new_message = MailThread._message_create(values) if 'add_sign' not in msg_values:
MailThread._notify_thread(new_message, values, **notif_kwargs) msg_values['add_sign'] = True
new_message = MailThread._message_create(msg_values)
MailThread._notify_thread(new_message, msg_values, **notif_kwargs)
return new_message return new_message
def _message_log_with_view(self, views_or_xmlid, **kwargs): def _message_log_with_view(self, views_or_xmlid, **kwargs):
@@ -1992,7 +2017,7 @@ class MailThread(models.AbstractModel):
self.ensure_one() self.ensure_one()
author_id, email_from = self._message_compute_author(author_id, email_from, raise_exception=False) author_id, email_from = self._message_compute_author(author_id, email_from, raise_exception=False)
message_values = { msg_values = {
'subject': subject, 'subject': subject,
'body': body, 'body': body,
'author_id': author_id, 'author_id': author_id,
@@ -2005,9 +2030,10 @@ class MailThread(models.AbstractModel):
'record_name': False, 'record_name': False,
'reply_to': self.env['mail.thread']._notify_get_reply_to(default=email_from)[False], 'reply_to': self.env['mail.thread']._notify_get_reply_to(default=email_from)[False],
'message_id': tools.generate_tracking_message_id('message-notify'), # why? this is all but a notify 'message_id': tools.generate_tracking_message_id('message-notify'), # why? this is all but a notify
'add_sign': False, # False as no notification -> no need to compute signature
} }
message_values.update(kwargs) msg_values.update(kwargs)
return self.sudo()._message_create(message_values) return self.sudo()._message_create(msg_values)
def _message_log_batch(self, bodies, author_id=None, email_from=None, subject=False, message_type='notification'): def _message_log_batch(self, bodies, author_id=None, email_from=None, subject=False, message_type='notification'):
""" Shortcut allowing to post notes on a batch of documents. It achieve the """ Shortcut allowing to post notes on a batch of documents. It achieve the
@@ -2028,6 +2054,7 @@ class MailThread(models.AbstractModel):
'record_name': False, 'record_name': False,
'reply_to': self.env['mail.thread']._notify_get_reply_to(default=email_from)[False], 'reply_to': self.env['mail.thread']._notify_get_reply_to(default=email_from)[False],
'message_id': tools.generate_tracking_message_id('message-notify'), # why? this is all but a notify 'message_id': tools.generate_tracking_message_id('message-notify'), # why? this is all but a notify
'add_sign': False,
} }
values_list = [dict(base_message_values, values_list = [dict(base_message_values,
res_id=record.id, res_id=record.id,
@@ -2121,6 +2148,9 @@ class MailThread(models.AbstractModel):
:return: recipients data (see ``MailThread._notify_get_recipients()``) :return: recipients data (see ``MailThread._notify_get_recipients()``)
""" """
# add lang to context immediately since it will be useful in various rendering later
self = self._fallback_lang()
msg_vals = msg_vals if msg_vals else {} msg_vals = msg_vals if msg_vals else {}
recipients_data = self._notify_get_recipients(message, msg_vals) recipients_data = self._notify_get_recipients(message, msg_vals)
if not recipients_data: if not recipients_data:
@@ -2170,8 +2200,8 @@ class MailThread(models.AbstractModel):
self.env['bus.bus'].sudo()._sendmany(bus_notifications) self.env['bus.bus'].sudo()._sendmany(bus_notifications)
def _notify_thread_by_email(self, message, recipients_data, msg_vals=False, def _notify_thread_by_email(self, message, recipients_data, msg_vals=False,
mail_auto_delete=True, # mail.mail mail_auto_delete=True, # mail.mail
model_description=False, # rendering model_description=False, force_email_company=False, force_email_lang=False, # rendering
check_existing=False, force_send=True, send_after_commit=True, # email send check_existing=False, force_send=True, send_after_commit=True, # email send
**kwargs): **kwargs):
""" Method to send email linked to notified messages. """ Method to send email linked to notified messages.
@@ -2196,10 +2226,11 @@ class MailThread(models.AbstractModel):
:param model_description: model description used in email notification process :param model_description: model description used in email notification process
(computed if not given); (computed if not given);
:param force_email_company: see ``_notify_by_email_prepare_rendering_context``;
:param force_email_lang: see ``_notify_by_email_prepare_rendering_context``;
:param check_existing: check for existing notifications to update based on :param check_existing: check for existing notifications to update based on
mailed recipient, otherwise create new notifications; mailed recipient, otherwise create new notifications;
:param force_send: send emails directly instead of using queue; :param force_send: send emails directly instead of using queue;
:param send_after_commit: if force_send, tells whether to send emails after :param send_after_commit: if force_send, tells whether to send emails after
the transaction has been committed using a post-commit hook; the transaction has been committed using a post-commit hook;
@@ -2209,14 +2240,18 @@ class MailThread(models.AbstractModel):
return True return True
model = msg_vals.get('model') if msg_vals else message.model model = msg_vals.get('model') if msg_vals else message.model
model_name = model_description or (self._fallback_lang().env['ir.model']._get(model).display_name if model else False) # one query for display name model_name = model_description or (self.env['ir.model']._get(model).display_name if model else False) # one query for display name
recipients_groups_data = self._notify_get_recipients_classify(partners_data, model_name, msg_vals=msg_vals) recipients_groups_data = self._notify_get_recipients_classify(partners_data, model_name, msg_vals=msg_vals)
if not recipients_groups_data: if not recipients_groups_data:
return True return True
force_send = self.env.context.get('mail_notify_force_send', force_send) force_send = self.env.context.get('mail_notify_force_send', force_send)
template_values = self._notify_by_email_prepare_rendering_context(message, msg_vals, model_description=model_description) # 10 queries template_values = self._notify_by_email_prepare_rendering_context(
message, msg_vals=msg_vals, model_description=model_description,
force_email_company=force_email_company,
force_email_lang=force_email_lang,
) # 10 queries
email_layout_xmlid = msg_vals.get('email_layout_xmlid') if msg_vals else message.email_layout_xmlid email_layout_xmlid = msg_vals.get('email_layout_xmlid') if msg_vals else message.email_layout_xmlid
template_xmlid = email_layout_xmlid if email_layout_xmlid else 'mail.message_notification_email' template_xmlid = email_layout_xmlid if email_layout_xmlid else 'mail.message_notification_email'
@@ -2316,7 +2351,8 @@ class MailThread(models.AbstractModel):
return True return True
@api.model @api.model
def _notify_by_email_prepare_rendering_context(self, message, msg_vals, model_description=False): def _notify_by_email_prepare_rendering_context(self, message, msg_vals=False, model_description=False,
force_email_company=False, force_email_lang=False):
""" Prepare rendering context for notification email. """ Prepare rendering context for notification email.
Signature: if asked a default signature is computed based on author. Either Signature: if asked a default signature is computed based on author. Either
@@ -2324,75 +2360,101 @@ class MailThread(models.AbstractModel):
user and we compute a default one based on the author's name. user and we compute a default one based on the author's name.
Company: either there is one defined on the record (company_id field set Company: either there is one defined on the record (company_id field set
with a value), either we use env.company. with a value), either we use env.company. A new parameter allows to force
its value.
Lang: when calling this method, ``_fallback_lang`` should already been Lang: when calling this method, ``_fallback_lang`` should already been
called, or a lang set in context with another way. A wild guess is done called, or a lang set in context with another way. A wild guess is done
based on templates to try to retrieve the recipient's language when a flow based on templates to try to retrieve the recipient's language when a flow
like "send by email" is performed. Lang is used to try to have the like "send by email" is performed. Lang is used to try to have the
notification layout in the same language as the email content. notification layout in the same language as the email content. A new
parameter allows to force its value.
:param msg_vals: dictionary of values used to create the message. If given it
may be used to access values related to ``message`` without accessing it
directly. It lessens query count in some optimized use cases by avoiding
access message content in db;
:param model_description: model description used in email notification process :param model_description: model description used in email notification process
(computed if not given); (computed if not given);
:param force_email_company: res.company record used when rendering notification
layout. Otherwise computed based on current record;
:param force_email_lang: lang used when rendering content, used notably to
compute model name;
""" """
if msg_vals is False:
msg_vals = {}
# compute send user and its related signature; try to use self.env.user instead of browsing
# user_ids if he is the author will give a sudo user, improving access performances and cache usage.
signature = '' signature = ''
user = self.env.user add_sign = msg_vals.get('add_sign') if 'add_sign' in msg_vals else message.add_sign
author = message.env['res.partner'].browse(msg_vals.get('author_id')) if msg_vals else message.author_id if add_sign:
model = msg_vals.get('model') if msg_vals else message.model author = message.env['res.partner'].browse(msg_vals.get('author_id')) if 'author_id' in msg_vals else message.author_id
add_sign = msg_vals.get('add_sign') if msg_vals else message.add_sign author_user = self.env.user if self.env.user.partner_id == author else author.user_ids[0] if author and author.user_ids else False
subtype_id = msg_vals.get('subtype_id') if msg_vals else message.subtype_id.id if author_user:
message_id = message.id signature = author_user.signature
record_name = msg_vals.get('record_name') if msg_vals else message.record_name else:
author_user = user if user.partner_id == author else author.user_ids[0] if author and author.user_ids else False
# trying to use user (self.env.user) instead of browing user_ids if he is the author will give a sudo user,
# improving access performances and cache usage.
if author_user:
user = author_user
if add_sign:
signature = user.signature
else:
if add_sign:
signature = "<p>-- <br/>%s</p>" % author.name signature = "<p>-- <br/>%s</p>" % author.name
company = self.company_id.sudo() if self and 'company_id' in self and self.company_id else self.env.company if force_email_company:
company = force_email_company
else:
company = self.company_id.sudo() if self and 'company_id' in self and self.company_id else self.env.company
if company.website: if company.website:
website_url = 'http://%s' % company.website if not company.website.lower().startswith(('http:', 'https:')) else company.website website_url = 'http://%s' % company.website if not company.website.lower().startswith(('http:', 'https:')) else company.website
else: else:
website_url = False website_url = False
# TDE FIXME: this whole brol should be cleaned ! # compute lang in which content was rendered or typed
lang = self.env.context.get('lang') lang = False
if {'default_template_id', 'default_model', 'default_res_id'} <= self.env.context.keys(): if force_email_lang:
lang = force_email_lang
elif {'default_template_id', 'default_model', 'default_res_id'} <= self.env.context.keys():
# TDE FIXME: this whole brol should be cleaned !
template = self.env['mail.template'].browse(self.env.context['default_template_id']) template = self.env['mail.template'].browse(self.env.context['default_template_id'])
if template and template.lang: if template and template.lang:
lang = template._render_lang([self.env.context['default_res_id']])[self.env.context['default_res_id']] lang = template._render_lang([self.env.context['default_res_id']])[self.env.context['default_res_id']]
if not lang:
lang = self.env.context.get('lang')
if not model_description and model: # record, model
model_description = self.env['ir.model'].with_context(lang=lang)._get(model).display_name if not model_description:
model = msg_vals.get('model') if 'model' in msg_vals else message.model
if model:
model_description = self.env['ir.model'].with_context(lang=lang)._get(model).display_name
record_name = msg_vals.get('record_name') if 'record_name' in msg_vals else message.record_name
# tracking
tracking = [] tracking = []
if msg_vals.get('tracking_value_ids', True) if msg_vals else bool(self): # could be tracking if msg_vals.get('tracking_value_ids', True) if msg_vals else bool(self): # could be tracking
for tracking_value in self.env['mail.tracking.value'].sudo().search([('mail_message_id', '=', message.id)]): for tracking_value in self.env['mail.tracking.value'].sudo().search([('mail_message_id', '=', message.id)]):
groups = tracking_value.field_groups groups = tracking_value.field_groups
if not groups or self.env.is_superuser() or self.user_has_groups(groups): if not groups or self.env.is_superuser() or self.user_has_groups(groups):
tracking.append((tracking_value.field_desc, tracking.append((tracking_value.field_desc,
tracking_value.get_old_display_value()[0], tracking_value.get_old_display_value()[0],
tracking_value.get_new_display_value()[0])) tracking_value.get_new_display_value()[0]))
subtype_id = msg_vals.get('subtype_id') if msg_vals and 'subtype_id' in msg_vals else message.subtype_id.id
is_discussion = subtype_id == self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment') is_discussion = subtype_id == self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment')
return { return {
# message
'is_discussion': is_discussion,
'message': message, 'message': message,
'signature': signature, 'subtype': message.subtype_id,
'website_url': website_url, 'tracking_values': tracking,
'company': company, # record
'model_description': model_description, 'model_description': model_description,
'record': self, 'record': self,
'record_name': record_name, 'record_name': record_name,
'tracking_values': tracking, # user / environment
'is_discussion': is_discussion, 'add_sign': add_sign,
'subtype': message.subtype_id, 'company': company,
'lang': lang, 'lang': lang,
'signature': signature,
'website_url': website_url,
# tools
'is_html_empty': is_html_empty,
} }
def _notify_by_email_get_base_mail_values(self, message, additional_values=None): def _notify_by_email_get_base_mail_values(self, message, additional_values=None):