mirror of https://github.com/OCA/social.git
[MIG] mail_tracking
* Improve tests * Show trackings even if partner removed * Disable CSRF protection to webhooks controllerspull/269/head
parent
6585c28029
commit
2a16997c1c
|
@ -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
|
||||
|
||||
|
|
|
@ -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, "
|
||||
|
|
|
@ -36,7 +36,7 @@ class MailTrackingController(http.Controller):
|
|||
}
|
||||
|
||||
@http.route('/mail/tracking/all/<string:db>',
|
||||
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/<string:db>/<string:event_type>',
|
||||
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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,63 +1,103 @@
|
|||
/* © 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
|
||||
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
|
||||
|
|
|
@ -40,21 +40,33 @@
|
|||
</t>
|
||||
</t>
|
||||
|
||||
<t t-extend="mail.thread.message">
|
||||
<t t-jquery="span[t-attf-class='oe_partner_follower']" t-operation="append">
|
||||
<t t-set='tracking' t-value='widget.partner_trackings[partner[0]]'/>
|
||||
<t t-if="tracking[1]">
|
||||
<span class="mail_tracking oe_mail_action_tracking"
|
||||
t-att-data-tracking="tracking[1]"
|
||||
t-attf-title="Status: #{tracking[0]}">
|
||||
<t t-call="mail.tracking.status"/>
|
||||
</span>
|
||||
</t>
|
||||
<t t-if="!tracking[1]">
|
||||
<span class="mail_tracking" t-attf-title="Status: #{tracking[0]}">
|
||||
<t t-call="mail.tracking.status"/>
|
||||
</span>
|
||||
</t>
|
||||
<t t-extend="mail.ChatThread.Message">
|
||||
<t t-jquery="p[class='o_mail_info']" t-operation="after">
|
||||
<p class="o_mail_tracking">
|
||||
<strong>To:</strong>
|
||||
<t t-set="first_tracking" t-value="true"/>
|
||||
<t t-foreach="message.partner_trackings" t-as="tracking">
|
||||
<t t-if="!first_tracking">
|
||||
-
|
||||
</t>
|
||||
<t t-if="tracking[3]">
|
||||
<a class="o_mail_action_tracking_partner"
|
||||
t-att-data-partner="tracking[3]"
|
||||
t-attf-href="#model=res.partner&id=#{tracking[3]}">
|
||||
<t t-esc="tracking[2]"/>
|
||||
</a>
|
||||
</t>
|
||||
<t t-if="!tracking[3]">
|
||||
<span><t t-esc="tracking[2]"/></span>
|
||||
</t>
|
||||
<span class="mail_tracking o_mail_action_tracking_status"
|
||||
t-att-data-tracking="tracking[1]"
|
||||
t-attf-title="Status: #{tracking[0]}">
|
||||
<t t-call="mail.tracking.status"/>
|
||||
</span>
|
||||
<t t-set="first_tracking" t-value="false"/>
|
||||
</t>
|
||||
</p>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
|
|
|
@ -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': '<p>This is a test message</p>',
|
||||
})
|
||||
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()
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- © 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -->
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
|
||||
<template id="assets_backend"
|
||||
name="mail_tracking assets"
|
||||
|
@ -15,5 +14,4 @@
|
|||
</xpath>
|
||||
</template>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- © 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -->
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_tracking_email_form">
|
||||
<field name="name">mail.tracking.email.form</field>
|
||||
|
@ -118,5 +117,4 @@
|
|||
parent="base.menu_email"
|
||||
action="action_view_mail_tracking_email"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- © 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -->
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_tracking_event_form">
|
||||
<field name="name">mail.tracking.event.form</field>
|
||||
|
@ -121,5 +120,4 @@
|
|||
action="action_view_mail_tracking_event"/>
|
||||
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- © 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -->
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="view_partner_form">
|
||||
<field name="name">Partner Form with tracking emails</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<div class="oe_right oe_button_box" position="inside">
|
||||
<div name="button_box" position="inside">
|
||||
<button name="%(mail_tracking.action_view_mail_tracking_email)d"
|
||||
context="{'search_default_recipient': email,
|
||||
'default_recipient': email}"
|
||||
|
@ -29,5 +28,4 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
|
Loading…
Reference in New Issue