[MIG] mail_tracking: Migration to 13.0

This commit is contained in:
Alexandre Díaz 2019-11-18 11:34:02 +01:00 committed by Jasmin Solanki
parent 200e016ab8
commit fc7d2e9f3a
15 changed files with 124 additions and 104 deletions

View File

@ -7,14 +7,14 @@
{ {
"name": "Email tracking", "name": "Email tracking",
"summary": "Email tracking system for all mails sent", "summary": "Email tracking system for all mails sent",
"version": "12.0.2.0.1", "version": "13.0.1.0.0",
"category": "Social Network", "category": "Social Network",
"website": "http://github.com/OCA/social", "website": "http://github.com/OCA/social",
"author": ("Tecnativa, " "Odoo Community Association (OCA)"), "author": ("Tecnativa, " "Odoo Community Association (OCA)"),
"license": "AGPL-3", "license": "AGPL-3",
"application": False, "application": False,
"installable": True, "installable": True,
"depends": ["decimal_precision", "mail"], "depends": ["mail"],
"data": [ "data": [
"data/tracking_data.xml", "data/tracking_data.xml",
"security/mail_tracking_email_security.xml", "security/mail_tracking_email_security.xml",

View File

@ -5,20 +5,11 @@ import logging
from psycopg2.extensions import AsIs from psycopg2.extensions import AsIs
from odoo.tools import column_exists
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
def column_exists(cr, table, column):
cr.execute(
"""
SELECT column_name
FROM information_schema.columns
WHERE table_name = %s AND column_name = %s""",
(table, column),
)
return bool(cr.fetchall())
def column_add_with_value(cr, table, column, field_type, value): def column_add_with_value(cr, table, column, field_type, value):
if not column_exists(cr, table, column): if not column_exists(cr, table, column):
cr.execute( cr.execute(

View File

@ -1,11 +0,0 @@
# Copyright 2019 Alexandre Díaz
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from openupgradelib.openupgrade import migrate
@migrate()
def migrate(env, version):
cr = env.cr
cr.execute("UPDATE mail_tracking_email SET token = NULL")

View File

@ -12,11 +12,10 @@ class MailBouncedMixin(models.AbstractModel):
_name = "mail.bounced.mixin" _name = "mail.bounced.mixin"
_description = "Mail bounced mixin" _description = "Mail bounced mixin"
_primary_email = ["email"] _primary_email = "email"
email_bounced = fields.Boolean(index=True) email_bounced = fields.Boolean(index=True)
@api.multi
def email_bounced_set(self, tracking_emails, reason, event=None): def email_bounced_set(self, tracking_emails, reason, event=None):
"""Inherit this method to make any other actions to the model that """Inherit this method to make any other actions to the model that
inherit the mixin""" inherit the mixin"""
@ -28,7 +27,7 @@ class MailBouncedMixin(models.AbstractModel):
return partners.write({"email_bounced": True}) return partners.write({"email_bounced": True})
def write(self, vals): def write(self, vals):
[email_field] = self._primary_email email_field = self._primary_email
if email_field not in vals: if email_field not in vals:
return super().write(vals) return super().write(vals)
email = vals[email_field].lower() if vals[email_field] else False email = vals[email_field].lower() if vals[email_field] else False

View File

@ -132,8 +132,8 @@ class MailMessage(models.Model):
# Search all recipients for this message # Search all recipients for this message
if message.partner_ids: if message.partner_ids:
partners |= message.partner_ids partners |= message.partner_ids
if message.needaction_partner_ids: if message.notified_partner_ids:
partners |= message.needaction_partner_ids partners |= message.notified_partner_ids
# Remove recipients already included # Remove recipients already included
partners -= partners_already partners -= partners_already
tracking_unkown_values = { tracking_unkown_values = {
@ -179,7 +179,6 @@ class MailMessage(models.Model):
message_dict.update(tracking_statuses[mail_message_id]) message_dict.update(tracking_statuses[mail_message_id])
return res return res
@api.multi
def _prepare_dict_failed_message(self): def _prepare_dict_failed_message(self):
"""Preare values to be used by the chatter widget""" """Preare values to be used by the chatter widget"""
self.ensure_one() self.ensure_one()
@ -200,7 +199,6 @@ class MailMessage(models.Model):
"failed_recipients": failed_recipients, "failed_recipients": failed_recipients,
} }
@api.multi
def get_failed_messages(self): def get_failed_messages(self):
"""Returns the list of failed messages to be used by the """Returns the list of failed messages to be used by the
failed_messages widget""" failed_messages widget"""
@ -209,7 +207,6 @@ class MailMessage(models.Model):
for msg in self.sorted("date", reverse=True) for msg in self.sorted("date", reverse=True)
] ]
@api.multi
def set_need_action_done(self): def set_need_action_done(self):
"""Set message tracking action as done """Set message tracking action as done

View File

@ -36,7 +36,6 @@ class MailResendMessage(models.TransientModel):
rec["partner_ids"].extend(partner_ids) rec["partner_ids"].extend(partner_ids)
return rec return rec
@api.multi
def resend_mail_action(self): def resend_mail_action(self):
for wizard in self: for wizard in self:
to_send = wizard.partner_ids.filtered("resend").mapped("partner_id") to_send = wizard.partner_ids.filtered("resend").mapped("partner_id")

View File

@ -29,7 +29,6 @@ class MailThread(models.AbstractModel):
("mail_tracking_ids.state", "in", list(failed_states)), ("mail_tracking_ids.state", "in", list(failed_states)),
] ]
@api.multi
@api.returns("self", lambda value: value.id) @api.returns("self", lambda value: value.id)
def message_post(self, *args, **kwargs): def message_post(self, *args, **kwargs):
"""Adds CC recipient to the message. """Adds CC recipient to the message.
@ -43,13 +42,12 @@ class MailThread(models.AbstractModel):
new_message.sudo().write({"email_cc": email_cc}) new_message.sudo().write({"email_cc": email_cc})
return new_message return new_message
@api.multi def _message_get_suggested_recipients(self):
def message_get_suggested_recipients(self):
"""Adds email Cc recipients as suggested recipients. """Adds email Cc recipients as suggested recipients.
If the recipient has a res.partner, use it. If the recipient has a res.partner, use it.
""" """
res = super().message_get_suggested_recipients() res = super()._message_get_suggested_recipients()
ResPartnerObj = self.env["res.partner"] ResPartnerObj = self.env["res.partner"]
email_cc_formated_list = [] email_cc_formated_list = []
for record in self: for record in self:
@ -65,7 +63,7 @@ class MailThread(models.AbstractModel):
if not partner_id: if not partner_id:
record._message_add_suggested_recipient(res, email=cc, reason=_("Cc")) record._message_add_suggested_recipient(res, email=cc, reason=_("Cc"))
else: else:
partner = ResPartnerObj.browse(partner_id, self._prefetch) partner = ResPartnerObj.browse(partner_id)
record._message_add_suggested_recipient( record._message_add_suggested_recipient(
res, partner=partner, reason=_("Cc") res, partner=partner, reason=_("Cc")
) )

View File

@ -8,8 +8,7 @@ import urllib.parse
import uuid import uuid
from datetime import datetime from datetime import datetime
from odoo import models, api, fields, tools from odoo import api, fields, models, tools
import odoo.addons.decimal_precision as dp
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@ -36,8 +35,8 @@ class MailTrackingEmail(models.Model):
compute="_compute_tracking_display_name", compute="_compute_tracking_display_name",
) )
timestamp = fields.Float( timestamp = fields.Float(
string='UTC timestamp', readonly=True, string="UTC timestamp", readonly=True, digits="MailTracking Timestamp"
digits=dp.get_precision('MailTracking Timestamp')) )
time = fields.Datetime(string="Time", readonly=True, index=True) time = fields.Datetime(string="Time", readonly=True, index=True)
date = fields.Date( date = fields.Date(
string="Date", readonly=True, compute="_compute_date", store=True string="Date", readonly=True, compute="_compute_date", store=True
@ -125,7 +124,6 @@ class MailTrackingEmail(models.Model):
).write({"mail_tracking_needs_action": True}) ).write({"mail_tracking_needs_action": True})
return records return records
@api.multi
def write(self, vals): def write(self, vals):
super().write(vals) super().write(vals)
state = vals.get("state") state = vals.get("state")
@ -255,7 +253,6 @@ class MailTrackingEmail(models.Model):
% {"url": track_url, "tracking_email_id": self.id} % {"url": track_url, "tracking_email_id": self.id}
) )
@api.multi
def _partners_email_bounced_set(self, reason, event=None): def _partners_email_bounced_set(self, reason, event=None):
recipients = [] recipients = []
if event and event.recipient_address: if event and event.recipient_address:
@ -267,7 +264,6 @@ class MailTrackingEmail(models.Model):
[("email", "=ilike", recipient)] [("email", "=ilike", recipient)]
).email_bounced_set(self, reason, event=event) ).email_bounced_set(self, reason, event=event)
@api.multi
def smtp_error(self, mail_server, smtp_server, exception): def smtp_error(self, mail_server, smtp_server, exception):
values = {"state": "error"} values = {"state": "error"}
IrMailServer = self.env["ir.mail_server"] IrMailServer = self.env["ir.mail_server"]
@ -292,7 +288,6 @@ class MailTrackingEmail(models.Model):
self.sudo()._partners_email_bounced_set("error") self.sudo()._partners_email_bounced_set("error")
self.sudo().write(values) self.sudo().write(values)
@api.multi
def tracking_img_add(self, email): def tracking_img_add(self, email):
self.ensure_one() self.ensure_one()
tracking_url = self._get_mail_tracking_img() tracking_url = self._get_mail_tracking_img()
@ -311,20 +306,18 @@ class MailTrackingEmail(models.Model):
if not self.mail_message_id.exists(): # pragma: no cover if not self.mail_message_id.exists(): # pragma: no cover
return True return True
mail_message = self.mail_message_id mail_message = self.mail_message_id
partners = ( partners = mail_message.notified_partner_ids | mail_message.partner_ids
mail_message.needaction_partner_ids | mail_message.partner_ids) if self.partner_id and self.partner_id not in partners:
if (self.partner_id and self.partner_id not in partners):
# If mail_message haven't tracking partner, then # If mail_message haven't tracking partner, then
# add it in order to see his tracking status in chatter # add it in order to see his tracking status in chatter
if mail_message.subtype_id: if mail_message.subtype_id:
mail_message.sudo().write({ mail_message.sudo().write(
'needaction_partner_ids': [(4, self.partner_id.id)], {"notified_partner_ids": [(4, self.partner_id.id)]}
}) )
else: else:
mail_message.sudo().write({"partner_ids": [(4, self.partner_id.id)]}) mail_message.sudo().write({"partner_ids": [(4, self.partner_id.id)]})
return True return True
@api.multi
def _tracking_sent_prepare(self, mail_server, smtp_server, message, message_id): def _tracking_sent_prepare(self, mail_server, smtp_server, message, message_id):
self.ensure_one() self.ensure_one()
ts = time.time() ts = time.time()
@ -368,7 +361,6 @@ class MailTrackingEmail(models.Model):
concurrent_event_ids = m_event.search(domain) concurrent_event_ids = m_event.search(domain)
return concurrent_event_ids return concurrent_event_ids
@api.multi
def event_create(self, event_type, metadata): def event_create(self, event_type, metadata):
event_ids = self.env["mail.tracking.event"] event_ids = self.env["mail.tracking.event"]
for tracking_email in self: for tracking_email in self:

View File

@ -5,8 +5,7 @@ import re
import time import time
from datetime import datetime from datetime import datetime
from odoo import models, api, fields from odoo import api, fields, models
import odoo.addons.decimal_precision as dp
class MailTrackingEvent(models.Model): class MailTrackingEvent(models.Model):
@ -24,8 +23,8 @@ class MailTrackingEvent(models.Model):
index=True, index=True,
) )
timestamp = fields.Float( timestamp = fields.Float(
string='UTC timestamp', readonly=True, string="UTC timestamp", readonly=True, digits="MailTracking Timestamp"
digits=dp.get_precision('MailTracking Timestamp')) )
time = fields.Datetime(string="Time", readonly=True) time = fields.Datetime(string="Time", readonly=True)
date = fields.Date( date = fields.Date(
string="Date", readonly=True, compute="_compute_date", store=True string="Date", readonly=True, compute="_compute_date", store=True
@ -81,7 +80,6 @@ class MailTrackingEvent(models.Model):
else: else:
email.recipient_address = False email.recipient_address = False
@api.multi
@api.depends("time") @api.depends("time")
def _compute_date(self): def _compute_date(self):
for email in self: for email in self:

View File

@ -11,23 +11,20 @@ class ResPartner(models.Model):
# tracking_emails_count and email_score are non-store fields in order # tracking_emails_count and email_score are non-store fields in order
# to improve performance # to improve performance
tracking_emails_count = fields.Integer( tracking_emails_count = fields.Integer(
compute="_compute_tracking_emails_count", readonly=True compute="_compute_email_score_and_count", readonly=True
) )
email_score = fields.Float(compute="_compute_email_score", readonly=True) email_score = fields.Float(compute="_compute_email_score_and_count", readonly=True)
@api.depends("email") @api.depends("email")
def _compute_email_score(self): def _compute_email_score_and_count(self):
for partner in self.filtered('email'): partners_mail = self.filtered("email")
partner.email_score = self.env['mail.tracking.email'].\ mail_tracking_obj = self.env["mail.tracking.email"]
email_score_from_email(partner.email) for partner in partners_mail:
partner.email_score = self.env[
@api.multi "mail.tracking.email"
@api.depends('email') ].email_score_from_email(partner.email)
def _compute_tracking_emails_count(self): partner.tracking_emails_count = mail_tracking_obj.search_count(
for partner in self: [("recipient_address", "=", partner.email.lower())]
count = 0 )
if partner.email: partners_no_mail = self - partners_mail
count = self.env["mail.tracking.email"].search_count( partners_no_mail.update({"email_score": 50.0, "tracking_emails_count": 0})
[("recipient_address", "=", partner.email.lower())]
)
partner.tracking_emails_count = count

View File

@ -169,9 +169,9 @@ odoo.define('mail_tracking.FailedMessageDiscuss', function (require) {
/** /**
* Render 'failed' mailbox menu entry in Discuss * Render 'failed' mailbox menu entry in Discuss
* - Initial render
* *
* @private * @override
* @returns {jQueryElementt}
*/ */
_renderSidebar: function () { _renderSidebar: function () {
var $sidebar = this._super.apply(this, arguments); var $sidebar = this._super.apply(this, arguments);
@ -184,6 +184,19 @@ odoo.define('mail_tracking.FailedMessageDiscuss', function (require) {
return $sidebar; return $sidebar;
}, },
/**
* Render 'failed' mailbox menu entry in Discuss
* - Update status render (not called if the mailbox is empty)
*
* @override
*/
_renderSidebarMailboxes: function () {
this._super.apply(this, arguments);
this.$('.o_mail_discuss_sidebar_mailboxes').append(
QWeb.render('mail_tracking.SidebarFailed',
this._sidebarQWebParams()));
},
/** /**
* Overrides to listen click on 'Set all as reviewed' button * Overrides to listen click on 'Set all as reviewed' button
* *

View File

@ -8,13 +8,13 @@
</t> </t>
<t t-name="mail_tracking.SidebarFailed"> <t t-name="mail_tracking.SidebarFailed">
<div t-attf-class="o_mail_discuss_title_main o_mail_discuss_item #{(activeThreadID === 'mailbox_failed') ? 'o_active': ''}" <div t-attf-class="o_mail_discuss_title_main o_mail_discuss_item #{(activeThreadID === 'mailbox_failed') ? 'o_active': ''}"
data-thread-id="mailbox_failed"> data-thread-id="mailbox_failed">
<span class="o_thread_name"><i class="fa fa-exclamation mr8"/>Failed</span> <span class="o_thread_name"><i class="fa fa-exclamation mr8"/>Failed</span>
<t t-set="counter" t-value="failedCounter"/> <t t-set="counter" t-value="failedCounter"/>
<t t-call="mail_tracking.SidebarFailedCounter"/> <t t-call="mail_tracking.SidebarFailedCounter"/>
</div> </div>
</t> </t>
<t t-extend="mail.widget.Thread.Empty"> <t t-extend="mail.widget.Thread.Empty">
<t t-jquery="t:last-child" t-operation="after"> <t t-jquery="t:last-child" t-operation="after">

View File

@ -77,17 +77,19 @@ class TestMailTracking(TransactionCase):
def test_message_post(self): def test_message_post(self):
# This message will generate a notification for recipient # This message will generate a notification for recipient
message = self.env['mail.message'].create({ message = self.env["mail.message"].create(
'subject': 'Message test', {
'author_id': self.sender.id, "subject": "Message test",
'email_from': self.sender.email, "author_id": self.sender.id,
'message_type': 'comment', "email_from": self.sender.email,
'model': 'res.partner', "message_type": "comment",
'res_id': self.recipient.id, "model": "res.partner",
'partner_ids': [(4, self.recipient.id)], "res_id": self.recipient.id,
'body': '<p>This is a test message</p>', "partner_ids": [(4, self.recipient.id)],
}) "body": "<p>This is a test message</p>",
message._notify(message, {}, force_send=True) }
)
message._moderate_accept()
# Search tracking created # Search tracking created
tracking_email = self.env["mail.tracking.email"].search( tracking_email = self.env["mail.tracking.email"].search(
[ [
@ -102,7 +104,7 @@ class TestMailTracking(TransactionCase):
message_dict = message.message_format()[0] message_dict = message.message_format()[0]
self.assertTrue(len(message_dict["partner_ids"]) > 0) self.assertTrue(len(message_dict["partner_ids"]) > 0)
# First partner is recipient # First partner is recipient
partner_id = message_dict["partner_ids"][0][0] partner_id = message_dict["partner_ids"][0]
self.assertEqual(partner_id, self.recipient.id) self.assertEqual(partner_id, self.recipient.id)
status = message_dict["partner_trackings"][0] status = message_dict["partner_trackings"][0]
# Tracking status must be sent and # Tracking status must be sent and
@ -137,7 +139,7 @@ class TestMailTracking(TransactionCase):
"body": "<p>This is a test message</p>", "body": "<p>This is a test message</p>",
} }
) )
message._notify(message, {}, force_send=True) message._moderate_accept()
# Search tracking created # Search tracking created
tracking_email = self.env["mail.tracking.email"].search( tracking_email = self.env["mail.tracking.email"].search(
[ [
@ -201,7 +203,7 @@ class TestMailTracking(TransactionCase):
"body": "<p>This is another test message</p>", "body": "<p>This is another test message</p>",
} }
) )
message._notify(message, {}, force_send=True) message._moderate_accept()
recipients = self.recipient._message_get_suggested_recipients() recipients = self.recipient._message_get_suggested_recipients()
self.assertEqual(len(recipients[self.recipient.id][0]), 3) self.assertEqual(len(recipients[self.recipient.id][0]), 3)
self._check_partner_trackings(message) self._check_partner_trackings(message)
@ -233,6 +235,45 @@ class TestMailTracking(TransactionCase):
values = tracking.mail_message_id.get_failed_messages()[0] values = tracking.mail_message_id.get_failed_messages()[0]
self.assertEqual(values["author"][0], -1) self.assertEqual(values["author"][0], -1)
def test_resend_failed_message(self):
# This message will generate a notification for recipient
message = self.env["mail.message"].create(
{
"subject": "Message test",
"author_id": self.sender.id,
"email_from": self.sender.email,
"message_type": "comment",
"model": "res.partner",
"res_id": self.recipient.id,
"partner_ids": [(4, self.recipient.id)],
"body": "<p>This is a test message</p>",
}
)
message._moderate_accept()
# Search tracking created
tracking_email = self.env["mail.tracking.email"].search(
[
("mail_message_id", "=", message.id),
("partner_id", "=", self.recipient.id),
]
)
# Force error state
tracking_email.state = "error"
# Create resend mail wizard
wizard = (
self.env["mail.resend.message"]
.sudo()
.with_context({"mail_message_to_resend": message.id})
.create({})
)
# Check failed recipient)s
self.assertTrue(any(wizard.partner_ids))
self.assertEqual(self.recipient.email, wizard.partner_ids[0].email)
# Resend message
wizard.resend_mail_action()
# Check tracking reset
self.assertFalse(tracking_email.state)
def mail_send(self, recipient): def mail_send(self, recipient):
mail = self.env["mail.mail"].create( mail = self.env["mail.mail"].create(
{ {

View File

@ -42,7 +42,10 @@
<label for="tracking_event_ids"/> <label for="tracking_event_ids"/>
<div> <div>
<field name="tracking_event_ids"> <field name="tracking_event_ids">
<tree string="Tracking 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')"> <tree string="Tracking events"
decoration-muted="event_type == 'deferral'"
decoration-danger="event_type in ('hard_bounce', 'soft_bounce', 'spam', 'reject')"
decoration-info="event_type in ('unsub', 'click', 'open')">
<field name="time"/> <field name="time"/>
<field name="event_type"/> <field name="event_type"/>
<field name="ip"/> <field name="ip"/>
@ -63,7 +66,10 @@
<field name="model">mail.tracking.email</field> <field name="model">mail.tracking.email</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="MailTracking emails" create="false" edit="false" delete="false" <tree string="MailTracking emails" create="false" edit="false" delete="false"
colors="grey:state in (False, 'deferred');black:state in ('sent', 'delivered');green:state in ('opened');red:state in ('rejected', 'spam', 'bounced', 'soft-bounced');blue:state in ('unsub')"> decoration-muted="state in (False, 'deferred')"
decoration-success="state == 'opened'"
decoration-danger="state in ('rejected', 'spam', 'bounced', 'soft-bounced', 'error')"
decoration-info="state == 'unsub'">
<field name="time"/> <field name="time"/>
<field name="date" invisible="1"/> <field name="date" invisible="1"/>
<field name="name"/> <field name="name"/>
@ -107,7 +113,6 @@
<record id="action_view_mail_tracking_email" model="ir.actions.act_window"> <record id="action_view_mail_tracking_email" model="ir.actions.act_window">
<field name="name">MailTracking emails</field> <field name="name">MailTracking emails</field>
<field name="res_model">mail.tracking.email</field> <field name="res_model">mail.tracking.email</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_mail_tracking_email_search"/> <field name="search_view_id" ref="view_mail_tracking_email_search"/>
</record> </record>

View File

@ -56,7 +56,9 @@
<field name="model">mail.tracking.event</field> <field name="model">mail.tracking.event</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="MailTracking events" create="false" edit="false" delete="false" <tree string="MailTracking events" create="false" edit="false" delete="false"
colors="grey:event_type in ('deferral',);black:event_type in ('sent', 'delivered');red:event_type in ('hard_bounce', 'soft_bounce', 'spam', 'reject');blue:event_type in ('unsub', 'click', 'open')"> decoration-muted="event_type == 'deferred'"
decoration-danger="event_type in ('hard_bounce', 'soft_bounce', 'spam', 'reject')"
decoration-info="event_type in ('unsub', 'click', 'open')">
<field name="time"/> <field name="time"/>
<field name="tracking_email_id"/> <field name="tracking_email_id"/>
<field name="recipient"/> <field name="recipient"/>
@ -111,7 +113,6 @@
<record id="action_view_mail_tracking_event" model="ir.actions.act_window"> <record id="action_view_mail_tracking_event" model="ir.actions.act_window">
<field name="name">MailTracking events</field> <field name="name">MailTracking events</field>
<field name="res_model">mail.tracking.event</field> <field name="res_model">mail.tracking.event</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_mail_tracking_event_search"/> <field name="search_view_id" ref="view_mail_tracking_event_search"/>
</record> </record>