[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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user