mirror of https://github.com/OCA/social.git
commit
6426abcf25
|
@ -0,0 +1,93 @@
|
||||||
|
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
|
||||||
|
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||||
|
:alt: License: AGPL-3
|
||||||
|
|
||||||
|
================================
|
||||||
|
Mandrill mail events integration
|
||||||
|
================================
|
||||||
|
|
||||||
|
This module logs Mandrill email messages events.
|
||||||
|
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
=============
|
||||||
|
|
||||||
|
To configure this module, you need to:
|
||||||
|
|
||||||
|
* Define an STMP server in Odoo at Settings > Technical > Email > Outgoing Mail Servers
|
||||||
|
using Mandrill credentials from Mandrill settings panel (Settings > SMTP & API Credentials)
|
||||||
|
* Define a webhook in Mandrill settings panel (Settings > Webhooks) with
|
||||||
|
several triggers (Message Is Sent, Message Is Delayed, ...) and 'Post to URL'
|
||||||
|
like https://your_odoodomain.com/mandrill/event
|
||||||
|
* Copy Webhook key and paste in your Odoo configuration file, in 'options'
|
||||||
|
section, using 'mandrill_webhook_key' variable. This is optional, but
|
||||||
|
recommended because it is used to validate Mandrill POST requests
|
||||||
|
|
||||||
|
|
||||||
|
Usage
|
||||||
|
=====
|
||||||
|
|
||||||
|
When any email message is sent via Mandrill SMTP server, Odoo will add
|
||||||
|
some metadata to email (Odoo DB, Odoo Model and Odoo Model record ID) using an
|
||||||
|
special SMTP header (X-MC-Metadata). More info at `Mandrill doc: Using Custom Message Metadata <https://mandrill.zendesk.com/hc/en-us/articles/205582417-Using-Custom-Message-Metadata>`_
|
||||||
|
|
||||||
|
Then when an event occurs related with that message (sent, open, click, bounce, ...)
|
||||||
|
Mandrill will trigger webhook configured and Odoo will log the message and the event.
|
||||||
|
|
||||||
|
In 'Setting > Technical > Email > Mandrill emails' you can see all messages sent
|
||||||
|
using Mandrill. When clicking in one of them you'll see message details and events
|
||||||
|
related with it
|
||||||
|
|
||||||
|
In 'Setting > Technical > Email > Mandrill events' you can see all Mandrill events
|
||||||
|
received
|
||||||
|
|
||||||
|
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
|
||||||
|
:alt: Try me on Runbot
|
||||||
|
:target: https://runbot.odoo-community.org/runbot/205/8.0
|
||||||
|
|
||||||
|
For further information, please visit:
|
||||||
|
|
||||||
|
* https://www.odoo.com/forum/help-1
|
||||||
|
|
||||||
|
|
||||||
|
Known issues / Roadmap
|
||||||
|
======================
|
||||||
|
|
||||||
|
* Define actions associated with events like open/click or bounce
|
||||||
|
(via configuration or via other addon)
|
||||||
|
* Create another addon 'mass_mailing_mandrill' (inheriting from mass_mailing
|
||||||
|
and this addon) to process bounces like mass_mailing addon does
|
||||||
|
|
||||||
|
|
||||||
|
Bug Tracker
|
||||||
|
===========
|
||||||
|
|
||||||
|
Bugs are tracked on `GitHub Issues <https://github.com/OCA/social/issues>`_.
|
||||||
|
In case of trouble, please check there if your issue has already been reported.
|
||||||
|
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback
|
||||||
|
`here <https://github.com/OCA/social/issues/new?body=module:%20mail_mandrill%0Aversion:%208.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
|
||||||
|
|
||||||
|
|
||||||
|
Credits
|
||||||
|
=======
|
||||||
|
|
||||||
|
Contributors
|
||||||
|
------------
|
||||||
|
|
||||||
|
* Rafael Blasco <rafabn@antiun.com>
|
||||||
|
* Antonio Espinosa <antonioea@antiun.com>
|
||||||
|
|
||||||
|
Maintainer
|
||||||
|
----------
|
||||||
|
|
||||||
|
.. image:: https://odoo-community.org/logo.png
|
||||||
|
:alt: Odoo Community Association
|
||||||
|
:target: https://odoo-community.org
|
||||||
|
|
||||||
|
This module is maintained by the OCA.
|
||||||
|
|
||||||
|
OCA, or the Odoo Community Association, is a nonprofit organization whose
|
||||||
|
mission is to support the collaborative development of Odoo features and
|
||||||
|
promote its widespread use.
|
||||||
|
|
||||||
|
To contribute to this module, please visit http://odoo-community.org.
|
|
@ -0,0 +1,6 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# License AGPL-3: Antiun Ingenieria S.L. - Antonio Espinosa
|
||||||
|
# See README.rst file on addon root folder for more details
|
||||||
|
|
||||||
|
from . import models
|
||||||
|
from . import controllers
|
|
@ -0,0 +1,25 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# License AGPL-3: Antiun Ingenieria S.L. - Antonio Espinosa
|
||||||
|
# See README.rst file on addon root folder for more details
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': "Mandrill mail events integration",
|
||||||
|
'category': 'Social Network',
|
||||||
|
'version': '8.0.1.0.0',
|
||||||
|
'depends': [
|
||||||
|
'mail',
|
||||||
|
],
|
||||||
|
'external_dependencies': {},
|
||||||
|
'data': [
|
||||||
|
'security/ir.model.access.csv',
|
||||||
|
'views/mail_mandrill_message_view.xml',
|
||||||
|
'views/mail_mandrill_event_view.xml',
|
||||||
|
],
|
||||||
|
'author': 'Antiun Ingeniería S.L., '
|
||||||
|
'Odoo Community Association (OCA)',
|
||||||
|
'website': 'http://www.antiun.com',
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
'demo': [],
|
||||||
|
'test': [],
|
||||||
|
'installable': True,
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# License AGPL-3: Antiun Ingenieria S.L. - Antonio Espinosa
|
||||||
|
# See README.rst file on addon root folder for more details
|
||||||
|
|
||||||
|
from . import main
|
|
@ -0,0 +1,130 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# License AGPL-3: Antiun Ingenieria S.L. - Antonio Espinosa
|
||||||
|
# See README.rst file on addon root folder for more details
|
||||||
|
|
||||||
|
import json
|
||||||
|
from hashlib import sha1
|
||||||
|
import hmac
|
||||||
|
import logging
|
||||||
|
from psycopg2 import OperationalError
|
||||||
|
|
||||||
|
import openerp
|
||||||
|
from openerp import api, http, SUPERUSER_ID, tools
|
||||||
|
from openerp.http import request
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class MailController(http.Controller):
|
||||||
|
|
||||||
|
def _mandrill_validation(self, **kw):
|
||||||
|
"""
|
||||||
|
Validate Mandrill POST reques using
|
||||||
|
https://mandrill.zendesk.com/hc/en-us/articles/
|
||||||
|
205583257-Authenticating-webhook-requests
|
||||||
|
"""
|
||||||
|
headers = request.httprequest.headers
|
||||||
|
signature = headers.get('X-Mandrill-Signature', False)
|
||||||
|
key = tools.config.options.get('mandrill_webhook_key', False)
|
||||||
|
if not key:
|
||||||
|
_logger.info("No Mandrill validation key configured. "
|
||||||
|
"Please add 'mandrill_webhook_key' to [options] "
|
||||||
|
"section in odoo configuration file to enable "
|
||||||
|
"Mandrill authentication webhoook requests. "
|
||||||
|
"More info at: "
|
||||||
|
"https://mandrill.zendesk.com/hc/en-us/articles/"
|
||||||
|
"205583257-Authenticating-webhook-requests")
|
||||||
|
return True
|
||||||
|
if not signature:
|
||||||
|
return False
|
||||||
|
url = tools.config.options.get('mandrill_webhook_url', False)
|
||||||
|
if not url:
|
||||||
|
url = request.httprequest.url_root.rstrip('/') + '/mandrill/event'
|
||||||
|
data = url
|
||||||
|
kw_keys = kw.keys()
|
||||||
|
if kw_keys:
|
||||||
|
kw_keys.sort()
|
||||||
|
for kw_key in kw_keys:
|
||||||
|
data += kw_key + kw.get(kw_key)
|
||||||
|
hashed = hmac.new(key, data, sha1)
|
||||||
|
hash_text = hashed.digest().encode("base64").rstrip('\n')
|
||||||
|
if hash_text == signature:
|
||||||
|
return True
|
||||||
|
_logger.info("HASH[%s] != SIGNATURE[%s]" % (hash_text, signature))
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _event_process(self, event):
|
||||||
|
message_id = event.get('_id')
|
||||||
|
event_type = event.get('event')
|
||||||
|
message = event.get('msg')
|
||||||
|
if not (message_id and event_type and message):
|
||||||
|
return False
|
||||||
|
|
||||||
|
info = "%s event for Message ID '%s'" % (event_type, message_id)
|
||||||
|
metadata = message.get('metadata')
|
||||||
|
db = None
|
||||||
|
if metadata:
|
||||||
|
db = metadata.get('odoo_db', None)
|
||||||
|
|
||||||
|
# Check database selected by mandrill event
|
||||||
|
if not db:
|
||||||
|
_logger.info('%s: No DB selected', info)
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
registry = openerp.registry(db)
|
||||||
|
except OperationalError:
|
||||||
|
_logger.info("%s: Selected BD '%s' not found", info, db)
|
||||||
|
return False
|
||||||
|
except:
|
||||||
|
_logger.info("%s: Selected BD '%s' connection error", info, db)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Database has been selected, process event
|
||||||
|
with registry.cursor() as cr:
|
||||||
|
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||||
|
res = env['mail.mandrill.message'].process(
|
||||||
|
message_id, event_type, event)
|
||||||
|
if res:
|
||||||
|
_logger.info('%s: OK', info)
|
||||||
|
else:
|
||||||
|
_logger.info('%s: FAILED', info)
|
||||||
|
return res
|
||||||
|
|
||||||
|
@http.route('/mandrill/event', type='http', auth='none')
|
||||||
|
def event(self, **kw):
|
||||||
|
"""
|
||||||
|
End-point to receive Mandrill event
|
||||||
|
Configuration in Mandrill app > Settings > Webhooks
|
||||||
|
(https://mandrillapp.com/settings/webhooks)
|
||||||
|
Add a webhook, selecting this type of events:
|
||||||
|
- Message Is Sent
|
||||||
|
- Message Is Bounced
|
||||||
|
- Message Is Opened
|
||||||
|
- Message Is Marked As Spam
|
||||||
|
- Message Is Rejected
|
||||||
|
- Message Is Delayed
|
||||||
|
- Message Is Soft-Bounced
|
||||||
|
- Message Is Clicked
|
||||||
|
- Message Recipient Unsubscribes
|
||||||
|
and setting this Post to URL:
|
||||||
|
https://your_odoodomain.com/mandrill/event
|
||||||
|
"""
|
||||||
|
if not self._mandrill_validation(**kw):
|
||||||
|
_logger.info('Validation error, ignoring this request')
|
||||||
|
return 'NO_AUTH'
|
||||||
|
events = []
|
||||||
|
try:
|
||||||
|
events = json.loads(kw.get('mandrill_events', '[]'))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if not events:
|
||||||
|
return 'NO_EVENTS'
|
||||||
|
res = []
|
||||||
|
for event in events:
|
||||||
|
res.append(self._event_process(event))
|
||||||
|
msg = 'ALL_EVENTS_FAILED'
|
||||||
|
if all(res):
|
||||||
|
msg = 'OK'
|
||||||
|
elif any(res):
|
||||||
|
msg = 'SOME_EVENTS_FAILED'
|
||||||
|
return msg
|
|
@ -0,0 +1,7 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# License AGPL-3: Antiun Ingenieria S.L. - Antonio Espinosa
|
||||||
|
# See README.rst file on addon root folder for more details
|
||||||
|
|
||||||
|
from . import mail_mail
|
||||||
|
from . import mail_mandrill_message
|
||||||
|
from . import mail_mandrill_event
|
|
@ -0,0 +1,38 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# License AGPL-3: Antiun Ingenieria S.L. - Antonio Espinosa
|
||||||
|
# See README.rst file on addon root folder for more details
|
||||||
|
|
||||||
|
import json
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from openerp import models, api
|
||||||
|
|
||||||
|
|
||||||
|
class MailMail(models.Model):
|
||||||
|
_inherit = 'mail.mail'
|
||||||
|
|
||||||
|
def _mandrill_headers_add(self):
|
||||||
|
for mail in self.sudo():
|
||||||
|
headers = {}
|
||||||
|
if mail.headers:
|
||||||
|
try:
|
||||||
|
headers.update(eval(mail.headers))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
metadata = {
|
||||||
|
'odoo_db': getattr(threading.currentThread(), 'dbname', None),
|
||||||
|
'odoo_model': mail.model,
|
||||||
|
'odoo_id': mail.res_id,
|
||||||
|
}
|
||||||
|
headers['X-MC-Metadata'] = json.dumps(metadata)
|
||||||
|
mail.headers = repr(headers)
|
||||||
|
return True
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def send(self, auto_commit=False, raise_exception=False):
|
||||||
|
self._mandrill_headers_add()
|
||||||
|
super(MailMail, self).send(
|
||||||
|
auto_commit=auto_commit,
|
||||||
|
raise_exception=raise_exception)
|
||||||
|
return True
|
|
@ -0,0 +1,167 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# License AGPL-3: Antiun Ingenieria S.L. - Antonio Espinosa
|
||||||
|
# See README.rst file on addon root folder for more details
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from openerp import models, fields, api
|
||||||
|
|
||||||
|
|
||||||
|
class MailMandrillEvent(models.Model):
|
||||||
|
_name = 'mail.mandrill.event'
|
||||||
|
_order = 'timestamp desc'
|
||||||
|
_rec_name = 'event_type'
|
||||||
|
|
||||||
|
timestamp = fields.Integer(string='Mandrill UTC timestamp', readonly=True)
|
||||||
|
time = fields.Datetime(string='Mandrill time', readonly=True)
|
||||||
|
date = fields.Date(string='Mandrill date', readonly=True)
|
||||||
|
event_type = fields.Selection(string='Event type', selection=[
|
||||||
|
('send', 'Sent'),
|
||||||
|
('deferral', 'Deferral'),
|
||||||
|
('hard_bounce', 'Hard bounce'),
|
||||||
|
('soft_bounce', 'Soft bounce'),
|
||||||
|
('open', 'Opened'),
|
||||||
|
('click', 'Clicked'),
|
||||||
|
('spam', 'Spam'),
|
||||||
|
('unsub', 'Unsubscribed'),
|
||||||
|
('reject', 'Rejected'),
|
||||||
|
], readonly=True)
|
||||||
|
url = fields.Char(string='Clicked URL', readonly=True)
|
||||||
|
ip = fields.Char(string='User IP', readonly=True)
|
||||||
|
user_agent = fields.Char(string='User agent', readonly=True)
|
||||||
|
mobile = fields.Boolean(string='Is mobile?', readonly=True)
|
||||||
|
os_family = fields.Char(string='Operating system family', readonly=True)
|
||||||
|
ua_family = fields.Char(string='User agent family', readonly=True)
|
||||||
|
ua_type = fields.Char(string='User agent type', readonly=True)
|
||||||
|
user_country_id = fields.Many2one(string='User country', readonly=True,
|
||||||
|
comodel_name='res.country')
|
||||||
|
message_id = fields.Many2one(string='Message', readonly=True,
|
||||||
|
comodel_name='mail.mandrill.message')
|
||||||
|
|
||||||
|
def _country_search(self, country_code, state_name):
|
||||||
|
country = False
|
||||||
|
if country_code:
|
||||||
|
country = self.env['res.country'].search([
|
||||||
|
('code', 'ilike', country_code),
|
||||||
|
])
|
||||||
|
if not country and state_name:
|
||||||
|
state = self.env['res.country.state'].search([
|
||||||
|
('name', 'ilike', state_name),
|
||||||
|
])
|
||||||
|
if state:
|
||||||
|
country = state.country_id
|
||||||
|
|
||||||
|
if country:
|
||||||
|
return country.id
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _process_bounce(self, message, event, event_type):
|
||||||
|
msg = event.get('msg')
|
||||||
|
bounce_type = msg.get('bounce_description', False) if msg else False
|
||||||
|
bounce_description = msg.get('diag', False) if msg else False
|
||||||
|
message.write({
|
||||||
|
'state': 'bounced',
|
||||||
|
'bounce_type': bounce_type,
|
||||||
|
'bounce_description': bounce_description,
|
||||||
|
})
|
||||||
|
ts = event.get('ts', 0)
|
||||||
|
time = datetime.datetime.fromtimestamp(ts)
|
||||||
|
return {
|
||||||
|
'message_id': message.id,
|
||||||
|
'event_type': event_type,
|
||||||
|
'timestamp': ts,
|
||||||
|
'time': time.strftime('%Y-%m-%d %H:%M:%S') if ts else False,
|
||||||
|
'date': time.strftime('%Y-%m-%d') if ts else False,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _process_status(self, message, event, event_type, state):
|
||||||
|
message.write({
|
||||||
|
'state': state,
|
||||||
|
})
|
||||||
|
ts = event.get('ts', 0)
|
||||||
|
time = datetime.datetime.fromtimestamp(ts)
|
||||||
|
return {
|
||||||
|
'message_id': message.id,
|
||||||
|
'event_type': event_type,
|
||||||
|
'timestamp': ts,
|
||||||
|
'time': time.strftime('%Y-%m-%d %H:%M:%S') if ts else False,
|
||||||
|
'date': time.strftime('%Y-%m-%d') if ts else False,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _process_action(self, message, event, event_type, state):
|
||||||
|
message.write({
|
||||||
|
'state': state,
|
||||||
|
})
|
||||||
|
ts = event.get('ts', 0)
|
||||||
|
url = event.get('url', False)
|
||||||
|
ip = event.get('ip', False)
|
||||||
|
user_agent = event.get('user_agent', False)
|
||||||
|
os_family = False
|
||||||
|
ua_family = False
|
||||||
|
ua_type = False
|
||||||
|
mobile = False
|
||||||
|
country_code = False
|
||||||
|
state = False
|
||||||
|
location = event.get('location')
|
||||||
|
if location:
|
||||||
|
country_code = location.get('country_short', False)
|
||||||
|
state = location.get('region', False)
|
||||||
|
ua_parsed = event.get('user_agent_parsed')
|
||||||
|
if ua_parsed:
|
||||||
|
os_family = ua_parsed.get('os_family', False)
|
||||||
|
ua_family = ua_parsed.get('ua_family', False)
|
||||||
|
ua_type = ua_parsed.get('type', False)
|
||||||
|
mobile = ua_parsed.get('mobile', False)
|
||||||
|
country_id = self._country_search(country_code, state)
|
||||||
|
time = datetime.datetime.fromtimestamp(ts)
|
||||||
|
return {
|
||||||
|
'message_id': message.id,
|
||||||
|
'event_type': event_type,
|
||||||
|
'timestamp': ts,
|
||||||
|
'time': time.strftime('%Y-%m-%d %H:%M:%S') if ts else False,
|
||||||
|
'date': time.strftime('%Y-%m-%d') if ts else False,
|
||||||
|
'user_country_id': country_id,
|
||||||
|
'ip': ip,
|
||||||
|
'url': url,
|
||||||
|
'mobile': mobile,
|
||||||
|
'user_agent': user_agent,
|
||||||
|
'os_family': os_family,
|
||||||
|
'ua_family': ua_family,
|
||||||
|
'ua_type': ua_type,
|
||||||
|
}
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def process_send(self, message, event):
|
||||||
|
return self._process_status(message, event, 'send', 'sent')
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def process_deferral(self, message, event):
|
||||||
|
return self._process_status(message, event, 'deferral', 'deferred')
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def process_hard_bounce(self, message, event):
|
||||||
|
return self._process_bounce(message, event, 'hard_bounce')
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def process_soft_bounce(self, message, event):
|
||||||
|
return self._process_bounce(message, event, 'soft_bounce')
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def process_open(self, message, event):
|
||||||
|
return self._process_action(message, event, 'open', 'opened')
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def process_click(self, message, event):
|
||||||
|
return self._process_action(message, event, 'click', 'opened')
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def process_spam(self, message, event):
|
||||||
|
return self._process_status(message, event, 'spam', 'spam')
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def process_unsub(self, message, event):
|
||||||
|
return self._process_status(message, event, 'unsub', 'unsub')
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def process_reject(self, message, event):
|
||||||
|
return self._process_status(message, event, 'reject', 'rejected')
|
|
@ -0,0 +1,102 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# License AGPL-3: Antiun Ingenieria S.L. - Antonio Espinosa
|
||||||
|
# See README.rst file on addon root folder for more details
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from openerp import models, fields, api
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class MailMandrillMessage(models.Model):
|
||||||
|
_name = 'mail.mandrill.message'
|
||||||
|
_order = 'timestamp desc'
|
||||||
|
_rec_name = 'name'
|
||||||
|
|
||||||
|
name = fields.Char(string='Subject', readonly=True)
|
||||||
|
mandrill_id = fields.Char(string='Mandrill message ID', required=True,
|
||||||
|
readonly=True)
|
||||||
|
timestamp = fields.Integer(string='Mandrill UTC timestamp', readonly=True)
|
||||||
|
time = fields.Datetime(string='Mandrill time', readonly=True)
|
||||||
|
date = fields.Date(string='Mandrill date', readonly=True)
|
||||||
|
recipient = fields.Char(string='Recipient email', readonly=True)
|
||||||
|
sender = fields.Char(string='Sender email', readonly=True)
|
||||||
|
state = fields.Selection([
|
||||||
|
('deferred', 'Deferred'),
|
||||||
|
('sent', 'Sent'),
|
||||||
|
('opened', 'Opened'),
|
||||||
|
('rejected', 'Rejected'),
|
||||||
|
('spam', 'Spam'),
|
||||||
|
('unsub', 'Unsubscribed'),
|
||||||
|
('bounced', 'Bounced'),
|
||||||
|
('soft-bounced', 'Soft bounced'),
|
||||||
|
], string='State', index=True, readonly=True,
|
||||||
|
help=" * The 'Sent' status indicates that message was succesfully "
|
||||||
|
"delivered to recipient Mail Exchange (MX) server.\n"
|
||||||
|
" * The 'Opened' status indicates that message was opened or "
|
||||||
|
"clicked by recipient.\n"
|
||||||
|
" * The 'Rejected' status indicates that recipient email "
|
||||||
|
"address is blacklisted by Mandrill. It is recomended to "
|
||||||
|
"delete this email address.\n"
|
||||||
|
" * The 'Spam' status indicates that Mandrill consider this "
|
||||||
|
"message as spam.\n"
|
||||||
|
" * The 'Unsubscribed' status indicates that recipient has "
|
||||||
|
"requested to be unsubscribed from this message.\n"
|
||||||
|
" * The 'Bounced' status indicates that message was bounced "
|
||||||
|
"by recipient Mail Exchange (MX) server.\n"
|
||||||
|
" * The 'Soft bounced' status indicates that message was soft "
|
||||||
|
"bounced by recipient Mail Exchange (MX) server.\n")
|
||||||
|
bounce_type = fields.Char(string='Bounce type', readonly=True)
|
||||||
|
bounce_description = fields.Char(string='Bounce description',
|
||||||
|
readonly=True)
|
||||||
|
tags = fields.Char(string='Tags', readonly=True)
|
||||||
|
metadata = fields.Text(string='Metadata', readonly=True)
|
||||||
|
event_ids = fields.One2many(
|
||||||
|
string='Mandrill events',
|
||||||
|
comodel_name='mail.mandrill.event', inverse_name='message_id')
|
||||||
|
|
||||||
|
def _message_prepare(self, message_id, event_type, event):
|
||||||
|
msg = event.get('msg')
|
||||||
|
ts = msg.get('ts', 0)
|
||||||
|
time = datetime.datetime.fromtimestamp(ts)
|
||||||
|
tags = msg.get('tags', [])
|
||||||
|
metadata = msg.get('metadata', {})
|
||||||
|
metatext = json.dumps(metadata, indent=4) if metadata else False
|
||||||
|
return {
|
||||||
|
'mandrill_id': message_id,
|
||||||
|
'timestamp': ts,
|
||||||
|
'time': time.strftime('%Y-%m-%d %H:%M:%S') if ts else False,
|
||||||
|
'date': time.strftime('%Y-%m-%d') if ts else False,
|
||||||
|
'recipient': msg.get('email', False),
|
||||||
|
'sender': msg.get('sender', False),
|
||||||
|
'name': msg.get('subject', False),
|
||||||
|
'tags': ', '.join(tags) if tags else False,
|
||||||
|
'metadata': metatext,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _event_prepare(self, message, event_type, event):
|
||||||
|
m_event = self.env['mail.mandrill.event']
|
||||||
|
method = getattr(m_event, 'process_' + event_type, None)
|
||||||
|
if method and hasattr(method, '__call__'):
|
||||||
|
return method(message, event)
|
||||||
|
else:
|
||||||
|
_logger.info('Unknown event type: %s' % event_type)
|
||||||
|
return False
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def process(self, message_id, event_type, event):
|
||||||
|
if not (message_id and event_type and event):
|
||||||
|
return False
|
||||||
|
msg = event.get('msg')
|
||||||
|
message = self.search([('mandrill_id', '=', message_id)])
|
||||||
|
if msg and not message:
|
||||||
|
data = self._message_prepare(message_id, event_type, event)
|
||||||
|
message = self.create(data) if data else False
|
||||||
|
if message:
|
||||||
|
m_event = self.env['mail.mandrill.event']
|
||||||
|
data = self._event_prepare(message, event_type, event)
|
||||||
|
return m_event.create(data) if data else False
|
||||||
|
return False
|
|
@ -0,0 +1,5 @@
|
||||||
|
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
|
||||||
|
"access_mail_mandrill_message_group_user","mail_mandrill_message group_user","model_mail_mandrill_message","base.group_user",1,0,0,0
|
||||||
|
"access_mail_mandrill_event_group_user","mail_mandrill_event group_user","model_mail_mandrill_event","base.group_user",1,0,0,0
|
||||||
|
"access_mail_mandrill_message_group_system","mail_mandrill_message group_system","model_mail_mandrill_message","base.group_system",1,1,1,1
|
||||||
|
"access_mail_mandrill_event_group_system","mail_mandrill_event group_system","model_mail_mandrill_event","base.group_system",1,1,1,1
|
|
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
|
@ -0,0 +1,5 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# License AGPL-3: Antiun Ingenieria S.L. - Antonio Espinosa
|
||||||
|
# See README.rst file on addon root folder for more details
|
||||||
|
|
||||||
|
from . import test_mail_mandrill
|
|
@ -0,0 +1,520 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# License AGPL-3: Antiun Ingenieria S.L. - Antonio Espinosa
|
||||||
|
# See README.rst file on addon root folder for more details
|
||||||
|
|
||||||
|
import json
|
||||||
|
from openerp.tests.common import TransactionCase
|
||||||
|
from openerp.tools.safe_eval import safe_eval
|
||||||
|
|
||||||
|
|
||||||
|
class TestMailMandrill(TransactionCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestMailMandrill, self).setUp()
|
||||||
|
message_obj = self.env['mail.mandrill.message']
|
||||||
|
self.partner_01 = self.env.ref('base.res_partner_1')
|
||||||
|
self.partner_02 = self.env.ref('base.res_partner_2')
|
||||||
|
self.model = 'res.partner'
|
||||||
|
self.res_id = self.partner_02.id
|
||||||
|
self.mandrill_message_id = '0123456789abcdef0123456789abcdef'
|
||||||
|
self.event_deferral = {
|
||||||
|
'msg': {
|
||||||
|
'sender': 'username01@example.com',
|
||||||
|
'tags': [],
|
||||||
|
'smtp_events': [
|
||||||
|
{
|
||||||
|
'destination_ip': '123.123.123.123',
|
||||||
|
'diag': 'Event description',
|
||||||
|
'source_ip': '145.145.145.145',
|
||||||
|
'ts': 1455192896,
|
||||||
|
'type': 'deferred',
|
||||||
|
'size': 19513
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'ts': 1455008558,
|
||||||
|
'clicks': [],
|
||||||
|
'resends': [],
|
||||||
|
'state': 'deferred',
|
||||||
|
'_version': '1abcdefghijkABCDEFGHIJ',
|
||||||
|
'template': None,
|
||||||
|
'_id': self.mandrill_message_id,
|
||||||
|
'email': 'username02@example.com',
|
||||||
|
'metadata': {
|
||||||
|
'odoo_id': self.res_id,
|
||||||
|
'odoo_db': 'test',
|
||||||
|
'odoo_model': self.model,
|
||||||
|
},
|
||||||
|
'opens': [],
|
||||||
|
'subject': 'My favorite subject'
|
||||||
|
},
|
||||||
|
'diag': '454 4.7.1 <username02@example.com>: Relay access denied',
|
||||||
|
'_id': self.mandrill_message_id,
|
||||||
|
'event': 'deferral',
|
||||||
|
'ts': 1455201028,
|
||||||
|
}
|
||||||
|
self.event_send = {
|
||||||
|
'msg': {
|
||||||
|
'_id': self.mandrill_message_id,
|
||||||
|
'subaccount': None,
|
||||||
|
'tags': [],
|
||||||
|
'smtp_events': [],
|
||||||
|
'ts': 1455201157,
|
||||||
|
'email': 'username02@example.com',
|
||||||
|
'metadata': {
|
||||||
|
'odoo_id': self.res_id,
|
||||||
|
'odoo_db': 'test',
|
||||||
|
'odoo_model': self.model,
|
||||||
|
},
|
||||||
|
'state': 'sent',
|
||||||
|
'sender': 'username01@example.com',
|
||||||
|
'template': None,
|
||||||
|
'reject': None,
|
||||||
|
'resends': [],
|
||||||
|
'clicks': [],
|
||||||
|
'opens': [],
|
||||||
|
'subject': 'My favorite subject',
|
||||||
|
},
|
||||||
|
'_id': self.mandrill_message_id,
|
||||||
|
'event': 'send',
|
||||||
|
'ts': 1455201159,
|
||||||
|
}
|
||||||
|
self.event_hard_bounce = {
|
||||||
|
'msg': {
|
||||||
|
'bounce_description': 'bad_mailbox',
|
||||||
|
'sender': 'username01@example.com',
|
||||||
|
'tags': [],
|
||||||
|
'diag': 'smtp;550 5.4.1 [username02@example.com]: '
|
||||||
|
'Recipient address rejected: Access denied',
|
||||||
|
'smtp_events': [],
|
||||||
|
'ts': 1455194565,
|
||||||
|
'template': None,
|
||||||
|
'_version': 'abcdefghi123456ABCDEFG',
|
||||||
|
'metadata': {
|
||||||
|
'odoo_id': self.res_id,
|
||||||
|
'odoo_db': 'test',
|
||||||
|
'odoo_model': self.model,
|
||||||
|
},
|
||||||
|
'resends': [],
|
||||||
|
'state': 'bounced',
|
||||||
|
'bgtools_code': 10,
|
||||||
|
'_id': self.mandrill_message_id,
|
||||||
|
'email': 'username02@example.com',
|
||||||
|
'subject': 'My favorite subject',
|
||||||
|
},
|
||||||
|
'_id': self.mandrill_message_id,
|
||||||
|
'event': 'hard_bounce',
|
||||||
|
'ts': 1455195340
|
||||||
|
}
|
||||||
|
self.event_soft_bounce = {
|
||||||
|
'msg': {
|
||||||
|
'bounce_description': 'general',
|
||||||
|
'sender': 'username01@example.com',
|
||||||
|
'tags': [],
|
||||||
|
'diag': 'X-Notes; Error transferring to FQDN.EXAMPLE.COM\n ; '
|
||||||
|
'SMTP Protocol Returned a Permanent Error 550 5.7.1 '
|
||||||
|
'Unable to relay\n\n--==ABCDEFGHIJK12345678ABCDEFGH',
|
||||||
|
'smtp_events': [],
|
||||||
|
'ts': 1455194562,
|
||||||
|
'template': None,
|
||||||
|
'_version': 'abcdefghi123456ABCDEFG',
|
||||||
|
'metadata': {
|
||||||
|
'odoo_id': self.res_id,
|
||||||
|
'odoo_db': 'test',
|
||||||
|
'odoo_model': self.model,
|
||||||
|
},
|
||||||
|
'resends': [],
|
||||||
|
'state': 'soft-bounced',
|
||||||
|
'bgtools_code': 40,
|
||||||
|
'_id': self.mandrill_message_id,
|
||||||
|
'email': 'username02@example.com',
|
||||||
|
'subject': 'My favorite subject',
|
||||||
|
},
|
||||||
|
'_id': self.mandrill_message_id,
|
||||||
|
'event': 'soft_bounce',
|
||||||
|
'ts': 1455195622
|
||||||
|
}
|
||||||
|
self.event_open = {
|
||||||
|
'ip': '111.111.111.111',
|
||||||
|
'ts': 1455189075,
|
||||||
|
'location': {
|
||||||
|
'country_short': 'PT',
|
||||||
|
'city': 'Porto',
|
||||||
|
'country': 'Portugal',
|
||||||
|
'region': 'Porto',
|
||||||
|
'longitude': -8.61098957062,
|
||||||
|
'postal_code': '-',
|
||||||
|
'latitude': 41.1496086121,
|
||||||
|
'timezone': '+01:00',
|
||||||
|
},
|
||||||
|
'msg': {
|
||||||
|
'sender': 'username01@example.com',
|
||||||
|
'tags': [],
|
||||||
|
'smtp_events': [
|
||||||
|
{
|
||||||
|
'destination_ip': '222.222.222.222',
|
||||||
|
'diag': '250 2.0.0 ABCDEFGHIJK123456ABCDE mail '
|
||||||
|
'accepted for delivery',
|
||||||
|
'source_ip': '111.1.1.1',
|
||||||
|
'ts': 1455185877,
|
||||||
|
'type': 'sent',
|
||||||
|
'size': 30276,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'ts': 1455185876,
|
||||||
|
'clicks': [],
|
||||||
|
'metadata': {
|
||||||
|
'odoo_id': self.res_id,
|
||||||
|
'odoo_db': 'test',
|
||||||
|
'odoo_model': self.model,
|
||||||
|
},
|
||||||
|
'resends': [],
|
||||||
|
'state': 'sent',
|
||||||
|
'_version': 'abcdefghi123456ABCDEFG',
|
||||||
|
'template': None,
|
||||||
|
'_id': self.mandrill_message_id,
|
||||||
|
'email': 'username02@example.com',
|
||||||
|
'opens': [
|
||||||
|
{
|
||||||
|
'ip': '111.111.111.111',
|
||||||
|
'ua': 'Windows/Windows 7/Outlook 2010/Outlook 2010',
|
||||||
|
'ts': 1455186247,
|
||||||
|
'location':
|
||||||
|
'Porto, PT'
|
||||||
|
}, {
|
||||||
|
'ip': '111.111.111.111',
|
||||||
|
'ua': 'Windows/Windows 7/Outlook 2010/Outlook 2010',
|
||||||
|
'ts': 1455189075,
|
||||||
|
'location': 'Porto, PT'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'subject': 'My favorite subject',
|
||||||
|
},
|
||||||
|
'_id': self.mandrill_message_id,
|
||||||
|
'user_agent_parsed': {
|
||||||
|
'ua_name': 'Outlook 2010',
|
||||||
|
'mobile': False,
|
||||||
|
'ua_company_url': 'http://www.microsoft.com/',
|
||||||
|
'os_icon': 'http://cdn.mandrill.com/img/email-client-icons/'
|
||||||
|
'windows-7.png',
|
||||||
|
'os_company': 'Microsoft Corporation.',
|
||||||
|
'ua_version': None,
|
||||||
|
'os_name': 'Windows 7',
|
||||||
|
'ua_family': 'Outlook 2010',
|
||||||
|
'os_url': 'http://en.wikipedia.org/wiki/Windows_7',
|
||||||
|
'os_company_url': 'http://www.microsoft.com/',
|
||||||
|
'ua_company': 'Microsoft Corporation.',
|
||||||
|
'os_family': 'Windows',
|
||||||
|
'type': 'Email Client',
|
||||||
|
'ua_icon': 'http://cdn.mandrill.com/img/email-client-icons/'
|
||||||
|
'outlook-2010.png',
|
||||||
|
'ua_url': 'http://en.wikipedia.org/wiki/Microsoft_Outlook',
|
||||||
|
},
|
||||||
|
'event': 'open',
|
||||||
|
'user_agent': 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; '
|
||||||
|
'Trident/7.0; SLCC2; .NET CLR 2.0.50727; '
|
||||||
|
'.NET CLR 3.5.30729; .NET CLR 3.0.30729; '
|
||||||
|
'Media Center PC 6.0; .NET4.0C; .NET4.0E; BRI/2; '
|
||||||
|
'Tablet PC 2.0; GWX:DOWNLOADED; '
|
||||||
|
'Microsoft Outlook 14.0.7166; ms-office; '
|
||||||
|
'MSOffice 14)',
|
||||||
|
}
|
||||||
|
self.event_click = {
|
||||||
|
'url': 'http://www.example.com/index.php',
|
||||||
|
'ip': '111.111.111.111',
|
||||||
|
'ts': 1455186402,
|
||||||
|
'user_agent': 'Mozilla/5.0 (Windows NT 6.1) '
|
||||||
|
'AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||||
|
'Chrome/48.0.2564.103 Safari/537.36',
|
||||||
|
'msg': {
|
||||||
|
'sender': 'username01@example.com',
|
||||||
|
'tags': [],
|
||||||
|
'smtp_events': [
|
||||||
|
{
|
||||||
|
'destination_ip': '222.222.222.222',
|
||||||
|
'diag': '250 2.0.0 Ok: queued as 12345678',
|
||||||
|
'source_ip': '111.1.1.1',
|
||||||
|
'ts': 1455186065,
|
||||||
|
'type': 'sent',
|
||||||
|
'size': 30994,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'ts': 1455186063,
|
||||||
|
'clicks': [
|
||||||
|
{
|
||||||
|
'url': 'http://www.example.com/index.php',
|
||||||
|
'ip': '111.111.111.111',
|
||||||
|
'ua': 'Windows/Windows 7/Chrome/Chrome 48.0.2564.103',
|
||||||
|
'ts': 1455186402,
|
||||||
|
'location': 'Madrid, ES',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'metadata': {
|
||||||
|
'odoo_id': self.res_id,
|
||||||
|
'odoo_db': 'test',
|
||||||
|
'odoo_model': self.model,
|
||||||
|
},
|
||||||
|
'resends': [],
|
||||||
|
'state': 'sent',
|
||||||
|
'_version': 'abcdefghi123456ABCDEFG',
|
||||||
|
'template': None,
|
||||||
|
'_id': self.mandrill_message_id,
|
||||||
|
'email': 'username02@example.com',
|
||||||
|
'opens': [
|
||||||
|
{
|
||||||
|
'ip': '111.111.111.111',
|
||||||
|
'ua': 'Windows/Windows 7/Chrome/Chrome 48.0.2564.103',
|
||||||
|
'ts': 1455186402,
|
||||||
|
'location': 'Madrid, ES',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'subject': 'My favorite subject',
|
||||||
|
},
|
||||||
|
'_id': self.mandrill_message_id,
|
||||||
|
'user_agent_parsed': {
|
||||||
|
'ua_name': 'Chrome 48.0.2564.103',
|
||||||
|
'mobile': False,
|
||||||
|
'ua_company_url': 'http://www.google.com/',
|
||||||
|
'os_icon': 'http://cdn.mandrill.com/img/email-client-icons/'
|
||||||
|
'windows-7.png',
|
||||||
|
'os_company': 'Microsoft Corporation.',
|
||||||
|
'ua_version': '48.0.2564.103',
|
||||||
|
'os_name': 'Windows 7',
|
||||||
|
'ua_family': 'Chrome',
|
||||||
|
'os_url': 'http://en.wikipedia.org/wiki/Windows_7',
|
||||||
|
'os_company_url': 'http://www.microsoft.com/',
|
||||||
|
'ua_company': 'Google Inc.',
|
||||||
|
'os_family': 'Windows',
|
||||||
|
'type': 'Browser',
|
||||||
|
'ua_icon': 'http://cdn.mandrill.com/img/email-client-icons/'
|
||||||
|
'chrome.png',
|
||||||
|
'ua_url': 'http://www.google.com/chrome',
|
||||||
|
},
|
||||||
|
'event': 'click',
|
||||||
|
'location': {
|
||||||
|
'country_short': 'ES',
|
||||||
|
'city': 'Madrid',
|
||||||
|
'country': 'Spain',
|
||||||
|
'region': 'Madrid',
|
||||||
|
'longitude': -3.70255994797,
|
||||||
|
'postal_code': '-',
|
||||||
|
'latitude': 40.4165000916,
|
||||||
|
'timezone': '+02:00',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
self.event_spam = {
|
||||||
|
'msg': {
|
||||||
|
'sender': 'username01@example.com',
|
||||||
|
'tags': [],
|
||||||
|
'smtp_events': [],
|
||||||
|
'ts': 1455186007,
|
||||||
|
'clicks': [],
|
||||||
|
'metadata': {
|
||||||
|
'odoo_id': self.res_id,
|
||||||
|
'odoo_db': 'test',
|
||||||
|
'odoo_model': self.model,
|
||||||
|
},
|
||||||
|
'resends': [],
|
||||||
|
'state': 'spam',
|
||||||
|
'_version': 'abcdefghi123456ABCDEFG',
|
||||||
|
'template': None,
|
||||||
|
'_id': self.mandrill_message_id,
|
||||||
|
'email': 'username02@example.com',
|
||||||
|
'opens': [],
|
||||||
|
'subject': 'My favorite subject',
|
||||||
|
},
|
||||||
|
'_id': self.mandrill_message_id,
|
||||||
|
'event': 'spam',
|
||||||
|
'ts': 1455186366
|
||||||
|
}
|
||||||
|
self.event_reject = {
|
||||||
|
'msg': {
|
||||||
|
'_id': self.mandrill_message_id,
|
||||||
|
'subaccount': None,
|
||||||
|
'tags': [],
|
||||||
|
'smtp_events': [],
|
||||||
|
'ts': 1455194291,
|
||||||
|
'email': 'username02@example.com',
|
||||||
|
'metadata': {
|
||||||
|
'odoo_id': self.res_id,
|
||||||
|
'odoo_db': 'test',
|
||||||
|
'odoo_model': self.model,
|
||||||
|
},
|
||||||
|
'state': 'rejected',
|
||||||
|
'sender': 'username01@example.com',
|
||||||
|
'template': None,
|
||||||
|
'reject': None,
|
||||||
|
'resends': [],
|
||||||
|
'clicks': [],
|
||||||
|
'opens': [],
|
||||||
|
'subject': 'My favorite subject',
|
||||||
|
},
|
||||||
|
'_id': self.mandrill_message_id,
|
||||||
|
'event': 'reject',
|
||||||
|
'ts': 1455194291,
|
||||||
|
}
|
||||||
|
self.event_unsub = {
|
||||||
|
'msg': {
|
||||||
|
'_id': self.mandrill_message_id,
|
||||||
|
'subaccount': None,
|
||||||
|
'tags': [],
|
||||||
|
'smtp_events': [],
|
||||||
|
'ts': 1455194291,
|
||||||
|
'email': 'username02@example.com',
|
||||||
|
'metadata': {
|
||||||
|
'odoo_id': self.res_id,
|
||||||
|
'odoo_db': 'test',
|
||||||
|
'odoo_model': self.model,
|
||||||
|
},
|
||||||
|
'state': 'unsub',
|
||||||
|
'sender': 'username01@example.com',
|
||||||
|
'template': None,
|
||||||
|
'reject': None,
|
||||||
|
'resends': [],
|
||||||
|
'clicks': [],
|
||||||
|
'opens': [],
|
||||||
|
'subject': 'My favorite subject',
|
||||||
|
},
|
||||||
|
'_id': self.mandrill_message_id,
|
||||||
|
'event': 'unsub',
|
||||||
|
'ts': 1455194291,
|
||||||
|
}
|
||||||
|
self.message = message_obj.create(
|
||||||
|
message_obj._message_prepare(
|
||||||
|
self.mandrill_message_id, 'deferral', self.event_deferral))
|
||||||
|
|
||||||
|
# Test Unit: mail_mail.py
|
||||||
|
def test_mandrill_headers_add(self):
|
||||||
|
mail_obj = self.env['mail.mail']
|
||||||
|
message = self.env['mail.message'].create({
|
||||||
|
'author_id': self.partner_01.id,
|
||||||
|
'subject': 'Test subject',
|
||||||
|
'body': 'Test body',
|
||||||
|
'partner_ids': [(4, self.partner_02.id)],
|
||||||
|
'model': self.model,
|
||||||
|
'res_id': self.res_id,
|
||||||
|
})
|
||||||
|
mail = mail_obj.create({
|
||||||
|
'mail_message_id': message.id,
|
||||||
|
})
|
||||||
|
mail._mandrill_headers_add()
|
||||||
|
headers = safe_eval(mail.headers)
|
||||||
|
self.assertIn('X-MC-Metadata', headers)
|
||||||
|
metadata = json.loads(headers.get('X-MC-Metadata', '[]'))
|
||||||
|
self.assertIn('odoo_db', metadata)
|
||||||
|
self.assertIn('odoo_model', metadata)
|
||||||
|
self.assertIn('odoo_id', metadata)
|
||||||
|
self.assertEqual(metadata['odoo_model'], self.model)
|
||||||
|
self.assertEqual(metadata['odoo_id'], self.res_id)
|
||||||
|
|
||||||
|
# Test Unit: mail_mandrill_message.py
|
||||||
|
def test_message_prepare(self):
|
||||||
|
data = self.env['mail.mandrill.message']._message_prepare(
|
||||||
|
self.mandrill_message_id, 'deferral', self.event_deferral)
|
||||||
|
self.assertEqual(data['mandrill_id'], self.mandrill_message_id)
|
||||||
|
self.assertEqual(data['timestamp'],
|
||||||
|
self.event_deferral['msg']['ts'])
|
||||||
|
self.assertEqual(data['recipient'],
|
||||||
|
self.event_deferral['msg']['email'])
|
||||||
|
self.assertEqual(data['sender'],
|
||||||
|
self.event_deferral['msg']['sender'])
|
||||||
|
self.assertEqual(data['name'],
|
||||||
|
self.event_deferral['msg']['subject'])
|
||||||
|
|
||||||
|
def test_event_prepare(self):
|
||||||
|
data = self.env['mail.mandrill.message']._event_prepare(
|
||||||
|
self.message, 'deferral', self.event_deferral)
|
||||||
|
self.assertEqual(self.message.state, 'deferred')
|
||||||
|
self.assertEqual(data['message_id'], self.message.id)
|
||||||
|
self.assertEqual(data['event_type'], 'deferral')
|
||||||
|
self.assertEqual(data['timestamp'], self.event_deferral['ts'])
|
||||||
|
|
||||||
|
def test_process(self):
|
||||||
|
event = self.env['mail.mandrill.message'].process(
|
||||||
|
self.mandrill_message_id, 'deferral', self.event_deferral)
|
||||||
|
self.assertEqual(event.message_id.mandrill_id,
|
||||||
|
self.mandrill_message_id)
|
||||||
|
self.assertEqual(event.message_id.state, 'deferred')
|
||||||
|
self.assertEqual(event.event_type, 'deferral')
|
||||||
|
self.assertEqual(event.timestamp, self.event_deferral['ts'])
|
||||||
|
|
||||||
|
# Test Unit: mail_mandrill_event.py
|
||||||
|
def test_process_send(self):
|
||||||
|
data = self.env['mail.mandrill.event'].process_send(
|
||||||
|
self.message, self.event_send)
|
||||||
|
self.assertEqual(self.message.state, 'sent')
|
||||||
|
self.assertEqual(data['message_id'], self.message.id)
|
||||||
|
self.assertEqual(data['event_type'], 'send')
|
||||||
|
self.assertEqual(data['timestamp'], self.event_send['ts'])
|
||||||
|
|
||||||
|
def test_process_deferral(self):
|
||||||
|
data = self.env['mail.mandrill.event'].process_deferral(
|
||||||
|
self.message, self.event_deferral)
|
||||||
|
self.assertEqual(self.message.state, 'deferred')
|
||||||
|
self.assertEqual(data['message_id'], self.message.id)
|
||||||
|
self.assertEqual(data['event_type'], 'deferral')
|
||||||
|
self.assertEqual(data['timestamp'], self.event_deferral['ts'])
|
||||||
|
|
||||||
|
def test_process_hard_bounce(self):
|
||||||
|
data = self.env['mail.mandrill.event'].process_hard_bounce(
|
||||||
|
self.message, self.event_hard_bounce)
|
||||||
|
self.assertEqual(self.message.state, 'bounced')
|
||||||
|
self.assertEqual(self.message.bounce_type,
|
||||||
|
self.event_hard_bounce['msg']['bounce_description'])
|
||||||
|
self.assertEqual(self.message.bounce_description,
|
||||||
|
self.event_hard_bounce['msg']['diag'])
|
||||||
|
self.assertEqual(data['message_id'], self.message.id)
|
||||||
|
self.assertEqual(data['event_type'], 'hard_bounce')
|
||||||
|
self.assertEqual(data['timestamp'], self.event_hard_bounce['ts'])
|
||||||
|
|
||||||
|
def test_process_soft_bounce(self):
|
||||||
|
data = self.env['mail.mandrill.event'].process_soft_bounce(
|
||||||
|
self.message, self.event_soft_bounce)
|
||||||
|
self.assertEqual(self.message.state, 'bounced')
|
||||||
|
self.assertEqual(self.message.bounce_type,
|
||||||
|
self.event_soft_bounce['msg']['bounce_description'])
|
||||||
|
self.assertEqual(self.message.bounce_description,
|
||||||
|
self.event_soft_bounce['msg']['diag'])
|
||||||
|
self.assertEqual(data['message_id'], self.message.id)
|
||||||
|
self.assertEqual(data['event_type'], 'soft_bounce')
|
||||||
|
self.assertEqual(data['timestamp'], self.event_soft_bounce['ts'])
|
||||||
|
|
||||||
|
def test_process_open(self):
|
||||||
|
data = self.env['mail.mandrill.event'].process_open(
|
||||||
|
self.message, self.event_open)
|
||||||
|
self.assertEqual(self.message.state, 'opened')
|
||||||
|
self.assertEqual(data['message_id'], self.message.id)
|
||||||
|
self.assertEqual(data['event_type'], 'open')
|
||||||
|
self.assertEqual(data['timestamp'], self.event_open['ts'])
|
||||||
|
self.assertEqual(data['ip'], self.event_open['ip'])
|
||||||
|
|
||||||
|
def test_process_click(self):
|
||||||
|
data = self.env['mail.mandrill.event'].process_click(
|
||||||
|
self.message, self.event_open)
|
||||||
|
self.assertEqual(self.message.state, 'opened')
|
||||||
|
self.assertEqual(data['message_id'], self.message.id)
|
||||||
|
self.assertEqual(data['event_type'], 'click')
|
||||||
|
self.assertEqual(data['timestamp'], self.event_open['ts'])
|
||||||
|
self.assertEqual(data['ip'], self.event_open['ip'])
|
||||||
|
|
||||||
|
def test_process_spam(self):
|
||||||
|
data = self.env['mail.mandrill.event'].process_spam(
|
||||||
|
self.message, self.event_spam)
|
||||||
|
self.assertEqual(self.message.state, 'spam')
|
||||||
|
self.assertEqual(data['message_id'], self.message.id)
|
||||||
|
self.assertEqual(data['event_type'], 'spam')
|
||||||
|
self.assertEqual(data['timestamp'], self.event_spam['ts'])
|
||||||
|
|
||||||
|
def test_process_reject(self):
|
||||||
|
data = self.env['mail.mandrill.event'].process_reject(
|
||||||
|
self.message, self.event_reject)
|
||||||
|
self.assertEqual(self.message.state, 'rejected')
|
||||||
|
self.assertEqual(data['message_id'], self.message.id)
|
||||||
|
self.assertEqual(data['event_type'], 'reject')
|
||||||
|
self.assertEqual(data['timestamp'], self.event_reject['ts'])
|
||||||
|
|
||||||
|
def test_process_unsub(self):
|
||||||
|
data = self.env['mail.mandrill.event'].process_unsub(
|
||||||
|
self.message, self.event_unsub)
|
||||||
|
self.assertEqual(self.message.state, 'unsub')
|
||||||
|
self.assertEqual(data['message_id'], self.message.id)
|
||||||
|
self.assertEqual(data['event_type'], 'unsub')
|
||||||
|
self.assertEqual(data['timestamp'], self.event_unsub['ts'])
|
|
@ -0,0 +1,109 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="view_mail_mandrill_event_form">
|
||||||
|
<field name="name">mail.mandrill.event.form</field>
|
||||||
|
<field name="model">mail.mandrill.event</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Mandrill email event">
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="message_id"/>
|
||||||
|
<field name="event_type"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="timestamp"/>
|
||||||
|
<field name="time"/>
|
||||||
|
<field name="date"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group attrs="{'invisible': [('event_type', 'not in', ('open', 'click'))]}">
|
||||||
|
<field name="url"/>
|
||||||
|
</group>
|
||||||
|
<group attrs="{'invisible': [('event_type', 'not in', ('open', 'click'))]}">
|
||||||
|
<group>
|
||||||
|
<field name="mobile"/>
|
||||||
|
<field name="ip"/>
|
||||||
|
<field name="user_country_id"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="user_agent"/>
|
||||||
|
<field name="ua_family"/>
|
||||||
|
<field name="ua_type"/>
|
||||||
|
<field name="os_family"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="view_mail_mandrill_event_tree">
|
||||||
|
<field name="name">mail.mandrill.event.tree</field>
|
||||||
|
<field name="model">mail.mandrill.event</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Mandrill email events" colors="grey:event_type in ('deferral');black:event_type in ('send');red:event_type in ('hard_bounce', 'soft_bounce', 'spam', 'reject');blue:event_type in ('unsub', 'click', 'open')">
|
||||||
|
<field name="time"/>
|
||||||
|
<field name="message_id" string="Message subject"/>
|
||||||
|
<field name="event_type"/>
|
||||||
|
<field name="date" invisible="1"/>
|
||||||
|
<field name="ip"/>
|
||||||
|
<field name="url"/>
|
||||||
|
<field name="user_country_id" string="Country"/>
|
||||||
|
<field name="os_family" string="OS"/>
|
||||||
|
<field name="ua_family" string="User agent"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="view_mail_mandrill_event_search">
|
||||||
|
<field name="name">mail.mandrill.event.search</field>
|
||||||
|
<field name="model">mail.mandrill.event</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Mandrill event Search">
|
||||||
|
<field name="message_id" string="Message subject"
|
||||||
|
filter_domain="[('message_id','ilike',self)]"/>
|
||||||
|
<field name="message_id" string="Subject"/>
|
||||||
|
<field name="time" string="Time"/>
|
||||||
|
<field name="date" string="Date"/>
|
||||||
|
<field name="ip" string="IP"/>
|
||||||
|
<field name="url" string="URL"/>
|
||||||
|
<filter name="send" string="Send" domain="[('event_type', '=', 'send')]"/>
|
||||||
|
<filter name="click" string="Click" domain="[('event_type', '=', 'click')]"/>
|
||||||
|
<filter name="open" string="Open" domain="[('event_type', '=', 'open')]"/>
|
||||||
|
<filter name="unsub" string="Unsubscribe" domain="[('event_type', '=', 'unsub')]"/>
|
||||||
|
<filter name="bounce" string="Bounce"
|
||||||
|
domain="[('event_type', 'in', ('hard_bounce', 'soft_bounce'))]"/>
|
||||||
|
<filter name="exception" string="Failed"
|
||||||
|
domain="[('event_type', 'in', ('reject', 'spam'))]"/>
|
||||||
|
<separator/>
|
||||||
|
<group expand="0" string="Group By">
|
||||||
|
<filter string="Type" domain="[]" context="{'group_by': 'event_type'}"/>
|
||||||
|
<filter string="Message subject" domain="[]" context="{'group_by': 'message_id'}"/>
|
||||||
|
<filter string="OS" domain="[('os_family', '!=', False)]" context="{'group_by': 'os_family'}"/>
|
||||||
|
<filter string="User agent" domain="[('ua_family', '!=', False)]" context="{'group_by': 'ua_family'}"/>
|
||||||
|
<filter string="User agent type" domain="[('ua_type', '!=', False)]" context="{'group_by': 'ua_type'}"/>
|
||||||
|
<filter string="Country" domain="[('user_country_id', '!=', False)]" context="{'group_by': 'user_country_id'}"/>
|
||||||
|
<filter string="Month" domain="[]" context="{'group_by': 'date'}"/>
|
||||||
|
</group>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_view_mail_mandrill_event" model="ir.actions.act_window">
|
||||||
|
<field name="name">Mandrill events</field>
|
||||||
|
<field name="res_model">mail.mandrill.event</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
<field name="search_view_id" ref="view_mail_mandrill_event_search"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Add menu entry in Settings/Email -->
|
||||||
|
<menuitem name="Mandrill events" id="menu_mail_mandrill_event"
|
||||||
|
parent="base.menu_email"
|
||||||
|
action="action_view_mail_mandrill_event"/>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
|
@ -0,0 +1,114 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="view_mail_mandrill_message_form">
|
||||||
|
<field name="name">mail.mandrill.message.form</field>
|
||||||
|
<field name="model">mail.mandrill.message</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Mandrill email message">
|
||||||
|
<header>
|
||||||
|
<field name="state" widget="statusbar"/>
|
||||||
|
</header>
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="tags"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="mandrill_id"/>
|
||||||
|
<field name="recipient"/>
|
||||||
|
<field name="sender"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="timestamp"/>
|
||||||
|
<field name="time"/>
|
||||||
|
<field name="date"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group attrs="{'invisible': [('bounce_type', '=', False)]}">
|
||||||
|
<field name="bounce_type"/>
|
||||||
|
<field name="bounce_description"/>
|
||||||
|
</group>
|
||||||
|
<label for="metadata"/>
|
||||||
|
<div>
|
||||||
|
<field name="metadata"/>
|
||||||
|
</div>
|
||||||
|
<label for="event_ids"/>
|
||||||
|
<div>
|
||||||
|
<field name="event_ids">
|
||||||
|
<tree string="Mandrill email events" colors="grey:event_type in ('deferral');black:event_type in ('send');red:event_type in ('hard_bounce', 'soft_bounce', 'spam', 'reject');blue:event_type in ('unsub', 'click', 'open')">
|
||||||
|
<field name="time"/>
|
||||||
|
<field name="event_type"/>
|
||||||
|
<field name="ip"/>
|
||||||
|
<field name="url"/>
|
||||||
|
<field name="user_country_id" string="Country"/>
|
||||||
|
<field name="os_family" string="OS"/>
|
||||||
|
<field name="ua_family" string="User agent"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</div>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="view_mail_mandrill_message_tree">
|
||||||
|
<field name="name">mail.mandrill.message.tree</field>
|
||||||
|
<field name="model">mail.mandrill.message</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Mandrill emails" colors="grey:state in (False, 'deferred');black:state in ('sent');green:state in ('opened');red:state in ('rejected', 'spam', 'bounced', 'soft-bounced');blue:state in ('unsub')">
|
||||||
|
<field name="state" invisible="1"/>
|
||||||
|
<field name="time"/>
|
||||||
|
<field name="date" invisible="1"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="sender" string="Sender"/>
|
||||||
|
<field name="recipient" string="Recipient"/>
|
||||||
|
<field name="tags" string="Tags"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="view_mail_mandrill_message_search">
|
||||||
|
<field name="name">mail.mandrill.message.search</field>
|
||||||
|
<field name="model">mail.mandrill.message</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Mandrill email Search">
|
||||||
|
<field name="sender" string="Email"
|
||||||
|
filter_domain="['|', ('sender','ilike',self), ('recipient','ilike',self)]"/>
|
||||||
|
<field name="name" string="Subject"/>
|
||||||
|
<field name="time" string="Time"/>
|
||||||
|
<field name="date" string="Date"/>
|
||||||
|
<filter name="deferred" string="Deferred" domain="[('state', '=', 'deferred')]"/>
|
||||||
|
<filter name="sent" string="Sent" domain="[('state', 'in', ('sent', 'opened'))]"/>
|
||||||
|
<filter name="unsub" string="Unsubscribed" domain="[('state', '=', 'unsub')]"/>
|
||||||
|
<filter name="exception" string="Failed"
|
||||||
|
domain="[('state', 'in', ('rejected', 'spam', 'bounced', 'soft-bounced'))]"/>
|
||||||
|
<separator/>
|
||||||
|
<group expand="0" string="Group By">
|
||||||
|
<filter string="State" domain="[]" context="{'group_by': 'state'}"/>
|
||||||
|
<filter string="Subject" domain="[]" context="{'group_by': 'name'}"/>
|
||||||
|
<filter string="Sender" domain="[]" context="{'group_by': 'sender'}"/>
|
||||||
|
<filter string="Month" domain="[]" context="{'group_by': 'date'}"/>
|
||||||
|
</group>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<record id="action_view_mail_mandrill_message" model="ir.actions.act_window">
|
||||||
|
<field name="name">Mandrill emails</field>
|
||||||
|
<field name="res_model">mail.mandrill.message</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
<field name="search_view_id" ref="view_mail_mandrill_message_search"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Add menu entry in Settings/Email -->
|
||||||
|
<menuitem name="Mandrill emails" id="menu_mail_mandrill_message"
|
||||||
|
parent="base.menu_email"
|
||||||
|
action="action_view_mail_mandrill_message"/>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
Loading…
Reference in New Issue