[MOV] mail, sms: move language computation in render mixin directly

PURPOSE

Merge rendering tools used in mail and sms templates as well as partially
in mailings and move them directly in mail.render.mixin.

SPECIFICATIONS

Move tools related to language computation from mail.template model to the
mail.render.mixin. It allows to update both

  * mail.template: simply update calls accordingly;
  * sms.template: remove code doing what is now available through mail.render
    .mixin inherit;

Lang field is moved to mail.render.mixin as it is used notably for the language
computation. It therefore adds the field on mailing.mailing model although
not used currently.

LINKS

Task ID 1963529
Community PR odoo/odoo#32397
This commit is contained in:
Thibault Delavallée
2020-03-24 10:24:42 +00:00
parent 822f468059
commit 2835e0ba38
7 changed files with 94 additions and 140 deletions
+78
View File
@@ -80,6 +80,13 @@ class MailRenderMixin(models.AbstractModel):
_name = 'mail.render.mixin'
_description = 'Mail Render Mixin'
# language for rendering
lang = fields.Char(
'Language', placeholder="${object.partner_id.lang}",
help="Optional translation language (ISO code) to select when sending out an email. "
"If not set, the english version will be used. This should usually be a placeholder expression "
"that provides the appropriate language, e.g. ${object.partner_id.lang}.")
# expression builder
model_object_field = fields.Many2one(
'ir.model.fields', string="Field", store=False,
help="Select target field from the related document model.\n"
@@ -250,3 +257,74 @@ class MailRenderMixin(models.AbstractModel):
rendered = self._render_template_postprocess(rendered)
return rendered
def _render_lang(self, res_ids):
""" Given some record ids, return the lang for each record based on
lang field of template or through specific context-based key.
:param list res_ids: list of ids of records (all belonging to same model
defined by self.model)
:return dict: {res_id: lang code (i.e. en_US)}
"""
self.ensure_one()
if not isinstance(res_ids, (list, tuple)):
raise ValueError(_('Template rendering for language should be called with a list of IDs.'))
if self.env.context.get('template_preview_lang'):
return dict((res_id, self.env.context['template_preview_lang']) for res_id in res_ids)
else:
rendered_langs = self._render_template(self.lang, self.model, res_ids)
return dict((res_id, lang)
for res_id, lang in rendered_langs.items())
def _classify_per_lang(self, res_ids):
""" Given some record ids, return for computed each lang a contextualized
template and its subset of res_ids.
:param list res_ids: list of ids of records (all belonging to same model
defined by self.model)
:return dict: {lang: (template with lang=lang_code if specific lang computed
or template, res_ids targeted by that language}
"""
self.ensure_one()
lang_to_res_ids = {}
for res_id, lang in self._render_lang(res_ids).items():
lang_to_res_ids.setdefault(lang, []).append(res_id)
return dict(
(lang, (self.with_context(lang=lang) if lang else self, lang_res_ids))
for lang, lang_res_ids in lang_to_res_ids.items()
)
def _render_field(self, field, res_ids, compute_lang=False, set_lang=False, post_process=False):
""" Given some record ids, render a given field of template rendered on
all records.
:param list res_ids: list of ids of records (all belonging to same model
defined by self.model)
:param compute_lang: compute rendering language based on template.lang
:param set_lang: force language
:param post_process: perform rendered str / html post processing (see
``_render_template_postprocess``)
:return dict: {res_id: string of rendered template based on record}
"""
self.ensure_one()
if compute_lang:
templates_res_ids = self._classify_per_lang(res_ids)
elif set_lang:
templates_res_ids = {set_lang: (self.with_context(lang=set_lang), res_ids)}
else:
templates_res_ids = {self._context.get('lang'): (self, res_ids)}
return dict(
(res_id, rendered)
for lang, (template, tpl_res_ids) in templates_res_ids.items()
for res_id, rendered in template._render_template(
template[field], template.model, tpl_res_ids,
post_process=post_process
).items()
)
-82
View File
@@ -29,13 +29,6 @@ class MailTemplate(models.Model):
name = fields.Char('Name')
model_id = fields.Many2one('ir.model', 'Applies to', help="The type of document this template can be used with")
model = fields.Char('Related Document Model', related='model_id.model', index=True, store=True, readonly=True)
lang = fields.Char('Language',
help="Optional translation language (ISO code) to select when sending out an email. "
"If not set, the english version will be used. "
"This should usually be a placeholder expression "
"that provides the appropriate language, e.g. "
"${object.partner_id.lang}.",
placeholder="${object.partner_id.lang}")
subject = fields.Char('Subject', translate=True, help="Subject (placeholders may be used here)")
email_from = fields.Char('From',
help="Sender address (placeholders may be used here). If not set, the default "
@@ -110,81 +103,6 @@ class MailTemplate(models.Model):
return True
# ------------------------------------------------------------
# RENDERING
# ------------------------------------------------------------
def _render_lang(self, res_ids):
""" Given some record ids, return the lang for each record based on
lang field of template or through specific context-based key.
:param list res_ids: list of ids of records (all belonging to same model
defined by self.model)
:return dict: {res_id: lang code (i.e. en_US)}
"""
self.ensure_one()
if not isinstance(res_ids, (list, tuple)):
raise ValueError(_('Template rendering for language should be called with a list of IDs.'))
if self.env.context.get('template_preview_lang'):
return dict((res_id, self.env.context['template_preview_lang']) for res_id in res_ids)
else:
rendered_langs = self._render_template(self.lang, self.model, res_ids)
return dict((res_id, lang)
for res_id, lang in rendered_langs.items())
def _classify_per_lang(self, res_ids):
""" Given some record ids, return for computed each lang a contextualized
template and its subset of res_ids.
:param list res_ids: list of ids of records (all belonging to same model
defined by self.model)
:return dict: {lang: (template with lang=lang_code if specific lang computed
or template, res_ids targeted by that language}
"""
self.ensure_one()
lang_to_res_ids = {}
for res_id, lang in self._render_lang(res_ids).items():
lang_to_res_ids.setdefault(lang, []).append(res_id)
return dict(
(lang, (self.with_context(lang=lang) if lang else self, lang_res_ids))
for lang, lang_res_ids in lang_to_res_ids.items()
)
def _render_field(self, field, res_ids, compute_lang=False, set_lang=False, post_process=False):
""" Given some record ids, render a given field of template rendered on
all records.
:param list res_ids: list of ids of records (all belonging to same model
defined by self.model)
:param compute_lang: compute rendering language based on template.lang
:param set_lang: force language
:param post_process: perform rendered str / html post processing (see
``_render_template_postprocess``)
:return dict: {res_id: string of rendered template based on record}
"""
self.ensure_one()
if compute_lang:
templates_res_ids = self._classify_per_lang(res_ids)
elif set_lang:
templates_res_ids = {set_lang: (self.with_context(lang=set_lang), res_ids)}
else:
templates_res_ids = {self._context.get('lang'): (self, res_ids)}
return dict(
(res_id, rendered)
for lang, (template, tpl_res_ids) in templates_res_ids.items()
for res_id, rendered in template._render_template(
template[field], template.model, tpl_res_ids,
post_process=post_process
).items()
)
# ------------------------------------------------------------
# MESSAGE/EMAIL VALUES GENERATION
# ------------------------------------------------------------
+1 -2
View File
@@ -180,8 +180,7 @@ class MailThread(models.AbstractModel):
if not template and template_xmlid:
template = self.env.ref(template_xmlid, raise_if_not_found=False)
if template:
template_w_lang = template._get_context_lang_per_id(self.ids)[self.id]
body = template._render_template(template_w_lang.body, self._name, self.ids)[self.id]
body = template._render_field('body', self.ids, compute_lang=True)[self.id]
else:
body = self.env['sms.template']._render_template(template_fallback, self._name, self.ids)[self.id]
return self._message_sms(body, partner_ids=partner_ids, **kwargs)
-43
View File
@@ -24,9 +24,6 @@ class SMSTemplate(models.Model):
help="The type of document this template can be used with", ondelete='cascade')
model = fields.Char('Related Document Model', related='model_id.model', index=True, store=True, readonly=True)
body = fields.Char('Body', translate=True, required=True)
lang = fields.Char('Language', help="Use this field to either force a specific language (ISO code) or dynamically "
"detect the language of your recipient by a placeholder expression "
"(e.g. ${object.partner_id.lang})")
# Use to create contextual action (same as for email template)
sidebar_action_id = fields.Many2one('ir.actions.act_window', 'Sidebar action', readonly=True, copy=False,
help="Sidebar action to make this template available on records "
@@ -67,43 +64,3 @@ class SMSTemplate(models.Model):
if template.sidebar_action_id:
template.sidebar_action_id.unlink()
return True
# ------------------------------------------------------------
# RENDERING
# ------------------------------------------------------------
def _get_context_lang_per_id(self, res_ids):
self.ensure_one()
if res_ids is None:
return {None: self}
if self.env.context.get('template_preview_lang'):
lang = self.env.context.get('template_preview_lang')
results = dict((res_id, self.with_context(lang=lang)) for res_id in res_ids)
else:
rendered_langs = self._render_template(self.lang, self.model, res_ids)
results = dict(
(res_id, self.with_context(lang=lang) if lang else self)
for res_id, lang in rendered_langs.items())
return results
def _get_ids_per_lang(self, res_ids):
self.ensure_one()
rids_to_tpl = self._get_context_lang_per_id(res_ids)
tpl_to_rids = {}
for res_id, template in rids_to_tpl.items():
tpl_to_rids.setdefault(template._context.get('lang', self.env.user.lang), []).append(res_id)
return tpl_to_rids
def _get_translated_bodies(self, res_ids):
""" return sms translated bodies into a dict {'res_id':'body'} """
self.ensure_one()
lang_to_rids = self._get_ids_per_lang(res_ids)
all_bodies = {}
for lang, rids in lang_to_rids.items():
template = self.with_context(lang=lang)
all_bodies.update(template._render_template(template.body, self.model, rids))
return all_bodies
+2 -2
View File
@@ -168,7 +168,7 @@ class SendSMS(models.TransientModel):
@api.onchange('composition_mode', 'res_model', 'res_id', 'template_id')
def _onchange_template_id(self):
if self.template_id and self.composition_mode == 'comment' and self.res_id:
self.body = self.template_id._get_translated_bodies([self.res_id])[self.res_id]
self.body = self.template_id._render_field('body', [self.res_id], compute_lang=True)[self.res_id]
elif self.template_id:
self.body = self.template_id.body
@@ -279,7 +279,7 @@ class SendSMS(models.TransientModel):
def _prepare_body_values(self, records):
if self.template_id and self.body == self.template_id.body:
all_bodies = self.template_id._get_translated_bodies(records.ids)
all_bodies = self.template_id._render_field('body', records.ids, compute_lang=True)
else:
all_bodies = self.env['mail.render.mixin']._render_template(self.body, records._name, records.ids)
return all_bodies
+4 -3
View File
@@ -45,6 +45,7 @@ class SMSTemplatePreview(models.TransientModel):
@api.depends('lang', 'resource_ref')
def _compute_sms_template_fields(self):
for wizard in self:
# Update body depending of the resource_ref
sms_template = wizard.sms_template_id.with_context(lang=wizard.lang)
wizard.body = sms_template._render_template(sms_template.body, sms_template.model, wizard.resource_ref.id) if wizard.resource_ref else sms_template.body
if wizard.sms_template_id and wizard.resource_ref:
wizard.body = wizard.sms_template_id._render_field('body', [wizard.resource_ref.id], set_lang=wizard.lang)[wizard.resource_ref.id]
else:
wizard.body = wizard.sms_template_id.body
@@ -50,18 +50,20 @@ class TestSmsTemplate(test_mail_full_common.TestSMSCommon, test_mail_full_common
self.assertEqual(self.sms_template.body, self.body_en)
self.assertEqual(self.sms_template.with_context(lang='fr_FR').body, self.body_fr)
rid_to_tpl = self.sms_template._get_context_lang_per_id((self.test_record | test_record_2).ids)
self.assertEqual(set(rid_to_tpl.keys()), set((self.test_record | test_record_2).ids))
for rid, tpl in rid_to_tpl.items():
rid_to_lang = self.sms_template._render_lang((self.test_record | test_record_2).ids)
self.assertEqual(set(rid_to_lang.keys()), set((self.test_record | test_record_2).ids))
for rid, lang in rid_to_lang.items():
# TDE FIXME: False or en_US ?
if rid == self.test_record.id:
self.assertEqual(tpl._context.get('lang'), 'en_US')
self.assertEqual(lang, 'en_US')
elif rid == test_record_2.id:
self.assertEqual(tpl._context.get('lang'), 'fr_FR')
self.assertEqual(lang, 'fr_FR')
else:
self.assertTrue(False)
lang_to_rids = self.sms_template._get_ids_per_lang((self.test_record | test_record_2).ids)
for lang, rids in lang_to_rids.items():
tpl_to_rids = self.sms_template._classify_per_lang((self.test_record | test_record_2).ids)
for lang, (tpl, rids) in tpl_to_rids.items():
# TDE FIXME: False or en_US ?
if lang == 'en_US':
self.assertEqual(rids, self.test_record.ids)
elif lang == 'fr_FR':
@@ -87,4 +89,3 @@ class TestSmsTemplate(test_mail_full_common.TestSMSCommon, test_mail_full_common
self.sms_template.unlink()
self.assertEqual(ActWindow.search_count([('id', '=', action_id)]), 0)