[REF] mass_mailing: rename mail.mail.statistics and reporting model

PURPOSE

Mass mailing is currently a bit messy. As SMS will be added as a way to notify
people in mass through SMS let us take this opportunity to somehow clean this
application: organization, light code cleaning, model renaming.

SPECIFICATIONS

Rename mail.mail.statistics to mailing.trace and mail.statistics.report
to mail.trace.report. Rationale :

  * mail.mail.statistics is linked to mail.mail model. Soon this model will
    hold data related to SMS sending. It makes sense to be broader in the
    naming;
  * mailing.trace is more inlined with marketing.trace model that is the
    marketing automation model using it in marketing automation (enterprise
    application);
  * mailing.trace is shorter to write;
  * mail.statistics.report model should sense to be updated at the same
    time;

MIGRATION

mail.mail.statistics model -> mailing.trace
mail_mail_statistics table -> mailing_trace
mail.statistics.report model -> mail.trace.report

fields updated (w column change)
  * link.tracker.click: mail_stat_id -> mailing_trace_id

fields updated (no column change)
  * mail.mail: statistics_ids -> mailing_trace_ids
  * mail.mass_mailing: statistics_ids -> mailing_trace_ids

LINKS

Task ID 2037906
Preparing task ID 1997464 (SMS addition in mass mailing)
PR #34938
This commit is contained in:
Thibault Delavallée
2019-07-17 15:39:06 +00:00
parent 20dfba6e11
commit 2896aad34e
18 changed files with 134 additions and 137 deletions
+2 -2
View File
@@ -28,13 +28,13 @@
'wizard/test_mailing_views.xml',
'wizard/mass_mailing_schedule_date_views.xml',
'views/mass_mailing_views_menus.xml',
'views/mass_mailing_stats_views.xml',
'views/mailing_trace_views.xml',
'views/link_tracker_views.xml',
'views/mass_mailing_contact_views.xml',
'views/mass_mailing_list_views.xml',
'views/mass_mailing_views.xml',
'views/res_config_settings_views.xml',
'report/mass_mailing_stats_report_views.xml',
'report/mailing_trace_report_views.xml',
'views/assets.xml',
'views/mass_mailing_templates_portal.xml',
'views/themes_templates.xml',
+4 -4
View File
@@ -94,15 +94,15 @@ class MassMailController(http.Controller):
@http.route('/mail/track/<int:mail_id>/blank.gif', type='http', auth='public')
def track_mail_open(self, mail_id, **post):
""" Email tracking. """
request.env['mail.mail.statistics'].sudo().set_opened(mail_mail_ids=[mail_id])
request.env['mailing.trace'].sudo().set_opened(mail_mail_ids=[mail_id])
response = werkzeug.wrappers.Response()
response.mimetype = 'image/gif'
response.data = base64.b64decode(b'R0lGODlhAQABAIAAANvf7wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==')
return response
@http.route('/r/<string:code>/m/<int:stat_id>', type='http', auth="public")
def full_url_redirect(self, code, stat_id, **post):
@http.route('/r/<string:code>/m/<int:mailing_trace_id>', type='http', auth="public")
def full_url_redirect(self, code, mailing_trace_id, **post):
# don't assume geoip is set, it is part of the website module
# which mass_mailing doesn't depend on
country_code = request.session.get('geoip', False) and request.session.geoip.get('country_code', False)
@@ -111,7 +111,7 @@ class MassMailController(http.Controller):
code,
ip=request.httprequest.remote_addr,
country_code=country_code,
mail_stat_id=stat_id
mailing_trace_id=mailing_trace_id
)
return werkzeug.utils.redirect(request.env['link.tracker'].get_url_from_code(code), 301)
+12 -12
View File
@@ -198,7 +198,7 @@
<!-- Generate link tracker information from it -->
<function model="mail.mass_mailing" name="convert_links" eval="[ref('mass_mailing.mass_mail_1')]"/>
<record id="mass_mail_1_stat_0" model="mail.mail.statistics">
<record id="mass_mail_1_stat_0" model="mailing.trace">
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
<field name="message_id">1111000@odoo.com</field>
<field name="model">res.partner</field>
@@ -207,7 +207,7 @@
<field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="replied" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
</record>
<record id="mass_mail_1_stat_1" model="mail.mail.statistics">
<record id="mass_mail_1_stat_1" model="mailing.trace">
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
<field name="message_id">1111001@odoo.com</field>
<field name="model">res.partner</field>
@@ -216,7 +216,7 @@
<field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="replied" eval="(DateTime.today() - relativedelta(days=0)).strftime('%Y-%m-%d %H:%M:%S')"/>
</record>
<record id="mass_mail_1_stat_2" model="mail.mail.statistics">
<record id="mass_mail_1_stat_2" model="mailing.trace">
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
<field name="message_id">1111002@odoo.com</field>
<field name="model">res.partner</field>
@@ -224,7 +224,7 @@
<field name="sent" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
</record>
<record id="mass_mail_1_stat_3" model="mail.mail.statistics">
<record id="mass_mail_1_stat_3" model="mailing.trace">
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
<field name="message_id">1111003@odoo.com</field>
<field name="model">res.partner</field>
@@ -232,7 +232,7 @@
<field name="sent" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="opened" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
</record>
<record id="mass_mail_1_stat_4" model="mail.mail.statistics">
<record id="mass_mail_1_stat_4" model="mailing.trace">
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
<field name="message_id">1111004@odoo.com</field>
<field name="model">res.partner</field>
@@ -240,7 +240,7 @@
<field name="sent" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="opened" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
</record>
<record id="mass_mail_1_stat_5" model="mail.mail.statistics">
<record id="mass_mail_1_stat_5" model="mailing.trace">
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
<field name="message_id">1111004@odoo.com</field>
<field name="model">res.partner</field>
@@ -248,7 +248,7 @@
<field name="sent" eval="False"/>
<field name="exception" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
</record>
<record id="mass_mail_1_stat_6" model="mail.mail.statistics">
<record id="mass_mail_1_stat_6" model="mailing.trace">
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
<field name="message_id">1111004@odoo.com</field>
<field name="model">res.partner</field>
@@ -264,7 +264,7 @@
use="code"/>
<value name="ip">100.01.02.03</value>
<value name="country_code">BE</value>
<value name="mail_stat_id" eval="ref('mass_mail_1_stat_0')"/>
<value name="mailing_trace_id" eval="ref('mass_mail_1_stat_0')"/>
</function>
<function model="link.tracker.click" name="add_click">
<value model="link.tracker.code"
@@ -272,7 +272,7 @@
use="code"/>
<value name="ip">100.01.02.03</value>
<value name="country_code">BE</value>
<value name="mail_stat_id" eval="ref('mass_mail_1_stat_0')"/>
<value name="mailing_trace_id" eval="ref('mass_mail_1_stat_0')"/>
</function>
<function model="link.tracker.click" name="add_click">
<value model="link.tracker.code"
@@ -280,7 +280,7 @@
use="code"/>
<value name="ip">100.01.02.04</value>
<value name="country_code">BE</value>
<value name="mail_stat_id" eval="ref('mass_mail_1_stat_1')"/>
<value name="mailing_trace_id" eval="ref('mass_mail_1_stat_1')"/>
</function>
<function model="link.tracker.click" name="add_click">
<value model="link.tracker.code"
@@ -288,7 +288,7 @@
use="code"/>
<value name="ip">100.01.02.04</value>
<value name="country_code">BE</value>
<value name="mail_stat_id" eval="ref('mass_mail_1_stat_0')"/>
<value name="mailing_trace_id" eval="ref('mass_mail_1_stat_0')"/>
</function>
<function model="link.tracker.click" name="add_click">
<value model="link.tracker.code"
@@ -296,7 +296,7 @@
use="code"/>
<value name="ip">100.01.02.05</value>
<value name="country_code">BE</value>
<value name="mail_stat_id" eval="ref('mass_mail_1_stat_2')"/>
<value name="mailing_trace_id" eval="ref('mass_mail_1_stat_2')"/>
</function>
</data>
+1 -1
View File
@@ -5,7 +5,7 @@ from . import link_tracker
from . import mass_mailing_contact
from . import mass_mailing_list
from . import mass_mailing
from . import mass_mailing_stats
from . import mailing_trace
from . import mail_mail
from . import mail_template
from . import mail_thread
+10 -10
View File
@@ -14,22 +14,22 @@ class LinkTracker(models.Model):
class LinkTrackerClick(models.Model):
_inherit = "link.tracker.click"
mail_stat_id = fields.Many2one('mail.mail.statistics', string='Mail Statistics')
mailing_trace_id = fields.Many2one('mailing.trace', string='Mail Statistics')
mass_mailing_id = fields.Many2one('mail.mass_mailing', string='Mass Mailing')
mass_mailing_campaign_id = fields.Many2one('mail.mass_mailing.campaign', string='Mass Mailing Campaign')
def _prepare_click_values_from_route(self, **route_values):
click_values = super(LinkTrackerClick, self)._prepare_click_values_from_route(**route_values)
if click_values.get('mail_stat_id'):
stat_sudo = self.env['mail.mail.statistics'].sudo().browse(route_values['mail_stat_id']).exists()
if not stat_sudo:
click_values['mail_stat_id'] = False
if click_values.get('mailing_trace_id'):
trace_sudo = self.env['mailing.trace'].sudo().browse(route_values['mailing_trace_id']).exists()
if not trace_sudo:
click_values['mailing_trace_id'] = False
else:
if not click_values.get('mass_mailing_campaign_id'):
click_values['mass_mailing_campaign_id'] = stat_sudo.mass_mailing_campaign_id.id
click_values['mass_mailing_campaign_id'] = trace_sudo.mass_mailing_campaign_id.id
if not click_values.get('mass_mailing_id'):
click_values['mass_mailing_id'] = stat_sudo.mass_mailing_id.id
click_values['mass_mailing_id'] = trace_sudo.mass_mailing_id.id
return click_values
@@ -37,8 +37,8 @@ class LinkTrackerClick(models.Model):
def add_click(self, code, **route_values):
click = super(LinkTrackerClick, self).add_click(code, **route_values)
if click and click.mail_stat_id:
click.mail_stat_id.set_opened()
click.mail_stat_id.set_clicked()
if click and click.mailing_trace_id:
click.mailing_trace_id.set_opened()
click.mailing_trace_id.set_clicked()
return click
+12 -12
View File
@@ -14,16 +14,16 @@ class MailMail(models.Model):
_inherit = ['mail.mail']
mailing_id = fields.Many2one('mail.mass_mailing', string='Mass Mailing')
statistics_ids = fields.One2many('mail.mail.statistics', 'mail_mail_id', string='Statistics')
mailing_trace_ids = fields.One2many('mailing.trace', 'mail_mail_id', string='Statistics')
@api.model
def create(self, values):
""" Override mail_mail creation to create an entry in mail.mail.statistics """
""" Override mail_mail creation to create an entry in mailing.trace """
# TDE note: should be after 'all values computed', to have values (FIXME after merging other branch holding create refactoring)
mail = super(MailMail, self).create(values)
if values.get('statistics_ids'):
if values.get('mailing_trace_ids'):
mail_sudo = mail.sudo()
mail_sudo.statistics_ids.write({'message_id': mail_sudo.message_id, 'state': 'outgoing'})
mail_sudo.mailing_trace_ids.write({'message_id': mail_sudo.message_id, 'state': 'outgoing'})
return mail
def _get_tracking_url(self):
@@ -54,12 +54,12 @@ class MailMail(models.Model):
def _send_prepare_body(self):
""" Override to add the tracking URL to the body and to add
Statistic_id in shorted urls """
trace ID in shortened urls """
# TDE: temporary addition (mail was parameter) due to semi-new-API
self.ensure_one()
body = super(MailMail, self)._send_prepare_body()
if self.mailing_id and body and self.statistics_ids:
if self.mailing_id and body and self.mailing_trace_ids:
for match in re.findall(URL_REGEX, self.body_html):
href = match[0]
url = match[1]
@@ -67,7 +67,7 @@ class MailMail(models.Model):
parsed = werkzeug.urls.url_parse(url, scheme='http')
if parsed.scheme.startswith('http') and parsed.path.startswith('/r/'):
new_href = href.replace(url, url + '/m/' + str(self.statistics_ids[0].id))
new_href = href.replace(url, url + '/m/' + str(self.mailing_trace_ids[0].id))
body = body.replace(href, new_href)
# generate tracking URL
@@ -96,8 +96,8 @@ class MailMail(models.Model):
mail_sent = not failure_type # we consider that a recipient error is a failure with mass mailling and show them as failed
for mail in self:
if mail.mailing_id:
if mail_sent is True and mail.statistics_ids:
mail.statistics_ids.write({'sent': fields.Datetime.now(), 'exception': False})
elif mail_sent is False and mail.statistics_ids:
mail.statistics_ids.write({'exception': fields.Datetime.now()})
return super(MailMail, self)._postprocess_sent_message(success_pids, failure_reason=failure_reason, failure_type=failure_type)
if mail_sent is True and mail.mailing_trace_ids:
mail.mailing_trace_ids.write({'sent': fields.Datetime.now(), 'exception': False})
elif mail_sent is False and mail.mailing_trace_ids:
mail.mailing_trace_ids.write({'exception': fields.Datetime.now()})
return super(MailMail, self)._postprocess_sent_message(success_pids, failure_reason=failure_reason, failure_type=failure_type)
+10 -10
View File
@@ -13,12 +13,12 @@ BLACKLIST_MAX_BOUNCED_LIMIT = 5
class MailThread(models.AbstractModel):
""" Update MailThread to add the support of bounce management in mass mailing statistics. """
""" Update MailThread to add the support of bounce management in mass mailing traces. """
_inherit = 'mail.thread'
@api.model
def message_route(self, message, message_dict, model=None, thread_id=None, custom_values=None):
""" Override to udpate mass mailing statistics based on bounce emails """
""" Override to udpate mass mailing traces based on bounce emails """
bounce_alias = self.env['ir.config_parameter'].sudo().get_param("mail.bounce.alias")
email_to = decode_message_header(message, 'To')
email_to_localpart = (tools.email_split(email_to) or [''])[0].split('@', 1)[0].lower()
@@ -28,19 +28,19 @@ class MailThread(models.AbstractModel):
bounce_match = bounce_re.search(email_to)
if bounce_match:
bounced_mail_id = bounce_match.group(1)
self.env['mail.mail.statistics'].set_bounced(mail_mail_ids=[bounced_mail_id])
self.env['mailing.trace'].set_bounced(mail_mail_ids=[bounced_mail_id])
return super(MailThread, self).message_route(message, message_dict, model, thread_id, custom_values)
@api.model
def message_route_process(self, message, message_dict, routes):
""" Override to update the parent mail statistics. The parent is found
""" Override to update the parent mailing traces. The parent is found
by using the References header of the incoming message and looking for
matching message_id in mail.mail.statistics. """
matching message_id in mailing.trace. """
if message.get('References') and routes:
message_ids = [x.strip() for x in decode_smtp_header(message['References']).split()]
self.env['mail.mail.statistics'].set_opened(mail_message_ids=message_ids)
self.env['mail.mail.statistics'].set_replied(mail_message_ids=message_ids)
self.env['mailing.trace'].set_opened(mail_message_ids=message_ids)
self.env['mailing.trace'].set_replied(mail_message_ids=message_ids)
return super(MailThread, self).message_route_process(message, message_dict, routes)
def message_post_with_template(self, template_id, **kwargs):
@@ -64,11 +64,11 @@ class MailThread(models.AbstractModel):
super(MailThread, self)._message_receive_bounce(email, partner, mail_id=None)
three_months_ago = fields.Datetime.to_string(datetime.datetime.now() - datetime.timedelta(weeks=13))
stats = self.env['mail.mail.statistics'] \
traces = self.env['mailing.trace'] \
.search(['&', ('bounced', '>', three_months_ago), ('email', '=ilike', email)]) \
.mapped('bounced')
if len(stats) >= BLACKLIST_MAX_BOUNCED_LIMIT:
if max(stats) > min(stats) + datetime.timedelta(weeks=1):
if len(traces) >= BLACKLIST_MAX_BOUNCED_LIMIT:
if max(traces) > min(traces) + datetime.timedelta(weeks=1):
blacklist_rec = self.env['mail.blacklist'].sudo()._add(email)
blacklist_rec._message_log(
body='This email has been automatically blacklisted because of too much bounced.')
@@ -4,14 +4,13 @@
from odoo import api, fields, models
class MailMailStats(models.Model):
""" MailMailStats models the statistics collected about emails. Those statistics
class MailingTrace(models.Model):
""" MailingTrace models the statistics collected about emails. Those statistics
are stored in a separated model and table to avoid bloating the mail_mail table
with statistics values. This also allows to delete emails send with mass mailing
without loosing the statistics about them. """
_name = 'mail.mail.statistics'
_description = 'Email Statistics'
_name = 'mailing.trace'
_description = 'Mailing Statistics'
_rec_name = 'message_id'
_order = 'message_id'
@@ -45,7 +44,7 @@ class MailMailStats(models.Model):
replied = fields.Datetime(help='Date when this email has been replied for the first time.')
bounced = fields.Datetime(help='Date when this email has bounced.')
# Link tracking
links_click_ids = fields.One2many('link.tracker.click', 'mail_stat_id', string='Links click')
links_click_ids = fields.One2many('link.tracker.click', 'mailing_trace_id', string='Links click')
clicked = fields.Datetime(help='Date when customer clicked on at least one tracked link')
# Status
state = fields.Selection(compute="_compute_state",
@@ -83,7 +82,7 @@ class MailMailStats(models.Model):
def create(self, values):
if 'mail_mail_id' in values:
values['mail_mail_id_int'] = values['mail_mail_id']
res = super(MailMailStats, self).create(values)
res = super(MailingTrace, self).create(values)
return res
def _get_records(self, mail_mail_ids=None, mail_message_ids=None, domain=None):
@@ -98,22 +97,21 @@ class MailMailStats(models.Model):
return self.search(base_domain)
def set_opened(self, mail_mail_ids=None, mail_message_ids=None):
statistics = self._get_records(mail_mail_ids, mail_message_ids, [('opened', '=', False)])
statistics.write({'opened': fields.Datetime.now(), 'bounced': False})
return statistics
traces = self._get_records(mail_mail_ids, mail_message_ids, [('opened', '=', False)])
traces.write({'opened': fields.Datetime.now(), 'bounced': False})
return traces
def set_clicked(self, mail_mail_ids=None, mail_message_ids=None):
statistics = self._get_records(mail_mail_ids, mail_message_ids, [('clicked', '=', False)])
statistics.write({'clicked': fields.Datetime.now()})
return statistics
traces = self._get_records(mail_mail_ids, mail_message_ids, [('clicked', '=', False)])
traces.write({'clicked': fields.Datetime.now()})
return traces
def set_replied(self, mail_mail_ids=None, mail_message_ids=None):
statistics = self._get_records(mail_mail_ids, mail_message_ids, [('replied', '=', False)])
statistics.write({'replied': fields.Datetime.now()})
return statistics
traces = self._get_records(mail_mail_ids, mail_message_ids, [('replied', '=', False)])
traces.write({'replied': fields.Datetime.now()})
return traces
def set_bounced(self, mail_mail_ids=None, mail_message_ids=None):
statistics = self._get_records(
mail_mail_ids, mail_message_ids, [('bounced', '=', False), ('opened', '=', False)])
statistics.write({'bounced': fields.Datetime.now()})
return statistics
traces = self._get_records(mail_mail_ids, mail_message_ids, [('bounced', '=', False), ('opened', '=', False)])
traces.write({'bounced': fields.Datetime.now()})
return traces
+19 -19
View File
@@ -89,7 +89,7 @@ class MassMailingCampaign(models.Model):
'the effectiveness of the mailings, without causing duplicate messages.')
color = fields.Integer(string='Color Index')
clicks_ratio = fields.Integer(compute="_compute_clicks_ratio", string="Number of clicks")
# stat fields
# trace statistics fields
total = fields.Integer(compute="_compute_statistics")
scheduled = fields.Integer(compute="_compute_statistics")
failed = fields.Integer(compute="_compute_statistics")
@@ -107,9 +107,9 @@ class MassMailingCampaign(models.Model):
def _compute_clicks_ratio(self):
self.env.cr.execute("""
SELECT COUNT(DISTINCT(stats.id)) AS nb_mails, COUNT(DISTINCT(clicks.mail_stat_id)) AS nb_clicks, stats.mass_mailing_campaign_id AS id
FROM mail_mail_statistics AS stats
LEFT OUTER JOIN link_tracker_click AS clicks ON clicks.mail_stat_id = stats.id
SELECT COUNT(DISTINCT(stats.id)) AS nb_mails, COUNT(DISTINCT(clicks.mailing_trace_id)) AS nb_clicks, stats.mass_mailing_campaign_id AS id
FROM mailing_trace AS stats
LEFT OUTER JOIN link_tracker_click AS clicks ON clicks.mailing_trace_id = stats.id
WHERE stats.mass_mailing_campaign_id IN %s
GROUP BY stats.mass_mailing_campaign_id
""", (tuple(self.ids), ))
@@ -134,7 +134,7 @@ class MassMailingCampaign(models.Model):
COUNT(CASE WHEN s.replied is not null THEN 1 ELSE null END) AS replied ,
COUNT(CASE WHEN s.bounced is not null THEN 1 ELSE null END) AS bounced
FROM
mail_mail_statistics s
mailing_trace s
RIGHT JOIN
mail_mass_mailing_campaign c
ON (c.id = s.mass_mailing_campaign_id)
@@ -169,7 +169,7 @@ class MassMailingCampaign(models.Model):
domain = [('mass_mailing_campaign_id', '=', campaign.id)]
if model:
domain += [('model', '=', model)]
res[campaign.id] = set(self.env['mail.mail.statistics'].search(domain).mapped('res_id'))
res[campaign.id] = set(self.env['mailing.trace'].search(domain).mapped('res_id'))
return res
@api.model
@@ -253,7 +253,7 @@ class MassMailing(models.Model):
contact_ab_pc = fields.Integer(string='A/B Testing percentage',
help='Percentage of the contacts that will be mailed. Recipients will be taken randomly.', default=100)
# statistics data
statistics_ids = fields.One2many('mail.mail.statistics', 'mass_mailing_id', string='Emails Statistics')
mailing_trace_ids = fields.One2many('mailing.trace', 'mass_mailing_id', string='Emails Statistics')
total = fields.Integer(compute="_compute_total")
scheduled = fields.Integer(compute="_compute_statistics")
expected = fields.Integer(compute="_compute_statistics")
@@ -277,9 +277,9 @@ class MassMailing(models.Model):
def _compute_clicks_ratio(self):
self.env.cr.execute("""
SELECT COUNT(DISTINCT(stats.id)) AS nb_mails, COUNT(DISTINCT(clicks.mail_stat_id)) AS nb_clicks, stats.mass_mailing_id AS id
FROM mail_mail_statistics AS stats
LEFT OUTER JOIN link_tracker_click AS clicks ON clicks.mail_stat_id = stats.id
SELECT COUNT(DISTINCT(stats.id)) AS nb_mails, COUNT(DISTINCT(clicks.mailing_trace_id)) AS nb_clicks, stats.mass_mailing_id AS id
FROM mailing_trace AS stats
LEFT OUTER JOIN link_tracker_click AS clicks ON clicks.mailing_trace_id = stats.id
WHERE stats.mass_mailing_id IN %s
GROUP BY stats.mass_mailing_id
""", (tuple(self.ids), ))
@@ -311,7 +311,7 @@ class MassMailing(models.Model):
COUNT(CASE WHEN s.bounced is not null THEN 1 ELSE null END) AS bounced,
COUNT(CASE WHEN s.exception is not null THEN 1 ELSE null END) AS failed
FROM
mail_mail_statistics s
mailing_trace s
RIGHT JOIN
mail_mass_mailing m
ON (m.id = s.mass_mailing_id)
@@ -441,10 +441,10 @@ class MassMailing(models.Model):
def retry_failed_mail(self):
failed_mails = self.env['mail.mail'].search([('mailing_id', 'in', self.ids), ('state', '=', 'exception')])
failed_mails.mapped('statistics_ids').unlink()
failed_mails.mapped('mailing_trace_ids').unlink()
failed_mails.sudo().unlink()
res_ids = self._get_recipients()
except_mailed = self.env['mail.mail.statistics'].search([
except_mailed = self.env['mailing.trace'].search([
('model', '=', self.mailing_model_real),
('res_id', 'in', res_ids),
('exception', '!=', False),
@@ -471,11 +471,11 @@ class MassMailing(models.Model):
def _action_view_documents_filtered(self, view_filter):
if view_filter in ('sent', 'opened', 'replied', 'bounced', 'clicked'):
opened_stats = self.statistics_ids.filtered(lambda stat: stat[view_filter])
opened_stats = self.mailing_trace_ids.filtered(lambda stat: stat[view_filter])
elif view_filter == ('delivered'):
opened_stats = self.statistics_ids.filtered(lambda stat: stat.sent and not stat.bounced)
opened_stats = self.mailing_trace_ids.filtered(lambda stat: stat.sent and not stat.bounced)
else:
opened_stats = self.env['mail.mail.statistics']
opened_stats = self.env['mailing.trace']
res_ids = opened_stats.mapped('res_id')
model_name = self.env['ir.model']._get(self.mailing_model_real).display_name
return {
@@ -551,7 +551,7 @@ class MassMailing(models.Model):
# + use a basic heuristic for extracting emails
query = """
SELECT lower(substring(t.%(mail_field)s, '([^ ,;<@]+@[^> ,;]+)'))
FROM mail_mail_statistics s
FROM mailing_trace s
JOIN %(target)s t ON (s.res_id = t.id)
WHERE substring(t.%(mail_field)s, '([^ ,;<@]+@[^> ,;]+)') IS NOT NULL
"""
@@ -561,7 +561,7 @@ class MassMailing(models.Model):
mail_field = 'email'
query = """
SELECT lower(substring(p.%(mail_field)s, '([^ ,;<@]+@[^> ,;]+)'))
FROM mail_mail_statistics s
FROM mailing_trace s
JOIN %(target)s t ON (s.res_id = t.id)
JOIN res_partner p ON (t.partner_id = p.id)
WHERE substring(p.%(mail_field)s, '([^ ,;<@]+@[^> ,;]+)') IS NOT NULL
@@ -626,7 +626,7 @@ class MassMailing(models.Model):
def _get_remaining_recipients(self):
res_ids = self._get_recipients()
already_mailed = self.env['mail.mail.statistics'].search_read([
already_mailed = self.env['mailing.trace'].search_read([
('model', '=', self.mailing_model_real),
('res_id', 'in', res_ids),
('mass_mailing_id', '=', self.id)], ['res_id'])
+1 -1
View File
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import mass_mailing_stats_report
from . import mailing_trace_report
@@ -1,11 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, tools
from odoo import fields, models, tools
class MassMailingReport(models.Model):
_name = 'mail.statistics.report'
class MailingTraceReport(models.Model):
_name = 'mailing.trace.report'
_auto = False
_description = 'Mass Mailing Statistics'
@@ -23,12 +23,12 @@ class MassMailingReport(models.Model):
email_from = fields.Char('From', readonly=True)
def init(self):
"""Mass Mail Statistical Report: based on mail.mail.statistics that models the various
"""Mass Mail Statistical Report: based on mailing.trace that models the various
statistics collected for each mailing, and mail.mass_mailing model that models the
various mailing performed. """
tools.drop_view_if_exists(self.env.cr, 'mail_statistics_report')
tools.drop_view_if_exists(self.env.cr, 'mailing_trace_report')
self.env.cr.execute("""
CREATE OR REPLACE VIEW mail_statistics_report AS (
CREATE OR REPLACE VIEW mailing_trace_report AS (
SELECT
min(ms.id) as id,
ms.scheduled as scheduled_date,
@@ -43,7 +43,7 @@ class MassMailingReport(models.Model):
mm.state,
mm.email_from
FROM
mail_mail_statistics as ms
mailing_trace as ms
left join mail_mass_mailing as mm ON (ms.mass_mailing_id=mm.id)
left join mail_mass_mailing_campaign as mc ON (ms.mass_mailing_campaign_id=mc.id)
left join utm_campaign as utm_campaign ON (mc.campaign_id = utm_campaign.id)
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_mail_statistics_report_pivot" model="ir.ui.view">
<field name="name">mail.statistics.report.pivot</field>
<field name="model">mail.statistics.report</field>
<record id="mailing_trace_report_view_pivot" model="ir.ui.view">
<field name="name">mailing.trace.report.view.pivot</field>
<field name="model">mailing.trace.report</field>
<field name="arch" type="xml">
<pivot string="Mass Mailing Statistics" disable_linking="True">
<field name="campaign" type="row"/>
@@ -16,9 +16,9 @@
</field>
</record>
<record id="view_mail_statistics_report_graph" model="ir.ui.view">
<field name="name">mail.statistics.report.graph</field>
<field name="model">mail.statistics.report</field>
<record id="mailing_trace_report_view_graph" model="ir.ui.view">
<field name="name">mailing.trace.report.view.graph</field>
<field name="model">mailing.trace.report</field>
<field name="arch" type="xml">
<graph string="Mass Mailing Statistics">
<field name="campaign"/>
@@ -27,9 +27,9 @@
</field>
</record>
<record id="view_mail_statistics_report_search" model="ir.ui.view">
<field name="name">mail.statistics.report.search</field>
<field name="model">mail.statistics.report</field>
<record id="mailing_trace_report_view_search" model="ir.ui.view">
<field name="name">mailing.trace.report.view.search</field>
<field name="model">mailing.trace.report</field>
<field name="arch" type="xml">
<search string="Mass Mailing Statistics">
<group expand="0" string="Extended Filters...">
@@ -51,14 +51,14 @@
</record>
<!-- Actions and Menuitems -->
<record id="action_mail_statistics_report" model="ir.actions.act_window">
<record id="mailing_trace_report_action" model="ir.actions.act_window">
<field name="name">Mass Mailing Analysis</field>
<field name="res_model">mail.statistics.report</field>
<field name="res_model">mailing.trace.report</field>
<field name="view_mode">graph,pivot</field>
<field name="help">Mass Mailing Statistics allows you to check different mailing related information like number of bounced mails, opened mails, replied mails. You can sort out your analysis by different groups to get accurate grained analysis.</field>
</record>
<menuitem name="Mass Mailing" id="mass_mailing_menu_root" sequence="60"/>
<menuitem name="Reporting" id="menu_mass_mailing_report" sequence="99"
parent="mass_mailing_menu_root" action="action_mail_statistics_report" groups="mass_mailing.group_mass_mailing_user"/>
parent="mass_mailing_menu_root" action="mailing_trace_report_action" groups="mass_mailing.group_mass_mailing_user"/>
</odoo>
@@ -7,9 +7,9 @@ access_mass_mailing_campaign,mail.mass_mailing.campaign,model_mail_mass_mailing_
access_mass_mailing_campaign_system,mail.mass_mailing.campaign.system,model_mail_mass_mailing_campaign,base.group_system,1,1,1,1
access_mass_mailing,mail.mass_mailing,model_mail_mass_mailing,mass_mailing.group_mass_mailing_user,1,1,1,1
access_mass_mailing_system,mail.mass_mailing.system,model_mail_mass_mailing,base.group_system,1,1,1,1
access_mail_mail_statistics_user,mail.mail.statistics,model_mail_mail_statistics,base.group_user,1,1,1,1
access_mail_mail_statistics_mass_mailing_user,mail.mail.statistics,model_mail_mail_statistics,mass_mailing.group_mass_mailing_user,1,1,1,1
access_mail_statistics_report,mail.statistics.report,model_mail_statistics_report,mass_mailing.group_mass_mailing_user,1,1,1,1
access_mailing_trace_user,mailing.trace.user,model_mailing_trace,base.group_user,1,1,1,1
access_mailing_trace_mm_user,access.mailing.trace.mm.user,model_mailing_trace,mass_mailing.group_mass_mailing_user,1,1,1,1
access_mailing_trace_report_mm_user,access.mailing.trace.report.mm.user,model_mailing_trace_report,mass_mailing.group_mass_mailing_user,1,1,1,1
access_utm_source,access_utm_source,utm.model_utm_source,mass_mailing.group_mass_mailing_user,1,1,1,0
access_ir_mail_server,access_ir_mail_server,base.model_ir_mail_server,mass_mailing.group_mass_mailing_user,1,0,0,0
access_mail_blacklist_mass_mailing_user,access.mail.blacklist.mass_mailing_user,mail.model_mail_blacklist,mass_mailing.group_mass_mailing_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
7 access_mass_mailing_campaign_system mail.mass_mailing.campaign.system model_mail_mass_mailing_campaign base.group_system 1 1 1 1
8 access_mass_mailing mail.mass_mailing model_mail_mass_mailing mass_mailing.group_mass_mailing_user 1 1 1 1
9 access_mass_mailing_system mail.mass_mailing.system model_mail_mass_mailing base.group_system 1 1 1 1
10 access_mail_mail_statistics_user access_mailing_trace_user mail.mail.statistics mailing.trace.user model_mail_mail_statistics model_mailing_trace base.group_user 1 1 1 1
11 access_mail_mail_statistics_mass_mailing_user access_mailing_trace_mm_user mail.mail.statistics access.mailing.trace.mm.user model_mail_mail_statistics model_mailing_trace mass_mailing.group_mass_mailing_user 1 1 1 1
12 access_mail_statistics_report access_mailing_trace_report_mm_user mail.statistics.report access.mailing.trace.report.mm.user model_mail_statistics_report model_mailing_trace_report mass_mailing.group_mass_mailing_user 1 1 1 1
13 access_utm_source access_utm_source utm.model_utm_source mass_mailing.group_mass_mailing_user 1 1 1 0
14 access_ir_mail_server access_ir_mail_server base.model_ir_mail_server mass_mailing.group_mass_mailing_user 1 0 0 0
15 access_mail_blacklist_mass_mailing_user access.mail.blacklist.mass_mailing_user mail.model_mail_blacklist mass_mailing.group_mass_mailing_user 1 1 1 1
@@ -80,7 +80,7 @@
<xpath expr="//field[@name='country_id']" position="after">
<field name="mass_mailing_campaign_id"/>
<field name="mass_mailing_id"/>
<field name="mail_stat_id"/>
<field name="mailing_trace_id"/>
</xpath>
</field>
</record>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- MAIL MAIL STATISTICS !-->
<record model="ir.ui.view" id="view_mail_mail_statistics_search">
<field name="name">mail.mail.statistics.search</field>
<field name="model">mail.mail.statistics</field>
<!-- MAILING TRACE !-->
<record model="ir.ui.view" id="mailing_trace_view_search">
<field name="name">mailing.trace.search</field>
<field name="model">mailing.trace</field>
<field name="arch" type="xml">
<search string="Mail Statistics">
<field name="mail_mail_id_int"/>
@@ -30,9 +30,9 @@
</field>
</record>
<record model="ir.ui.view" id="view_mail_mail_statistics_tree">
<field name="name">mail.mail.statistics.tree</field>
<field name="model">mail.mail.statistics</field>
<record model="ir.ui.view" id="mailing_trace_view_tree">
<field name="name">mailing.trace.tree</field>
<field name="model">mailing.trace</field>
<field name="arch" type="xml">
<tree string="Mail Statistics" create="0">
<field name="email"/>
@@ -49,9 +49,9 @@
</field>
</record>
<record model="ir.ui.view" id="view_mail_mail_statistics_form">
<field name="name">mail.mail.statistics.form</field>
<field name="model">mail.mail.statistics</field>
<record model="ir.ui.view" id="mailing_trace_view_form">
<field name="name">mailing.trace.form</field>
<field name="model">mailing.trace</field>
<field name="arch" type="xml">
<form string="Mail Statistics" create="0">
<header>
@@ -88,13 +88,13 @@
<record id="action_view_mail_mail_statistics" model="ir.actions.act_window">
<field name="name">Mail Statistics</field>
<field name="res_model">mail.mail.statistics</field>
<field name="res_model">mailing.trace</field>
<field name="view_mode">tree,form</field>
</record>
<record id="action_view_mail_mail_statistics_mailing" model="ir.actions.act_window">
<field name="name">Mail Statistics</field>
<field name="res_model">mail.mail.statistics</field>
<field name="res_model">mailing.trace</field>
<field name="view_mode">graph,tree,form,pivot</field>
<field name="domain">[]</field>
<field name="context">{'search_default_mass_mailing_id': active_id}</field>
@@ -102,7 +102,7 @@
<record id="action_view_mail_mail_statistics_mailing_list" model="ir.actions.act_window">
<field name="name">Emails Sent</field>
<field name="res_model">mail.mail.statistics</field>
<field name="res_model">mailing.trace</field>
<field name="view_mode">tree,graph,form,pivot</field>
<field name="domain">[]</field>
<field name="context">{'search_default_mass_mailing_id': active_id}</field>
@@ -110,7 +110,7 @@
<record id="view_mail_mail_statistics_graph" model="ir.ui.view">
<field name="name">Mail Statistics Graph</field>
<field name="model">mail.mail.statistics</field>
<field name="model">mailing.trace</field>
<field name="arch" type="xml">
<graph string="Mail Statistics" type="bar" stacked="True" interval="day">
<field name="state_update" type="row" interval="day"/>
@@ -16,7 +16,7 @@ class MailComposeMessage(models.TransientModel):
def get_mail_values(self, res_ids):
""" Override method that generated the mail content by creating the
mail.mail.statistics values in the o2m of mail_mail, when doing pure
mailing.trace values in the o2m of mail_mail, when doing pure
email mass mailing. """
self.ensure_one()
res = super(MailComposeMessage, self).get_mail_values(res_ids)
@@ -71,7 +71,7 @@ class MailComposeMessage(models.TransientModel):
mail_values['state'] = 'cancel'
elif seen_list is not None:
seen_list.add(mail_to)
stat_vals = {
trace_vals = {
'model': self.model,
'res_id': res_id,
'mass_mailing_id': mass_mailing.id,
@@ -79,12 +79,12 @@ class MailComposeMessage(models.TransientModel):
}
if mail_values.get('body_html') and mass_mail_layout:
mail_values['body_html'] = mass_mail_layout.render({'body': mail_values['body_html']}, engine='ir.qweb', minimal_qcontext=True)
# propagate ignored state to stat when still-born
# propagate ignored state to trace when still-born
if mail_values.get('state') == 'cancel':
stat_vals['ignored'] = fields.Datetime.now()
trace_vals['ignored'] = fields.Datetime.now()
mail_values.update({
'mailing_id': mass_mailing.id,
'statistics_ids': [(0, 0, stat_vals)],
'mailing_trace_ids': [(0, 0, trace_vals)],
# email-mode: keep original message for routing
'notification': mass_mailing.reply_to_mode == 'thread',
'auto_delete': not mass_mailing.keep_archives,
+2 -2
View File
@@ -48,7 +48,7 @@ class TestLinkTracker(common.MassMailingCase):
mailing = self.env['mail.mass_mailing'].create({'name': 'Test Mailing', "subject": "Hi!"})
code = self.link.code
self.assertEqual(self.link.count, 1)
stat = self.env['mail.mail.statistics'].create({'mass_mailing_id': mailing.id})
stat = self.env['mailing.trace'].create({'mass_mailing_id': mailing.id})
self.assertFalse(stat.opened)
self.assertFalse(stat.clicked)
@@ -57,7 +57,7 @@ class TestLinkTracker(common.MassMailingCase):
code,
ip='100.00.00.01',
country_code='BEL',
mail_stat_id=stat.id
mailing_trace_id=stat.id
)
self.assertEqual(self.link.count, 2)
self.assertEqual(click.mass_mailing_id, mailing)
@@ -9,7 +9,7 @@ class TestAutoBlacklist(common.TransactionCase):
mass_mailing_contacts = self.env['mail.mass_mailing.contact']
mass_mailing = self.env['mail.mass_mailing']
mail_blacklist = self.env['mail.blacklist']
mail_statistics = self.env['mail.mail.statistics']
mail_statistics = self.env['mailing.trace']
mail_thread = self.env['mail.thread']
# create mailing contact record
@@ -45,7 +45,6 @@ class TestAutoBlacklist(common.TransactionCase):
})
self.mailing_contact_1._message_receive_bounce(self.mailing_contact_1.email, self.mailing_contact_1)
# create mass mailing record
self.mass_mailing = mass_mailing.create({
'name': 'test',