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

  1. # -*- coding: utf-8 -*-
  2. # Copyright 2016-2017 Compassion CH (http://www.compassion.ch)
  3. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
  4. from odoo import models, fields, api, tools
  5. from odoo.tools.config import config
  6. from odoo.tools.safe_eval import safe_eval
  7. import base64
  8. import logging
  9. import re
  10. import time
  11. _logger = logging.getLogger(__name__)
  12. try:
  13. from sendgrid import SendGridAPIClient
  14. from sendgrid.helpers.mail import Email, Attachment, CustomArg, Content, \
  15. Personalization, Substitution, Mail, Header
  16. except ImportError:
  17. _logger.warning("ImportError raised while loading module.")
  18. _logger.debug("ImportError details:", exc_info=True)
  19. STATUS_OK = 202
  20. class MailMessage(models.Model):
  21. """ Add SendGrid related fields so that they dispatch in all
  22. subclasses of mail.message object
  23. """
  24. _inherit = 'mail.message'
  25. body_text = fields.Text(help='Text only version of the body')
  26. sent_date = fields.Datetime(copy=False)
  27. substitution_ids = fields.Many2many(
  28. 'sendgrid.substitution', string='Substitutions', copy=True)
  29. sendgrid_template_id = fields.Many2one(
  30. 'sendgrid.template', 'Sendgrid Template')
  31. send_method = fields.Char(compute='_compute_send_method')
  32. @api.multi
  33. def _compute_send_method(self):
  34. """ Check whether to use traditional send method, sendgrid or disable.
  35. """
  36. send_method = self.env['ir.config_parameter'].get_param(
  37. 'mail_sendgrid.send_method', 'traditional')
  38. for email in self:
  39. email.send_method = send_method
  40. class MailMail(models.Model):
  41. """ Email message sent through SendGrid """
  42. _inherit = 'mail.mail'
  43. tracking_email_ids = fields.One2many(
  44. 'mail.tracking.email', 'mail_id', string='Registered events',
  45. readonly=True)
  46. click_count = fields.Integer(
  47. compute='_compute_tracking', store=True, readonly=True)
  48. opened = fields.Boolean(
  49. compute='_compute_tracking', store=True, readonly=True)
  50. tracking_event_ids = fields.One2many(
  51. 'mail.tracking.event', compute='_compute_events')
  52. @api.depends('tracking_email_ids', 'tracking_email_ids.click_count',
  53. 'tracking_email_ids.state')
  54. def _compute_tracking(self):
  55. for email in self:
  56. click_count = sum(email.tracking_email_ids.mapped(
  57. 'click_count'))
  58. opened = self.env['mail.tracking.email'].search_count([
  59. ('state', '=', 'opened'),
  60. ('mail_id', '=', email.id)
  61. ])
  62. email.update({
  63. 'click_count': click_count,
  64. 'opened': opened > 0
  65. })
  66. def _compute_events(self):
  67. for email in self:
  68. email.tracking_event_ids = email.tracking_email_ids.mapped(
  69. 'tracking_event_ids')
  70. @api.multi
  71. def send(self, auto_commit=False, raise_exception=False):
  72. """ Override send to select the method to send the e-mail. """
  73. traditional = self.filtered(lambda e: e.send_method == 'traditional')
  74. sendgrid = self.filtered(lambda e: e.send_method == 'sendgrid')
  75. if traditional:
  76. super(MailMail, traditional).send(auto_commit, raise_exception)
  77. if sendgrid:
  78. sendgrid.send_sendgrid()
  79. return True
  80. @api.multi
  81. def send_sendgrid(self):
  82. """ Use sendgrid transactional e-mails : e-mails are sent one by
  83. one. """
  84. outgoing = self.filtered(lambda em: em.state == 'outgoing')
  85. api_key = config.get('sendgrid_api_key')
  86. if outgoing and not api_key:
  87. _logger.error(
  88. 'Missing sendgrid_api_key in conf file. Skipping Sendgrid '
  89. 'send.'
  90. )
  91. return
  92. sg = SendGridAPIClient(apikey=api_key)
  93. for email in outgoing:
  94. try:
  95. response = sg.client.mail.send.post(
  96. request_body=email._prepare_sendgrid_data().get())
  97. except Exception as e:
  98. _logger.error(e.message or "mail not sent.")
  99. continue
  100. status = response.status_code
  101. msg = response.body
  102. if status == STATUS_OK:
  103. _logger.info("e-mail sent. " + str(msg))
  104. email._track_sendgrid_emails()
  105. email.write({
  106. 'sent_date': fields.Datetime.now(),
  107. 'state': 'sent'
  108. })
  109. if not self.env.context.get('test_mode'):
  110. # Commit at each e-mail processed to avoid any errors
  111. # invalidating state.
  112. self.env.cr.commit() # pylint: disable=invalid-commit
  113. email._postprocess_sent_message(mail_sent=True)
  114. else:
  115. email._postprocess_sent_message(mail_sent=False)
  116. _logger.error("Failed to send email: {}".format(str(msg)))
  117. def _prepare_sendgrid_data(self):
  118. """
  119. Prepare and creates the Sendgrid Email object
  120. :return: sendgrid.helpers.mail.Email object
  121. """
  122. self.ensure_one()
  123. s_mail = Mail()
  124. s_mail.from_email = Email(self.email_from)
  125. if self.reply_to:
  126. s_mail.reply_to = Email(self.reply_to)
  127. # Add custom fields to match the tracking
  128. s_mail.add_custom_arg(CustomArg('odoo_id', self.message_id))
  129. s_mail.add_custom_arg(CustomArg('odoo_db', self.env.cr.dbname))
  130. headers = {
  131. 'Message-Id': self.message_id
  132. }
  133. if self.headers:
  134. try:
  135. headers.update(safe_eval(self.headers))
  136. except Exception:
  137. pass
  138. for h_name, h_val in headers.iteritems():
  139. s_mail.add_header(Header(h_name, h_val))
  140. html = self.body_html or ' '
  141. p = re.compile(r'<.*?>') # Remove HTML markers
  142. text_only = self.body_text or p.sub('', html.replace('<br/>', '\n'))
  143. s_mail.add_content(Content("text/plain", text_only or ' '))
  144. s_mail.add_content(Content("text/html", html))
  145. test_address = config.get('sendgrid_test_address')
  146. # We use only one personalization for transactional e-mail
  147. personalization = Personalization()
  148. subject = self.subject and self.subject.encode(
  149. "utf_8") or "(No subject)"
  150. personalization.subject = subject
  151. addresses = set()
  152. if not test_address:
  153. if self.email_to:
  154. addresses = set(self.email_to.split(','))
  155. for address in addresses:
  156. personalization.add_to(Email(address))
  157. for recipient in self.recipient_ids:
  158. if recipient.email not in addresses:
  159. personalization.add_to(Email(recipient.email))
  160. addresses.add(recipient.email)
  161. if self.email_cc and self.email_cc not in addresses:
  162. personalization.add_cc(Email(self.email_cc))
  163. else:
  164. _logger.info('Sending email to test address {}'.format(
  165. test_address))
  166. personalization.add_to(Email(test_address))
  167. self.email_to = test_address
  168. if self.sendgrid_template_id:
  169. s_mail.template_id = self.sendgrid_template_id.remote_id
  170. for substitution in self.substitution_ids:
  171. personalization.add_substitution(Substitution(
  172. substitution.key, substitution.value.encode('utf-8')))
  173. s_mail.add_personalization(personalization)
  174. for attachment in self.attachment_ids:
  175. s_attachment = Attachment()
  176. # Datas are not encoded properly for sendgrid
  177. s_attachment.content = base64.b64encode(base64.b64decode(
  178. attachment.datas))
  179. s_attachment.filename = attachment.name
  180. s_mail.add_attachment(s_attachment)
  181. return s_mail
  182. def _track_sendgrid_emails(self):
  183. """ Create tracking e-mails after successfully sent with Sendgrid. """
  184. self.ensure_one()
  185. m_tracking = self.env['mail.tracking.email'].sudo()
  186. track_vals = self._prepare_sendgrid_tracking()
  187. for recipient in tools.email_split_and_format(self.email_to):
  188. track_vals['recipient'] = recipient
  189. m_tracking += m_tracking.create(track_vals)
  190. for partner in self.recipient_ids:
  191. track_vals.update({
  192. 'partner_id': partner.id,
  193. 'recipient': partner.email,
  194. })
  195. m_tracking += m_tracking.create(track_vals)
  196. return m_tracking
  197. def _prepare_sendgrid_tracking(self):
  198. ts = time.time()
  199. return {
  200. 'name': self.subject,
  201. 'timestamp': '%.6f' % ts,
  202. 'time': fields.Datetime.now(),
  203. 'mail_id': self.id,
  204. 'mail_message_id': self.mail_message_id.id,
  205. 'sender': self.email_from,
  206. }