MERGE] [FIX] email_template: fix relative -> absolute url conversion

Local URLs are converted into absolute URLs, notably because when using the
email designer, images are added using local URLs. Previously to this fix
the template was analyzed to find local URLs and make them absolute.
However this causes 2 issues :
- mako-based URLs are broken because a scheme is added before the mako that
generated the image src
- when changing the base url, the templates are not updated
The URLs are now converted dynamically when generating the content of the
html. This is done by passing a new parameter that enable the post processing
of the generated content.
Also fixed double body generation when using templates; fields parameter
was not propagated correctly.

bzr revid: tde@openerp.com-20140304112957-l9b10gyjqphs5fgc
This commit is contained in:
Thibault Delavallée 2014-03-04 12:29:57 +01:00
commit db2bdee343
4 changed files with 73 additions and 69 deletions

View File

@ -24,6 +24,8 @@ import base64
import datetime
import dateutil.relativedelta as relativedelta
import logging
import lxml
import urlparse
import openerp
from openerp import SUPERUSER_ID
@ -70,6 +72,7 @@ try:
except ImportError:
_logger.warning("jinja2 not available, templating features will not work!")
class email_template(osv.osv):
"Templates for sending email"
_name = "email.template"
@ -82,7 +85,48 @@ class email_template(osv.osv):
res['model_id'] = self.pool['ir.model'].search(cr, uid, [('model', '=', res.pop('model'))], context=context)[0]
return res
def render_template_batch(self, cr, uid, template, model, res_ids, context=None):
def _replace_local_links(self, cr, uid, html, context=None):
""" Post-processing of html content to replace local links to absolute
links, using web.base.url as base url. """
if not html:
return html
# form a tree
root = lxml.html.fromstring(html)
if not len(root) and root.text is None and root.tail is None:
html = '<div>%s</div>' % html
root = lxml.html.fromstring(html)
base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
(base_scheme, base_netloc, bpath, bparams, bquery, bfragment) = urlparse.urlparse(base_url)
def _process_link(url):
new_url = url
(scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
if not scheme and not netloc:
new_url = urlparse.urlunparse((base_scheme, base_netloc, path, params, query, fragment))
return new_url
# check all nodes, replace :
# - img src -> check URL
# - a href -> check URL
for node in root.iter():
if node.tag == 'a':
node.set('href', _process_link(node.get('href')))
elif node.tag == 'img' and not node.get('src', 'data').startswith('data'):
node.set('src', _process_link(node.get('src')))
html = lxml.html.tostring(root, pretty_print=False, method='html')
# this is ugly, but lxml/etree tostring want to put everything in a 'div' that breaks the editor -> remove that
if html.startswith('<div>') and html.endswith('</div>'):
html = html[5:-6]
return html
def render_post_process(self, cr, uid, html, context=None):
html = self._replace_local_links(cr, uid, html, context=context)
return html
def render_template_batch(self, cr, uid, template, model, res_ids, context=None, post_process=False):
"""Render the given template text, replace mako expressions ``${expr}``
with the result of evaluating these expressions with
an evaluation context containing:
@ -125,6 +169,10 @@ class email_template(osv.osv):
if render_result == u"False":
render_result = u""
results[res_id] = render_result
if post_process:
for res_id, result in results.iteritems():
results[res_id] = self.render_post_process(cr, uid, result, context=context)
return results
def get_email_template_batch(self, cr, uid, template_id=False, res_ids=None, context=None):
@ -356,17 +404,20 @@ class email_template(osv.osv):
results = dict()
for template, template_res_ids in templates_to_res_ids.iteritems():
# generate fields value for all res_ids linked to the current template
for field in ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to']:
generated_field_values = self.render_template_batch(cr, uid, getattr(template, field), template.model, template_res_ids, context=context)
for field in fields:
generated_field_values = self.render_template_batch(
cr, uid, getattr(template, field), template.model, template_res_ids,
post_process=(field == 'body_html'),
context=context)
for res_id, field_value in generated_field_values.iteritems():
results.setdefault(res_id, dict())[field] = field_value
# update values for all res_ids
for res_id in template_res_ids:
values = results[res_id]
if template.user_signature:
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['body_html']:
if values.get('body_html'):
values['body'] = tools.html_sanitize(values['body_html'])
values.update(
mail_server_id=template.mail_server_id.id or False,

View File

@ -162,16 +162,18 @@ class mail_compose_message(osv.TransientModel):
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):
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
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids', 'attachments', 'mail_server_id']
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']
values = dict.fromkeys(res_ids, False)
template_values = self.pool.get('email.template').generate_email_batch(cr, uid, template_id, res_ids, context=context)
template_values = self.pool.get('email.template').generate_email_batch(cr, uid, template_id, res_ids, fields=fields, context=context)
for res_id in res_ids:
res_id_values = dict((field, template_values[res_id][field]) for field in fields if template_values[res_id].get(field))
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
@ -189,7 +191,10 @@ class mail_compose_message(osv.TransientModel):
""" Override to handle templates. """
# generate template-based values
if wizard.template_id:
template_values = self.generate_email_for_composer_batch(cr, uid, wizard.template_id.id, res_ids, context=context)
template_values = self.generate_email_for_composer_batch(
cr, uid, wizard.template_id.id, res_ids,
fields=['email_to', 'partner_to', 'email_cc', 'attachment_ids', 'mail_server_id'],
context=context)
else:
template_values = dict.fromkeys(res_ids, dict())
# generate composer values
@ -206,8 +211,8 @@ class mail_compose_message(osv.TransientModel):
template_values[res_id].update(composer_values[res_id])
return template_values
def render_template_batch(self, cr, uid, template, model, res_ids, context=None):
return self.pool.get('email.template').render_template_batch(cr, uid, template, model, res_ids, context=context)
def render_template_batch(self, cr, uid, template, model, res_ids, context=None, post_process=False):
return self.pool.get('email.template').render_template_batch(cr, uid, template, model, res_ids, context=context, post_process=post_process)
# Compatibility methods
def generate_email_for_composer(self, cr, uid, template_id, res_id, context=None):

View File

@ -352,10 +352,10 @@ class mail_compose_message(osv.TransientModel):
:return dict results: for each res_id, the generated template values for
subject, body, email_from and reply_to
"""
subjects = self.render_template_batch(cr, uid, wizard.subject, wizard.model, res_ids, context)
bodies = self.render_template_batch(cr, uid, wizard.body, wizard.model, res_ids, context)
emails_from = self.render_template_batch(cr, uid, wizard.email_from, wizard.model, res_ids, context)
replies_to = self.render_template_batch(cr, uid, wizard.reply_to, wizard.model, res_ids, context)
subjects = self.render_template_batch(cr, uid, wizard.subject, wizard.model, res_ids, context=context)
bodies = self.render_template_batch(cr, uid, wizard.body, wizard.model, res_ids, context=context, post_process=True)
emails_from = self.render_template_batch(cr, uid, wizard.email_from, wizard.model, res_ids, context=context)
replies_to = self.render_template_batch(cr, uid, wizard.reply_to, wizard.model, res_ids, context=context)
results = dict.fromkeys(res_ids, False)
for res_id in res_ids:
@ -367,7 +367,7 @@ class mail_compose_message(osv.TransientModel):
}
return results
def render_template_batch(self, cr, uid, template, model, res_ids, context=None):
def render_template_batch(self, cr, uid, template, model, res_ids, context=None, post_process=False):
""" Render the given template text, replace mako-like expressions ``${expr}``
with the result of evaluating these expressions with an evaluation context
containing:

View File

@ -19,9 +19,6 @@
#
##############################################################################
import lxml
import urlparse
from openerp.osv import osv, fields
from openerp.tools.translate import _
@ -39,52 +36,3 @@ class EmailTemplate(osv.Model):
help='Link to the website',
),
}
def _postprocess_html_replace_links(self, cr, uid, body_html, context=None):
""" Post-processing of body_html. Indeed the content generated by the
website builder contains references to local addresses, for example
for images. This method changes those addresses to absolute addresses. """
html = body_html
if not body_html:
return html
# form a tree
root = lxml.html.fromstring(html)
if not len(root) and root.text is None and root.tail is None:
html = '<div>%s</div>' % html
root = lxml.html.fromstring(html)
base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
(base_scheme, base_netloc, bpath, bparams, bquery, bfragment) = urlparse.urlparse(base_url)
def _process_link(url):
new_url = url
(scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
if not scheme and not netloc:
new_url = urlparse.urlunparse((base_scheme, base_netloc, path, params, query, fragment))
return new_url
# check all nodes, replace :
# - img src -> check URL
# - a href -> check URL
for node in root.iter():
if node.tag == 'a':
node.set('href', _process_link(node.get('href')))
elif node.tag == 'img' and not node.get('src', 'data').startswith('data'):
node.set('src', _process_link(node.get('src')))
html = lxml.html.tostring(root, pretty_print=False, method='html')
# this is ugly, but lxml/etree tostring want to put everything in a 'div' that breaks the editor -> remove that
if html.startswith('<div>') and html.endswith('</div>'):
html = html[5:-6]
return html
def create(self, cr, uid, values, context=None):
if 'body_html' in values:
values['body_html'] = self._postprocess_html_replace_links(cr, uid, values['body_html'], context=context)
return super(EmailTemplate, self).create(cr, uid, values, context=context)
def write(self, cr, uid, ids, values, context=None):
if 'body_html' in values:
values['body_html'] = self._postprocess_html_replace_links(cr, uid, values['body_html'], context=context)
return super(EmailTemplate, self).write(cr, uid, ids, values, context=context)