diff --git a/mail_tracking/README.rst b/mail_tracking/README.rst index e2a9bd20..40ba269c 100644 --- a/mail_tracking/README.rst +++ b/mail_tracking/README.rst @@ -63,7 +63,7 @@ These are all available status icons: .. 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 + :target: https://runbot.odoo-community.org/runbot/205/9.0 If you want to see all tracking emails and events you can go to diff --git a/mail_tracking/__openerp__.py b/mail_tracking/__openerp__.py index fe23e6af..accf9ce0 100644 --- a/mail_tracking/__openerp__.py +++ b/mail_tracking/__openerp__.py @@ -5,7 +5,7 @@ { "name": "Email tracking", "summary": "Email tracking system for all mails sent", - "version": "8.0.2.0.1", + "version": "9.0.1.0.0", "category": "Social Network", "website": "http://www.tecnativa.com", "author": "Tecnativa, " diff --git a/mail_tracking/controllers/main.py b/mail_tracking/controllers/main.py index 91e6063f..da14949d 100644 --- a/mail_tracking/controllers/main.py +++ b/mail_tracking/controllers/main.py @@ -36,7 +36,7 @@ class MailTrackingController(http.Controller): } @http.route('/mail/tracking/all/', - type='http', auth='none') + type='http', auth='none', csrf=False) def mail_tracking_all(self, db, **kw): env = _env_get(db) if not env: @@ -49,7 +49,7 @@ class MailTrackingController(http.Controller): return response @http.route('/mail/tracking/event//', - type='http', auth='none') + type='http', auth='none', csrf=False) def mail_tracking_event(self, db, event_type, **kw): env = _env_get(db) if not env: diff --git a/mail_tracking/models/mail_mail.py b/mail_tracking/models/mail_mail.py index 9f5b004b..b33a1c5e 100644 --- a/mail_tracking/models/mail_mail.py +++ b/mail_tracking/models/mail_mail.py @@ -12,8 +12,7 @@ from openerp import models, api, fields class MailMail(models.Model): _inherit = 'mail.mail' - @api.model - def _tracking_email_prepare(self, mail, partner, email): + def _tracking_email_prepare(self, partner, email): ts = time.time() dt = datetime.utcfromtimestamp(ts) email_to_list = email.get('email_to', []) @@ -22,22 +21,16 @@ class MailMail(models.Model): 'name': email.get('subject', False), 'timestamp': '%.6f' % ts, 'time': fields.Datetime.to_string(dt), - 'mail_id': mail.id if mail else False, - 'mail_message_id': mail.mail_message_id.id if mail else False, + 'mail_id': self.id, + 'mail_message_id': self.mail_message_id.id, 'partner_id': partner.id if partner else False, 'recipient': email_to, - 'sender': mail.email_from, + 'sender': self.email_from, } - @api.model - def send_get_email_dict(self, mail, partner=None): - email = super(MailMail, self).send_get_email_dict( - mail, partner=partner) - m_tracking = self.env['mail.tracking.email'] - tracking_email = False - if mail: - vals = self._tracking_email_prepare(mail, partner, email) - tracking_email = m_tracking.sudo().create(vals) - if tracking_email: - email = tracking_email.tracking_img_add(email) - return email + @api.multi + def send_get_email_dict(self, partner=None): + email = super(MailMail, self).send_get_email_dict(partner=partner) + vals = self._tracking_email_prepare(partner, email) + tracking_email = self.env['mail.tracking.email'].sudo().create(vals) + return tracking_email.tracking_img_add(email) diff --git a/mail_tracking/models/mail_message.py b/mail_tracking/models/mail_message.py index 682ef619..fc6562bf 100644 --- a/mail_tracking/models/mail_message.py +++ b/mail_tracking/models/mail_message.py @@ -3,8 +3,6 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from openerp import models, api -import logging -_logger = logging.getLogger(__name__) class MailMessage(models.Model): @@ -33,22 +31,49 @@ class MailMessage(models.Model): status = tracking_status_map.get(tracking_email_status, 'unknown') return status + @api.multi + def tracking_status(self): + res = {} + for message in self: + partner_trackings = [] + partners_already = self.env['res.partner'] + partners = self.env['res.partner'] + trackings = self.env['mail.tracking.email'].search([ + ('mail_message_id', '=', message.id), + ]) + # Search all trackings for this message + for tracking in trackings: + status = self._partner_tracking_status_get(tracking) + recipient = ( + tracking.partner_id.display_name or tracking.recipient) + partner_trackings.append(( + status, tracking.id, recipient, tracking.partner_id.id)) + if tracking.partner_id: + partners_already |= tracking.partner_id + # Search all recipients for this message + if message.partner_ids: + partners |= message.partner_ids + if message.needaction_partner_ids: + partners |= message.needaction_partner_ids + # Remove recipients already included + partners -= partners_already + for partner in partners: + # If there is partners not included, then status is 'unknown' + partner_trackings.append(( + 'unknown', False, partner.display_name, partner.id)) + res[message.id] = partner_trackings + return res + @api.model def _message_read_dict_postprocess(self, messages, message_tree): res = super(MailMessage, self)._message_read_dict_postprocess( messages, message_tree) + mail_message_ids = {m.get('id') for m in messages if m.get('id')} + mail_messages = self.browse(mail_message_ids) + partner_trackings = mail_messages.tracking_status() for message_dict in messages: mail_message_id = message_dict.get('id', False) if mail_message_id: - partner_trackings = {} - for partner in message_dict.get('partner_ids', []): - partner_id = partner[0] - tracking_email = self.env['mail.tracking.email'].search([ - ('mail_message_id', '=', mail_message_id), - ('partner_id', '=', partner_id), - ], limit=1) - status = self._partner_tracking_status_get(tracking_email) - partner_trackings[str(partner_id)] = ( - status, tracking_email.id) - message_dict['partner_trackings'] = partner_trackings + message_dict['partner_trackings'] = \ + partner_trackings[mail_message_id] return res diff --git a/mail_tracking/models/mail_tracking_email.py b/mail_tracking/models/mail_tracking_email.py index d498ad66..c72129af 100644 --- a/mail_tracking/models/mail_tracking_email.py +++ b/mail_tracking/models/mail_tracking_email.py @@ -167,7 +167,7 @@ class MailTrackingEmail(models.Model): @api.depends('name', 'recipient') def _compute_display_name(self): for email in self: - parts = [email.name] + parts = [email.name or ''] if email.recipient: parts.append(email.recipient) email.display_name = ' - '.join(parts) @@ -225,13 +225,14 @@ class MailTrackingEmail(models.Model): def _message_partners_check(self, message, message_id): mail_message = self.mail_message_id - partners = mail_message.notified_partner_ids | mail_message.partner_ids + partners = ( + mail_message.needaction_partner_ids | mail_message.partner_ids) if (self.partner_id and self.partner_id not in partners): # If mail_message haven't tracking partner, then - # add it in order to see his trackking status in chatter + # add it in order to see his tracking status in chatter if mail_message.subtype_id: mail_message.sudo().write({ - 'notified_partner_ids': [(4, self.partner_id.id)], + 'needaction_partner_ids': [(4, self.partner_id.id)], }) else: mail_message.sudo().write({ diff --git a/mail_tracking/static/src/css/mail_tracking.css b/mail_tracking/static/src/css/mail_tracking.css index 63d1ac49..bca73d3a 100644 --- a/mail_tracking/static/src/css/mail_tracking.css +++ b/mail_tracking/static/src/css/mail_tracking.css @@ -11,3 +11,11 @@ .mail_tracking span.mail_tracking_opened { color: #a34a8b; } + +.o_mail_thread .o_thread_message .o_thread_message_core .o_mail_info { + margin: 0; +} + +.o_mail_thread .o_thread_message .o_thread_message_core .o_mail_tracking { + margin: 0 0 2px 0; +} diff --git a/mail_tracking/static/src/js/mail_tracking.js b/mail_tracking/static/src/js/mail_tracking.js index 505ce5d8..473b9f7a 100644 --- a/mail_tracking/static/src/js/mail_tracking.js +++ b/mail_tracking/static/src/js/mail_tracking.js @@ -1,63 +1,103 @@ /* © 2016 Antonio Espinosa - License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). */ -(function ($, window, document) { - 'use strict'; +odoo.define('mail_tracking.partner_tracking', function(require){ +"use strict"; - openerp.mail_tracking = function (instance) { - var _t = instance.web._t, - _lt = instance.web._lt; - var QWeb = instance.web.qweb; - var mail_orig = instance.mail; - var mail_inherit = function() { - instance.mail.MessageCommon.include({ - init: function (parent, datasets, options) { - this._super(parent, datasets, options); - this.partner_trackings = datasets.partner_trackings || []; - } - }); - instance.mail.ThreadMessage.include({ - bind_events: function () { - this._super(); - this.$('.oe_mail_action_tracking').on('click', this.on_tracking_status_clicked); - }, - on_tracking_status_clicked: function (event) { - event.preventDefault(); - var tracking_email_id = $(event.delegateTarget).data('tracking'); - var state = { - 'model': 'mail.tracking.email', - 'id': tracking_email_id, - 'title': _t("Message tracking"), - }; - instance.webclient.action_manager.do_push_state(state); - console.log('tracking_email_id = ' + tracking_email_id); - var action = { - type:'ir.actions.act_window', - view_type: 'form', - view_mode: 'form', - res_model: 'mail.tracking.email', - views: [[false, 'form']], - target: 'new', - res_id: tracking_email_id, - }; - this.do_action(action); - } - }); +var $ = require('$'); +var core = require('web.core'); +var session = require('web.session'); +var Model = require('web.Model'); +var ActionManager = require('web.ActionManager'); +var chat_manager = require('mail.chat_manager'); +var ChatThread = require('mail.ChatThread'); +var Chatter = require('mail.Chatter'); + +var _t = core._t; +var MessageModel = new Model('mail.message', session.context); + +// chat_manager is a simple dictionary, not an OdooClass +chat_manager._make_message_super = chat_manager.make_message; +chat_manager.make_message = function(data) { + var msg = this._make_message_super(data); + msg.partner_trackings = data.partner_trackings || []; + return msg; +}; + +ChatThread.include({ + on_tracking_partner_click: function (event) { + var partner_id = $(event.currentTarget).data('partner'); + var state = { + 'model': 'res.partner', + 'id': partner_id, + 'title': _t("Tracking partner"), }; + event.preventDefault(); + this.action_manager.do_push_state(state); + var action = { + type:'ir.actions.act_window', + view_type: 'form', + view_mode: 'form', + res_model: 'res.partner', + views: [[false, 'form']], + target: 'current', + res_id: partner_id, + }; + this.do_action(action); + }, + on_tracking_status_click: function (event) { + var tracking_email_id = $(event.currentTarget).data('tracking'); + var state = { + 'model': 'mail.tracking.email', + 'id': tracking_email_id, + 'title': _t("Message tracking"), + }; + event.preventDefault(); + this.action_manager.do_push_state(state); + var action = { + type:'ir.actions.act_window', + view_type: 'form', + view_mode: 'form', + res_model: 'mail.tracking.email', + views: [[false, 'form']], + target: 'new', + res_id: tracking_email_id, + }; + this.do_action(action); + }, + bind_events: function () { + this.$el.on('click', '.o_mail_action_tracking_partner', + this.on_tracking_partner_click); + this.$el.on('click', '.o_mail_action_tracking_status', + this.on_tracking_status_click); + }, + init: function (parent, options) { + this._super.apply(this, arguments); + this.action_manager = this.findAncestor(function(ancestor){ + return ancestor instanceof ActionManager; + }); + }, + start: function () { + this._super(); + this.bind_events(); + }, + render: function(messages, options) { + var self = this, render_super = this._super, + msgs = {}, + msg_ids = []; + // Update trackings (async) each time we re-render thread + _.each(messages, function (message) { + msgs[message.id] = message; + msg_ids.push(message.id); + }); + MessageModel.call('tracking_status', [msg_ids]).then(function (trackings) { + _.each(trackings, function (tracking, id) { + msgs[id].partner_trackings = tracking; + }); + render_super.apply(self, [messages, options]); + }); + }, - // Tricky way to guarantee that this module is loaded always - // after mail module. - // When --load=web,mail_tracking is specified in init script, then - // web and mail_tracking are the first modules to load in JS - if (instance.mail.MessageCommon === undefined) { - instance.mail = function(instance) { - instance.mail = mail_orig; - instance.mail(instance, instance.mail); - mail_inherit(); - }; - } else { - mail_inherit(); - } - }; +}); -}(window.jQuery, window, document)); +}); // odoo.define diff --git a/mail_tracking/static/src/xml/mail_tracking.xml b/mail_tracking/static/src/xml/mail_tracking.xml index 3d37216c..68b5d12b 100644 --- a/mail_tracking/static/src/xml/mail_tracking.xml +++ b/mail_tracking/static/src/xml/mail_tracking.xml @@ -40,21 +40,33 @@ - - - - - - - - - - - - - + + +

+ To: + + + + - + + + + + + + + + + + + + + +

diff --git a/mail_tracking/tests/test_mail_tracking.py b/mail_tracking/tests/test_mail_tracking.py index a6eacc60..5862db90 100644 --- a/mail_tracking/tests/test_mail_tracking.py +++ b/mail_tracking/tests/test_mail_tracking.py @@ -65,19 +65,20 @@ class TestMailTracking(TransactionCase): self.assertTrue(tracking_email) self.assertEqual(tracking_email.state, 'sent') # message_dict read by web interface - message_dict = self.env['mail.message'].message_read(message.id) - # First item is message content - self.assertTrue(len(message_dict) > 0) - message_dict = message_dict[0] + message_dict = message.message_read() + # First item in threads is message content + message_dict = message_dict['threads'][0][0] self.assertTrue(len(message_dict['partner_ids']) > 0) # First partner is recipient partner_id = message_dict['partner_ids'][0][0] self.assertEqual(partner_id, self.recipient.id) - status = message_dict['partner_trackings'][str(partner_id)] + status = message_dict['partner_trackings'][0] # Tracking status must be sent and # mail tracking must be the one search before self.assertEqual(status[0], 'sent') self.assertEqual(status[1], tracking_email.id) + self.assertEqual(status[2], self.recipient.display_name) + self.assertEqual(status[3], self.recipient.id) # And now open the email metadata = { 'ip': '127.0.0.1', @@ -88,11 +89,11 @@ class TestMailTracking(TransactionCase): tracking_email.event_create('open', metadata) self.assertEqual(tracking_email.state, 'opened') - def mail_send(self): + def mail_send(self, recipient): mail = self.env['mail.mail'].create({ 'subject': 'Test subject', 'email_from': 'from@domain.com', - 'email_to': 'to@domain.com', + 'email_to': recipient, 'body_html': '

This is a test message

', }) mail.send() @@ -106,7 +107,7 @@ class TestMailTracking(TransactionCase): controller = MailTrackingController() db = self.env.cr.dbname image = base64.decodestring(BLANK) - mail, tracking = self.mail_send() + mail, tracking = self.mail_send(self.recipient.email) self.assertEqual(mail.email_to, tracking.recipient) self.assertEqual(mail.email_from, tracking.sender) with mock.patch(mock_request) as mock_func: @@ -115,7 +116,7 @@ class TestMailTracking(TransactionCase): self.assertEqual(image, res.response[0]) def test_concurrent_open(self): - mail, tracking = self.mail_send() + mail, tracking = self.mail_send(self.recipient.email) ts = time.time() metadata = { 'ip': '127.0.0.1', @@ -146,7 +147,7 @@ class TestMailTracking(TransactionCase): self.assertEqual(len(opens), 2) def test_concurrent_click(self): - mail, tracking = self.mail_send() + mail, tracking = self.mail_send(self.recipient.email) ts = time.time() metadata = { 'ip': '127.0.0.1', @@ -188,11 +189,66 @@ class TestMailTracking(TransactionCase): def test_smtp_error(self): with mock.patch(mock_send_email) as mock_func: mock_func.side_effect = Warning('Test error') - mail, tracking = self.mail_send() + mail, tracking = self.mail_send(self.recipient.email) self.assertEqual('error', tracking.state) self.assertEqual('Warning', tracking.error_type) self.assertEqual('Test error', tracking.error_description) + def test_partner_email_change(self): + mail, tracking = self.mail_send(self.recipient.email) + tracking.event_create('open', {}) + orig_score = self.recipient.email_score + orig_email = self.recipient.email + self.recipient.email = orig_email + '2' + self.assertEqual(50.0, self.recipient.email_score) + self.recipient.email = orig_email + self.assertEqual(orig_score, self.recipient.email_score) + + def test_process_hard_bounce(self): + mail, tracking = self.mail_send(self.recipient.email) + tracking.event_create('hard_bounce', {}) + self.assertEqual('bounced', tracking.state) + + def test_process_soft_bounce(self): + mail, tracking = self.mail_send(self.recipient.email) + tracking.event_create('soft_bounce', {}) + self.assertEqual('soft-bounced', tracking.state) + + def test_process_delivered(self): + mail, tracking = self.mail_send(self.recipient.email) + tracking.event_create('delivered', {}) + self.assertEqual('delivered', tracking.state) + + def test_process_deferral(self): + mail, tracking = self.mail_send(self.recipient.email) + tracking.event_create('deferral', {}) + self.assertEqual('deferred', tracking.state) + + def test_process_spam(self): + mail, tracking = self.mail_send(self.recipient.email) + tracking.event_create('spam', {}) + self.assertEqual('spam', tracking.state) + + def test_process_unsub(self): + mail, tracking = self.mail_send(self.recipient.email) + tracking.event_create('unsub', {}) + self.assertEqual('unsub', tracking.state) + + def test_process_reject(self): + mail, tracking = self.mail_send(self.recipient.email) + tracking.event_create('reject', {}) + self.assertEqual('rejected', tracking.state) + + def test_process_open(self): + mail, tracking = self.mail_send(self.recipient.email) + tracking.event_create('open', {}) + self.assertEqual('opened', tracking.state) + + def test_process_click(self): + mail, tracking = self.mail_send(self.recipient.email) + tracking.event_create('click', {}) + self.assertEqual('opened', tracking.state) + def test_db(self): db = self.env.cr.dbname controller = MailTrackingController() diff --git a/mail_tracking/views/assets.xml b/mail_tracking/views/assets.xml index 6e737fbb..e4cac0d7 100644 --- a/mail_tracking/views/assets.xml +++ b/mail_tracking/views/assets.xml @@ -1,8 +1,7 @@ - - + - - + diff --git a/mail_tracking/views/mail_tracking_email_view.xml b/mail_tracking/views/mail_tracking_email_view.xml index f2b12af0..6ac50302 100644 --- a/mail_tracking/views/mail_tracking_email_view.xml +++ b/mail_tracking/views/mail_tracking_email_view.xml @@ -1,8 +1,7 @@ - - + mail.tracking.email.form @@ -118,5 +117,4 @@ parent="base.menu_email" action="action_view_mail_tracking_email"/> - - + diff --git a/mail_tracking/views/mail_tracking_event_view.xml b/mail_tracking/views/mail_tracking_event_view.xml index 9dfa8c66..67d49e7e 100644 --- a/mail_tracking/views/mail_tracking_event_view.xml +++ b/mail_tracking/views/mail_tracking_event_view.xml @@ -1,8 +1,7 @@ - - + mail.tracking.event.form @@ -121,5 +120,4 @@ action="action_view_mail_tracking_event"/> - - + diff --git a/mail_tracking/views/res_partner_view.xml b/mail_tracking/views/res_partner_view.xml index 4f227608..1df681aa 100644 --- a/mail_tracking/views/res_partner_view.xml +++ b/mail_tracking/views/res_partner_view.xml @@ -1,15 +1,14 @@ - - + Partner Form with tracking emails res.partner -
+