You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
242 lines
8.8 KiB
242 lines
8.8 KiB
# -*- coding: utf-8 -*-
|
|
# Copyright 2016-2017 Compassion CH (http://www.compassion.ch)
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
from odoo import models, fields, api, tools
|
|
from odoo.tools.config import config
|
|
from odoo.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.warning("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'
|
|
|
|
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')
|
|
|
|
@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 MailMail(models.Model):
|
|
""" Email message sent through SendGrid """
|
|
_inherit = 'mail.mail'
|
|
|
|
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:
|
|
click_count = sum(email.tracking_email_ids.mapped(
|
|
'click_count'))
|
|
opened = self.env['mail.tracking.email'].search_count([
|
|
('state', '=', 'opened'),
|
|
('mail_id', '=', email.id)
|
|
])
|
|
email.update({
|
|
'click_count': click_count,
|
|
'opened': opened > 0
|
|
})
|
|
|
|
def _compute_events(self):
|
|
for email in self:
|
|
email.tracking_event_ids = email.tracking_email_ids.mapped(
|
|
'tracking_event_ids')
|
|
|
|
@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(MailMail, traditional).send(auto_commit, raise_exception)
|
|
if sendgrid:
|
|
sendgrid.send_sendgrid()
|
|
return True
|
|
|
|
@api.multi
|
|
def send_sendgrid(self):
|
|
""" Use sendgrid transactional e-mails : e-mails are sent one by
|
|
one. """
|
|
outgoing = self.filtered(lambda em: em.state == 'outgoing')
|
|
api_key = config.get('sendgrid_api_key')
|
|
if outgoing and not api_key:
|
|
_logger.error(
|
|
'Missing sendgrid_api_key in conf file. Skipping Sendgrid '
|
|
'send.'
|
|
)
|
|
return
|
|
|
|
sg = SendGridAPIClient(apikey=api_key)
|
|
for email in outgoing:
|
|
try:
|
|
response = sg.client.mail.send.post(
|
|
request_body=email._prepare_sendgrid_data().get())
|
|
except Exception as e:
|
|
_logger.error(e.message or "mail not sent.")
|
|
continue
|
|
|
|
status = response.status_code
|
|
msg = response.body
|
|
|
|
if status == STATUS_OK:
|
|
_logger.info("e-mail sent. " + str(msg))
|
|
email._track_sendgrid_emails()
|
|
email.write({
|
|
'sent_date': fields.Datetime.now(),
|
|
'state': 'sent'
|
|
})
|
|
if not self.env.context.get('test_mode'):
|
|
# Commit at each e-mail processed to avoid any errors
|
|
# invalidating state.
|
|
self.env.cr.commit() # pylint: disable=invalid-commit
|
|
email._postprocess_sent_message(mail_sent=True)
|
|
else:
|
|
email._postprocess_sent_message(mail_sent=False)
|
|
_logger.error("Failed to send email: {}".format(str(msg)))
|
|
|
|
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 or ' '))
|
|
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 = set()
|
|
if not test_address:
|
|
if self.email_to:
|
|
addresses = set(self.email_to.split(','))
|
|
for address in addresses:
|
|
personalization.add_to(Email(address))
|
|
for recipient in self.recipient_ids:
|
|
if recipient.email not in addresses:
|
|
personalization.add_to(Email(recipient.email))
|
|
addresses.add(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,
|
|
}
|