Browse Source

Add Sendgrid module from v9

pull/185/head
Emanuel Cino 7 years ago
parent
commit
8e8219357b
  1. 120
      mail_sendgrid/README.rst
  2. 14
      mail_sendgrid/__init__.py
  3. 51
      mail_sendgrid/__openerp__.py
  4. 13
      mail_sendgrid/controllers/__init__.py
  5. 53
      mail_sendgrid/controllers/json_request.py
  6. 31
      mail_sendgrid/controllers/sendgrid_event_webhook.py
  7. 18
      mail_sendgrid/models/__init__.py
  8. 29
      mail_sendgrid/models/email_lang_template.py
  9. 83
      mail_sendgrid/models/email_template.py
  10. 174
      mail_sendgrid/models/email_tracking.py
  11. 259
      mail_sendgrid/models/mail_mail.py
  12. 14
      mail_sendgrid/models/mail_tracking_event.py
  13. 104
      mail_sendgrid/models/sendgrid_template.py
  14. 28
      mail_sendgrid/models/substitution.py
  15. 4
      mail_sendgrid/security/ir.model.access.csv
  16. BIN
      mail_sendgrid/static/description/icon.png
  17. 10
      mail_sendgrid/static/description/icon.svg
  18. 12
      mail_sendgrid/tests/__init__.py
  19. 178
      mail_sendgrid/tests/test_mail_sendgrid.py
  20. 31
      mail_sendgrid/views/email_template_view.xml
  21. 21
      mail_sendgrid/views/mail_compose_message_view.xml
  22. 77
      mail_sendgrid/views/sendgrid_email_view.xml
  23. 73
      mail_sendgrid/views/sendgrid_template_view.xml
  24. 13
      mail_sendgrid/wizards/__init__.py
  25. 30
      mail_sendgrid/wizards/email_template_preview.py
  26. 49
      mail_sendgrid/wizards/mail_compose_message.py

120
mail_sendgrid/README.rst

@ -0,0 +1,120 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:alt: License: AGPL-3
==================================
SendGrid Mail Sending and Tracking
==================================
This module integrates
`SendGrid <https://sendgrid.com/>`_ with Odoo. It can send transactional emails
through SendGrid, using templates defined on the
`SendGrid web interface <https://sendgrid.com/templates>`_. It also supports
substitution of placeholder variables in these templates. The list of available
templates can be fetched automatically.
E-mails sent through SendGrid will be tracked using Sendgrid Webhook Events.
Installation
============
You need to install python-sendgrid v3 API in order to install the module.
If you're using a multi-database installation (with or without dbfilter option)
where /web/databse/selector returns a list of more than one database, then
you need to add ``mail_sendgrid`` addon to wide load addons list
(by default, only ``web`` addon), setting ``--load`` option.
For example, ``--load=web,mail_tracking,mail_sendgrid``
Configuration
=============
You can add the following system parameters to configure the usage of SendGrid:
* ``mail_sendgrid.substitution_prefix`` Any symbol or character used as a
prefix for `SendGrid Substitution Tags <https://sendgrid.com/docs/API_Reference/SMTP_API/substitution_tags.html>`_.
``{`` is used by default.
* ``mail_sendgrid.substitution_suffix`` Any symbol or character used as a
suffix for SendGrid Substitution Tags.
``}`` is used by default.
* ``mail_sendgrid.send_method`` Use value 'sendgrid' to override the traditional SMTP server used to send e-mails with sendgrid.
Use any other value to disable traditional e-mail sending. By default, SendGrid will co-exist with traditional system
(two buttons for sending either normally or with SendGrid).
In order to use this module, the following variables have to be defined in the
server command-line options (or in a configuration file):
- ``sendgrid_api_key`` A valid API key obtained from the
SendGrid web interface <https://app.sendgrid.com/settings/api_keys> with
full access for the ``Mail Send`` permission and read access for the
``Template Engine`` permission.
Optionally, the following configuration variables can be set as well:
- ``sendgrid_test_address`` Destination email address for testing purposes.
You can use ``odoo@sink.sendgrid.net``, which is an address that
will simply receive and discard all incoming email.
For tracking events to work, make sure you configure your Sendgrid Account with the correct Event Notification Url.
You can do it under 'Settings -> Mail Settings -> Event Notification '.
Set the URL to ``https://<your_domain>/mail/tracking/sendgrid/<your_database>``
Replace '<your_domain>' with your Odoo install domain name
and '<your_database>' with your database name.
Usage
=====
If you designed templates in Sendgrid that you wan't to use with Odoo:
* Go to 'Settings -> Email -> SendGrid Templates'
* Create a new Template
* Click the "Update" button : this will automatically import all your templates
In e-mail templates 'Settings -> Email -> Templates', you can attach a SendGrid template for any language.
You can substitute Sendgrid keywords with placeholders or static text like in the body of the e-mail.
The preview wizard now renders your e-mail with the SendGrid template applied.
From e-mails, use the "Send (SendGrid)" button to send the e-mail using Sendgrid.
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/205/9.0
Known issues / Roadmap
======================
* Extend the features from SendGrid
Bug Tracker
===========
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/social/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.
Credits
=======
Images
------
* Sengrid logo: `SVG Icon <http://seeklogo.com/vector-logo/289294/sendgrid>`_.
Contributors
------------
* Emanuel Cino <ecino@compassion.ch>
* Roman Zoller <rzcomp@gmail.com>
Maintainer
----------
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit http://odoo-community.org.

14
mail_sendgrid/__init__.py

@ -0,0 +1,14 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2015 Compassion CH (http://www.compassion.ch)
# Releasing children from poverty in Jesus' name
# @author: Roman Zoller
#
# The licence is in the file __openerp__.py
#
##############################################################################
from . import models
from . import wizards
from . import controllers

51
mail_sendgrid/__openerp__.py

@ -0,0 +1,51 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# ______ Releasing children from poverty _
# / ____/___ ____ ___ ____ ____ ___________(_)___ ____
# / / / __ \/ __ `__ \/ __ \/ __ `/ ___/ ___/ / __ \/ __ \
# / /___/ /_/ / / / / / / /_/ / /_/ (__ |__ ) / /_/ / / / /
# \____/\____/_/ /_/ /_/ .___/\__,_/____/____/_/\____/_/ /_/
# /_/
# in Jesus' name
#
# Copyright (C) 2015-2017 Compassion CH (http://www.compassion.ch)
# @author: Emanuel Cino, Roman Zoller
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'SendGrid',
'version': '9.0.1.0.0',
'category': 'Social Network',
'author': 'Compassion CH',
'website': 'http://www.compassion.ch',
'depends': ['mail_tracking'],
'data': [
'security/ir.model.access.csv',
'views/sendgrid_email_view.xml',
'views/sendgrid_template_view.xml',
'views/mail_compose_message_view.xml',
'views/email_template_view.xml',
],
'demo': [],
'installable': True,
'auto_install': False,
'external_dependencies': {
'python': ['sendgrid'],
},
}

13
mail_sendgrid/controllers/__init__.py

@ -0,0 +1,13 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2016 Compassion CH (http://www.compassion.ch)
# Releasing children from poverty in Jesus' name
# @author: Emanuel Cino <ecino@compassion.ch>
#
# The licence is in the file __openerp__.py
#
##############################################################################
from . import json_request
from . import sendgrid_event_webhook

53
mail_sendgrid/controllers/json_request.py

@ -0,0 +1,53 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2016 Compassion CH (http://www.compassion.ch)
# Releasing children from poverty in Jesus' name
# @author: Emanuel Cino <ecino@compassion.ch>
#
# The licence is in the file __openerp__.py
#
##############################################################################
import simplejson
from openerp.http import JsonRequest, Root, Response
# Monkeypatch type of request rooter to use RESTJsonRequest
old_get_request = Root.get_request
def get_request(self, httprequest):
if (httprequest.mimetype == "application/json" and
httprequest.environ['PATH_INFO'].startswith('/mail')):
return RESTJsonRequest(httprequest)
return old_get_request(self, httprequest)
Root.get_request = get_request
class RESTJsonRequest(JsonRequest):
""" Special RestJson Handler to enable receiving lists in JSON
body
"""
def __init__(self, *args):
try:
super(RESTJsonRequest, self).__init__(*args)
except AttributeError:
# The JSON may contain a list
self.params = dict()
self.context = dict(self.session.context)
def _json_response(self, result=None, error=None):
response = {}
if error is not None:
response['error'] = error
if result is not None:
response['result'] = result
mime = 'application/json'
body = simplejson.dumps(response)
return Response(
body, headers=[('Content-Type', mime),
('Content-Length', len(body))])

31
mail_sendgrid/controllers/sendgrid_event_webhook.py

@ -0,0 +1,31 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2016 Compassion CH (http://www.compassion.ch)
# Releasing children from poverty in Jesus' name
# @author: Emanuel Cino <ecino@compassion.ch>
#
# The licence is in the file __openerp__.py
#
##############################################################################
import logging
from openerp import http
from openerp.addons.mail_tracking.controllers.main import \
MailTrackingController, _env_get
_logger = logging.getLogger(__name__)
class SendgridTrackingController(MailTrackingController):
"""
Sendgrid is posting JSON so we must define a new route for tracking.
"""
@http.route('/mail/tracking/sendgrid/<string:db>',
type='json', auth='none', csrf=False)
def mail_tracking_sendgrid(self, db, **kw):
try:
_env_get(db, self._tracking_event, None, None, **kw)
return {'status': 200}
except:
return {'status': 400}

18
mail_sendgrid/models/__init__.py

@ -0,0 +1,18 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2015 Compassion CH (http://www.compassion.ch)
# Releasing children from poverty in Jesus' name
# @author: Roman Zoller
#
# The licence is in the file __openerp__.py
#
##############################################################################
from . import mail_mail
from . import substitution
from . import sendgrid_template
from . import email_template
from . import email_lang_template
from . import email_tracking
from . import mail_tracking_event

29
mail_sendgrid/models/email_lang_template.py

@ -0,0 +1,29 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2016 Compassion CH (http://www.compassion.ch)
# Releasing children from poverty in Jesus' name
# @author: Emanuel Cino
#
# The licence is in the file __openerp__.py
#
##############################################################################
from openerp import models, fields
class LanguageTemplate(models.Model):
""" This class is the relation between and email_template object
and a sendgrid_template. It allows to specify a different
sendgrid_template for any selected language.
"""
_name = 'sendgrid.email.lang.template'
email_template_id = fields.Many2one('mail.template', 'E-mail Template')
lang = fields.Selection('_lang_get', 'Language', required=True)
sendgrid_template_id = fields.Many2one(
'sendgrid.template', 'Sendgrid Template', required=True)
def _lang_get(self):
languages = self.env['res.lang'].search([])
return [(language.code, language.name) for language in languages]

83
mail_sendgrid/models/email_template.py

@ -0,0 +1,83 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2015 Compassion CH (http://www.compassion.ch)
# Releasing children from poverty in Jesus' name
# @author: Roman Zoller
#
# The licence is in the file __openerp__.py
#
##############################################################################
from openerp import models, fields, api
class EmailTemplate(models.Model):
_inherit = 'mail.template'
##########################################################################
# FIELDS #
##########################################################################
substitution_ids = fields.One2many(
'sendgrid.substitution', 'email_template_id', 'Substitutions')
sendgrid_template_ids = fields.One2many(
'sendgrid.email.lang.template', 'email_template_id',
'Sendgrid Templates')
sendgrid_localized_template = fields.Many2one(
'sendgrid.template', compute='_compute_localized_template')
def _compute_localized_template(self):
lang = self.env.context.get('lang', 'en_US')
for template in self:
lang_template = template.sendgrid_template_ids.filtered(
lambda t: t.lang == lang)
if lang_template and len(lang_template) == 1:
template.sendgrid_localized_template = \
lang_template.sendgrid_template_id
@api.multi
def update_substitutions(self):
self.ensure_one()
new_substitutions = list()
for language_template in self.sendgrid_template_ids:
sendgrid_template = language_template.sendgrid_template_id
lang = language_template.lang
substitutions = self.substitution_ids.filtered(
lambda s: s.lang == lang)
keywords = sendgrid_template.get_keywords()
# Add new keywords from the sendgrid template
for key in keywords:
if key not in substitutions.mapped('key'):
substitution_vals = {
'key': key,
'lang': lang,
'email_template_id': self.id
}
new_substitutions.append((0, 0, substitution_vals))
return self.write({'substitution_ids': new_substitutions})
@api.multi
def render_substitutions(self, res_ids):
"""
:param res_ids: resource ids for rendering the template
Returns values for substitutions in a mail.message creation
:return:
Values for mail creation (for each resource id given)
{res_id: list of substitutions values [0, 0 {substitution_vals}]}
"""
self.ensure_one()
if isinstance(res_ids, (int, long)):
res_ids = [res_ids]
substitutions = self.substitution_ids.filtered(
lambda s: s.lang == self.env.context.get('lang', 'en_US'))
substitution_vals = {res_id: list() for res_id in res_ids}
for substitution in substitutions:
values = self.render_template(
substitution.value, self.model, res_ids)
for res_id in res_ids:
substitution_vals[res_id].append((0, 0, {
'key': substitution.key,
'value': values[res_id]
}))
return substitution_vals

174
mail_sendgrid/models/email_tracking.py

@ -0,0 +1,174 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2016-2017 Compassion CH (http://www.compassion.ch)
# Releasing children from poverty in Jesus' name
# @author: Emanuel Cino <ecino@compassion.ch>
#
# The licence is in the file __openerp__.py
#
##############################################################################
import logging
from datetime import datetime
from werkzeug.useragents import UserAgent
from openerp import models, fields, api
_logger = logging.getLogger(__name__)
class MailTrackingEmail(models.Model):
""" Count the user clicks on links inside e-mails sent.
Add tracking methods to process Sendgrid Notifications
"""
_inherit = 'mail.tracking.email'
click_count = fields.Integer(compute='_compute_clicks', store=True)
@api.depends('tracking_event_ids')
def _compute_clicks(self):
for mail in self:
mail.click_count = len(mail.tracking_event_ids.filtered(
lambda event: event.event_type == 'click'))
@property
def _sendgrid_mandatory_fields(self):
return ('event', 'sg_event_id', 'timestamp',
'odoo_id', 'odoo_db')
@property
def _sendgrid_event_type_mapping(self):
return {
# Sendgrid event type: tracking event type
'bounce': 'hard_bounce',
'click': 'click',
'deferred': 'deferral',
'delivered': 'delivered',
'dropped': 'reject',
'group_unsubscribe': 'unsub',
'open': 'open',
'processed': 'sent',
'spamreport': 'spam',
'unsubscribe': 'unsub',
}
def _sendgrid_event_type_verify(self, event):
event = event or {}
sendgrid_event_type = event.get('event')
if sendgrid_event_type not in self._sendgrid_event_type_mapping:
_logger.error("Sendgrid: event type '%s' not supported",
sendgrid_event_type)
return False
# OK, event type is valid
return True
def _db_verify(self, event):
event = event or {}
odoo_db = event.get('odoo_db')
current_db = self.env.cr.dbname
if odoo_db != current_db:
_logger.error("Sendgrid: Database '%s' is not the current "
"database",
odoo_db)
return False
# OK, DB is current
return True
def _sendgrid_metadata(self, sendgrid_event_type, event, metadata):
# Get sendgrid timestamp when found
ts = event.get('timestamp', False)
try:
ts = float(ts)
except:
ts = False
if ts:
dt = datetime.utcfromtimestamp(ts)
metadata.update({
'timestamp': ts,
'time': fields.Datetime.to_string(dt),
'date': fields.Date.to_string(dt),
})
# Common field mapping (sendgrid_field: odoo_field)
mapping = {
'email': 'recipient',
'ip': 'ip',
'url': 'url',
}
for k, v in mapping.iteritems():
if event.get(k, False):
metadata[v] = event[k]
# Special field mapping
if event.get('useragent'):
user_agent = UserAgent(event['useragent'])
metadata.update({
'user_agent': user_agent.string,
'os_family': user_agent.platform,
'ua_family': user_agent.browser,
'mobile': user_agent.platform in [
'android', 'iphone', 'ipad']
})
# Mapping for special events
if sendgrid_event_type == 'bounced':
metadata.update({
'error_type': event.get('type', False),
'error_description': event.get('reason', False),
'error_details': event.get('status', False),
})
elif sendgrid_event_type == 'dropped':
metadata.update({
'error_type': event.get('reason', False),
})
return metadata
def _sendgrid_tracking_get(self, event):
tracking = False
message_id = event.get('odoo_id', False)
if message_id:
tracking = self.search([
('mail_id.message_id', '=', message_id),
('recipient', '=ilike', event.get('email'))], limit=1)
return tracking
def _event_is_from_sendgrid(self, event):
event = event or {}
return all([k in event for k in self._sendgrid_mandatory_fields])
@api.model
def event_process(self, request, post, metadata, event_type=None):
res = super(MailTrackingEmail, self).event_process(
request, post, metadata, event_type=event_type)
is_json = hasattr(request, 'jsonrequest') and isinstance(
request.jsonrequest, list)
if res == 'NONE' and is_json:
for event in request.jsonrequest:
if self._event_is_from_sendgrid(event):
if not self._sendgrid_event_type_verify(event):
res = 'ERROR: Event type not supported'
elif not self._db_verify(event):
res = 'ERROR: Invalid DB'
else:
res = 'OK'
if res == 'OK':
sendgrid_event_type = event.get('event')
mapped_event_type = self._sendgrid_event_type_mapping.get(
sendgrid_event_type) or event_type
if not mapped_event_type:
res = 'ERROR: Bad event'
tracking = self._sendgrid_tracking_get(event)
if not tracking:
res = 'ERROR: Tracking not found'
if res == 'OK':
# Complete metadata with sendgrid event info
metadata = self._sendgrid_metadata(
sendgrid_event_type, event, metadata)
# Create event
tracking.event_create(mapped_event_type, metadata)
if res != 'NONE':
if event_type:
_logger.info(
"sendgrid: event '%s' process '%s'",
event_type, res)
else:
_logger.info("sendgrid: event process '%s'", res)
return res

259
mail_sendgrid/models/mail_mail.py

@ -0,0 +1,259 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2015-2016 Compassion CH (http://www.compassion.ch)
# Releasing children from poverty in Jesus' name
# @author: Roman Zoller, Emanuel Cino <ecino@compassion.ch>
#
# The licence is in the file __openerp__.py
#
##############################################################################
from openerp import models, fields, api, exceptions, tools, _
from openerp.tools.config import config
from openerp.tools.safe_eval import safe_eval
import base64
import logging
import re
import time
_logger = logging.getLogger(__name__)
try:
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Email, Attachment, CustomArg, Content, \
Personalization, Substitution, Mail, Header
except ImportError:
_logger.error("ImportError raised while loading module.")
_logger.debug("ImportError details:", exc_info=True)
STATUS_OK = 202
class MailMessage(models.Model):
""" Add SendGrid related fields so that they dispatch in all
subclasses of mail.message object
"""
_inherit = 'mail.message'
##########################################################################
# FIELDS #
##########################################################################
body_text = fields.Text(help='Text only version of the body')
sent_date = fields.Datetime(copy=False)
substitution_ids = fields.Many2many(
'sendgrid.substitution', string='Substitutions', copy=True)
sendgrid_template_id = fields.Many2one(
'sendgrid.template', 'Sendgrid Template')
send_method = fields.Char(compute='_compute_send_method')
##########################################################################
# FIELDS METHODS #
##########################################################################
@api.multi
def _compute_send_method(self):
""" Check whether to use traditional send method, sendgrid or disable.
"""
send_method = self.env['ir.config_parameter'].get_param(
'mail_sendgrid.send_method', 'traditional')
for email in self:
email.send_method = send_method
class OdooMail(models.Model):
""" Email message sent through SendGrid """
_inherit = 'mail.mail'
##########################################################################
# FIELDS #
##########################################################################
tracking_email_ids = fields.One2many(
'mail.tracking.email', 'mail_id', string='Registered events',
readonly=True)
click_count = fields.Integer(
compute='_compute_tracking', store=True, readonly=True)
opened = fields.Boolean(
compute='_compute_tracking', store=True, readonly=True)
tracking_event_ids = fields.One2many(
'mail.tracking.event', compute='_compute_events')
@api.depends('tracking_email_ids', 'tracking_email_ids.click_count',
'tracking_email_ids.state')
def _compute_tracking(self):
for email in self:
email.click_count = sum(email.tracking_email_ids.mapped(
'click_count'))
opened = len(email.tracking_email_ids.filtered(
lambda t: t.state == 'opened'))
email.opened = opened > 0
def _compute_events(self):
for email in self:
email.tracking_event_ids = email.tracking_email_ids.mapped(
'tracking_event_ids')
##########################################################################
# PUBLIC METHODS #
##########################################################################
@api.multi
def send(self, auto_commit=False, raise_exception=False):
""" Override send to select the method to send the e-mail. """
traditional = self.filtered(lambda e: e.send_method == 'traditional')
sendgrid = self.filtered(lambda e: e.send_method == 'sendgrid')
if traditional:
super(OdooMail, traditional).send(auto_commit, raise_exception)
if sendgrid:
sendgrid.send_sendgrid()
unknown = self - traditional - sendgrid
if unknown:
_logger.warning(
"Traditional e-mails are disabled. Please remove system "
"parameter mail_sendgrid.send_method if you want to send "
"e-mails through your configured SMTP.")
unknown.write({'state': 'exception'})
return True
@api.multi
def send_sendgrid(self):
""" Use sendgrid transactional e-mails : e-mails are sent one by
one. """
api_key = config.get('sendgrid_api_key')
if not api_key:
raise exceptions.Warning(
'ConfigError',
_('Missing sendgrid_api_key in conf file'))
sg = SendGridAPIClient(apikey=api_key)
for email in self.filtered(lambda em: em.state == 'outgoing'):
# Commit at each e-mail processed to avoid any errors
# invalidating state.
with self.env.cr.savepoint():
try:
response = sg.client.mail.send.post(
request_body=email._prepare_sendgrid_data().get())
except Exception as e:
_logger.error(e.message)
continue
status = response.status_code
msg = response.body
if status == STATUS_OK:
_logger.info(str(msg))
email._track_sendgrid_emails()
email.write({
'sent_date': fields.Datetime.now(),
'state': 'sent'
})
else:
_logger.error("Failed to send email: {}".format(str(msg)))
##########################################################################
# PRIVATE METHODS #
##########################################################################
def _prepare_sendgrid_data(self):
"""
Prepare and creates the Sendgrid Email object
:return: sendgrid.helpers.mail.Email object
"""
self.ensure_one()
s_mail = Mail()
s_mail.from_email = Email(self.email_from)
if self.reply_to:
s_mail.reply_to = Email(self.reply_to)
# Add custom fields to match the tracking
s_mail.add_custom_arg(CustomArg('odoo_id', self.message_id))
s_mail.add_custom_arg(CustomArg('odoo_db', self.env.cr.dbname))
headers = {
'Message-Id': self.message_id
}
if self.headers:
try:
headers.update(safe_eval(self.headers))
except Exception:
pass
for h_name, h_val in headers.iteritems():
s_mail.add_header(Header(h_name, h_val))
html = self.body_html or ' '
p = re.compile(r'<.*?>') # Remove HTML markers
text_only = self.body_text or p.sub('', html.replace('<br/>', '\n'))
s_mail.add_content(Content("text/plain", text_only))
s_mail.add_content(Content("text/html", html))
test_address = config.get('sendgrid_test_address')
# We use only one personalization for transactional e-mail
personalization = Personalization()
subject = self.subject and self.subject.encode(
"utf_8") or "(No subject)"
personalization.subject = subject
addresses = list()
if not test_address:
if self.email_to and self.email_to not in addresses:
personalization.add_to(Email(self.email_to))
addresses.append(self.email_to)
for recipient in self.recipient_ids:
if recipient.email not in addresses:
personalization.add_to(Email(recipient.email))
addresses.append(recipient.email)
if self.email_cc and self.email_cc not in addresses:
personalization.add_cc(Email(self.email_cc))
else:
_logger.info('Sending email to test address {}'.format(
test_address))
personalization.add_to(Email(test_address))
self.email_to = test_address
if self.sendgrid_template_id:
s_mail.template_id = self.sendgrid_template_id.remote_id
for substitution in self.substitution_ids:
personalization.add_substitution(Substitution(
substitution.key, substitution.value.encode('utf-8')))
s_mail.add_personalization(personalization)
for attachment in self.attachment_ids:
s_attachment = Attachment()
# Datas are not encoded properly for sendgrid
s_attachment.content = base64.b64encode(base64.b64decode(
attachment.datas))
s_attachment.filename = attachment.name
s_mail.add_attachment(s_attachment)
return s_mail
def _track_sendgrid_emails(self):
""" Create tracking e-mails after successfully sent with Sendgrid. """
self.ensure_one()
m_tracking = self.env['mail.tracking.email'].sudo()
track_vals = self._prepare_sendgrid_tracking()
for recipient in tools.email_split_and_format(self.email_to):
track_vals['recipient'] = recipient
m_tracking += m_tracking.create(track_vals)
for partner in self.recipient_ids:
track_vals.update({
'partner_id': partner.id,
'recipient': partner.email,
})
m_tracking += m_tracking.create(track_vals)
return m_tracking
def _prepare_sendgrid_tracking(self):
ts = time.time()
return {
'name': self.subject,
'timestamp': '%.6f' % ts,
'time': fields.Datetime.now(),
'mail_id': self.id,
'mail_message_id': self.mail_message_id.id,
'sender': self.email_from,
}

14
mail_sendgrid/models/mail_tracking_event.py

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
# © 2017 Emanuel Cino - <ecino@compassion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import models, api
class MailTrackingEvent(models.Model):
_inherit = "mail.tracking.event"
@api.model
def process_sent(self, tracking_email, metadata):
return self._process_status(
tracking_email, metadata, 'sent', 'sent')

104
mail_sendgrid/models/sendgrid_template.py

@ -0,0 +1,104 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2015 Compassion CH (http://www.compassion.ch)
# Releasing children from poverty in Jesus' name
# @author: Roman Zoller
#
# The licence is in the file __openerp__.py
#
##############################################################################
from openerp import models, fields, api, exceptions, _
from openerp.tools.config import config
import json
import re
import logging
_logger = logging.getLogger(__name__)
try:
import sendgrid
except ImportError:
_logger.error("ImportError raised while loading module.")
_logger.debug("ImportError details:", exc_info=True)
class SendgridTemplate(models.Model):
""" Reference to a template available on the SendGrid user account. """
_name = 'sendgrid.template'
##########################################################################
# FIELDS #
##########################################################################
name = fields.Char()
remote_id = fields.Char(readonly=True)
html_content = fields.Html(readonly=True)
plain_content = fields.Text(readonly=True)
detected_keywords = fields.Char(compute='_compute_keywords')
def _compute_keywords(self):
for template in self:
if template.html_content:
keywords = template.get_keywords()
self.detected_keywords = ';'.join(keywords)
@api.model
def update(self):
api_key = config.get('sendgrid_api_key')
if not api_key:
raise exceptions.Warning(
'ConfigError',
_('Missing sendgrid_api_key in conf file'))
sg = sendgrid.SendGridAPIClient(apikey=api_key)
template_client = sg.client.templates
msg = template_client.get().body
result = json.loads(msg)
for template in result.get("templates", list()):
id = template["id"]
msg = template_client._(id).get().body
template_versions = json.loads(msg)['versions']
for version in template_versions:
if version['active']:
template_vals = version
break
else:
continue
vals = {
"remote_id": id,
"name": template["name"],
"html_content": template_vals["html_content"],
"plain_content": template_vals["plain_content"],
}
record = self.search([('remote_id', '=', id)])
if record:
record.write(vals)
else:
self.create(vals)
return True
def get_keywords(self):
""" Search in the Sendgrid template for keywords included with the
following syntax: {keyword_name} and returns the list of keywords.
keyword_name shouldn't be longer than 20 characters and only contain
alphanumeric characters (underscore is allowed).
You can replace the substitution prefix and suffix by adding values
in the system parameters
- mail_sendgrid.substitution_prefix
- mail_sendgrid.substitution_suffix
"""
self.ensure_one()
params = self.env['ir.config_parameter']
prefix = params.search([
('key', '=', 'mail_sendgrid.substitution_prefix')
]).value or '{'
suffix = params.search([
('key', '=', 'mail_sendgrid.substitution_suffix')
]) or '}'
pattern = prefix + r'\w{0,20}' + suffix
return list(set(re.findall(pattern, self.html_content)))

28
mail_sendgrid/models/substitution.py

@ -0,0 +1,28 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2015 Compassion CH (http://www.compassion.ch)
# Releasing children from poverty in Jesus' name
# @author: Roman Zoller, Emanuel Cino
#
# The licence is in the file __openerp__.py
#
##############################################################################
from openerp import models, fields
class Substitution(models.Model):
""" Substitution values for a SendGrid email message """
_name = 'sendgrid.substitution'
##########################################################################
# FIELDS #
##########################################################################
key = fields.Char()
lang = fields.Char()
email_template_id = fields.Many2one(
'mail.template', ondelete='cascade')
email_id = fields.Many2one(
'mail.mail', ondelete='cascade')
value = fields.Char()

4
mail_sendgrid/security/ir.model.access.csv

@ -0,0 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_sendgrid_substitution,Full access on sendgrid_substitution,model_sendgrid_substitution,base.group_user,1,1,1,1
access_sendgrid_template,Full access on sendgrid_template,model_sendgrid_template,base.group_user,1,1,1,1
access_sendgrid_lang_template,Full access on sendgrid_lang_template,model_sendgrid_email_lang_template,base.group_user,1,1,1,1

BIN
mail_sendgrid/static/description/icon.png

After

Width: 128  |  Height: 128  |  Size: 3.1 KiB

10
mail_sendgrid/static/description/icon.svg

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="256px" height="256px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<g>
<polygon fill="#9DE1F3" points="85.3335 85.333 0.0005 85.333 0.0005 170.666 0.0005 256 85.3335 256 170.6665 256 170.6665 170.666 170.6665 85.333"></polygon>
<polygon fill="#27B4E1" points="85.3335 0.0004 85.3335 85.3334 85.3335 170.6664 170.6665 170.6664 255.9995 170.6664 255.9995 0.0004"></polygon>
<polygon fill="#1A82E2" points="0 256 85.333 256 85.333 170.667 0 170.667"></polygon>
<polygon fill="#1A82E2" points="170.667 85.333 256 85.333 256 0 170.667 0"></polygon>
<polygon fill="#239FD7" points="85.334 170.667 170.667 170.667 170.667 85.334 85.334 85.334"></polygon>
</g>
</svg>

12
mail_sendgrid/tests/__init__.py

@ -0,0 +1,12 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2017 Compassion CH (http://www.compassion.ch)
# Releasing children from poverty in Jesus' name
# @author: Emanuel Cino
#
# The licence is in the file __openerp__.py
#
##############################################################################
from . import test_mail_sendgrid

178
mail_sendgrid/tests/test_mail_sendgrid.py

@ -0,0 +1,178 @@
# -*- coding: utf-8 -*-
# © 2017 Emanuel Cino - <ecino@compassion.ch>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import mock
from openerp.tests.common import TransactionCase
mock_base_send = 'openerp.addons.mail.models.mail_mail.MailMail.send'
mock_sendgrid_api_client = ('openerp.addons.mail_sendgrid.models.mail_mail'
'.SendGridAPIClient')
mock_sendgrid_send = ('openerp.addons.mail_sendgrid.models.mail_mail.'
'OdooMail.send_sendgrid')
mock_config = ('openerp.addons.mail_sendgrid.models.mail_mail.'
'config')
class FakeClient(object):
""" Mock Sendgrid APIClient """
status_code = 202
body = 'ok'
def __init__(self):
self.client = self
self.mail = self
self.send = self
def post(self, **kwargs):
return self
class FakeRequest(object):
""" Simulate a Sendgrid JSON request """
def __init__(self, data):
self.jsonrequest = [data]
class TestMailSendgrid(TransactionCase):
def setUp(self):
super(TestMailSendgrid, self).setUp()
self.sendgrid_template = self.env['sendgrid.template'].create({
'name': 'Test Template',
'remote_id': 'a74795d7-f926-4bad-8e7a-ae95fabd70fc',
'html_content': u'<h1>Test Sendgrid</h1><%body%>{footer}'
})
self.mail_template = self.env['mail.template'].create({
'name': 'Test Template',
'model_id': self.env.ref('base.model_res_partner').id,
'subject': 'Test e-mail',
'body_html': u'Dear ${object.name}, hello!',
'sendgrid_template_ids': [
(0, 0, {'lang': 'en_US', 'sendgrid_template_id':
self.sendgrid_template.id})]
})
self.recipient = self.env.ref('base.partner_demo')
self.mail_wizard = self.env['mail.compose.message'].create({
'template_id': self.mail_template.id,
'composition_mode': 'comment',
'model': 'res.partner',
'res_id': self.recipient.id
})
self.mail_wizard.onchange_template_id_wrapper()
self.timestamp = u'1471021089'
self.event = {
'timestamp': self.timestamp,
'sg_event_id': u"f_JoKtrLQaOXUc4thXgROg",
'email': self.recipient.email,
'odoo_db': self.env.cr.dbname,
'odoo_id': u'<xxx.xxx.xxx-openerp-xxx-res.partner@test_db>'
}
self.metadata = {
'ip': '127.0.0.1',
'user_agent': False,
'os_family': False,
'ua_family': False,
}
self.request = FakeRequest(self.event)
def create_email(self, vals=None):
mail_vals = self.mail_wizard.render_message(self.recipient.ids)[
self.recipient.id]
mail_vals['recipient_ids'] = [(6, 0, self.recipient.ids)]
if vals is not None:
mail_vals.update(vals)
return self.env['mail.mail'].create(mail_vals)
def test_substitutions(self):
""" Test substitutions in templates. """
self.assertEqual(self.sendgrid_template.detected_keywords, "{footer}")
self.mail_template.update_substitutions()
substitutions = self.mail_template.substitution_ids
self.assertEqual(len(substitutions), 1)
self.assertEqual(substitutions.key, '{footer}')
def test_create_email(self):
""" Test that Sendgrid template is pushed in e-mail. """
self.mail_template.update_substitutions()
mail_values = self.mail_wizard.render_message(self.recipient.ids)[
self.recipient.id]
# Test Sendgrid HTML preview
self.assertEqual(
self.mail_wizard.body_sendgrid,
self.sendgrid_template.html_content.replace(
'<%body%>', mail_values['body'])
)
mail = self.env['mail.mail'].create(mail_values)
self.assertEqual(mail.sendgrid_template_id.id,
self.sendgrid_template.id)
self.assertEqual(len(mail.substitution_ids), 1)
@mock.patch(mock_base_send)
@mock.patch(mock_sendgrid_send)
def test_send_email_default(self, mock_sendgrid, mock_email):
""" Tests that sending an e-mail by default doesn't use Sendgrid,
and that Sendgrid is used when system parameter is set.
"""
self.env['ir.config_parameter'].set_param(
'mail_sendgrid.send_method', False)
mock_sendgrid.return_value = True
mock_email.return_value = True
mail = self.create_email()
mail.send()
self.assertTrue(mock_email.called)
self.assertFalse(mock_sendgrid.called)
self.env['ir.config_parameter'].set_param(
'mail_sendgrid.send_method', 'sendgrid')
# Force again computation of send_method
self.env.invalidate_all()
mail.send()
self.assertEqual(mock_email.call_count, 1)
self.assertEqual(mock_sendgrid.call_count, 1)
@mock.patch(mock_sendgrid_api_client)
@mock.patch(mock_config)
def test_mail_tracking(self, m_config, mock_sendgrid):
""" Test various tracking events. """
self.env['ir.config_parameter'].set_param(
'mail_sendgrid.send_method', 'sendgrid')
mail = self.create_email()
mock_sendgrid.return_value = FakeClient()
m_config.get.return_value = "ushuwejhfkj"
mail.send()
self.assertEqual(mock_sendgrid.called, True)
self.assertEqual(mail.state, 'sent')
mail_tracking = mail.tracking_email_ids
self.assertEqual(len(mail_tracking), 1)
self.assertFalse(mail_tracking.state)
# Test mail processed
self.event.update({
'event': u'processed',
'odoo_id': mail.message_id
})
response = self.env['mail.tracking.email'].event_process(
self.request, self.event, self.metadata)
self.assertEqual(response, 'OK')
self.assertEqual(mail_tracking.state, 'sent')
# Test mail delivered
self.event['event'] = 'delivered'
self.env['mail.tracking.email'].event_process(
self.request, self.event, self.metadata)
self.assertEqual(mail_tracking.state, 'delivered')
self.assertEqual(mail_tracking.recipient, self.recipient.email)
self.assertFalse(mail.opened)
# Test mail opened
self.event['event'] = 'open'
self.env['mail.tracking.email'].event_process(
self.request, self.event, self.metadata)
self.assertEqual(mail_tracking.state, 'opened')
self.assertTrue(mail.opened)
# Test click e-mail
self.event['event'] = 'click'
self.env['mail.tracking.email'].event_process(
self.request, self.event, self.metadata)
self.assertEqual(mail_tracking.state, 'opened')
self.assertEqual(mail.click_count, 1)

31
mail_sendgrid/views/email_template_view.xml

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Form view -->
<record id="view_email_template_sendgrid_form" model="ir.ui.view">
<field name="name">sendgrid.sendgrid.form</field>
<field name="model">mail.template</field>
<field name="inherit_id" ref="mail.email_template_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='email_from']/../.." position="after">
<page string="SendGrid">
<group>
<field name="sendgrid_template_ids">
<tree editable="top">
<field name="lang"/>
<field name="sendgrid_template_id"/>
</tree>
</field>
<button name="update_substitutions" string="Get substitutions from templates" type="object" colspan="2"/>
<field name="substitution_ids">
<tree editable="top">
<field name="key"/>
<field name="lang"/>
<field name="value"/>
</tree>
</field>
</group>
</page>
</xpath>
</field>
</record>
</odoo>

21
mail_sendgrid/views/mail_compose_message_view.xml

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Add sendgrid preview in compose mail wizard -->
<record model="ir.ui.view" id="email_compose_message_sendgrid_form">
<field name="name">mail.compose.sendgrid.form</field>
<field name="model">mail.compose.message</field>
<field name="inherit_id" ref="mail.email_compose_message_wizard_form"/>
<field name="arch" type="xml">
<field name="body" position="replace">
<notebook>
<page string="Html">
<field name="body"/>
</page>
<page string="SendGrid Preview">
<field name="body_sendgrid"/>
</page>
</notebook>
</field>
</field>
</record>
</odoo>

77
mail_sendgrid/views/sendgrid_email_view.xml

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Extension of mail.mail form view -->
<record model="ir.ui.view" id="email_form_view">
<field name="name">mail.mail.sendgrid</field>
<field name="model">mail.mail</field>
<field name="inherit_id" ref="mail.view_mail_form"/>
<field name="arch" type="xml">
<button name="send" position="replace">
<field name="send_method" invisible="1"/>
<button name="send" string="Send Now" type="object" class="oe_highlight" attrs="{'invisible': ['|', ('send_method', 'not in', ['sendgrid','traditional']), ('state', '!=', 'outgoing')]}"/>
<button name="send_sendgrid" string="Send (SendGrid)" type="object" class="oe_highlight" attrs="{'invisible': ['|', ('send_method', '=', 'sendgrid'), ('state', '!=', 'outgoing')]}"/>
</button>
<field name="body_html" position="attributes">
<attribute name="widget">html</attribute>
</field>
<xpath expr="//field[@name='attachment_ids']/.." position="after">
<page string="SendGrid">
<group>
<field name="sendgrid_template_id"/>
<field name="sent_date" readonly="1"/>
<field name="opened" readonly="1"/>
<field name="click_count" readonly="1"/>
<field name="body_text"/>
</group>
<field name="substitution_ids" widget="one2many_list"/>
<field name="tracking_event_ids">
<tree default_order="time desc">
<field name="tracking_email_id"/>
<field name="time"/>
<field name="event_type"/>
<field name="url"/>
</tree>
</field>
</page>
</xpath>
</field>
</record>
<!-- Extension of mail.mail tree view -->
<record model="ir.ui.view" id="sendgrid_email_tree_view">
<field name="name">mail.mail.sendgrid.tree</field>
<field name="model">mail.mail</field>
<field name="inherit_id" ref="mail.view_mail_tree"/>
<field name="arch" type="xml">
<field name="date" position="after">
<field name="opened"/>
<field name="click_count"/>
</field>
</field>
</record>
<!-- Extension of mail.mail search view -->
<record model="ir.ui.view" id="sendgrid_email_search_view">
<field name="name">mail.mail.sendgrid.search</field>
<field name="model">mail.mail</field>
<field name="inherit_id" ref="mail.view_mail_search"/>
<field name="arch" type="xml">
<xpath expr="//filter[@name='received']" position="after">
<filter name="opened" string="Opened" domain="[('opened','=',True)]"/>
<filter name="clicked" string="Clicked" domain="[('click_count','>',0)]"/>
</xpath>
</field>
</record>
<!-- Substitution line view -->
<record id="view_sendgrid_substitution_line_tree" model="ir.ui.view">
<field name="name">sendgrid.substitution.tree</field>
<field name="model">sendgrid.substitution</field>
<field name="arch" type="xml">
<tree string="Template substitutions" editable="bottom">
<field name="key"/>
<field name="value"/>
</tree>
</field>
</record>
</odoo>

73
mail_sendgrid/views/sendgrid_template_view.xml

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Tree view -->
<record id="view_sendgrid_template_tree" model="ir.ui.view">
<field name="name">sendgrid.template.tree</field>
<field name="model">sendgrid.template</field>
<field name="arch" type="xml">
<tree string="Templates">
<field name="remote_id"/>
<field name="name"/>
</tree>
</field>
</record>
<!-- Form view -->
<record id="view_sendgrid_template_form" model="ir.ui.view">
<field name="name">sendgrid.template.form</field>
<field name="model">sendgrid.template</field>
<field name="arch" type="xml">
<form string="Template">
<sheet>
<group>
<field name="name"/>
<field name="remote_id"/>
<field name="detected_keywords"/>
</group>
<notebook>
<page string="Html">
<field name="html_content"/>
</page>
<page string="Plain text">
<field name="plain_content"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- Action opening the tree view -->
<record id="open_view_sendgrid_template_tree" model="ir.actions.act_window">
<field name="name">Template</field>
<field name="res_model">sendgrid.template</field>
<field name="view_type">form</field>
<field name="view_mode">form,tree</field>
<field name="view_id" ref="view_sendgrid_template_tree"/>
</record>
<record model="ir.actions.server" id="update_sendgrid_templates">
<field name="name">Update Sendgrid Templates</field>
<field name="model_id" ref="model_sendgrid_template"/>
<field name="code">
self.update(cr, uid, context=context)
action = {
'name': 'Sendgrid templates',
'type': 'ir.actions.act_window',
'res_model': 'sendgrid.template',
'view_type': 'form',
'view_mode': 'tree,form'
}
</field>
</record>
<!-- Add menu entry in Settings/Email -->
<menuitem name="SendGrid Templates" id="menu_sendgrid_template"
parent="base.menu_email"
sequence="8"
action="open_view_sendgrid_template_tree"/>
<menuitem name="Update SendGrid" id="menu_update_sendgrid_template"
parent="base.menu_email"
sequence="9"
action="update_sendgrid_templates"/>
</odoo>

13
mail_sendgrid/wizards/__init__.py

@ -0,0 +1,13 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2016 Compassion CH (http://www.compassion.ch)
# Releasing children from poverty in Jesus' name
# @author: Emanuel Cino
#
# The licence is in the file __openerp__.py
#
##############################################################################
from . import mail_compose_message
from . import email_template_preview

30
mail_sendgrid/wizards/email_template_preview.py

@ -0,0 +1,30 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2016 Compassion CH (http://www.compassion.ch)
# Releasing children from poverty in Jesus' name
# @author: Emanuel Cino <ecino@compassion.ch>
#
# The licence is in the file __openerp__.py
#
##############################################################################
from openerp import models, api
class EmailTemplatePreview(models.TransientModel):
""" Put the preview inside sendgrid template """
_inherit = 'email_template.preview'
@api.multi
def on_change_res_id(self, res_id):
result = super(EmailTemplatePreview, self).on_change_res_id(res_id)
body_html = result['value']['body_html']
template_id = self.env.context.get('template_id')
template = self.env['sendgrid'].browse(template_id)
sendgrid_template = template.sendgrid_localized_template
if sendgrid_template:
body_html = sendgrid_template.html_content.replace(
'<%body%>', body_html)
result['value']['body_html'] = body_html
return result

49
mail_sendgrid/wizards/mail_compose_message.py

@ -0,0 +1,49 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2016 Compassion CH (http://www.compassion.ch)
# Releasing children from poverty in Jesus' name
# @author: Emanuel Cino <ecino@compassion.ch>
#
# The licence is in the file __openerp__.py
#
##############################################################################
from openerp import models, fields, api
class EmailComposeMessage(models.TransientModel):
""" Email message sent through SendGrid """
_inherit = 'mail.compose.message'
body_sendgrid = fields.Html(compute='_compute_sendgrid_view')
@api.depends('body')
def _compute_sendgrid_view(self):
for wizard in self:
template = wizard.template_id
sendgrid_template = template.sendgrid_localized_template
res_id = self.env.context.get('active_id')
render_body = self.render_template(
wizard.body, wizard.model, [res_id], post_process=True)[res_id]
if sendgrid_template and wizard.body:
wizard.body_sendgrid = sendgrid_template.html_content.replace(
'<%body%>', render_body)
else:
wizard.body_sendgrid = render_body
@api.multi
def get_mail_values(self, res_ids):
""" Attach sendgrid template to e-mail and render substitutions """
mail_values = super(EmailComposeMessage, self).get_mail_values(res_ids)
template = self.template_id
sendgrid_template_id = template.sendgrid_localized_template.id
if sendgrid_template_id:
substitutions = template.render_substitutions(res_ids)
for res_id, value in mail_values.iteritems():
value['sendgrid_template_id'] = sendgrid_template_id
value['substitution_ids'] = substitutions[res_id]
return mail_values
Loading…
Cancel
Save