Skip to content

Commit

Permalink
[IMP] Email stars and generic webhook
Browse files Browse the repository at this point in the history
  • Loading branch information
antespi committed Aug 7, 2016
1 parent c840908 commit 4621c8d
Show file tree
Hide file tree
Showing 12 changed files with 341 additions and 33 deletions.
1 change: 1 addition & 0 deletions mail_tracking/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@

from . import models
from . import controllers
from .hooks import post_init_hook
6 changes: 4 additions & 2 deletions mail_tracking/__openerp__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
{
"name": "Email tracking",
"summary": "Email tracking system for all mails sent",
"version": "8.0.1.0.0",
"version": "8.0.2.0.0",
"category": "Social Network",
"website": "http://www.tecnativa.com",
"author": "Tecnativa, "
Expand All @@ -23,8 +23,10 @@
"views/assets.xml",
"views/mail_tracking_email_view.xml",
"views/mail_tracking_event_view.xml",
"views/res_partner_view.xml",
],
"qweb": [
"static/src/xml/mail_tracking.xml",
]
],
"post_init_hook": "post_init_hook",
}
71 changes: 52 additions & 19 deletions mail_tracking/controllers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@
BLANK = 'R0lGODlhAQABAIAAANvf7wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='


def _env_get(db):
reg = False
try:
reg = registry(db)
except OperationalError:
_logger.warning("Selected BD '%s' not found", db)
except:
_logger.warning("Selected BD '%s' connection error", db)
if reg:
return api.Environment(reg.cursor(), SUPERUSER_ID, {})
return False


class MailTrackingController(http.Controller):

def _request_metadata(self):
Expand All @@ -22,29 +35,49 @@ def _request_metadata(self):
'ua_family': request.user_agent.browser,
}

@http.route('/mail/tracking/all/<string:db>',
type='http', auth='none')
def mail_tracking_all(self, db, **kw):
env = _env_get(db)
if not env:
return 'NOT FOUND'
metadata = self._request_metadata()
response = env['mail.tracking.email'].event_process(
http.request, kw, metadata)
env.cr.commit()
env.cr.close()
return response

@http.route('/mail/tracking/event/<string:db>/<string:event_type>',
type='http', auth='none')
def mail_tracking_event(self, db, event_type, **kw):
env = _env_get(db)
if not env:
return 'NOT FOUND'
metadata = self._request_metadata()
response = env['mail.tracking.email'].event_process(
http.request, kw, metadata, event_type=event_type)
env.cr.commit()
env.cr.close()
return response

@http.route('/mail/tracking/open/<string:db>'
'/<int:tracking_email_id>/blank.gif',
type='http', auth='none')
def mail_tracking_open(self, db, tracking_email_id, **kw):
reg = False
try:
reg = registry(db)
except OperationalError:
_logger.warning("Selected BD '%s' not found", db)
except:
_logger.warning("Selected BD '%s' connection error", db)
if reg:
with reg.cursor() as cr:
env = api.Environment(cr, SUPERUSER_ID, {})
tracking_email = env['mail.tracking.email'].search([
('id', '=', tracking_email_id),
])
if tracking_email:
metadata = self._request_metadata()
tracking_email.event_process('open', metadata)
else:
_logger.warning(
"MailTracking email '%s' not found", tracking_email_id)
env = _env_get(db)
if env:
tracking_email = env['mail.tracking.email'].search([
('id', '=', tracking_email_id),
])
if tracking_email:
metadata = self._request_metadata()
tracking_email.event_create('open', metadata)
else:
_logger.warning(
"MailTracking email '%s' not found", tracking_email_id)
env.cr.commit()
env.cr.close()

# Always return GIF blank image
response = werkzeug.wrappers.Response()
Expand Down
24 changes: 24 additions & 0 deletions mail_tracking/hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# © 2016 Antonio Espinosa - <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

import logging
from openerp import api, SUPERUSER_ID

_logger = logging.getLogger(__name__)


def post_init_hook(cr, registry):
with api.Environment.manage():
env = api.Environment(cr, SUPERUSER_ID, {})
# Recalculate all partner tracking_email_ids
partners = env['res.partner'].search([
('email', '!=', False),
])
emails = partners.mapped('email')
_logger.info(
"Recalculating 'tracking_email_ids' in 'res.partner' "
"model for %d email addresses", len(emails))
for email in emails:
env['mail.tracking.email'].tracking_ids_recalculate(
'res.partner', 'email', 'tracking_email_ids', email)
1 change: 1 addition & 0 deletions mail_tracking/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
from . import mail_message
from . import mail_tracking_email
from . import mail_tracking_event
from . import res_partner
4 changes: 2 additions & 2 deletions mail_tracking/models/ir_mail_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def _tracking_email_id_body_get(self, body):
if match:
try:
tracking_email_id = int(match.group(1))
except:
except: # pragma: no cover
pass
return tracking_email_id

Expand Down Expand Up @@ -61,7 +61,7 @@ def _smtp_server_get(self, mail_server_id, smtp_server):
mail_server = mail_server_ids[0] if mail_server_ids else None
if mail_server:
smtp_server_used = mail_server.smtp_host
else:
else: # pragma: no cover
smtp_server_used = smtp_server or tools.config.get('smtp_server')
return smtp_server_used

Expand Down
107 changes: 104 additions & 3 deletions mail_tracking/models/mail_tracking_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import logging
import urlparse
import time
import re
from datetime import datetime

from openerp import models, api, fields, tools
Expand All @@ -16,10 +17,13 @@
class MailTrackingEmail(models.Model):
_name = "mail.tracking.email"
_order = 'time desc'
_rec_name = 'name'
_rec_name = 'display_name'
_description = 'MailTracking email'

name = fields.Char(string="Subject", readonly=True, index=True)
display_name = fields.Char(
string="Display name", readonly=True, store=True,
compute="_compute_display_name")
timestamp = fields.Float(
string='UTC timestamp', readonly=True,
digits=dp.get_precision('MailTracking Timestamp'))
Expand All @@ -33,6 +37,9 @@ class MailTrackingEmail(models.Model):
partner_id = fields.Many2one(
string="Partner", comodel_name='res.partner', readonly=True)
recipient = fields.Char(string='Recipient email', readonly=True)
recipient_address = fields.Char(
string='Recipient email address', readonly=True, store=True,
compute='_compute_recipient_address')
sender = fields.Char(string='Sender email', readonly=True)
state = fields.Selection([
('error', 'Error'),
Expand Down Expand Up @@ -77,13 +84,99 @@ class MailTrackingEmail(models.Model):
string="Tracking events", comodel_name='mail.tracking.event',
inverse_name='tracking_email_id', readonly=True)

@api.model
def tracking_ids_recalculate(self, model, email_field, tracking_field,
email, new_tracking=None):
objects = self.env[model].search([
(email_field, '=ilike', email),
])
for obj in objects:
trackings = obj[tracking_field]
if new_tracking:
trackings |= new_tracking
trackings = trackings._email_score_tracking_filter()
if set(obj[tracking_field].ids) != set(trackings.ids):
if trackings:
obj.write({
tracking_field: [(6, False, trackings.ids)]
})
else:
obj.write({
tracking_field: [(5, False, False)]
})
return True

@api.model
def _tracking_ids_to_write(self, email):
trackings = self.env['mail.tracking.email'].search([
('recipient_address', '=ilike', email)
])
trackings = trackings._email_score_tracking_filter()
if trackings:
return [(6, False, trackings.ids)]
else:
return [(5, False, False)]

@api.multi
def _email_score_tracking_filter(self):
"""Default email score filter for tracking emails"""
# Consider only last 10 tracking emails
return self.sorted(key=lambda r: r.time, reverse=True)[:10]

@api.multi
def email_score(self):
"""Default email score algorimth"""
score = 50.0
trackings = self._email_score_tracking_filter()
for tracking in trackings:
if tracking.state in ('error',):
score -= 50.0
elif tracking.state in ('rejected', 'spam', 'bounced'):
score -= 25.0
elif tracking.state in ('soft-bounced', 'unsub'):
score -= 10.0
elif tracking.state in ('delivered',):
score += 5.0
elif tracking.state in ('opened',):
score += 10.0
if score > 100.0:
score = 100.0
return score

@api.multi
@api.depends('recipient')
def _compute_recipient_address(self):
for email in self:
matches = re.search(r'<(.*@.*)>', email.recipient)
if matches:
email.recipient_address = matches.group(1)
else:
email.recipient_address = email.recipient

@api.multi
@api.depends('name', 'recipient')
def _compute_display_name(self):
for email in self:
parts = [email.name]
if email.recipient:
parts.append(email.recipient)
email.display_name = ' - '.join(parts)

@api.multi
@api.depends('time')
def _compute_date(self):
for email in self:
email.date = fields.Date.to_string(
fields.Date.from_string(email.time))

@api.model
def create(self, vals):
tracking = super(MailTrackingEmail, self).create(vals)
self.tracking_ids_recalculate(
'res.partner', 'email', 'tracking_email_ids',
tracking.recipient_address, new_tracking=tracking)
return tracking

def _get_mail_tracking_img(self):
base_url = self.env['ir.config_parameter'].get_param('web.base.url')
path_url = (
Expand Down Expand Up @@ -159,15 +252,23 @@ def _event_prepare(self, event_type, metadata):
method = getattr(m_event, 'process_' + event_type, None)
if method and hasattr(method, '__call__'):
return method(self, metadata)
else:
else: # pragma: no cover
_logger.info('Unknown event type: %s' % event_type)
return False

@api.multi
def event_process(self, event_type, metadata):
def event_create(self, event_type, metadata):
event_ids = self.env['mail.tracking.event']
for tracking_email in self:
vals = tracking_email._event_prepare(event_type, metadata)
if vals:
event_ids += event_ids.sudo().create(vals)
return event_ids

@api.model
def event_process(self, request, post, metadata, event_type=None):
# Generic event process hook, inherit it and
# - return 'OK' if processed
# - return 'NONE' if this request is not for you
# - return 'ERROR' if any error
return 'NONE' # pragma: no cover
40 changes: 40 additions & 0 deletions mail_tracking/models/res_partner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
# © 2016 Antonio Espinosa - <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from openerp import models, api, fields


class ResPartner(models.Model):
_inherit = 'res.partner'

tracking_email_ids = fields.Many2many(
string="Tracking emails", comodel_name="mail.tracking.email",
readonly=True)
tracking_emails_count = fields.Integer(
string="Tracking emails count", store=True, readonly=True,
compute="_compute_tracking_emails_count")
email_score = fields.Float(
string="Email score",
compute="_compute_email_score", store=True, readonly=True)

@api.one
@api.depends('tracking_email_ids.state')
def _compute_email_score(self):
self.email_score = self.tracking_email_ids.email_score()

@api.one
@api.depends('tracking_email_ids')
def _compute_tracking_emails_count(self):
self.tracking_emails_count = self.env['mail.tracking.email'].\
search_count([
('recipient_address', '=ilike', self.email)
])

@api.multi
def write(self, vals):
email = vals.get('email')
if email is not None:
vals['tracking_email_ids'] = \
self.env['mail.tracking.email']._tracking_ids_to_write(email)
return super(ResPartner, self).write(vals)
Loading

0 comments on commit 4621c8d

Please sign in to comment.