[IMP] mail, mass_mailing: add dynamic placeholder mixin and use it in mailing and templates

PURPOSE

Upgrade the "mass_mailing" and "mass_mailing_sms" modules with a dynamic
placeholder generator as it already exists in "mail module" as depicted in
https://www.screencast.com/t/cnFA0gIY.

SPECIFICATIONS

As duplicated code already exists for that and that a third version of this
code has to be added, instead create a mixin for this dynamic placeholder
generator to avoid code duplication.

Thereby

  * a mail.render mixin for the dynamic placeholder generator must be
    created in mail;
  * dynamic placeholder generator code present in mail.template.py must be
    moved to that mixin and replaced by a simple inherit;
  * use the mixin in

    * mail templates: mail.template.py (mail module);
    * mass mailings: mailing.py (mass_mailing module);
    * sms templates: sms.template (mass_mailing_sms module);

In a near future, some code will be added in this mixin, notably the template
rendering that could be moved outside of mail.template core model and moved
in that rendering mixin.

LINKS

Task ID 2070612
PR #36722

Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
laa
2019-11-07 16:19:37 +00:00
committed by Thibault Delavallée
parent dc70fb35a1
commit 72aa049844
6 changed files with 86 additions and 107 deletions
+1
View File
@@ -6,6 +6,7 @@ from . import mail_tracking_value
from . import mail_alias
from . import mail_followers
from . import mail_notification
from . import mail_render_mixin
from . import mail_message
from . import mail_activity
from . import mail_mail
+68
View File
@@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class MailRenderMixin(models.AbstractModel):
_name = 'mail.render.mixin'
_description = 'Mail Render Mixin'
model_object_field = fields.Many2one(
'ir.model.fields', string="Field", store=False,
help="Select target field from the related document model.\n"
"If it is a relationship field you will be able to select "
"a target field at the destination of the relationship.")
sub_object = fields.Many2one(
'ir.model', 'Sub-model', readonly=True, store=False,
help="When a relationship field is selected as first field, "
"this field shows the document model the relationship goes to.")
sub_model_object_field = fields.Many2one(
'ir.model.fields', 'Sub-field', store=False,
help="When a relationship field is selected as first field, "
"this field lets you select the target field within the "
"destination document model (sub-model).")
null_value = fields.Char('Default Value', store=False, help="Optional value to use if the target field is empty")
copyvalue = fields.Char(
'Placeholder Expression',store=False,
help="Final placeholder expression, to be copy-pasted in the desired template field.")
@api.onchange('model_object_field', 'sub_model_object_field', 'null_value')
def _onchange_dynamic_placeholder(self):
""" Generate the dynamic placeholder """
if self.model_object_field:
if self.model_object_field.ttype in ['many2one', 'one2many', 'many2many']:
model = self.env['ir.model']._get(self.model_object_field.relation)
if model:
self.sub_object = model.id
sub_field_name = self.sub_model_object_field.name
self.copyvalue = self._build_expression(self.model_object_field.name,
sub_field_name, self.null_value or False)
else:
self.sub_object = False
self.sub_model_object_field = False
self.copyvalue = self._build_expression(self.model_object_field.name, False, self.null_value or False)
else:
self.sub_object = False
self.copyvalue = False
self.sub_model_object_field = False
self.null_value = False
@api.model
def _build_expression(self, field_name, sub_field_name, null_value):
"""Returns a placeholder expression for use in a template field,
based on the values provided in the placeholder assistant.
:param field_name: main field name
:param sub_field_name: sub field name (M2O)
:param null_value: default value if the target value is empty
:return: final placeholder expression """
expression = ''
if field_name:
expression = "${object." + field_name
if sub_field_name:
expression += "." + sub_field_name
if null_value:
expression += " or '''%s'''" % null_value
expression += "}"
return expression
+1 -50
View File
@@ -80,6 +80,7 @@ except ImportError:
class MailTemplate(models.Model):
"Templates for sending email"
_name = "mail.template"
_inherit = ['mail.render.mixin']
_description = 'Email Templates'
_order = 'name'
@@ -134,20 +135,6 @@ class MailTemplate(models.Model):
"emails created from this template")
auto_delete = fields.Boolean('Auto Delete', default=True, help="Permanently delete this email after sending it, to save space")
# Fake fields used to implement the placeholder assistant
model_object_field = fields.Many2one('ir.model.fields', string="Field",
help="Select target field from the related document model.\n"
"If it is a relationship field you will be able to select "
"a target field at the destination of the relationship.")
sub_object = fields.Many2one('ir.model', 'Sub-model', readonly=True,
help="When a relationship field is selected as first field, "
"this field shows the document model the relationship goes to.")
sub_model_object_field = fields.Many2one('ir.model.fields', 'Sub-field',
help="When a relationship field is selected as first field, "
"this field lets you select the target field within the "
"destination document model (sub-model).")
null_value = fields.Char('Default Value', help="Optional value to use if the target field is empty")
copyvalue = fields.Char('Placeholder Expression', help="Final placeholder expression, to be copy-pasted in the desired template field.")
scheduled_date = fields.Char('Scheduled Date', help="If set, the queue manager will send the email after the date. If not set, the email will be send as soon as possible. Jinja2 placeholders may be used.")
@api.onchange('model_id')
@@ -158,42 +145,6 @@ class MailTemplate(models.Model):
else:
self.model = False
def build_expression(self, field_name, sub_field_name, null_value):
"""Returns a placeholder expression for use in a template field,
based on the values provided in the placeholder assistant.
:param field_name: main field name
:param sub_field_name: sub field name (M2O)
:param null_value: default value if the target value is empty
:return: final placeholder expression """
expression = ''
if field_name:
expression = "${object." + field_name
if sub_field_name:
expression += "." + sub_field_name
if null_value:
expression += " or '''%s'''" % null_value
expression += "}"
return expression
@api.onchange('model_object_field', 'sub_model_object_field', 'null_value')
def onchange_sub_model_object_value_field(self):
if self.model_object_field:
if self.model_object_field.ttype in ['many2one', 'one2many', 'many2many']:
model = self.env['ir.model']._get(self.model_object_field.relation)
if model:
self.sub_object = model.id
self.copyvalue = self.build_expression(self.model_object_field.name, self.sub_model_object_field and self.sub_model_object_field.name or False, self.null_value or False)
else:
self.sub_object = False
self.sub_model_object_field = False
self.copyvalue = self.build_expression(self.model_object_field.name, False, self.null_value or False)
else:
self.sub_object = False
self.copyvalue = False
self.sub_model_object_field = False
self.null_value = False
def unlink(self):
self.unlink_action()
return super(MailTemplate, self).unlink()
+2 -2
View File
@@ -40,7 +40,7 @@ class MassMailing(models.Model):
A mass mailing is an occurence of sending emails. """
_name = 'mailing.mailing'
_description = 'Mass Mailing'
_inherit = [ 'mail.thread', 'mail.activity.mixin']
_inherit = [ 'mail.thread', 'mail.activity.mixin', 'mail.render.mixin']
# number of periods for tracking mail_mail statistics
_period_number = 6
_order = 'sent_date DESC'
@@ -128,7 +128,7 @@ class MassMailing(models.Model):
replied_ratio = fields.Integer(compute="_compute_statistics", string='Replied Ratio')
bounced_ratio = fields.Integer(compute="_compute_statistics", string='Bounced Ratio')
next_departure = fields.Datetime(compute="_compute_next_departure", string='Scheduled date')
def _compute_total(self):
for mass_mailing in self:
mass_mailing.total = len(mass_mailing.sudo()._get_recipients())
@@ -177,6 +177,19 @@
'inline-field': 'body_html'
}" attrs="{'readonly': [('state', 'in', ('sending', 'done'))]}"/>
</page>
<page string="Dynamic Placeholder Generator" groups="base.group_no_one">
<group>
<field name="model_object_field" attrs="{'invisible': True}"/>
<field name="model_object_field"
domain="[('model_id','=',mailing_model_real),('ttype','!=','one2many'),('ttype','!=','many2many')]"/>
<field name="sub_object" readonly="1"/>
<field name="sub_model_object_field"
domain="[('model_id','=',sub_object),('ttype','!=','one2many'),('ttype','!=','many2many')]"
attrs="{'readonly':[('sub_object','=',False)],'required':[('sub_object','!=',False)]}"/>
<field name="null_value"/>
<field name="copyvalue"/>
</group>
</page>
<page string="Settings">
<group>
<group>
+1 -55
View File
@@ -7,6 +7,7 @@ from odoo import api, fields, models, _
class SMSTemplate(models.Model):
"Templates for sending SMS"
_name = "sms.template"
_inherit = ['mail.render.mixin']
_description = 'SMS Templates'
@api.model
@@ -30,61 +31,6 @@ class SMSTemplate(models.Model):
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 "
"of the related document model")
# Fake fields used to implement the placeholder assistant
model_object_field = fields.Many2one('ir.model.fields', string="Field", store=False,
help="Select target field from the related document model.\n"
"If it is a relationship field you will be able to select "
"a target field at the destination of the relationship.")
sub_object = fields.Many2one('ir.model', 'Sub-model', readonly=True, store=False,
help="When a relationship field is selected as first field, "
"this field shows the document model the relationship goes to.")
sub_model_object_field = fields.Many2one('ir.model.fields', 'Sub-field', store=False,
help="When a relationship field is selected as first field, "
"this field lets you select the target field within the "
"destination document model (sub-model).")
null_value = fields.Char('Default Value', store=False, help="Optional value to use if the target field is empty")
copyvalue = fields.Char('Placeholder Expression', store=False,
help="Final placeholder expression, to be copy-pasted in the desired template field.")
@api.onchange('model_object_field', 'sub_model_object_field', 'null_value')
def _onchange_dynamic_placeholder(self):
""" Generate the dynamic placeholder """
if self.model_object_field:
if self.model_object_field.ttype in ['many2one', 'one2many', 'many2many']:
model = self.env['ir.model']._get(self.model_object_field.relation)
if model:
self.sub_object = model.id
sub_field_name = self.sub_model_object_field.name
self.copyvalue = self._build_expression(self.model_object_field.name,
sub_field_name, self.null_value or False)
else:
self.sub_object = False
self.sub_model_object_field = False
self.copyvalue = self._build_expression(self.model_object_field.name, False, self.null_value or False)
else:
self.sub_object = False
self.copyvalue = False
self.sub_model_object_field = False
self.null_value = False
@api.model
def _build_expression(self, field_name, sub_field_name, null_value):
"""Returns a placeholder expression for use in a template field,
based on the values provided in the placeholder assistant.
:param field_name: main field name
:param sub_field_name: sub field name (M2O)
:param null_value: default value if the target value is empty
:return: final placeholder expression """
expression = ''
if field_name:
expression = "${object." + field_name
if sub_field_name:
expression += "." + sub_field_name
if null_value:
expression += " or '''%s'''" % null_value
expression += "}"
return expression
@api.returns('self', lambda value: value.id)
def copy(self, default=None):