diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py
index 929dda2f379..1abfdcd07fc 100644
--- a/addons/crm/models/crm_lead.py
+++ b/addons/crm/models/crm_lead.py
@@ -50,7 +50,8 @@ class Lead(models.Model):
_name = "crm.lead"
_description = "Lead/Opportunity"
_order = "priority desc,activity_date_deadline,id desc"
- _inherit = ['mail.thread', 'mail.activity.mixin', 'utm.mixin', 'format.address.mixin']
+ _inherit = ['mail.thread', 'mail.activity.mixin', 'utm.mixin', 'format.address.mixin', 'mail.blacklist.mixin']
+ _primary_email = ['email_from']
def _default_probability(self):
stage_id = self._default_stage_id()
@@ -80,9 +81,6 @@ class Lead(models.Model):
tag_ids = fields.Many2many('crm.lead.tag', 'crm_lead_tag_rel', 'lead_id', 'tag_id', string='Tags', help="Classify and analyze your lead/opportunity categories like: Training, Service")
contact_name = fields.Char('Contact Name', track_visibility='onchange', track_sequence=3)
partner_name = fields.Char("Customer Name", track_visibility='onchange', track_sequence=2, index=True, help='The name of the future partner company that will be created while converting the lead into opportunity')
- opt_out = fields.Boolean(string='Opt-Out', oldname='optout',
- help="If opt-out is checked, this contact has refused to receive emails for mass mailing and marketing campaign. "
- "Filter 'Available for Mass Mailing' allows users to filter the leads when performing mass mailing.")
type = fields.Selection([('lead', 'Lead'), ('opportunity', 'Opportunity')], index=True, required=True,
default=lambda self: 'lead' if self.env['res.users'].has_group('crm.group_use_lead') else 'opportunity',
help="Type is used to separate Leads and Opportunities")
diff --git a/addons/crm/views/crm_lead_views.xml b/addons/crm/views/crm_lead_views.xml
index 85d6b260d0e..55e0080cdaf 100644
--- a/addons/crm/views/crm_lead_views.xml
+++ b/addons/crm/views/crm_lead_views.xml
@@ -80,7 +80,13 @@
-
+
+
+
+
+
+
@@ -103,7 +109,6 @@
-
@@ -395,9 +400,10 @@
-
+
+
@@ -562,7 +568,6 @@
-
diff --git a/addons/delivery/views/partner_view.xml b/addons/delivery/views/partner_view.xml
index 5a66d2cfaba..97ec61dfe63 100644
--- a/addons/delivery/views/partner_view.xml
+++ b/addons/delivery/views/partner_view.xml
@@ -6,7 +6,7 @@
res.partner
-
+
diff --git a/addons/mail/__manifest__.py b/addons/mail/__manifest__.py
index 892215c5b25..81b201b98e9 100644
--- a/addons/mail/__manifest__.py
+++ b/addons/mail/__manifest__.py
@@ -37,6 +37,7 @@
'views/ir_model_views.xml',
'views/res_partner_views.xml',
'views/mail_blacklist_views.xml',
+ 'views/mail_channel_partner_views.xml',
],
'demo': [
'data/mail_demo.xml',
diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py
index ffe37f5c59a..603efd0eecb 100644
--- a/addons/mail/models/mail_channel.py
+++ b/addons/mail/models/mail_channel.py
@@ -20,9 +20,11 @@ _logger = logging.getLogger(__name__)
class ChannelPartner(models.Model):
_name = 'mail.channel.partner'
+ _inherit = ['mail.blacklist.mixin']
_description = 'Listeners of a Channel'
_table = 'mail_channel_partner'
_rec_name = 'partner_id'
+ _primary_email = ['partner_email']
partner_id = fields.Many2one('res.partner', string='Recipient', ondelete='cascade')
partner_email = fields.Char('Email', related='partner_id.email')
@@ -337,13 +339,15 @@ class Channel(models.Model):
@api.multi
def _notify_email_recipients(self, message, recipient_ids):
+ # Excluded Blacklisted
+ whitelist = self.env['res.partner'].sudo().search([('id', 'in', recipient_ids), ('is_blacklisted', '=', False)])
# real mailing list: multiple recipients (hidden by X-Forge-To)
if self.alias_domain and self.alias_name:
return {
- 'email_to': ','.join(formataddr((partner.name, partner.email)) for partner in self.env['res.partner'].sudo().browse(recipient_ids)),
+ 'email_to': ','.join(formataddr((partner.name, partner.email)) for partner in whitelist),
'recipient_ids': [],
}
- return super(Channel, self)._notify_email_recipients(message, recipient_ids)
+ return super(Channel, self)._notify_email_recipients(message, whitelist.ids)
def _extract_moderation_values(self, message_type, **kwargs):
""" This method is used to compute moderation status before the creation
diff --git a/addons/mail/models/res_partner.py b/addons/mail/models/res_partner.py
index b0bdf1e09b2..f56e245fae0 100644
--- a/addons/mail/models/res_partner.py
+++ b/addons/mail/models/res_partner.py
@@ -16,13 +16,10 @@ class Partner(models.Model):
""" Update partner to add a field about notification preferences. Add a generic opt-out field that can be used
to restrict usage of automatic email templates. """
_name = "res.partner"
- _inherit = ['res.partner', 'mail.thread', 'mail.activity.mixin']
+ _inherit = ['res.partner', 'mail.thread', 'mail.activity.mixin', 'mail.blacklist.mixin']
_mail_flat_thread = False
message_bounce = fields.Integer('Bounce', help="Counter of the number of bounced emails for this contact", default=0)
- opt_out = fields.Boolean(
- 'Opt-Out', help="If opt-out is checked, this contact has refused to receive emails for mass mailing and marketing campaign. "
- "Filter 'Available for Mass Mailing' allows users to filter the partners when performing mass mailing.")
channel_ids = fields.Many2many('mail.channel', 'mail_channel_partner', 'partner_id', 'channel_id', string='Channels', copy=False)
@api.multi
diff --git a/addons/mail/views/mail_channel_partner_views.xml b/addons/mail/views/mail_channel_partner_views.xml
new file mode 100644
index 00000000000..d0de87156a8
--- /dev/null
+++ b/addons/mail/views/mail_channel_partner_views.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ res.partner.view.form.short.inherit.mail
+ res.partner
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/mail/views/res_partner_views.xml b/addons/mail/views/res_partner_views.xml
index 8a5cafad3da..de961c710b0 100644
--- a/addons/mail/views/res_partner_views.xml
+++ b/addons/mail/views/res_partner_views.xml
@@ -7,8 +7,16 @@
res.partner
+
+
+
+
+
+
+
+
-
diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py
index 63a87cbed74..f4cc59c2a34 100644
--- a/addons/mail/wizard/mail_compose_message.py
+++ b/addons/mail/wizard/mail_compose_message.py
@@ -279,7 +279,7 @@ class MailComposer(models.TransientModel):
records = self.env[self.model].browse(res_ids)
reply_to_value = self.env['mail.thread']._notify_get_reply_to_on_records(default=self.email_from, records=records)
- blacklist = []
+ blacklisted_rec_ids = []
if mass_mail_mode and hasattr(self.env[self.model], "_primary_email"):
blacklist = self.env['mail.blacklist'].sudo().search([]).mapped('email')
if blacklist:
@@ -339,11 +339,10 @@ class MailComposer(models.TransientModel):
{'model': 'mail.message', 'res_id': 0}
)
# Filter out the blacklisted records by setting the mail state to cancel -> Used for Mass Mailing stats
- if blacklist:
- if res_id in blacklisted_rec_ids:
- mail_values['state'] = 'cancel'
- # Do not post the mail into the recipient's chatter
- mail_values['notification'] = False
+ if res_id in blacklisted_rec_ids:
+ mail_values['state'] = 'cancel'
+ # Do not post the mail into the recipient's chatter
+ mail_values['notification'] = False
results[res_id] = mail_values
return results
diff --git a/addons/mass_mailing/__manifest__.py b/addons/mass_mailing/__manifest__.py
index 9e11a9ee1bf..1340694a550 100644
--- a/addons/mass_mailing/__manifest__.py
+++ b/addons/mass_mailing/__manifest__.py
@@ -38,6 +38,7 @@
'views/snippets_themes_options.xml',
'views/res_users_views.xml',
'views/mass_mailing_blacklist_views.xml',
+ 'views/unsubscribe_templates.xml',
],
'demo': [
'data/mass_mailing_demo.xml',
diff --git a/addons/mass_mailing/controllers/main.py b/addons/mass_mailing/controllers/main.py
index b642033b35c..4aacf1408a5 100644
--- a/addons/mass_mailing/controllers/main.py
+++ b/addons/mass_mailing/controllers/main.py
@@ -21,21 +21,59 @@ class MassMailController(http.Controller):
mailing = request.env['mail.mass_mailing'].sudo().browse(mailing_id)
if mailing.exists():
res_id = res_id and int(res_id)
- res_ids = []
- if mailing.mailing_model_name == 'mail.mass_mailing.list':
- contacts = request.env['mail.mass_mailing.contact'].sudo().search([
- ('email', '=', email),
- ('list_ids', 'in', [mailing_list.id for mailing_list in mailing.contact_list_ids])
- ])
- res_ids = contacts.ids
- else:
- res_ids = [res_id]
-
right_token = mailing._unsubscribe_token(res_id, email)
if not consteq(str(token), right_token):
raise exceptions.AccessDenied()
- mailing.update_opt_out(email, res_ids, True)
- return _('You have been unsubscribed successfully')
+
+ if mailing.mailing_model_real == 'mail.mass_mailing.contact':
+ # Unsubscribe directly + Let the user choose his subscriptions
+ mailing.update_opt_out(email, mailing.contact_list_ids.ids, True)
+
+ contacts = request.env['mail.mass_mailing.contact'].sudo().search([('email', '=', email)])
+ subscription_list_ids = contacts.mapped('subscription_list_ids')
+ # In many user are found : if user is opt_out on the list with contact_id 1 but not with contact_id 2,
+ # assume that the user is not opt_out on both
+ # TODO DBE Fixme : Optimise the following to get real opt_out and opt_in
+ opt_out_list_ids = subscription_list_ids.filtered(lambda rel: rel.opt_out).mapped('list_id')
+ opt_in_list_ids = subscription_list_ids.filtered(lambda rel: not rel.opt_out).mapped('list_id')
+ opt_out_list_ids = set([list.id for list in opt_out_list_ids if list not in opt_in_list_ids])
+
+ unique_list_ids = set([list.list_id.id for list in subscription_list_ids])
+ list_ids = request.env['mail.mass_mailing.list'].sudo().browse(unique_list_ids)
+ unsubscribed_list = ', '.join(str(list.name) for list in mailing.contact_list_ids if list.is_public)
+ return request.render('mass_mailing.page_unsubscribe', {
+ 'contacts': contacts,
+ 'list_ids': list_ids,
+ 'opt_out_list_ids': opt_out_list_ids,
+ 'unsubscribed_list': unsubscribed_list,
+ 'email': email,
+ 'mailing_id': mailing_id,
+ 'res_id': res_id,
+ 'show_blacklist_button': request.env['ir.config_parameter'].sudo().get_param('mass_mailing.show_blacklist_buttons'),
+ })
+ else:
+ blacklist_rec = request.env['mail.blacklist'].sudo()._add(email)
+ blacklist_rec._message_log(_("""The %s asked to not be contacted anymore
+ using an unsubscribe link.""" % request.env['ir.model']._get(mailing.mailing_model_real).display_name))
+ return request.render('mass_mailing.page_unsubscribed', {
+ 'email': email,
+ 'mailing_id': mailing_id,
+ 'res_id': res_id,
+ 'show_blacklist_button': request.env['ir.config_parameter'].sudo().get_param(
+ 'mass_mailing.show_blacklist_buttons'),
+ })
+ return request.redirect('/web')
+
+ @http.route('/mail/mailing/unsubscribe', type='json', auth='none')
+ def unsubscribe(self, mailing_id, opt_in_ids, opt_out_ids, email, res_id, token):
+ mailing = request.env['mail.mass_mailing'].sudo().browse(mailing_id)
+ if mailing._unsubscribe_token(res_id, email) != token:
+ return 'unauthorized'
+ if mailing.exists():
+ mailing.update_opt_out(email, opt_in_ids, False)
+ mailing.update_opt_out(email, opt_out_ids, True)
+ return True
+ return 'error'
@http.route('/mail/track//blank.gif', type='http', auth='none')
def track_mail_open(self, mail_id, **post):
@@ -55,3 +93,52 @@ class MassMailController(http.Controller):
request.env['link.tracker.click'].add_click(code, request.httprequest.remote_addr, country_code, stat_id=stat_id)
return werkzeug.utils.redirect(request.env['link.tracker'].get_url_from_code(code), 301)
+
+ @http.route('/mailing/blacklist/check', type='json', auth='none')
+ def blacklist_check(self, mailing_id, res_id, email, token):
+ mailing = request.env['mail.mass_mailing'].sudo().browse(mailing_id)
+ if mailing._unsubscribe_token(res_id, email) != token:
+ return 'unauthorized'
+ if email:
+ record = request.env['mail.blacklist'].sudo().with_context(active_test=False).search([('email', '=ilike', email)])
+ if record['active']:
+ return True
+ return False
+ return 'error'
+
+ @http.route('/mailing/blacklist/add', type='json', auth='none')
+ def blacklist_add(self, mailing_id, res_id, email, token):
+ mailing = request.env['mail.mass_mailing'].sudo().browse(mailing_id)
+ if mailing._unsubscribe_token(res_id, email) != token:
+ return 'unauthorized'
+ if email:
+ blacklist_rec = request.env['mail.blacklist'].sudo()._add(email)
+ blacklist_rec._message_log(_("""The %s asked to not be contacted anymore
+ using the unsubscription page.""" % request.env['ir.model']._get(mailing.mailing_model_real).display_name))
+ return True
+ return 'error'
+
+ @http.route('/mailing/blacklist/remove', type='json', auth='none')
+ def blacklist_remove(self, mailing_id, res_id, email, token):
+ mailing = request.env['mail.mass_mailing'].sudo().browse(mailing_id)
+ if mailing._unsubscribe_token(res_id, email) != token:
+ return 'unauthorized'
+ if email:
+ request.env['mail.blacklist'].sudo()._remove(email)
+ return True
+ return 'error'
+
+ @http.route('/mailing/feedback', type='json', auth='none')
+ def send_feedback(self, mailing_id, res_id, email, feedback, token):
+ mailing = request.env['mail.mass_mailing'].sudo().browse(mailing_id)
+ if mailing._unsubscribe_token(res_id, email) != token:
+ return 'unauthorized'
+ if mailing.exists() and email:
+ model = request.env[mailing.mailing_model_real]
+ [email_field] = model._primary_email
+ record = model.sudo().search([(email_field, '=ilike', email)])
+ if record:
+ record.sudo().message_post(body=_("Feedback from %s: %s" % (email, feedback)))
+ return True
+ return False
+ return 'error'
diff --git a/addons/mass_mailing/data/mass_mailing_demo.xml b/addons/mass_mailing/data/mass_mailing_demo.xml
index c4f3e1bc1b1..03beb90455b 100644
--- a/addons/mass_mailing/data/mass_mailing_demo.xml
+++ b/addons/mass_mailing/data/mass_mailing_demo.xml
@@ -28,7 +28,6 @@
Carol Cartridge
cc@example.com
-
diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py
index 06e63072140..358fd79f2fb 100644
--- a/addons/mass_mailing/models/mass_mailing.py
+++ b/addons/mass_mailing/models/mass_mailing.py
@@ -38,6 +38,53 @@ class MassMailingTag(models.Model):
('name_uniq', 'unique (name)', "Tag name already exists !"),
]
+class MassMailingContactListRel(models.Model):
+ """ Intermediate model between mass mailing list and mass mailing contact
+ Indicates if a contact is opted out for a particular list
+ """
+ _name = 'mail.mass_mailing.list_contact_rel'
+ _description = 'Mass Mailing Subscription Information'
+ _table = 'mail_mass_mailing_contact_list_rel'
+ _rec_name = 'contact_id'
+
+ contact_id = fields.Many2one('mail.mass_mailing.contact', string='Contact', ondelete='cascade', required=True)
+ list_id = fields.Many2one('mail.mass_mailing.list', string='Mailing List', ondelete='cascade', required=True)
+ opt_out = fields.Boolean(string='Opt Out',
+ help='The contact has chosen not to receive mails anymore from this list', default=False)
+ unsubscription_date = fields.Datetime(string='Unsubscription Date')
+ contact_count = fields.Integer(related='list_id.contact_nbr', store=False)
+ message_bounce = fields.Integer(related='contact_id.message_bounce', store=False)
+ is_blacklisted = fields.Boolean(related='contact_id.is_blacklisted', store=False)
+
+ _sql_constraints = [
+ ('unique_contact_list', 'unique (contact_id, list_id)',
+ 'A contact cannot be subscribed multiple times to the same list!')
+ ]
+
+ @api.model
+ def create(self, vals):
+ if 'opt_out' in vals:
+ vals['unsubscription_date'] = vals['opt_out'] and fields.Datetime.now()
+ return super(MassMailingContactListRel, self).create(vals)
+
+ @api.multi
+ def write(self, vals):
+ if 'opt_out' in vals:
+ vals['unsubscription_date'] = vals['opt_out'] and fields.Datetime.now()
+ return super(MassMailingContactListRel, self).write(vals)
+
+ def action_open_mailing_list_contact(self):
+ contact_id = self.contact_id
+ action = {
+ 'name': _(contact_id.name),
+ 'type': 'ir.actions.act_window',
+ 'res_model': 'mail.mass_mailing.contact',
+ 'view_mode': 'form',
+ 'target': 'current',
+ 'res_id': contact_id.id
+ }
+ return action
+
class MassMailingList(models.Model):
"""Model of a contact list. """
@@ -52,8 +99,12 @@ class MassMailingList(models.Model):
contact_ids = fields.Many2many(
'mail.mass_mailing.contact', 'mail_mass_mailing_contact_list_rel', 'list_id', 'contact_id',
string='Mailing Lists')
+ subscription_contact_ids = fields.One2many('mail.mass_mailing.list_contact_rel', 'list_id',
+ string='Subscription Information')
+ is_public = fields.Boolean(default=True, help="The mailing list can be accessible by recipient in the unsubscription"
+ " page to allows him to update his subscription preferences.")
- # Compute number of contacts non opt-out for a mailing list
+ # Compute number of contacts non opt-out, non blacklisted and valid email recipient for a mailing list
def _compute_contact_nbr(self):
self.env.cr.execute('''
select
@@ -62,7 +113,9 @@ class MassMailingList(models.Model):
mail_mass_mailing_contact_list_rel r
left join mail_mass_mailing_contact c on (r.contact_id=c.id)
where
- c.opt_out <> true
+ COALESCE(r.opt_out,FALSE) = FALSE
+ AND c.email NOT IN (select email from mail_blacklist where active = TRUE)
+ AND substring(c.email, '([^ ,;<@]+@[^> ,;]+)') IS NOT NULL
group by
list_id
''')
@@ -70,6 +123,10 @@ class MassMailingList(models.Model):
for mailing_list in self:
mailing_list.contact_nbr = data.get(mailing_list.id, 0)
+ @api.multi
+ def name_get(self):
+ return [(list.id, "%s (%s)" % (list.name, list.contact_nbr)) for list in self]
+
@api.multi
def action_merge(self, src_lists, archive):
"""
@@ -113,7 +170,8 @@ class MassMailingList(models.Model):
mail_mass_mailing_contact_list_rel contact_list_rel,
mail_mass_mailing_list mailing_list
WHERE contact.id=contact_list_rel.contact_id
- AND contact.opt_out = FALSE
+ AND COALESCE(contact_list_rel.opt_out,FALSE) = FALSE
+ AND contact.email NOT IN (select email from mail_blacklist where active = TRUE)
AND mailing_list.id=contact_list_rel.list_id
AND mailing_list.id IN %s
AND NOT EXISTS
@@ -143,7 +201,7 @@ class MassMailingContact(models.Model):
be able to deal with large contact list to email without bloating the partner
base."""
_name = 'mail.mass_mailing.contact'
- _inherit = 'mail.thread'
+ _inherit = ['mail.thread', 'mail.blacklist.mixin']
_description = 'Mass Mailing Contact'
_order = 'email'
_rec_name = 'email'
@@ -156,25 +214,12 @@ class MassMailingContact(models.Model):
list_ids = fields.Many2many(
'mail.mass_mailing.list', 'mail_mass_mailing_contact_list_rel',
'contact_id', 'list_id', string='Mailing Lists')
- opt_out = fields.Boolean(string='Opt Out', help='The contact has chosen not to receive mails anymore from this list')
- unsubscription_date = fields.Datetime(string='Unsubscription Date')
+ subscription_list_ids = fields.One2many('mail.mass_mailing.list_contact_rel',
+ 'contact_id', string='Subscription Information')
message_bounce = fields.Integer(string='Bounced', help='Counter of the number of bounced emails for this contact.', default=0)
country_id = fields.Many2one('res.country', string='Country')
tag_ids = fields.Many2many('res.partner.category', string='Tags')
- @api.model_create_multi
- def create(self, vals_list):
- for vals in vals_list:
- if 'opt_out' in vals:
- vals['unsubscription_date'] = vals['opt_out'] and fields.Datetime.now()
- return super(MassMailingContact, self).create(vals_list)
-
- @api.multi
- def write(self, vals):
- if 'opt_out' in vals:
- vals['unsubscription_date'] = vals['opt_out'] and fields.Datetime.now()
- return super(MassMailingContact, self).write(vals)
-
def get_name_email(self, name):
name, email = self.env['res.partner']._parse_partner_name(name)
if name and not email:
@@ -246,6 +291,7 @@ class MassMailingCampaign(models.Model):
total = fields.Integer(compute="_compute_statistics")
scheduled = fields.Integer(compute="_compute_statistics")
failed = fields.Integer(compute="_compute_statistics")
+ ignored = fields.Integer(compute="_compute_statistics")
sent = fields.Integer(compute="_compute_statistics", string="Sent Emails")
delivered = fields.Integer(compute="_compute_statistics")
opened = fields.Integer(compute="_compute_statistics")
@@ -278,8 +324,9 @@ class MassMailingCampaign(models.Model):
c.id as campaign_id,
COUNT(s.id) AS total,
COUNT(CASE WHEN s.sent is not null THEN 1 ELSE null END) AS sent,
- COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is null THEN 1 ELSE null END) AS scheduled,
+ COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is null AND s.ignored is null THEN 1 ELSE null END) AS scheduled,
COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is not null THEN 1 ELSE null END) AS failed,
+ COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is null AND s.ignored is not null THEN 1 ELSE null END) AS ignored,
COUNT(CASE WHEN s.id is not null AND s.bounced is null THEN 1 ELSE null END) AS delivered,
COUNT(CASE WHEN s.opened is not null THEN 1 ELSE null END) AS opened,
COUNT(CASE WHEN s.replied is not null THEN 1 ELSE null END) AS replied ,
@@ -296,7 +343,7 @@ class MassMailingCampaign(models.Model):
""", (tuple(self.ids), ))
for row in self.env.cr.dictfetchall():
- total = row['total'] or 1
+ total = (row['total'] - row['ignored']) or 1
row['delivered'] = row['sent'] - row['bounced']
row['received_ratio'] = 100.0 * row['delivered'] / total
row['opened_ratio'] = 100.0 * row['opened'] / total
@@ -408,6 +455,7 @@ class MassMailing(models.Model):
total = fields.Integer(compute="_compute_total")
scheduled = fields.Integer(compute="_compute_statistics")
expected = fields.Integer(compute="_compute_statistics")
+ ignored = fields.Integer(compute="_compute_statistics")
sent = fields.Integer(compute="_compute_statistics")
delivered = fields.Integer(compute="_compute_statistics")
opened = fields.Integer(compute="_compute_statistics")
@@ -451,8 +499,9 @@ class MassMailing(models.Model):
m.id as mailing_id,
COUNT(s.id) AS expected,
COUNT(CASE WHEN s.sent is not null THEN 1 ELSE null END) AS sent,
- COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is null THEN 1 ELSE null END) AS scheduled,
+ COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is null AND s.ignored is null THEN 1 ELSE null END) AS scheduled,
COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is not null THEN 1 ELSE null END) AS failed,
+ COUNT(CASE WHEN s.scheduled is not null AND s.sent is null AND s.exception is null AND s.ignored is not null THEN 1 ELSE null END) AS ignored,
COUNT(CASE WHEN s.sent is not null AND s.bounced is null THEN 1 ELSE null END) AS delivered,
COUNT(CASE WHEN s.opened is not null THEN 1 ELSE null END) AS opened,
COUNT(CASE WHEN s.clicked is not null THEN 1 ELSE null END) AS clicked,
@@ -470,7 +519,7 @@ class MassMailing(models.Model):
m.id
""", (tuple(self.ids), ))
for row in self.env.cr.dictfetchall():
- total = row['expected'] or 1
+ total = row['expected'] = (row['expected'] - row['ignored']) or 1
row['received_ratio'] = 100.0 * row['delivered'] / total
row['opened_ratio'] = 100.0 * row['opened'] / total
row['clicks_ratio'] = 100.0 * row['clicked'] / total
@@ -555,14 +604,24 @@ class MassMailing(models.Model):
def _group_expand_states(self, states, domain, order):
return [key for key, val in type(self).state.selection]
- def update_opt_out(self, email, res_ids, value):
- model = self.env[self.mailing_model_real].with_context(active_test=False)
- if 'opt_out' in model._fields:
- email_fname = 'email_from'
- if 'email' in model._fields:
- email_fname = 'email'
- records = model.search([('id', 'in', res_ids), (email_fname, 'ilike', email)])
- records.write({'opt_out': value})
+ def update_opt_out(self, email, list_ids, value):
+ if len(list_ids) > 0:
+ model = self.env['mail.mass_mailing.contact'].with_context(active_test=False)
+ records = model.search([('email', '=ilike', email)])
+ opt_out_records = self.env['mail.mass_mailing.list_contact_rel'].search([
+ ('contact_id', 'in', records.ids),
+ ('list_id', 'in', list_ids),
+ ('opt_out', '!=', value)
+ ])
+
+ opt_out_records.write({'opt_out': value})
+ message = _('The recipient unsubscribed from %s mailing list(s)') \
+ if value else _('The recipient subscribed to %s mailing list(s)')
+ for record in records:
+ # filter the list_id by record
+ record_lists = opt_out_records.filtered(lambda rec: rec.contact_id.id == record.id)
+ if len(record_lists) > 0:
+ record.sudo().message_post(body=_(message % ', '.join(str(list.name) for list in record_lists.mapped('list_id'))))
#------------------------------------------------------
# Views & Actions
@@ -629,19 +688,16 @@ class MassMailing(models.Model):
self.ensure_one()
opt_out = {}
target = self.env[self.mailing_model_real]
- mail_field = 'email' if 'email' in target._fields else 'email_from'
- if 'opt_out' in target._fields:
- # avoid loading a large number of records in memory
- # + use a basic heuristic for extracting emails
- query = """
- SELECT lower(substring(%(mail_field)s, '([^ ,;<@]+@[^> ,;]+)'))
- FROM %(target)s
- WHERE opt_out AND
- substring(%(mail_field)s, '([^ ,;<@]+@[^> ,;]+)') IS NOT NULL;
- """
- query = query % {'target': target._table, 'mail_field': mail_field}
- self._cr.execute(query)
- opt_out = set(m[0] for m in self._cr.fetchall())
+ if self.mailing_model_real == "mail.mass_mailing.contact":
+ # if user is opt_out on One list but not on another
+ # or if two user with same email address, one opted in and the other one opted out, send the mail anyway
+ # TODO DBE Fixme : Optimise the following to get real opt_out and opt_in
+ target_list_contacts = self.env['mail.mass_mailing.list_contact_rel'].search(
+ [('list_id', 'in', self.contact_list_ids.ids)])
+ opt_out_contacts = target_list_contacts.filtered(lambda rel: rel.opt_out).mapped('contact_id.email')
+ opt_in_contacts = target_list_contacts.filtered(lambda rel: not rel.opt_out).mapped('contact_id.email')
+ opt_out = set(c for c in opt_out_contacts if c not in opt_in_contacts)
+
_logger.info(
"Mass-mailing %s targets %s, blacklist: %s emails",
self, target._name, len(opt_out))
diff --git a/addons/mass_mailing/models/mass_mailing_stats.py b/addons/mass_mailing/models/mass_mailing_stats.py
index 0a095943f99..190ed75805d 100644
--- a/addons/mass_mailing/models/mass_mailing_stats.py
+++ b/addons/mass_mailing/models/mass_mailing_stats.py
@@ -33,6 +33,8 @@ class MailMailStats(models.Model):
string='Mass Mailing Campaign',
store=True, readonly=True, index=True)
# Bounce and tracking
+ ignored = fields.Datetime(help='Date when the email has been invalidated. '
+ 'Invalid emails are blacklisted, opted-out or invalid email format')
scheduled = fields.Datetime(help='Date when the email has been created', default=fields.Datetime.now)
sent = fields.Datetime(help='Date when the email has been sent')
exception = fields.Datetime(help='Date of technical error leading to the email not being sent')
@@ -49,17 +51,20 @@ class MailMailStats(models.Model):
('sent', 'Sent'),
('opened', 'Opened'),
('replied', 'Replied'),
- ('bounced', 'Bounced')], store=True)
+ ('bounced', 'Bounced'),
+ ('ignored', 'Ignored')], store=True)
state_update = fields.Datetime(compute="_compute_state", string='State Update',
help='Last state update of the mail',
store=True)
recipient = fields.Char(compute="_compute_recipient")
- @api.depends('sent', 'opened', 'clicked', 'replied', 'bounced', 'exception')
+ @api.depends('sent', 'opened', 'clicked', 'replied', 'bounced', 'exception', 'ignored')
def _compute_state(self):
self.update({'state_update': fields.Datetime.now()})
for stat in self:
- if stat.exception:
+ if stat.ignored:
+ stat.state = 'ignored'
+ elif stat.exception:
stat.state = 'exception'
elif stat.sent:
stat.state = 'sent'
diff --git a/addons/mass_mailing/models/res_config_settings.py b/addons/mass_mailing/models/res_config_settings.py
index aedcef27d69..467d6560686 100644
--- a/addons/mass_mailing/models/res_config_settings.py
+++ b/addons/mass_mailing/models/res_config_settings.py
@@ -11,6 +11,9 @@ class ResConfigSettings(models.TransientModel):
mass_mailing_outgoing_mail_server = fields.Boolean(string="Specific Mail Server", config_parameter='mass_mailing.outgoing_mail_server',
help='Use a specific mail server in priority. Otherwise Odoo relies on the first outgoing mail server available (based on their sequencing) as it does for normal mails.')
mass_mailing_mail_server_id = fields.Many2one('ir.mail_server', string='Mail Server', config_parameter='mass_mailing.mail_server_id')
+ show_blacklist_buttons = fields.Boolean(string="Show blacklist buttons on unsubscribe page",
+ config_parameter='mass_mailing.show_blacklist_buttons',
+ help="""Allow the recipient to manage himself his state in the blacklist via the unsubscription page.""")
@api.onchange('mass_mailing_outgoing_mail_server')
def _onchange_mass_mailing_outgoing_mail_server(self):
diff --git a/addons/mass_mailing/security/ir.model.access.csv b/addons/mass_mailing/security/ir.model.access.csv
index 39f87789271..ec1b467a73f 100644
--- a/addons/mass_mailing/security/ir.model.access.csv
+++ b/addons/mass_mailing/security/ir.model.access.csv
@@ -13,3 +13,4 @@ access_mail_statistics_report,mail.statistics.report,model_mail_statistics_repor
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
+access_mail_mass_mailing_list_contact_rel,access.mail.mass_mailing_list_contact_rel,model_mail_mass_mailing_list_contact_rel,mass_mailing.group_mass_mailing_user,1,1,1,1
diff --git a/addons/mass_mailing/static/src/js/unsubscribe.js b/addons/mass_mailing/static/src/js/unsubscribe.js
new file mode 100644
index 00000000000..5da1a195dd5
--- /dev/null
+++ b/addons/mass_mailing/static/src/js/unsubscribe.js
@@ -0,0 +1,192 @@
+odoo.define('mass_mailing.unsubscribe', function (require) {
+ 'use strict';
+
+ var ajax = require('web.ajax');
+ var core = require('web.core');
+ require('web.dom_ready');
+
+ var _t = core._t;
+
+ var email = $("input[name='email']").val();
+ var mailing_id = parseInt($("input[name='mailing_id']").val());
+ var res_id = parseInt($("input[name='res_id']").val());
+ var token = (location.search.split('token' + '=')[1] || '').split('&')[0];
+
+ if (!$('.o_unsubscribe_form').length) {
+ return $.Deferred().reject("DOM doesn't contain '.o_unsubscribe_form'");
+ }
+ if (email != '' && email != undefined){
+ ajax.jsonRpc('/mailing/blacklist/check', 'call', {'email': email, 'mailing_id': mailing_id, 'res_id': res_id, 'token': token})
+ .then(function (result) {
+ if (result == 'unauthorized'){
+ $('#button_add_blacklist').hide();
+ $('#button_remove_blacklist').hide();
+ }
+ else if (result == true) {
+ $('#button_remove_blacklist').show();
+ toggle_opt_out_section(false);
+ }
+ else if (result == false) {
+ $('#button_add_blacklist').show();
+ toggle_opt_out_section(true);
+ }
+ else {
+ $('#subscription_info').html(_t('An error occured. Please try again later or contact us.'));
+ $('#info_state').removeClass('alert-success').removeClass('alert-info').removeClass('alert-warning').addClass('alert-error');
+ }
+ })
+ .fail(function () {
+ $('#subscription_info').html(_t('An error occured. Please try again later or contact us.'));
+ $('#info_state').removeClass('alert-success').removeClass('alert-info').removeClass('alert-warning').addClass('alert-error');
+ });
+ }
+ else {
+ $('#div_blacklist').hide();
+ }
+
+ var unsubscribed_list = $("input[name='unsubscribed_list']").val();
+ if (unsubscribed_list){
+ $('#subscription_info').html(_t('You have been successfully unsubscribed from ' + unsubscribed_list + " ."));
+ }
+ else{
+ $('#subscription_info').html(_t('You have been successfully unsubscribed .'));
+ }
+
+ $('#unsubscribe_form').on('submit', function (e) {
+ e.preventDefault();
+
+ var checked_ids = [];
+ $("input[type='checkbox']:checked").each(function (i){
+ checked_ids[i] = parseInt($(this).val());
+ });
+
+ var unchecked_ids = [];
+ $("input[type='checkbox']:not(:checked)").each(function (i){
+ unchecked_ids[i] = parseInt($(this).val());
+ });
+
+ ajax.jsonRpc('/mail/mailing/unsubscribe', 'call', {'opt_in_ids': checked_ids, 'opt_out_ids': unchecked_ids, 'email': email, 'mailing_id': mailing_id, 'res_id': res_id, 'token': token})
+ .then(function (result) {
+ if (result == 'unauthorized'){
+ $('#subscription_info').html(_t('You are not authorized to do this!'));
+ $('#info_state').removeClass('alert-success').removeClass('alert-info').removeClass('alert-error').addClass('alert-warning');
+ }
+ else if (result == true) {
+ $('#subscription_info').html(_t('Your changes have been saved.'));
+ $('#info_state').removeClass('alert-info').addClass('alert-success');
+ }
+ else {
+ $('#subscription_info').html(_t('An error occurred. Your changes have not been saved, try again later.'));
+ $('#info_state').removeClass('alert-info').addClass('alert-warning');
+ }
+ })
+ .fail(function () {
+ $('#subscription_info').html(_t('An error occurred. Your changes have not been saved, try again later.'));
+ $('#info_state').removeClass('alert-info').addClass('alert-warning');
+ });
+ });
+
+ // ==================
+ // Blacklist
+ // ==================
+ $('#button_add_blacklist').click(function (e) {
+ e.preventDefault();
+
+ ajax.jsonRpc('/mailing/blacklist/add', 'call', {'email': email, 'mailing_id': mailing_id, 'res_id': res_id, 'token': token})
+ .then(function (result) {
+ if (result == 'unauthorized'){
+ $('#subscription_info').html(_t('You are not authorized to do this!'));
+ $('#info_state').removeClass('alert-success').removeClass('alert-info').removeClass('alert-error').addClass('alert-warning');
+ }
+ else
+ {
+ if (result) {
+ $('#subscription_info').html(_t('You have been successfully added to our blacklist . '
+ + 'You will not be contacted anymore by our services.'));
+ $('#info_state').removeClass('alert-warning').removeClass('alert-info').removeClass('alert-error').addClass('alert-success');
+ toggle_opt_out_section(false);
+ }
+ else {
+ $('#subscription_info').html(_t('An error occured. Please try again later or contact us.'));
+ $('#info_state').removeClass('alert-success').removeClass('alert-info').removeClass('alert-warning').addClass('alert-error');
+ }
+ $('#button_add_blacklist').hide();
+ $('#button_remove_blacklist').show();
+ $('#unsubscribed_info').hide();
+ }
+ })
+ .fail(function () {
+ $('#subscription_info').html(_t('An error occured. Please try again later or contact us.'));
+ $('#info_state').removeClass('alert-success').removeClass('alert-info').removeClass('alert-warning').addClass('alert-error');
+ });
+ });
+
+ $('#button_remove_blacklist').click(function (e) {
+ e.preventDefault();
+
+ ajax.jsonRpc('/mailing/blacklist/remove', 'call', {'email': email, 'mailing_id': mailing_id, 'res_id': res_id, 'token': token})
+ .then(function (result) {
+ if (result == 'unauthorized'){
+ $('#subscription_info').html(_t('You are not authorized to do this!'));
+ $('#info_state').removeClass('alert-success').removeClass('alert-info').removeClass('alert-error').addClass('alert-warning');
+ }
+ else
+ {
+ if (result) {
+ $('#subscription_info').html(_t("You have been successfully removed from our blacklist . "
+ + "You are now able to be contacted by our services."));
+ $('#info_state').removeClass('alert-warning').removeClass('alert-info').removeClass('alert-error').addClass('alert-success');
+ toggle_opt_out_section(true);
+ }
+ else {
+ $('#subscription_info').html(_t('An error occured. Please try again later or contact us.'));
+ $('#info_state').removeClass('alert-success').removeClass('alert-info').removeClass('alert-warning').addClass('alert-error');
+ }
+ $('#button_add_blacklist').show();
+ $('#button_remove_blacklist').hide();
+ $('#unsubscribed_info').hide();
+ }
+ })
+ .fail(function () {
+ $('#subscription_info').html(_t('An error occured. Please try again later or contact us.'));
+ $('#info_state').removeClass('alert-success').removeClass('alert-info').removeClass('alert-warning').addClass('alert-error');
+ });
+ });
+
+ // ==================
+ // Feedback
+ // ==================
+ $('#button_feedback').click(function (e) {
+ var feedback = $("textarea[name='opt_out_feedback']").val();
+ e.preventDefault();
+ ajax.jsonRpc('/mailing/feedback', 'call', {'mailing_id': mailing_id, 'res_id': res_id, 'email': email, 'feedback': feedback, 'token': token})
+ .then(function (result) {
+ if (result == 'unauthorized'){
+ $('#subscription_info').html(_t('You are not authorized to do this!'));
+ $('#info_state').removeClass('alert-success').removeClass('alert-info').removeClass('alert-error').addClass('alert-warning');
+ }
+ else if (result == true){
+ $('#subscription_info').html(_t('Thank you! Your feedback has been sent successfully!'));
+ $('#info_state').removeClass('alert-warning').removeClass('alert-info').removeClass('alert-error').addClass('alert-success');
+ $("#div_feedback").hide();
+ }
+ else {
+ $('#subscription_info').html(_t('An error occured. Please try again later or contact us.'));
+ $('#info_state').removeClass('alert-success').removeClass('alert-info').removeClass('alert-error').addClass('alert-warning');
+ }
+ })
+ .fail(function () {
+ $('#subscription_info').html(_t('An error occured. Please try again later or contact us.'));
+ $('#info_state').removeClass('alert-info').removeClass('alert-success').removeClass('alert-error').addClass('alert-warning');
+ });
+ });
+});
+
+function toggle_opt_out_section(value) {
+ var result = !value;
+ $("#div_opt_out").find('*').attr('disabled',result);
+ $("#button_add_blacklist").attr('disabled', false);
+ $("#button_remove_blacklist").attr('disabled', false);
+ if (value) { $('[name="button_subscription"]').addClass('clickable'); }
+ else { $('[name="button_subscription"]').removeClass('clickable'); }
+}
diff --git a/addons/mass_mailing/static/src/scss/mass_mailing.scss b/addons/mass_mailing/static/src/scss/mass_mailing.scss
index 24b1bd08615..e13cea29676 100644
--- a/addons/mass_mailing/static/src/scss/mass_mailing.scss
+++ b/addons/mass_mailing/static/src/scss/mass_mailing.scss
@@ -14,3 +14,7 @@
}
}
}
+
+.o_white_body {
+ background-color: white;
+}
diff --git a/addons/mass_mailing/tests/__init__.py b/addons/mass_mailing/tests/__init__.py
index 2c4f6d291f4..9c7151327be 100644
--- a/addons/mass_mailing/tests/__init__.py
+++ b/addons/mass_mailing/tests/__init__.py
@@ -3,3 +3,7 @@
from . import test_mass_mailing_list_merge
from . import test_mass_mailing_content
+from . import test_mail_auto_blacklist
+from . import test_blacklist_access_rights
+from . import test_mass_mailing
+from . import test_blacklisted_channel_partner
diff --git a/addons/mass_mailing/tests/test_blacklist_access_rights.py b/addons/mass_mailing/tests/test_blacklist_access_rights.py
new file mode 100644
index 00000000000..a9c74bf5c62
--- /dev/null
+++ b/addons/mass_mailing/tests/test_blacklist_access_rights.py
@@ -0,0 +1,142 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo.exceptions import AccessError
+from odoo.tests import common
+
+
+class TestAccessRights(common.TransactionCase):
+
+ def setUp(self):
+ super(TestAccessRights, self).setUp()
+
+ users = self.env['res.users'].with_context(no_reset_password=True)
+ partners = self.env['res.partner'].with_context(no_reset_password=True)
+ mass_mailing_contacts = self.env['mail.mass_mailing.contact'].with_context(no_reset_password=True)
+
+ group_user = self.env.ref('mass_mailing.group_mass_mailing_user')
+
+ # Clear blacklist
+ blacklist = self.env['mail.blacklist'].sudo().search([])
+ i = len(blacklist)
+ while i != 0:
+ blacklist[i-1].sudo().unlink()
+ i -= 1
+
+ # Create users
+ self.user_mm_user = users.create({
+ 'name': 'Andrew User',
+ 'login': 'user',
+ 'email': 'a.m@example.com',
+ 'groups_id': [(6, 0, [group_user.id])]
+ })
+ self.user_employee = users.create({
+ 'name': 'Bert Tartignole',
+ 'login': 'bert',
+ 'email': 'b.t@example.com',
+ 'groups_id': [(6, 0, [self.env.ref('base.group_user').id])]
+ })
+ self.user_portal = users.create({
+ 'name': 'Chell Gladys',
+ 'login': 'chell',
+ 'email': 'chell@gladys.portal',
+ 'groups_id': [(6, 0, [self.env.ref('base.group_portal').id])]
+ })
+ self.contact_blacklisted = mass_mailing_contacts.create({
+ 'name': 'Jack Black',
+ 'email': 'contact@black.list'
+ })
+ self.partner_blacklisted = partners.create({
+ 'name': 'Joe Black',
+ 'email': 'test@black.list'
+ })
+
+ # Create a blacklist entry
+ self.blacklist_entry = self.env['mail.blacklist'].create({
+ 'email': 'test@black.list',
+ })
+ self.blacklist_contact_entry = self.env['mail.blacklist'].create({
+ 'email': 'contact@black.list',
+ })
+
+ def test_access_mass_mailing_user(self):
+ """ Test Mass Mailing user's access rights """
+ # ===================
+ # Test on res_partner
+ # ===================
+ # mass mailing user can see a blacklist entry
+ self.blacklist_entry.sudo(self.user_mm_user).read()
+ # mass mailing user can edit a blacklist entry
+ self.blacklist_entry.sudo(self.user_mm_user).write({'email': 'edit@black.list'})
+ # mass mailing user can create a blacklist entry
+ self.env['mail.blacklist'].sudo(self.user_mm_user).create({
+ 'email': 'another@black.list',
+ })
+ self.assertTrue(len(self.env['mail.blacklist'].search([])) == 3,
+ 'Number of blacklisted address incorrect, should be equals to 3')
+ # mass mailing user can delete a blacklist entry
+ self.blacklist_entry.sudo(self.user_mm_user).unlink()
+ self.assertTrue(len(self.env['mail.blacklist'].search([])) == 2,
+ 'Number of blacklisted address incorrect, should be equals to 2')
+
+ # mass mailing user can see if user is balcklisted using mixin
+ self.assertFalse(self.partner_blacklisted.sudo(self.user_mm_user).is_blacklisted,
+ 'Blacklist state incorrect. Should not be blacklisted.')
+
+ # ============================
+ # Test on mass mailing contact
+ # ============================
+ self.blacklist_contact_entry.sudo(self.user_mm_user).read()
+ self.blacklist_contact_entry.sudo(self.user_mm_user).write({'email': 'edit@black.list'})
+ self.env['mail.blacklist'].sudo(self.user_mm_user).create({
+ 'email': 'again_another@black.list',
+ })
+ self.assertTrue(len(self.env['mail.blacklist'].search([])) == 3,
+ 'Number of blacklisted address incorrect, should be equals to 3')
+ self.blacklist_contact_entry.sudo(self.user_mm_user).unlink()
+ self.assertTrue(len(self.env['mail.blacklist'].search([])) == 2,
+ 'Number of blacklisted address incorrect, should be equals to 2')
+
+ def test_access_portal_user(self):
+ """ Test portal user's access rights """
+ # Portal user can't see blacklist entries
+ with self.assertRaises(AccessError):
+ self.blacklist_entry.sudo(self.user_portal).read()
+ # Portal user can't edit a blacklist entry
+ with self.assertRaises(AccessError):
+ self.blacklist_entry.sudo(self.user_portal).write({'email': 'test@edit.be'})
+ # Portal user can't create a blacklist entry
+ with self.assertRaises(AccessError):
+ self.env['mail.blacklist'].sudo(self.user_portal).create({
+ 'email': 'another@black.list',
+ })
+ # Portal user can't delete a blacklist entry
+ with self.assertRaises(AccessError):
+ self.blacklist_entry.sudo(self.user_portal).unlink()
+
+ # Portal user can't see if user is balcklisted using mixin
+ with self.assertRaises(AccessError):
+ self.partner_blacklisted.sudo(self.user_portal).is_blacklisted()
+ self.assertTrue(len(self.env['mail.blacklist'].search([])) == 2,
+ 'Number of blacklisted address incorrect, should be equals to 2')
+
+ def test_access_employee(self):
+ """ Test classic employee's access rights """
+ # Employee can't see blacklist entries
+ with self.assertRaises(AccessError):
+ self.blacklist_entry.sudo(self.user_employee).read()
+ # Employee can't edit a blacklist entry
+ with self.assertRaises(AccessError):
+ self.blacklist_entry.sudo(self.user_employee).write({'email': 'test@edit.be'})
+ # Employee can't create a blacklist entry
+ with self.assertRaises(AccessError):
+ self.env['mail.blacklist'].sudo(self.user_employee).create({
+ 'email': 'another@black.list',
+ })
+ # Employee can't delete a blacklist entry
+ with self.assertRaises(AccessError):
+ self.blacklist_entry.sudo(self.user_employee).unlink()
+
+ # Employee can see if user is balcklisted using mixin
+ self.assertTrue(self.partner_blacklisted.sudo(self.user_employee).is_blacklisted,
+ 'Blacklist state incorrect. Should be blacklisted.')
diff --git a/addons/mass_mailing/tests/test_blacklisted_channel_partner.py b/addons/mass_mailing/tests/test_blacklisted_channel_partner.py
new file mode 100644
index 00000000000..9d285f42d47
--- /dev/null
+++ b/addons/mass_mailing/tests/test_blacklisted_channel_partner.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from email.utils import formataddr
+from odoo.addons.test_mail.tests import common
+
+
+class TestChannelPartnersNotification(common.MockEmails):
+
+ def _join_channel(self, channel, partners):
+ for partner in partners:
+ channel.write({'channel_last_seen_partner_ids': [(0, 0, {'partner_id': partner.id})]})
+ channel.invalidate_cache()
+
+ def test_channel_blacklisted_recipients(self):
+ """ Posting a message on a channel should send one email to all recipients, except the blacklisted ones """
+
+ self.test_channel = self.env['mail.channel'].create({
+ 'name': 'Test',
+ 'description': 'Description',
+ 'alias_name': 'test',
+ 'public': 'public',
+ })
+ self.test_partner = self.env['res.partner'].create({
+ 'name': 'Test Partner',
+ 'email': 'test@example.com',
+ })
+
+ self.blacklisted_partner = self.env['res.partner'].create({
+ 'name': 'Blacklisted Partner',
+ 'email': 'test@black.list',
+ })
+
+ # Set Blacklist
+ self.env['mail.blacklist'].create({
+ 'email': 'test@black.list',
+ })
+
+ self.env['ir.config_parameter'].set_param('mail.catchall.domain', 'schlouby.fr')
+ self.test_channel.write({'email_send': True})
+ self._join_channel(self.test_channel, self.test_partner)
+ self.test_channel.message_post(body="Test", message_type='comment', subtype='mt_comment')
+
+ self.assertEqual(len(self._mails), 1, 'Number of mail incorrect. Should be equal to 1.')
+ for email in self._mails:
+ self.assertEqual(
+ set(email['email_to']),
+ set([formataddr((self.test_partner.name, self.test_partner.email))]),
+ 'email_to incorrect. Should be equal to "%s"' % (
+ formataddr((self.test_partner.name, self.test_partner.email))))
diff --git a/addons/mass_mailing/tests/test_mass_mailing.py b/addons/mass_mailing/tests/test_mass_mailing.py
new file mode 100644
index 00000000000..3b956ae0c4e
--- /dev/null
+++ b/addons/mass_mailing/tests/test_mass_mailing.py
@@ -0,0 +1,251 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from odoo.tests import common
+
+
+class TestAccessRights(common.TransactionCase):
+
+ def test_01_mass_mail_blacklist(self):
+ mass_mailing_contacts = self.env['mail.mass_mailing.contact']
+ mass_mailing = self.env['mail.mass_mailing']
+ mail_blacklist = self.env['mail.blacklist']
+
+ # create mailing contact record
+ self.mailing_contact_1 = mass_mailing_contacts.create({'name': 'test email 1', 'email': 'test1@email.com'})
+ self.mailing_contact_2 = mass_mailing_contacts.create({'name': 'test email 2', 'email': 'test2@email.com'})
+ self.mailing_contact_3 = mass_mailing_contacts.create({'name': 'test email 3', 'email': 'test3@email.com'})
+ self.mailing_contact_4 = mass_mailing_contacts.create({'name': 'test email 4', 'email': 'test4@email.com'})
+ self.mailing_contact_5 = mass_mailing_contacts.create({'name': 'test email 5', 'email': 'test5@email.com'})
+
+ # create blacklist record
+ mail_blacklist.create({'email': self.mailing_contact_3.email})
+ mail_blacklist.create({'email': self.mailing_contact_4.email})
+
+ # create mass mailing record
+ self.mass_mailing = mass_mailing.create({
+ 'name': 'test',
+ 'mailing_domain': [('id', 'in',
+ [self.mailing_contact_1.id, self.mailing_contact_2.id, self.mailing_contact_3.id,
+ self.mailing_contact_4.id, self.mailing_contact_5.id])],
+ 'body_html': 'This is mass mail marketing demo'})
+ self.mass_mailing.put_in_queue()
+ res_ids = self.mass_mailing.get_remaining_recipients()
+ composer_values = {
+ 'body': self.mass_mailing.convert_links()[self.mass_mailing.id],
+ 'subject': self.mass_mailing.name,
+ 'model': self.mass_mailing.mailing_model_real,
+ 'email_from': self.mass_mailing.email_from,
+ 'composition_mode': 'mass_mail',
+ 'mass_mailing_id': self.mass_mailing.id,
+ 'mailing_list_ids': [(4, l.id) for l in self.mass_mailing.contact_list_ids],
+ }
+ composer = self.env['mail.compose.message'].with_context(active_ids=res_ids,
+ mass_mailing_seen_list=self.mass_mailing._get_seen_list()).create(
+ composer_values)
+ composer.send_mail()
+ self.assertEqual(self.mass_mailing.ignored, 2,
+ 'blacklist ignored email number incorrect, should be equals to 2')
+
+ def test_02_mass_mail_simple_opt_out(self):
+ mass_mailing_contacts = self.env['mail.mass_mailing.contact']
+ mass_mailing_lists = self.env['mail.mass_mailing.list']
+ mass_mailing_optout = self.env['mail.mass_mailing.list_contact_rel']
+ mass_mailing = self.env['mail.mass_mailing']
+
+ # create mailing contact record
+ mailing_contact_1 = mass_mailing_contacts.create({'name': 'test email 1', 'email': 'test1@email.com'})
+ mailing_contact_2 = mass_mailing_contacts.create({'name': 'test email 2', 'email': 'test2@email.com'})
+
+ # create mailing list record
+ mailing_list_1 = mass_mailing_lists.create({
+ 'name': 'A',
+ 'contact_ids': [
+ (4, mailing_contact_1.id),
+ (4, mailing_contact_2.id)
+ ]
+ })
+
+ # Set Opt out
+ mass_mailing_optout.search([('contact_id', '=', mailing_contact_1.id), ('list_id', '=', mailing_list_1.id)]).write({
+ 'opt_out': True,
+ })
+
+ # create mass mailing record
+ self.mass_mailing = mass_mailing.create({
+ 'name': 'One',
+ 'mailing_model_id': self.env['ir.model']._get(mass_mailing_contacts).id,
+ 'body_html': 'This is mass mail marketing demo'})
+ self.mass_mailing.contact_list_ids = [mailing_list_1.id]
+ self.mass_mailing.put_in_queue()
+ res_ids = self.mass_mailing.get_remaining_recipients()
+ composer_values = {
+ 'body': self.mass_mailing.convert_links()[self.mass_mailing.id],
+ 'subject': self.mass_mailing.name,
+ 'model': self.mass_mailing.mailing_model_real,
+ 'email_from': self.mass_mailing.email_from,
+ 'composition_mode': 'mass_mail',
+ 'mass_mailing_id': self.mass_mailing.id,
+ 'mailing_list_ids': [(4, l.id) for l in self.mass_mailing.contact_list_ids],
+ }
+ composer = self.env['mail.compose.message'].with_context(
+ active_ids=res_ids,
+ mass_mailing_seen_list=self.mass_mailing._get_seen_list(),
+ mass_mailing_opt_out_list=self.mass_mailing._get_opt_out_list()).create(composer_values)
+ composer.send_mail()
+ self.assertEqual(self.mass_mailing.ignored, 1,
+ 'Opt Out ignored email number incorrect, should be equals to 1')
+
+ def test_03_mass_mail_multi_opt_out(self):
+ mass_mailing_contacts = self.env['mail.mass_mailing.contact']
+ mass_mailing_lists = self.env['mail.mass_mailing.list']
+ mass_mailing_optout = self.env['mail.mass_mailing.list_contact_rel']
+ mass_mailing = self.env['mail.mass_mailing']
+
+ # create mailing contact record
+ mailing_contact_1 = mass_mailing_contacts.create({'name': 'test email 1', 'email': 'test1@email.com'})
+ mailing_contact_2 = mass_mailing_contacts.create({'name': 'test email 2', 'email': 'test2@email.com'})
+
+ # create mailing list record
+ mailing_list_1 = mass_mailing_lists.create({
+ 'name': 'A',
+ 'contact_ids': [
+ (4, mailing_contact_1.id),
+ (4, mailing_contact_2.id)
+ ]
+ })
+ mailing_list_2 = mass_mailing_lists.create({
+ 'name': 'B',
+ 'contact_ids': [
+ (4, mailing_contact_1.id),
+ (4, mailing_contact_2.id)
+ ]
+ })
+
+ # Set Opt out
+ mass_mailing_optout.search([('contact_id', '=', mailing_contact_1.id), ('list_id', '=', mailing_list_1.id)]).write({
+ 'opt_out': True,
+ })
+
+ # create mass mailing record
+ self.mass_mailing = mass_mailing.create({
+ 'name': 'One',
+ 'mailing_model_id': self.env['ir.model']._get(mass_mailing_contacts).id,
+ 'body_html': 'This is mass mail marketing demo'})
+ self.mass_mailing.contact_list_ids = [mailing_list_1.id, mailing_list_2.id]
+ self.mass_mailing.put_in_queue()
+ res_ids = self.mass_mailing.get_remaining_recipients()
+ composer_values = {
+ 'body': self.mass_mailing.convert_links()[self.mass_mailing.id],
+ 'subject': self.mass_mailing.name,
+ 'model': self.mass_mailing.mailing_model_real,
+ 'email_from': self.mass_mailing.email_from,
+ 'composition_mode': 'mass_mail',
+ 'mass_mailing_id': self.mass_mailing.id,
+ 'mailing_list_ids': [(4, l.id) for l in self.mass_mailing.contact_list_ids],
+ }
+ composer = self.env['mail.compose.message'].with_context(
+ active_ids=res_ids,
+ mass_mailing_seen_list=self.mass_mailing._get_seen_list(),
+ mass_mailing_opt_out_list=self.mass_mailing._get_opt_out_list()).create(composer_values)
+ composer.send_mail()
+ # if user is opt_out on One list but not on another, send the mail anyway
+ self.assertEqual(self.mass_mailing.ignored, 0,
+ 'Opt Out ignored email number incorrect, should be equals to 0')
+
+ def test_04_mass_mail_multi_users_different_opt_out(self):
+ mass_mailing_contacts = self.env['mail.mass_mailing.contact']
+ mass_mailing_lists = self.env['mail.mass_mailing.list']
+ mass_mailing_optout = self.env['mail.mass_mailing.list_contact_rel']
+ mass_mailing = self.env['mail.mass_mailing']
+
+ # create mailing contact record
+ mailing_contact_1 = mass_mailing_contacts.create({'name': 'test email (A)', 'email': 'test@email.com'})
+ mailing_contact_2 = mass_mailing_contacts.create({'name': 'test email (B)', 'email': 'test@email.com'})
+
+ # create mailing list record
+ mailing_list_1 = mass_mailing_lists.create({
+ 'name': 'A',
+ 'contact_ids': [
+ (4, mailing_contact_1.id),
+ (4, mailing_contact_2.id)
+ ]
+ })
+
+ # Set Opt out
+ mass_mailing_optout.search([('contact_id', '=', mailing_contact_1.id), ('list_id', '=', mailing_list_1.id)]).write({
+ 'opt_out': True,
+ })
+
+ # create mass mailing record
+ self.mass_mailing = mass_mailing.create({
+ 'name': 'One',
+ 'mailing_model_id': self.env['ir.model']._get(mass_mailing_contacts).id,
+ 'body_html': 'This is mass mail marketing demo'})
+ self.mass_mailing.contact_list_ids = [mailing_list_1.id]
+ self.mass_mailing.put_in_queue()
+ res_ids = self.mass_mailing.get_remaining_recipients()
+ composer_values = {
+ 'body': self.mass_mailing.convert_links()[self.mass_mailing.id],
+ 'subject': self.mass_mailing.name,
+ 'model': self.mass_mailing.mailing_model_real,
+ 'email_from': self.mass_mailing.email_from,
+ 'composition_mode': 'mass_mail',
+ 'mass_mailing_id': self.mass_mailing.id,
+ 'mailing_list_ids': [(4, l.id) for l in self.mass_mailing.contact_list_ids],
+ }
+ composer = self.env['mail.compose.message'].with_context(
+ active_ids=res_ids,
+ mass_mailing_seen_list=self.mass_mailing._get_seen_list(),
+ mass_mailing_opt_out_list=self.mass_mailing._get_opt_out_list()).create(composer_values)
+ composer.send_mail()
+ # if user A is opt_out on One list but user B is not opt_out on same list,
+ # send the mail anyway to the opt-in contact. Ignore (as normal behaviour) opted-out contact.
+ self.assertEqual(self.mass_mailing.ignored, 1,
+ 'Opt Out ignored email number incorrect, should be equals to 1')
+
+
+class TestOnResPartner(common.TransactionCase):
+
+ def test_mass_mail_on_res_partner(self):
+ partners = self.env['res.partner']
+ mass_mailing = self.env['mail.mass_mailing']
+
+ # create mailing contact record
+ partner_a = partners.create({
+ 'name': 'test email 1',
+ 'email': 'test1@email.com',
+ })
+ partner_b = partners.create({
+ 'name': 'test email 2',
+ 'email': 'test2@email.com',
+ })
+
+ # Set Blacklist
+ self.blacklist_contact_entry = self.env['mail.blacklist'].create({
+ 'email': 'test2@email.com',
+ })
+
+ # create mass mailing record
+ self.mass_mailing = mass_mailing.create({
+ 'name': 'One',
+ 'mailing_domain': [('id', 'in', [partner_a.id, partner_b.id])],
+ 'body_html': 'This is mass mail marketing demo'})
+ self.mass_mailing.mailing_model_real = 'res.partner'
+ self.mass_mailing.put_in_queue()
+ res_ids = self.mass_mailing.get_remaining_recipients()
+ composer_values = {
+ 'body': self.mass_mailing.convert_links()[self.mass_mailing.id],
+ 'subject': self.mass_mailing.name,
+ 'model': self.mass_mailing.mailing_model_real,
+ 'email_from': self.mass_mailing.email_from,
+ 'composition_mode': 'mass_mail',
+ 'mass_mailing_id': self.mass_mailing.id,
+ 'mailing_list_ids': [(4, l.id) for l in self.mass_mailing.contact_list_ids],
+ }
+ composer = self.env['mail.compose.message'].with_context(
+ active_ids=res_ids,
+ mass_mailing_seen_list=self.mass_mailing._get_seen_list()).create(composer_values)
+ composer.send_mail()
+ # if user is opt_out on One list but not on another, send the mail anyway
+ self.assertEqual(self.mass_mailing.ignored, 1,
+ 'Blacklisted ignored email number incorrect, should be equals to 1')
diff --git a/addons/mass_mailing/views/mass_mailing_stats_views.xml b/addons/mass_mailing/views/mass_mailing_stats_views.xml
index 0090a92f214..a547d79f03b 100644
--- a/addons/mass_mailing/views/mass_mailing_stats_views.xml
+++ b/addons/mass_mailing/views/mass_mailing_stats_views.xml
@@ -13,6 +13,7 @@
+
diff --git a/addons/mass_mailing/views/mass_mailing_template.xml b/addons/mass_mailing/views/mass_mailing_template.xml
index 54c82eab9de..9d965e53de0 100644
--- a/addons/mass_mailing/views/mass_mailing_template.xml
+++ b/addons/mass_mailing/views/mass_mailing_template.xml
@@ -5,6 +5,7 @@
+
diff --git a/addons/mass_mailing/views/mass_mailing_views.xml b/addons/mass_mailing/views/mass_mailing_views.xml
index 3b62b1a34c7..c636a1d1bc7 100644
--- a/addons/mass_mailing/views/mass_mailing_views.xml
+++ b/addons/mass_mailing/views/mass_mailing_views.xml
@@ -12,20 +12,68 @@
parent="mass_mailing_menu_root" sequence="100"/>
-
-
-
- List Subscription
- mail.mass_mailing.contact
-
- {'group_by': ['create_date:month']}
+
+ mail.mass_mailing.list_contact_rel.view.form
+ mail.mass_mailing.list_contact_rel
+ 10
+
+
+
-
- List Unsubscription
- mail.mass_mailing.contact
-
- [('opt_out','=',True)]
- {'group_by': ['unsubscription_date:month']}
+
+
+ mail.mass_mailing.list_contact_rel.list_contact.view.tree
+ mail.mass_mailing.list_contact_rel
+
+
+
+
+
+
+
+
+
+
+
+
+
+ mail.mass_mailing.list_contact_rel.view.search
+ mail.mass_mailing.list_contact_rel
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -38,7 +86,8 @@
-
+
+
@@ -60,7 +109,7 @@
-
+
@@ -126,24 +175,38 @@
-
+
+
+
+
+
+
-
times
-
-
+
+
+
+
+
+
+
+
+
+
+
@@ -175,7 +238,7 @@
mail.mass_mailing.contact
form
tree,kanban,form,graph,pivot
- {'search_default_not_opt_out': 1}
+ {'search_default_not_blacklisted': 1}
Create a contact in your address book
@@ -187,11 +250,11 @@
Recipients
- mail.mass_mailing.contact
+ mail.mass_mailing.list_contact_rel
form
tree,form,graph
- [('list_ids', 'in', [active_id])]
- {'search_default_not_opt_out': 1, 'default_list_ids': [(4,active_id)]}
+ [('list_id', 'in', [active_id])]
+ {'search_default_valid_recipients': 1, 'default_list_id': active_id}
Create a new recipient
@@ -211,6 +274,7 @@
+
+
@@ -256,6 +321,9 @@
+
+
+
@@ -276,6 +344,9 @@
+
+
+
@@ -416,7 +487,20 @@
-
+
+
+
+
+
+ email(s) have been ignored and will not be sent.
+
+
+
+
+
+ options="{'model': 'mailing_model_real'}">
diff --git a/addons/mass_mailing/views/res_config_settings_views.xml b/addons/mass_mailing/views/res_config_settings_views.xml
index 04aa2faae3b..38600acbec9 100644
--- a/addons/mass_mailing/views/res_config_settings_views.xml
+++ b/addons/mass_mailing/views/res_config_settings_views.xml
@@ -44,6 +44,19 @@
+
+
+
+
+
+
+
+ Allow the recipient to manage himself his state in the blacklist via the unsubscription page.
+
+
+
Mailing Lists
diff --git a/addons/mass_mailing/views/unsubscribe_templates.xml b/addons/mass_mailing/views/unsubscribe_templates.xml
new file mode 100644
index 00000000000..57224e0e802
--- /dev/null
+++ b/addons/mass_mailing/views/unsubscribe_templates.xml
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Odoo
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Copyright © Company name
+
+
+
+
+
+
diff --git a/addons/mass_mailing/wizard/mail_compose_message.py b/addons/mass_mailing/wizard/mail_compose_message.py
index be219e93a23..1c326e700b0 100644
--- a/addons/mass_mailing/wizard/mail_compose_message.py
+++ b/addons/mass_mailing/wizard/mail_compose_message.py
@@ -77,9 +77,9 @@ 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 exception state to stat when still-born
+ # propagate ignored state to stat when still-born
if mail_values.get('state') == 'cancel':
- stat_vals['exception'] = fields.Datetime.now()
+ stat_vals['ignored'] = fields.Datetime.now()
mail_values.update({
'mailing_id': mass_mailing.id,
'statistics_ids': [(0, 0, stat_vals)],
diff --git a/addons/web/static/src/scss/form_view.scss b/addons/web/static/src/scss/form_view.scss
index 755572ceb25..6f5e955e652 100644
--- a/addons/web/static/src/scss/form_view.scss
+++ b/addons/web/static/src/scss/form_view.scss
@@ -72,10 +72,11 @@
display: none!important;
}
- .o_row, .o_row > div {
- display: inline-block;
+ .o_row:not(.o_row_readonly) {
+ &, & > div {
+ display: inline-block;
+ }
}
-
}
.o_form_uri {
diff --git a/addons/website_mass_mailing/__manifest__.py b/addons/website_mass_mailing/__manifest__.py
index 0bdd41c121e..ac128e27a7f 100644
--- a/addons/website_mass_mailing/__manifest__.py
+++ b/addons/website_mass_mailing/__manifest__.py
@@ -14,7 +14,6 @@ On a simple click, your visitors can subscribe to mailing lists managed in the E
'data': [
'security/mass_mailing_security.xml',
'views/website_mass_mailing_templates.xml',
- 'views/unsubscribe_templates.xml',
'views/snippets_templates.xml',
'views/mass_mailing_view.xml',
'views/res_config_settings_views.xml',
diff --git a/addons/website_mass_mailing/controllers/main.py b/addons/website_mass_mailing/controllers/main.py
index 455ab1bdae0..835c8391964 100644
--- a/addons/website_mass_mailing/controllers/main.py
+++ b/addons/website_mass_mailing/controllers/main.py
@@ -7,31 +7,10 @@ from odoo.addons.mass_mailing.controllers.main import MassMailController
class MassMailController(MassMailController):
- @route('/mail/mailing//unsubscribe', type='http', website=True, auth='public')
- def mailing(self, mailing_id, email=None, res_id=None, **post):
- mailing = request.env['mail.mass_mailing'].sudo().browse(mailing_id)
- if mailing.exists():
- if mailing.mailing_model_real == 'mail.mass_mailing.contact':
- contacts = request.env['mail.mass_mailing.contact'].sudo().search([('email', '=', email)])
- return request.render('website_mass_mailing.page_unsubscribe', {
- 'contacts': contacts,
- 'email': email,
- 'mailing_id': mailing_id})
- else:
- super(MassMailController, self).mailing(mailing_id, email=email, res_id=res_id, **post)
- return request.render('website_mass_mailing.page_unsubscribed')
-
- @route('/mail/mailing/unsubscribe', type='json', auth='none')
- def unsubscribe(self, mailing_id, opt_in_ids, opt_out_ids, email):
- mailing = request.env['mail.mass_mailing'].sudo().browse(mailing_id)
- if mailing.exists():
- mailing.update_opt_out(email, opt_in_ids, False)
- mailing.update_opt_out(email, opt_out_ids, True)
-
@route('/website_mass_mailing/is_subscriber', type='json', website=True, auth="public")
def is_subscriber(self, list_id, **post):
email = None
- if request.uid != request.website.user_id.id:
+ if not request.env.user._is_public():
email = request.env.user.email
elif request.session.get('mass_mailing_email'):
email = request.session['mass_mailing_email']
diff --git a/addons/website_mass_mailing/static/src/js/website_mass_mailing.js b/addons/website_mass_mailing/static/src/js/website_mass_mailing.js
index 031b795e876..1820f2f0d8f 100644
--- a/addons/website_mass_mailing/static/src/js/website_mass_mailing.js
+++ b/addons/website_mass_mailing/static/src/js/website_mass_mailing.js
@@ -131,44 +131,3 @@ sAnimation.registry.newsletter_popup = sAnimation.Class.extend({
}
});
});
-
-//==============================================================================
-
-odoo.define('mass_mailing.unsubscribe', function (require) {
- 'use strict';
-
- var ajax = require('web.ajax');
- var core = require('web.core');
- require('web.dom_ready');
-
- var _t = core._t;
-
- if (!$('.o_unsubscribe_form').length) {
- return $.Deferred().reject("DOM doesn't contain '.o_unsubscribe_form'");
- }
-
- $('#unsubscribe_form').on('submit', function (e) {
- e.preventDefault();
-
- var email = $("input[name='email']").val();
- var mailing_id = parseInt($("input[name='mailing_id']").val());
-
- var checked_ids = [];
- $("input[type='checkbox']:checked").each(function (i){
- checked_ids[i] = parseInt($(this).val());
- });
-
- var unchecked_ids = [];
- $("input[type='checkbox']:not(:checked)").each(function (i){
- unchecked_ids[i] = parseInt($(this).val());
- });
-
- ajax.jsonRpc('/mail/mailing/unsubscribe', 'call', {'opt_in_ids': checked_ids, 'opt_out_ids': unchecked_ids, 'email': email, 'mailing_id': mailing_id})
- .then(function (result) {
- $('.alert-info').html(_t('Your changes have been saved.')).removeClass('alert-info').addClass('alert-success');
- })
- .fail(function () {
- $('.alert-info').html(_t('Your changes have not been saved, try again later.')).removeClass('alert-info').addClass('alert-warning');
- });
- });
-});
diff --git a/addons/website_mass_mailing/views/unsubscribe_templates.xml b/addons/website_mass_mailing/views/unsubscribe_templates.xml
deleted file mode 100644
index 68527e9d8d1..00000000000
--- a/addons/website_mass_mailing/views/unsubscribe_templates.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
You have been successfully unsubscribed !
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-