123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- # -*- coding: utf-8 -*-
- # © 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
- # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
- import logging
- import urlparse
- import time
- import re
- from datetime import datetime
- from openerp import models, api, fields, tools
- import openerp.addons.decimal_precision as dp
- _logger = logging.getLogger(__name__)
- EVENT_OPEN_DELTA = 10 # seconds
- EVENT_CLICK_DELTA = 5 # seconds
- class MailTrackingEmail(models.Model):
- _name = "mail.tracking.email"
- _order = 'time desc'
- _rec_name = 'display_name'
- _description = 'MailTracking email'
- # This table is going to grow fast and to infinite, so we index:
- # - name: Search in tree view
- # - time: default order fields
- # - recipient_address: Used for email_store calculation (non-store)
- # - state: Search and group_by in tree view
- name = fields.Char(string="Asunto", readonly=True, index=True)
- display_name = fields.Char(
- string="Nombre Mostrado", readonly=True, store=True,
- compute="_compute_display_name")
- timestamp = fields.Float(
- string='UTC Fecha-Hora', readonly=True,
- digits=dp.get_precision('MailTracking Timestamp'))
- time = fields.Datetime(string="Hora", readonly=True, index=True)
- date = fields.Date(
- string="Fecha", readonly=True, compute="_compute_date", store=True)
- mail_message_id = fields.Many2one(
- string="Mensaje", comodel_name='mail.message', readonly=True)
- mail_id = fields.Many2one(
- string="Email", comodel_name='mail.mail', readonly=True)
- partner_id = fields.Many2one(
- string="Partner", comodel_name='res.partner', readonly=True)
- recipient = fields.Char(string='Recipiente email', readonly=True)
- recipient_address = fields.Char(
- string='Dirección del Recipiente del email', readonly=True, store=True,
- compute='_compute_recipient_address', index=True)
- sender = fields.Char(string='Email que envió', readonly=True)
- state = fields.Selection([
- ('error', 'Error'),
- ('deferred', 'Diferido'),
- ('sent', 'Enviado'),
- ('delivered', 'Entregado'),
- ('opened', 'Abierto'),
- ('rejected', 'Rechazado'),
- ('spam', 'Spam'),
- ('unsub', 'Desuscripto'),
- ('bounced', 'Rebotado'),
- ('soft-bounced', 'Rebotado Suavemente'),
- ], string='Estado', index=True, readonly=True, default=False,
- help="* El estado 'Error' indica que se produjo un error al intentar enviar el correo electrónico, por ejemplo, No destinatario válido \n"
- "* El estado de 'Enviados' indica que el mensaje fue enviado con éxito a través del servidor de correo saliente (SMTP). \n"
- "* El estado 'Entregado' indica que el mensaje fue entregado con éxito al servidor receptor de intercambio de correo (MX). \n"
- "* El estado de 'Abierto' indica que se abre o se hace clic por destinatario que el mensaje. \n"
- "* El estado 'Rechazado' indica que dirección de correo electrónico del destinatario en una lista negra por el servidor de correo saliente (SMTP). Está recomendado eliminar esta dirección de correo electrónico. \n"
- "* El estado de 'Spam' indica que el servidor de correo saliente (SMTP) debe tener en cuenta este mensaje como spam. \n"
- "* El estado de 'Desuscripto' indica que destinatario ha solicitado para anular su inscripción en este mensaje . \n"
- "* El estado 'rebotados' indica que el mensaje se ha rechazado por el servidor del destinatario de intercambio de correo (MX). \n"
- "* La 'suave rebotó' estado indica que el mensaje fue suave rebotó por el receptor de intercambio de correo (MX) del servidor. \n")
- error_smtp_server = fields.Char(string='Error del servidor SMTP ', readonly=True)
- error_type = fields.Char(string='Tipo de Error', readonly=True)
- error_description = fields.Char(
- string='Descripción del Error', readonly=True)
- bounce_type = fields.Char(string='Tipo de Rebote', readonly=True)
- bounce_description = fields.Char(
- string='Descripcion del Rebote', readonly=True)
- tracking_event_ids = fields.One2many(
- string="Estadisticas de Seguimientos", comodel_name='mail.tracking.event',
- inverse_name='tracking_email_id', readonly=True)
- @api.model
- def _email_score_tracking_filter(self, domain, order='time desc',
- limit=10):
- """Default tracking search. Ready to be inherited."""
- return self.search(domain, limit=limit, order=order)
- @api.model
- def email_is_bounced(self, email):
- return len(self._email_score_tracking_filter([
- ('recipient_address', '=ilike', email),
- ('state', 'in', ('error', 'rejected', 'spam', 'bounced')),
- ])) > 0
- @api.model
- def email_score_from_email(self, email):
- return self._email_score_tracking_filter([
- ('recipient_address', '=ilike', email)
- ]).email_score()
- @api.model
- def _email_score_weights(self):
- """Default email score weights. Ready to be inherited"""
- return {
- 'error': -50.0,
- 'rejected': -25.0,
- 'spam': -25.0,
- 'bounced': -25.0,
- 'soft-bounced': -10.0,
- 'unsub': -10.0,
- 'delivered': 1.0,
- 'opened': 5.0,
- }
- @api.multi
- def email_score(self):
- """Default email score algorimth. Ready to be inherited
- Must return a value beetwen 0.0 and 100.0
- - Bad reputation: Value between 0 and 50.0
- - Unknown reputation: Value 50.0
- - Good reputation: Value between 50.0 and 100.0
- """
- weights = self._email_score_weights()
- score = 50.0
- for tracking in self:
- score += weights.get(tracking.state, 0.0)
- if score > 100.0:
- score = 100.0
- elif score < 0.0:
- score = 0.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 or '']
- 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))
- def _get_mail_tracking_img(self):
- m_config = self.env['ir.config_parameter']
- base_url = (m_config.get_param('mail_tracking.base.url') or
- m_config.get_param('web.base.url'))
- path_url = (
- 'mail/tracking/open/%(db)s/%(tracking_email_id)s/blank.gif' % {
- 'db': self.env.cr.dbname,
- 'tracking_email_id': self.id,
- })
- track_url = urlparse.urljoin(base_url, path_url)
- return (
- '<img src="%(url)s" alt="" '
- 'data-odoo-tracking-email="%(tracking_email_id)s"/>' % {
- 'url': track_url,
- 'tracking_email_id': self.id,
- })
- @api.multi
- def _partners_email_bounced_set(self, reason):
- for tracking_email in self:
- self.env['res.partner'].search([
- ('email', '=ilike', tracking_email.recipient_address)
- ]).email_bounced_set(tracking_email, reason)
- @api.multi
- def smtp_error(self, mail_server, smtp_server, exception):
- self.sudo().write({
- 'error_smtp_server': tools.ustr(smtp_server),
- 'error_type': exception.__class__.__name__,
- 'error_description': tools.ustr(exception),
- 'state': 'error',
- })
- self.sudo()._partners_email_bounced_set('error')
- return True
- @api.multi
- def tracking_img_add(self, email):
- self.ensure_one()
- tracking_url = self._get_mail_tracking_img()
- if tracking_url:
- body = tools.append_content_to_html(
- email.get('body', ''), tracking_url, plaintext=False,
- container_tag='div')
- email['body'] = body
- return email
- def _message_partners_check(self, message, message_id):
- mail_message = self.mail_message_id
- partners = mail_message.notified_partner_ids | mail_message.partner_ids
- if (self.partner_id and self.partner_id not in partners):
- # If mail_message haven't tracking partner, then
- # add it in order to see his tracking status in chatter
- if mail_message.subtype_id:
- mail_message.sudo().write({
- 'notified_partner_ids': [(4, self.partner_id.id)],
- })
- else:
- mail_message.sudo().write({
- 'partner_ids': [(4, self.partner_id.id)],
- })
- return True
- @api.multi
- def _tracking_sent_prepare(self, mail_server, smtp_server, message,
- message_id):
- self.ensure_one()
- ts = time.time()
- dt = datetime.utcfromtimestamp(ts)
- self._message_partners_check(message, message_id)
- self.sudo().write({'state': 'sent'})
- return {
- 'recipient': message['To'],
- 'timestamp': '%.6f' % ts,
- 'time': fields.Datetime.to_string(dt),
- 'tracking_email_id': self.id,
- 'event_type': 'sent',
- 'smtp_server': smtp_server,
- }
- def _event_prepare(self, event_type, metadata):
- self.ensure_one()
- m_event = self.env['mail.tracking.event']
- method = getattr(m_event, 'process_' + event_type, None)
- if method and hasattr(method, '__call__'):
- return method(self, metadata)
- else: # pragma: no cover
- _logger.info('Unknown event type: %s' % event_type)
- return False
- def _concurrent_events(self, event_type, metadata):
- m_event = self.env['mail.tracking.event']
- self.ensure_one()
- concurrent_event_ids = False
- if event_type in {'open', 'click'}:
- ts = metadata.get('timestamp', time.time())
- delta = EVENT_OPEN_DELTA if event_type == 'open' \
- else EVENT_CLICK_DELTA
- domain = [
- ('timestamp', '>=', ts - delta),
- ('timestamp', '<=', ts + delta),
- ('tracking_email_id', '=', self.id),
- ('event_type', '=', event_type),
- ]
- if event_type == 'click':
- domain.append(('url', '=', metadata.get('url', False)))
- concurrent_event_ids = m_event.search(domain)
- return concurrent_event_ids
- @api.multi
- def event_create(self, event_type, metadata):
- event_ids = self.env['mail.tracking.event']
- for tracking_email in self:
- other_ids = tracking_email._concurrent_events(event_type, metadata)
- if not other_ids:
- vals = tracking_email._event_prepare(event_type, metadata)
- if vals:
- event_ids += event_ids.sudo().create(vals)
- else:
- _logger.debug("Concurrent event '%s' discarded", event_type)
- if event_type in {'hard_bounce', 'spam', 'reject'}:
- self.sudo()._partners_email_bounced_set(event_type)
- 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
|