Antonio Espinosa
9 years ago
16 changed files with 1343 additions and 0 deletions
-
110mail_mandrill/README.rst
-
6mail_mandrill/__init__.py
-
25mail_mandrill/__openerp__.py
-
5mail_mandrill/controllers/__init__.py
-
130mail_mandrill/controllers/main.py
-
0mail_mandrill/i18n/.gitkeep
-
7mail_mandrill/models/__init__.py
-
38mail_mandrill/models/mail_mail.py
-
167mail_mandrill/models/mail_mandrill_event.py
-
102mail_mandrill/models/mail_mandrill_message.py
-
5mail_mandrill/security/ir.model.access.csv
-
BINmail_mandrill/static/description/icon.png
-
5mail_mandrill/tests/__init__.py
-
520mail_mandrill/tests/test_mail_mandrill.py
-
109mail_mandrill/views/mail_mandrill_event_view.xml
-
114mail_mandrill/views/mail_mandrill_message_view.xml
@ -0,0 +1,110 @@ |
|||||
|
.. 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**>`_. |
||||
|
|
||||
|
|
||||
|
License |
||||
|
======= |
||||
|
|
||||
|
This program is free software: you can redistribute it and/or modify |
||||
|
it under the terms of the GNU Affero General Public License as published |
||||
|
by the Free Software Foundation, either version 3 of the License, or |
||||
|
(at your option) any later version. |
||||
|
|
||||
|
This program is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
GNU Affero General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU Affero General Public License |
||||
|
along with this program. If not, see <http://www.gnu.org/licenses/agpl-3.0-standalone.html>. |
||||
|
|
||||
|
|
||||
|
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 |
After Width: 128 | Height: 128 | 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> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue