Files
odoo_source/addons/mass_mailing/models/utm.py
T
dht-odoo 10490139f1 [IMP] {mass_mailing}_sms: allow internal users to access UTM campaigns
Before this commit:
- UTM campaigns are accessible for all internal users, but with email
  marketing related modules installed, if user who does not have rights
  for email marketing tries to access the campaign, an AccessError is
  raised. It's because few fields related to email marketing are added
  in model `utm.campaign` which are accesible only to the marketing
  users.
- When the user is having `mass_mailing.group_mass_mailing_campaign`
  group enabled but no rights for email marketing, the root menu of
  for the 'Email Marketing' app is visible to that user, which is
  not correct.

With this commit:
- While adding marketing related fields to the campaign, we provide the
  group("mass_mailing.group_mass_mailing_user") at field level whenever
  neccessary so it doesn't raise AcessError. Also, we show those fields
  on the campaign views only when `mass_mailing.group_mass_mailing_campaign`
  group is enabled for the user. To do this, a compute field has been
  introduced to make sure that user has rights to access email marketing
  as well as the feature to manage mass mailing from campaigns is enabled.
  The reason behind introducing a new compute field is, at view level,
  it is not possible to check that user is having multiple groups.
- We've applied the group "mass_mailing.group_mass_mailing_user" on the
  root menu of Email Marketing app, so if the user has UI only group
  "mass_mailing.group_mass_mailing_campaign" and but no rights for
  email marketing, the root menu won't won't appear.

TaskID-2417993

closes odoo/odoo#74113

Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
2021-10-07 19:07:04 +00:00

163 lines
7.2 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
class UtmCampaign(models.Model):
_inherit = 'utm.campaign'
mailing_mail_ids = fields.One2many(
'mailing.mailing', 'campaign_id',
domain=[('mailing_type', '=', 'mail')],
string='Mass Mailings',
groups="mass_mailing.group_mass_mailing_user")
mailing_mail_count = fields.Integer('Number of Mass Mailing',
compute="_compute_mailing_mail_count",
groups="mass_mailing.group_mass_mailing_user")
is_mailing_campaign_activated = fields.Boolean(compute="_compute_is_mailing_campaign_activated")
# A/B Testing
ab_testing_mailings_count = fields.Integer("A/B Test Mailings #", compute="_compute_mailing_mail_count")
ab_testing_completed = fields.Boolean("A/B Testing Campaign Finished")
ab_testing_schedule_datetime = fields.Datetime('Send Final On',
default=lambda self: fields.Datetime.now() + relativedelta(days=1),
help="Date that will be used to know when to determine and send the winner mailing")
ab_testing_total_pc = fields.Integer("Total A/B test percentage", compute="_compute_ab_testing_total_pc", store=True)
ab_testing_winner_selection = fields.Selection([
('manual', 'Manual'),
('opened_ratio', 'Highest Open Rate'),
('clicks_ratio', 'Highest Click Rate'),
('replied_ratio', 'Highest Reply Rate')], string="Winner Selection", default="opened_ratio",
help="Selection to determine the winner mailing that will be sent.")
# stat fields
received_ratio = fields.Integer(compute="_compute_statistics", string='Received Ratio')
opened_ratio = fields.Integer(compute="_compute_statistics", string='Opened Ratio')
replied_ratio = fields.Integer(compute="_compute_statistics", string='Replied Ratio')
bounced_ratio = fields.Integer(compute="_compute_statistics", string='Bounced Ratio')
@api.depends('mailing_mail_ids')
def _compute_ab_testing_total_pc(self):
for campaign in self:
campaign.ab_testing_total_pc = sum([
mailing.ab_testing_pc for mailing in campaign.mailing_mail_ids.filtered('ab_testing_enabled')
])
@api.depends('mailing_mail_ids')
def _compute_mailing_mail_count(self):
if self.ids:
mailing_data = self.env['mailing.mailing'].read_group(
[('campaign_id', 'in', self.ids), ('mailing_type', '=', 'mail')],
['campaign_id', 'ab_testing_enabled'],
['campaign_id', 'ab_testing_enabled'],
lazy=False,
)
ab_testing_mapped_data = {}
mapped_data = {}
for data in mailing_data:
if data['ab_testing_enabled']:
ab_testing_mapped_data.setdefault(data['campaign_id'][0], []).append(data['__count'])
mapped_data.setdefault(data['campaign_id'][0], []).append(data['__count'])
else:
mapped_data = dict()
ab_testing_mapped_data = dict()
for campaign in self:
campaign.mailing_mail_count = sum(mapped_data.get(campaign.id, []))
campaign.ab_testing_mailings_count = sum(ab_testing_mapped_data.get(campaign.id, []))
@api.constrains('ab_testing_total_pc', 'ab_testing_completed')
def _check_ab_testing_total_pc(self):
for campaign in self:
if not campaign.ab_testing_completed and campaign.ab_testing_total_pc >= 100:
raise ValidationError(_("The total percentage for an A/B testing campaign should be less than 100%"))
def _compute_statistics(self):
""" Compute statistics of the mass mailing campaign """
default_vals = {
'received_ratio': 0,
'opened_ratio': 0,
'replied_ratio': 0,
'bounced_ratio': 0
}
if not self.ids:
self.update(default_vals)
return
self.env.cr.execute("""
SELECT
c.id as campaign_id,
COUNT(s.id) AS expected,
COUNT(s.sent_datetime) AS sent,
COUNT(s.trace_status) FILTER (WHERE s.trace_status in ('sent', 'open', 'reply')) AS delivered,
COUNT(s.trace_status) FILTER (WHERE s.trace_status in ('open', 'reply')) AS open,
COUNT(s.trace_status) FILTER (WHERE s.trace_status = 'reply') AS reply,
COUNT(s.trace_status) FILTER (WHERE s.trace_status = 'bounce') AS bounce,
COUNT(s.trace_status) FILTER (WHERE s.trace_status = 'cancel') AS cancel
FROM
mailing_trace s
RIGHT JOIN
utm_campaign c
ON (c.id = s.campaign_id)
WHERE
c.id IN %s
GROUP BY
c.id
""", (tuple(self.ids), ))
all_stats = self.env.cr.dictfetchall()
stats_per_campaign = {
stats['campaign_id']: stats
for stats in all_stats
}
for campaign in self:
stats = stats_per_campaign.get(campaign.id)
if not stats:
vals = default_vals
else:
total = (stats['expected'] - stats['cancel']) or 1
delivered = stats['sent'] - stats['bounce']
vals = {
'received_ratio': 100.0 * delivered / total,
'opened_ratio': 100.0 * stats['open'] / total,
'replied_ratio': 100.0 * stats['reply'] / total,
'bounced_ratio': 100.0 * stats['bounce'] / total
}
campaign.update(vals)
def _compute_is_mailing_campaign_activated(self):
self.is_mailing_campaign_activated = self.env.user.has_group('mass_mailing.group_mass_mailing_campaign')
def _get_mailing_recipients(self, model=None):
"""Return the recipients of a mailing campaign. This is based on the statistics
build for each mailing. """
res = dict.fromkeys(self.ids, {})
for campaign in self:
domain = [('campaign_id', '=', campaign.id)]
if model:
domain += [('model', '=', model)]
res[campaign.id] = set(self.env['mailing.trace'].search(domain).mapped('res_id'))
return res
@api.model
def _cron_process_mass_mailing_ab_testing(self):
""" Cron that manages A/B testing and sends a winner mailing computed based on
the value set on the A/B testing campaign.
In case there is no mailing sent for an A/B testing campaign we ignore this campaign
"""
ab_testing_campaign = self.search([
('ab_testing_schedule_datetime', '<=', fields.Datetime.now()),
('ab_testing_winner_selection', '!=', 'manual'),
('ab_testing_completed', '=', False),
])
for campaign in ab_testing_campaign:
ab_testing_mailings = campaign.mailing_mail_ids.filtered(lambda m: m.ab_testing_enabled)
if not ab_testing_mailings.filtered(lambda m: m.state == 'done'):
continue
ab_testing_mailings.action_send_winner_mailing()
return ab_testing_campaign