[IMP] email_template: added an option to automatically generate

recipients instead of using template-based values. This should be the default way of using
templates actually, to avoid confusion with mako, ill-defined templates, ...

email_template code has been updated to handle recipient_ids when creating an email, depending
on its generated template values or default recipients.

mail_compose_message has also been updated, delegating the calculation of recipient when
having a template to the template itself, using some context keys to force the use
of partners instead of email_to and email_cc.

Also updated email_template form view to be a bit less confusing.

bzr revid: tde@openerp.com-20140325140324-bljvybigk3zv5ugm
This commit is contained in:
Thibault Delavallée 2014-03-25 15:03:24 +01:00
parent 73bebb6563
commit 97d49659d0
4 changed files with 115 additions and 88 deletions

View File

@ -231,6 +231,11 @@ class email_template(osv.osv):
'email_from': fields.char('From',
help="Sender address (placeholders may be used here). If not set, the default "
"value will be the author's email alias if configured, or email address."),
'use_default_to': fields.boolean(
'Default recipients',
help="Default recipients of the record:\n"
"- partner (using id on a partner or the partner_id field) OR\n"
"- email (using email_from or email field)"),
'email_to': fields.char('To (Emails)', help="Comma-separated recipient addresses (placeholders may be used here)"),
'partner_to': fields.char('To (Partners)',
help="Comma-separated ids of recipient partners (placeholders may be used here)",
@ -386,6 +391,42 @@ class email_template(osv.osv):
})
return {'value': result}
def generate_recipients_batch(self, cr, uid, results, template_id, res_ids, context=None):
"""Generates the recipients of the template. Default values can ben generated
instead of the template values if requested by template or context.
Emails (email_to, email_cc) can be transformed into partners if requested
in the context. """
if context is None:
context = {}
template = self.browse(cr, uid, template_id, context=context)
if template.use_default_to or context.get('tpl_force_default_to'):
if template.model and hasattr(self.pool[template.model], 'message_get_default_recipients'):
default_recipients = self.pool[template.model].message_get_default_recipients(cr, uid, res_ids, context=context)
elif template.model:
ctx = dict(context, thread_model=template.model)
default_recipients = self.pool['mail.thread'].message_get_default_recipients(cr, uid, res_ids, context=ctx)
else:
default_recipients = {}
for res_id, recipients in default_recipients:
results[res_id].pop('partner_to')
results[res_id].update(default_recipients)
for res_id, values in results.iteritems():
partner_ids = values.get('partner_ids', list())
if context and context.get('tpl_partners_only'):
mails = tools.email_split(values.pop('email_to', '')) + tools.email_split(values.pop('email_cc', ''))
for mail in mails:
partner_id = self.pool.get('res.partner').find_or_create(cr, uid, mail, context=context)
partner_ids.append(partner_id)
partner_to = values.pop('partner_to', '')
if partner_to:
# placeholders could generate '', 3, 2 due to some empty field values
tpl_partner_ids = [pid for pid in partner_to.split(',') if pid]
partner_ids += self.pool['res.partner'].exists(cr, SUPERUSER_ID, tpl_partner_ids, context=context)
results[res_id]['partner_ids'] = partner_ids
return results
def generate_email_batch(self, cr, uid, template_id, res_ids, context=None, fields=None):
"""Generates an email from the template for given the given model based on
records given by res_ids.
@ -420,14 +461,18 @@ class email_template(osv.osv):
context=context)
for res_id, field_value in generated_field_values.iteritems():
results.setdefault(res_id, dict())[field] = field_value
# compute recipients
results = self.generate_recipients_batch(cr, uid, results, template.id, template_res_ids, context=context)
# update values for all res_ids
for res_id in template_res_ids:
values = results[res_id]
# body: add user signature, sanitize
if 'body_html' in fields and template.user_signature:
signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
values['body_html'] = tools.append_content_to_html(values['body_html'], signature)
if values.get('body_html'):
values['body'] = tools.html_sanitize(values['body_html'])
# technical settings
values.update(
mail_server_id=template.mail_server_id.id or False,
auto_delete=template.auto_delete,
@ -484,17 +529,8 @@ class email_template(osv.osv):
# create a mail_mail based on values, without attachments
values = self.generate_email(cr, uid, template_id, res_id, context=context)
if not values.get('email_from'):
raise osv.except_osv(_('Warning!'),_("Sender email is missing or empty after template rendering. Specify one to deliver your message"))
# process partner_to field that is a comma separated list of partner_ids -> recipient_ids
# NOTE: only usable if force_send is True, because otherwise the value is
# not stored on the mail_mail, and therefore lost -> fixed in v8
values['recipient_ids'] = []
partner_to = values.pop('partner_to', '')
if partner_to:
# placeholders could generate '', 3, 2 due to some empty field values
tpl_partner_ids = [pid for pid in partner_to.split(',') if pid]
values['recipient_ids'] += [(4, pid) for pid in self.pool['res.partner'].exists(cr, SUPERUSER_ID, tpl_partner_ids, context=context)]
raise osv.except_osv(_('Warning!'), _("Sender email is missing or empty after template rendering. Specify one to deliver your message"))
values['recipient_ids'] = [(4, pid) for pid in values.get('partner_ids', list())]
attachment_ids = values.pop('attachment_ids', [])
attachments = values.pop('attachments', [])
msg_id = mail_mail.create(cr, uid, values, context=context)

View File

@ -9,8 +9,10 @@
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only"/><h1><field name="name" required="1"/></h1>
<label for="model_id"/><field name="model_id" required="1" on_change="onchange_model_id(model_id)" class="oe_inline"/>
<field name="model" invisible="1"/>
<group>
<field name="model_id" required="1" on_change="onchange_model_id(model_id)"/>
<field name="model" invisible="1"/>
</group>
</div>
<div class="oe_right oe_button_box" name="buttons">
<field name="ref_ir_act_window" invisible="1"/>
@ -25,43 +27,28 @@
context="{'template_id':active_id}"/>
</div>
<notebook>
<page string="Mailing Template">
<group>
<group>
<field name="email_from"
placeholder="Override author's email"/>
<field name="email_to"
placeholder="Comma-separated recipient addresses"/>
<field name="partner_to"
placeholder="Comma-separated ids of recipient partners"/>
<field name="email_cc"
placeholder="Comma-separated carbon copy recipients addresses"/>
<field name="reply_to"
placeholder="Preferred reply address"/>
<field name="subject"
placeholder="Subject (placeholders may be used here)"/>
<field name="user_signature" string="Author Signature (mass mail only)"/>
</group>
<group class="oe_edit_only">
<h3 colspan="2">Dynamic placeholder generator</h3>
<field name="model_object_field"
domain="[('model_id','=',model_id),('ttype','!=','one2many'),('ttype','!=','many2many')]"
on_change="onchange_sub_model_object_value_field(model_object_field)"/>
<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)]}"
on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field)"/>
<field name="null_value"
on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field,null_value)"/>
<field name="copyvalue"/>
</group>
</group>
<h3>Body</h3>
<field name="body_html" width="250" height="450"
placeholder="Rich-text/HTML content of the message (placeholders may be used here)"/>
<page string="Content">
<label for="subject"/>
<h2 style="display: inline-block;"><field name="subject" placeholder="Subject (placeholders may be used here)"/></h2>
<field name="body_html"/>
<field name="attachment_ids" widget="many2many_binary"/>
</page>
<page string="Email Configuration">
<group>
<field name="email_from"
placeholder="Override author's email"/>
<field name="use_default_to"/>
<field name="email_to" attrs="{'invisible': [('use_default_to', '=', True)]}"
placeholder="Comma-separated recipient addresses"/>
<field name="partner_to" attrs="{'invisible': [('use_default_to', '=', True)]}"
placeholder="Comma-separated ids of recipient partners"/>
<field name="email_cc" attrs="{'invisible': [('use_default_to', '=', True)]}"
placeholder="Comma-separated carbon copy recipients addresses"/>
<field name="reply_to"
placeholder="Preferred reply address"/>
<field name="user_signature" string="Author Signature (mass mail only)"/>
</group>
</page>
<page string="Advanced Settings">
<group>
<field name="lang"/>
@ -72,6 +59,21 @@
attrs="{'invisible':[('report_template','=',False)]}"/>
</group>
</page>
<page string="Dynamic Placeholder Generator">
<group>
<field name="model_object_field"
domain="[('model_id','=',model_id),('ttype','!=','one2many'),('ttype','!=','many2many')]"
on_change="onchange_sub_model_object_value_field(model_object_field)"/>
<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)]}"
on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field)"/>
<field name="null_value"
on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field,null_value)"/>
<field name="copyvalue"/>
</group>
</page>
</notebook>
</sheet>
</form>
@ -85,17 +87,20 @@
<field name="arch" type="xml">
<form string="Templates" version="7.0">
<sheet>
<div class="oe_title">
<label for="name"/><field name="name" required="1" class="oe_inline"/><br />
<label for="model_id"/><field name="model_id" required="1" class="oe_inline"
options="{'no_open': True, 'no_create': True}"
on_change="onchange_model_id(model_id)"/>
</div>
<div class="oe_right oe_button_box" name="buttons">
<button name="%(wizard_email_template_preview)d" string="Preview"
type="action" target="new"
context="{'template_id':active_id}"/>
</div>
<group>
<group>
<field name="name" required="True"/>
<field name="model_id" required="1" options="{'no_open': True, 'no_create': True}"
on_change="onchange_model_id(model_id)"/>
</group>
<group>
<div class="oe_right oe_button_box" name="buttons">
<button name="%(wizard_email_template_preview)d" string="Preview"
type="action" target="new"
context="{'template_id':active_id}"/>
</div>
</group>
</group>
<notebook>
<page string="Body">
<field name="body_html" nolabel="1"/>

View File

@ -30,10 +30,11 @@
<record id="wizard_email_template_preview" model="ir.actions.act_window">
<field name="name">Template Preview</field>
<field name="res_model">email_template.preview</field>
<field name="src_model">email_template.preview</field>
<field name="src_model">email.template</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="view_id" ref="email_template_preview_form"/>
<field name="auto_refresh" eval="1" />
<field name="target">new</field>
<field name="context">{'template_id':active_id}</field>

View File

@ -157,47 +157,29 @@ class mail_compose_message(osv.TransientModel):
# Wizard validation and send
#------------------------------------------------------
def _get_or_create_partners_from_values(self, cr, uid, rendered_values, context=None):
""" Check for email_to, email_cc, partner_to """
partner_ids = []
mails = tools.email_split(rendered_values.pop('email_to', '')) + tools.email_split(rendered_values.pop('email_cc', ''))
for mail in mails:
partner_id = self.pool.get('res.partner').find_or_create(cr, uid, mail, context=context)
partner_ids.append(partner_id)
partner_to = rendered_values.pop('partner_to', '')
if partner_to:
# placeholders could generate '', 3, 2 due to some empty field values
tpl_partner_ids = [pid for pid in partner_to.split(',') if pid]
partner_ids += self.pool['res.partner'].exists(cr, SUPERUSER_ID, tpl_partner_ids, context=context)
return partner_ids
def generate_email_for_composer_batch(self, cr, uid, template_id, res_ids, context=None, fields=None):
""" Call email_template.generate_email(), get fields relevant for
mail.compose.message, transform email_cc and email_to into partner_ids """
# filter template values
if context is None:
context = {}
if fields is None:
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids', 'mail_server_id']
returned_fields = fields + ['attachments']
returned_fields = fields + ['partner_ids', 'attachments']
values = dict.fromkeys(res_ids, False)
template_values = self.pool.get('email.template').generate_email_batch(cr, uid, template_id, res_ids, fields=fields, context=context)
ctx = dict(context, tpl_partners_only=True)
template_values = self.pool.get('email.template').generate_email_batch(cr, uid, template_id, res_ids, fields=fields, context=ctx)
for res_id in res_ids:
res_id_values = dict((field, template_values[res_id][field]) for field in returned_fields if template_values[res_id].get(field))
res_id_values['body'] = res_id_values.pop('body_html', '')
# transform email_to, email_cc into partner_ids
ctx = dict((k, v) for k, v in (context or {}).items() if not k.startswith('default_'))
partner_ids = self._get_or_create_partners_from_values(cr, uid, res_id_values, context=ctx)
# legacy template behavior: void values do not erase existing values and the
# related key is removed from the values dict
if partner_ids:
res_id_values['partner_ids'] = list(partner_ids)
values[res_id] = res_id_values
return values
def render_message_batch(self, cr, uid, wizard, res_ids, context=None):
""" Override to handle templates. """
# generate composer values
composer_values = super(mail_compose_message, self).render_message_batch(cr, uid, wizard, res_ids, context)
# generate template-based values
if wizard.template_id:
template_values = self.generate_email_for_composer_batch(
@ -206,10 +188,13 @@ class mail_compose_message(osv.TransientModel):
context=context)
else:
template_values = dict.fromkeys(res_ids, dict())
# generate composer values
composer_values = super(mail_compose_message, self).render_message_batch(cr, uid, wizard, res_ids, context)
for res_id in res_ids:
if wizard.template_id:
# recipients are managed by the template
composer_values[res_id].pop('partner_ids')
composer_values[res_id].pop('email_to')
composer_values[res_id].pop('email_cc')
# remove attachments from template values as they should not be rendered
template_values[res_id].pop('attachment_ids', None)
# update template values by composer values