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.

174 lines
6.4 KiB

  1. # -*- encoding: utf-8 -*-
  2. ##############################################################################
  3. #
  4. # Copyright (C) 2016-2017 Compassion CH (http://www.compassion.ch)
  5. # Releasing children from poverty in Jesus' name
  6. # @author: Emanuel Cino <ecino@compassion.ch>
  7. #
  8. # The licence is in the file __openerp__.py
  9. #
  10. ##############################################################################
  11. import logging
  12. from datetime import datetime
  13. from werkzeug.useragents import UserAgent
  14. from openerp import models, fields, api
  15. _logger = logging.getLogger(__name__)
  16. class MailTrackingEmail(models.Model):
  17. """ Count the user clicks on links inside e-mails sent.
  18. Add tracking methods to process Sendgrid Notifications
  19. """
  20. _inherit = 'mail.tracking.email'
  21. click_count = fields.Integer(compute='_compute_clicks', store=True)
  22. @api.depends('tracking_event_ids')
  23. def _compute_clicks(self):
  24. for mail in self:
  25. mail.click_count = len(mail.tracking_event_ids.filtered(
  26. lambda event: event.event_type == 'click'))
  27. @property
  28. def _sendgrid_mandatory_fields(self):
  29. return ('event', 'sg_event_id', 'timestamp',
  30. 'odoo_id', 'odoo_db')
  31. @property
  32. def _sendgrid_event_type_mapping(self):
  33. return {
  34. # Sendgrid event type: tracking event type
  35. 'bounce': 'hard_bounce',
  36. 'click': 'click',
  37. 'deferred': 'deferral',
  38. 'delivered': 'delivered',
  39. 'dropped': 'reject',
  40. 'group_unsubscribe': 'unsub',
  41. 'open': 'open',
  42. 'processed': 'sent',
  43. 'spamreport': 'spam',
  44. 'unsubscribe': 'unsub',
  45. }
  46. def _sendgrid_event_type_verify(self, event):
  47. event = event or {}
  48. sendgrid_event_type = event.get('event')
  49. if sendgrid_event_type not in self._sendgrid_event_type_mapping:
  50. _logger.error("Sendgrid: event type '%s' not supported",
  51. sendgrid_event_type)
  52. return False
  53. # OK, event type is valid
  54. return True
  55. def _db_verify(self, event):
  56. event = event or {}
  57. odoo_db = event.get('odoo_db')
  58. current_db = self.env.cr.dbname
  59. if odoo_db != current_db:
  60. _logger.error("Sendgrid: Database '%s' is not the current "
  61. "database",
  62. odoo_db)
  63. return False
  64. # OK, DB is current
  65. return True
  66. def _sendgrid_metadata(self, sendgrid_event_type, event, metadata):
  67. # Get sendgrid timestamp when found
  68. ts = event.get('timestamp', False)
  69. try:
  70. ts = float(ts)
  71. except:
  72. ts = False
  73. if ts:
  74. dt = datetime.utcfromtimestamp(ts)
  75. metadata.update({
  76. 'timestamp': ts,
  77. 'time': fields.Datetime.to_string(dt),
  78. 'date': fields.Date.to_string(dt),
  79. })
  80. # Common field mapping (sendgrid_field: odoo_field)
  81. mapping = {
  82. 'email': 'recipient',
  83. 'ip': 'ip',
  84. 'url': 'url',
  85. }
  86. for k, v in mapping.iteritems():
  87. if event.get(k, False):
  88. metadata[v] = event[k]
  89. # Special field mapping
  90. if event.get('useragent'):
  91. user_agent = UserAgent(event['useragent'])
  92. metadata.update({
  93. 'user_agent': user_agent.string,
  94. 'os_family': user_agent.platform,
  95. 'ua_family': user_agent.browser,
  96. 'mobile': user_agent.platform in [
  97. 'android', 'iphone', 'ipad']
  98. })
  99. # Mapping for special events
  100. if sendgrid_event_type == 'bounced':
  101. metadata.update({
  102. 'error_type': event.get('type', False),
  103. 'error_description': event.get('reason', False),
  104. 'error_details': event.get('status', False),
  105. })
  106. elif sendgrid_event_type == 'dropped':
  107. metadata.update({
  108. 'error_type': event.get('reason', False),
  109. })
  110. return metadata
  111. def _sendgrid_tracking_get(self, event):
  112. tracking = False
  113. message_id = event.get('odoo_id', False)
  114. if message_id:
  115. tracking = self.search([
  116. ('mail_id.message_id', '=', message_id),
  117. ('recipient', '=ilike', event.get('email'))], limit=1)
  118. return tracking
  119. def _event_is_from_sendgrid(self, event):
  120. event = event or {}
  121. return all([k in event for k in self._sendgrid_mandatory_fields])
  122. @api.model
  123. def event_process(self, request, post, metadata, event_type=None):
  124. res = super(MailTrackingEmail, self).event_process(
  125. request, post, metadata, event_type=event_type)
  126. is_json = hasattr(request, 'jsonrequest') and isinstance(
  127. request.jsonrequest, list)
  128. if res == 'NONE' and is_json:
  129. for event in request.jsonrequest:
  130. if self._event_is_from_sendgrid(event):
  131. if not self._sendgrid_event_type_verify(event):
  132. res = 'ERROR: Event type not supported'
  133. elif not self._db_verify(event):
  134. res = 'ERROR: Invalid DB'
  135. else:
  136. res = 'OK'
  137. if res == 'OK':
  138. sendgrid_event_type = event.get('event')
  139. mapped_event_type = self._sendgrid_event_type_mapping.get(
  140. sendgrid_event_type) or event_type
  141. if not mapped_event_type:
  142. res = 'ERROR: Bad event'
  143. tracking = self._sendgrid_tracking_get(event)
  144. if not tracking:
  145. res = 'ERROR: Tracking not found'
  146. if res == 'OK':
  147. # Complete metadata with sendgrid event info
  148. metadata = self._sendgrid_metadata(
  149. sendgrid_event_type, event, metadata)
  150. # Create event
  151. tracking.event_create(mapped_event_type, metadata)
  152. if res != 'NONE':
  153. if event_type:
  154. _logger.info(
  155. "sendgrid: event '%s' process '%s'",
  156. event_type, res)
  157. else:
  158. _logger.info("sendgrid: event process '%s'", res)
  159. return res