summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Beguin <dbe@odoo.com>2018-07-12 17:02:36 +0200
committerDavid Beguin <dbe@odoo.com>2018-08-10 17:46:08 +0200
commit59b4836e8d14abdca49e43cdd72b2151e78230d4 (patch)
tree65585f3fe32dc96470e79264d99433e1004b2f10
parent98ce81cac51793dbab45e8f305a2086b9095aead (diff)
[IMP] mail, mass_mailing : apply blacklist, opt_out per mailing list
Purpose ======= - Apply the blacklist implementation to Improve mailing subscription to be more compliant with the European GDPR law. Keep a list of people who does not want to receive promotional emails (or mass mailing in general) anymore. - Allows the recipient to update himself his mailing preferences Specifications =========== This commit is regrouping some main changes on mass mailing. Apply Blacklist for following models through blacklist.mixin : - crm.lead - res.partner - mail.mass_mailing.contact - mail.channel.partner - Opt_out per mailing list instead of per mailing contact. - Replace opt_out by blacklist in crm.lead + res.partner models - Added 'ignored' state for mass_mailing. Ignored = blacklisted, opted-out Ignored email are not included into final statistics to avoid confusion. - Unsubscribe(d) pages migrated from website_mass_mailing to mass_mailing module as thoses pages should work without having the website module installed Detailed implementation =================== Mass-mailing : - A blacklisted email is notified by the ban icon next to the email field. (at the left of the email field for display purpose) - Renaming the '_get_blacklist' method that was actually searching opt_out list into 'get_opt_out_list' - Opt out per mailing list : Add opt_out + related fields (for display and ergonomy reasons) on the relational model - Display relational model tree view instead of mass_mailing.contact tree view when clicking on mass_mailing_list in kanban view -> In order to be align between contact_nbr displayed in kanban tile and the content of the tree view -> mass_mailing contact is accesible via the user icon in this tree view - Remove custom filters 'filter_contact_subscription' and 'filter_contact_unsubscription' as opt_out is not on mass_mailing.contact model anymore - Add 'is_public' to mailing list The name of this mailing list can be seen (or not) by recipient in the unsubscription page If the mailing lists used in the mass mailing are not public, the user in only informed that he has been unsubscribed. If the mailing lists are public, the user is informed that he has been unsubscribed from the mailing lists and he has the choice to modify his subscription to all the public mailing list he is or was subscribed to. - Unsubscription Page : - mass_mailing_contact : Opt_out per mailing list, done by email and not by id, as multiple contact can have the same email The recipient can add/remove himself to/from the blacklist The recipient can send a feedback about why he unsubscribed - crm.lead + res.partner : Once the recipient unsubscribe, he is automatically blacklisted The recipient can 'Come back' and remove himself from the blacklist if he changes his mind - Show blacklist button parameter added in config : The idea is to enable/disable the fact that the recipient can add himself to the blacklist by showing or not the 'blacklist me' button Only applies for mass_mailing.contact. The recipient, once blacklisted, can always, no matter the value of this parameter, 'come back' and unblacklist himself crm.lead + res.partner : - Replace opt_out by blacklist in crm.lead + res.partner models. As when a res.partner or crm.lead unsubcribe, we assume that the recipient does not want to receive mass mailing anymore, at all, even if we adds him to a mailing list afterwards. He is then blacklisted to avoid this. With this behaviour, if a new lead is created with the same email address, he won't be able to receive mail in mass_mode. But he will still be able to receive '1 to 1' direct email. Task ID 33224 Closes #25966
-rw-r--r--addons/crm/models/crm_lead.py6
-rw-r--r--addons/crm/views/crm_lead_views.xml17
-rw-r--r--addons/delivery/views/partner_view.xml2
-rw-r--r--addons/mail/__manifest__.py1
-rw-r--r--addons/mail/models/mail_channel.py8
-rw-r--r--addons/mail/models/res_partner.py5
-rw-r--r--addons/mail/views/mail_channel_partner_views.xml22
-rw-r--r--addons/mail/views/res_partner_views.xml10
-rw-r--r--addons/mail/wizard/mail_compose_message.py11
-rw-r--r--addons/mass_mailing/__manifest__.py1
-rw-r--r--addons/mass_mailing/controllers/main.py111
-rw-r--r--addons/mass_mailing/data/mass_mailing_demo.xml1
-rw-r--r--addons/mass_mailing/models/mass_mailing.py144
-rw-r--r--addons/mass_mailing/models/mass_mailing_stats.py11
-rw-r--r--addons/mass_mailing/models/res_config_settings.py3
-rw-r--r--addons/mass_mailing/security/ir.model.access.csv1
-rw-r--r--addons/mass_mailing/static/src/js/unsubscribe.js192
-rw-r--r--addons/mass_mailing/static/src/scss/mass_mailing.scss4
-rw-r--r--addons/mass_mailing/tests/__init__.py4
-rw-r--r--addons/mass_mailing/tests/test_blacklist_access_rights.py142
-rw-r--r--addons/mass_mailing/tests/test_blacklisted_channel_partner.py49
-rw-r--r--addons/mass_mailing/tests/test_mass_mailing.py251
-rw-r--r--addons/mass_mailing/views/mass_mailing_stats_views.xml1
-rw-r--r--addons/mass_mailing/views/mass_mailing_template.xml1
-rw-r--r--addons/mass_mailing/views/mass_mailing_views.xml133
-rw-r--r--addons/mass_mailing/views/res_config_settings_views.xml13
-rw-r--r--addons/mass_mailing/views/unsubscribe_templates.xml128
-rw-r--r--addons/mass_mailing/wizard/mail_compose_message.py4
-rw-r--r--addons/web/static/src/scss/form_view.scss7
-rw-r--r--addons/website_mass_mailing/__manifest__.py1
-rw-r--r--addons/website_mass_mailing/controllers/main.py23
-rw-r--r--addons/website_mass_mailing/static/src/js/website_mass_mailing.js41
-rw-r--r--addons/website_mass_mailing/views/unsubscribe_templates.xml73
33 files changed, 1170 insertions, 251 deletions
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 @@
<field name="contact_name"/>
<field name="title" placeholder="Title" domain="[]" options='{"no_open": True}'/>
</div>
- <field name="email_from" widget="email"/>
+ <field name="is_blacklisted" invisible="1"/>
+ <label for="email_from" class="oe_inline"/>
+ <div class="o_row o_row_readonly">
+ <i class="fa fa-ban" style="color: red;" role="img" title="This email is blacklisted for mass mailing"
+ aria-label="Blacklisted" attrs="{'invisible': [('is_blacklisted', '=', False)]}"></i>
+ <field name="email_from" widget="email"/>
+ </div>
<field name="function"/>
<field name="phone" widget="phone"/>
<field name="mobile"/>
@@ -103,7 +109,6 @@
<page name="extra" string="Extra Info">
<group>
<group string="Email">
- <field name="opt_out"/>
<field name="message_bounce" readonly="1" groups="base.group_no_one"/>
</group>
<group string="Tracking" groups="base.group_multi_company,base.group_no_one" name="categorization">
@@ -395,9 +400,10 @@
<separator />
<filter string="Current" name="current" domain="[('active', '=', True)]"/>
<filter string="Archived" name="archived" domain="[('active', '=', False)]"/>
- <filter string="Exclude Opt Out"
- name='not_opt_out' domain="[('opt_out', '=', False)]"
- help="Leads that did not ask not to be included in mass mailing campaigns"/>
+ <separator />
+ <filter string="Exclude blacklisted"
+ name='not_blacklisted' domain="[('is_blacklisted', '=', False)]"
+ help="Leads that did ask to not receive mass mailing anymore"/>
<separator/>
<filter string="My Activities" name="activities_my"
domain="[('activity_ids.user_id', '=', uid)]"/>
@@ -562,7 +568,6 @@
</div>
<field name="function"/>
<field name="mobile"/>
- <field name="opt_out"/>
</group>
<group string="Marketing">
<field name="campaign_id" />
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 @@
<field name="model">res.partner</field>
<field name="inherit_id" ref="mail.res_partner_view_form_inherit_mail" />
<field name="arch" type="xml">
- <field name="opt_out" position="after">
+ <field name="message_bounce" position="before">
<field name="property_delivery_carrier_id"/>
</field>
</field>
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 @@
+<?xml version="1.0"?>
+<odoo>
+ <data>
+ <record model="ir.ui.view" id="res_partner_view_form_short">
+ <field name="name">res.partner.view.form.short.inherit.mail</field>
+ <field name="model">res.partner</field>
+ <field name="inherit_id" ref="base.view_partner_short_form" />
+ <field name="priority" eval="20"/>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='email']" position="replace">
+ <field name="is_blacklisted" invisible="1"/>
+ <label for="email" class="oe_inline"/>
+ <div class="o_row o_row_readonly">
+ <i class="fa fa-ban" style="color: red;" role="img" title="This email is blacklisted for mass mailing"
+ aria-label="Blacklisted" attrs="{'invisible': [('is_blacklisted', '=', False)]}"></i>
+ <field name="email" widget="email"/>
+ </div>
+ </xpath>
+ </field>
+ </record>
+ </data>
+</odoo>
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 @@
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
+ <xpath expr="//field[@name='email']" position="replace">
+ <field name="is_blacklisted" invisible="1"/>
+ <label for="email" class="oe_inline"/>
+ <div class="o_row o_row_readonly">
+ <i class="fa fa-ban" style="color: red;" role="img" title="This email is blacklisted for mass mailing"
+ aria-label="Blacklisted" attrs="{'invisible': [('is_blacklisted', '=', False)]}"></i>
+ <field name="email" widget="email"/>
+ </div>
+ </xpath>
<xpath expr="//page[@name='sales_purchases']//field[@name='user_id']" position="after">
- <field name="opt_out" string="Mailing Opt-Out" groups="base.group_no_one"/>
<field name="message_bounce" groups="base.group_no_one"/>
</xpath>
<xpath expr="//sheet" position="after">
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/<int:mail_id>/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 @@
<field name="name">Carol Cartridge</field>
<field name="email">cc@example.com</field>
<field name="list_ids" eval="[(6,0,[ref('mass_mailing.mass_mail_list_2')])]"/>
- <field name="opt_out" eval="True"/>
</record>
<!-- Create campaign and mailings -->
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
''')
@@ -71,6 +124,10 @@ class MassMailingList(models.Model):
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):
"""
Insert all the contact from the mailing lists 'src_lists' to the
@@ -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 <strong>unsubscribed from %s</strong> mailing list(s)') \
+ if value else _('The recipient <strong>subscribed to %s</strong> 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 <strong>successfully unsubscribed from ' + unsubscribed_list + "</strong>."));
+ }
+ else{
+ $('#subscription_info').html(_t('You have been <strong>successfully unsubscribed</strong>.'));
+ }
+
+ $('#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 <strong>added to our blacklist</strong>. '
+ + '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 <strong>removed from our blacklist</strong>. "
+ + "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 @@
<filter string="Received" name="filter_received" domain="[('sent', '!=', False), ('bounced', '=', False)]"/>
<filter string="Scheduled" name="filter_scheduled" domain="[('scheduled', '!=', False), ('sent', '=', False), ('exception', '=', False)]"/>
<filter string="Failed" name="filter_failed" domain="[('scheduled', '!=', False), ('sent', '=', False), ('exception', '!=', False)]"/>
+ <filter string="Ignored" name="filter_ignored" domain="[('scheduled', '!=', False), ('sent', '=', False), ('exception', '=', False), ('ignored', '!=', False)]"/>
<separator/>
<filter string="Opened" name="filter_opened" domain="[('opened', '!=', False)]"/>
<filter string="Clicked" name="filter_clicked" domain="[('clicked', '!=', False)]"/>
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 @@
<link rel="stylesheet" type="text/scss" href="/mass_mailing/static/src/scss/mass_mailing.scss"/>
<link rel="stylesheet" href="/mass_mailing/static/src/css/email_template.css"/>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing.js"></script>
+ <script type="text/javascript" src="/mass_mailing/static/src/js/unsubscribe.js"></script>
</xpath>
</template>
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"/>
<!-- MAILING CONTACT -->
+ <record model="ir.ui.view" id="mail_mass_mailing_list_contact_rel_view_form">
+ <field name="name">mail.mass_mailing.list_contact_rel.view.form</field>
+ <field name="model">mail.mass_mailing.list_contact_rel</field>
+ <field name="priority">10</field>
+ <field name="arch" type="xml">
+ <form string="Mailing List contacts">
+ <sheet>
+ <group>
+ <field name="list_id"/>
+ <field name="is_blacklisted" invisible="1"/>
+ <label for="contact_id" class="oe_inline"/>
+ <div class="o_row o_row_readonly">
+ <i class="fa fa-ban" style="color: red;" role="img" title="This email is blacklisted for mass mailing"
+ aria-label="Blacklisted" attrs="{'invisible': [('is_blacklisted', '=', False)]}"></i>
+ <field name="contact_id"/>
+ </div>
+ <field name="unsubscription_date" readonly="1"/>
+ <field name="opt_out"/>
+ <field name="message_bounce" readonly="1"/>
+ </group>
+ </sheet>
+ </form>
+ </field>
+ </record>
- <!-- Custom reports (aka filters) -->
- <record id="filter_contact_subscription" model="ir.filters">
- <field name="name">List Subscription</field>
- <field name="model_id">mail.mass_mailing.contact</field>
- <field name="user_id" eval="False"/>
- <field name="context">{'group_by': ['create_date:month']}</field>
+ <record model="ir.ui.view" id="mail_mass_mailing_list_contact_rel_list_contact_view_tree">
+ <field name="name">mail.mass_mailing.list_contact_rel.list_contact.view.tree</field>
+ <field name="model">mail.mass_mailing.list_contact_rel</field>
+ <field name="arch" type="xml">
+ <tree string="Mailing Lists">
+ <field name="contact_id"/>
+ <field name="unsubscription_date"/>
+ <field name="opt_out"/>
+ <field name="message_bounce"/>
+ <field name="is_blacklisted"/>
+ <button name="action_open_mailing_list_contact" string="Open Contact" type="object" icon="fa-user"/>
+ </tree>
+ </field>
</record>
- <record id="filter_contact_unsubscription" model="ir.filters">
- <field name="name">List Unsubscription</field>
- <field name="model_id">mail.mass_mailing.contact</field>
- <field name="user_id" eval="False"/>
- <field name="domain">[('opt_out','=',True)]</field>
- <field name="context">{'group_by': ['unsubscription_date:month']}</field>
+
+ <record model="ir.ui.view" id="mail_mass_mailing_list_contact_view_search">
+ <field name="name">mail.mass_mailing.list_contact_rel.view.search</field>
+ <field name="model">mail.mass_mailing.list_contact_rel</field>
+ <field name="arch" type="xml">
+ <search string="Mailing List Contacts">
+ <field name="contact_id"/>
+ <field name="opt_out"/>
+ <field name="list_id"/>
+ <separator/>
+ <filter string="Exclude Opt Out" name="not_opt_out" domain="[('opt_out', '=', False)]"/>
+ <separator/>
+ <filter string="Exclude Blacklisted" name="not_blacklisted" domain="[('contact_id.is_blacklisted', '=', False)]"/>
+ <separator/>
+ <filter string="Valid Recipents" name="valid_recipients" domain="[('opt_out', '=', False), ('contact_id.is_blacklisted', '=', False)]"/>
+ <separator/>
+ <filter string="Exclude Bounced" name="not_bounced" domain="[('message_bounce', '&lt;', 3)]"/>
+ <group expand="0" string="Group By">
+ <filter string="Creation Date" name="group_create_date"
+ context="{'group_by': 'create_date'}"/>
+ </group>
+ </search>
+ </field>
</record>
<record model="ir.ui.view" id="view_mail_mass_mailing_contact_search">
@@ -38,7 +86,8 @@
<field name="tag_ids"/>
<field name="list_ids"/>
<separator/>
- <filter string="Exclude Opt Out" name="not_opt_out" domain="[('opt_out', '=', False)]"/>
+ <filter string="Exclude Blacklisted" name="not_blacklisted" domain="[('is_blacklisted', '=', False)]"/>
+ <separator/>
<separator/>
<filter string="Exclude Bounced" name="not_bounced" domain="[('message_bounce', '&lt;', 3)]"/>
<group expand="0" string="Group By">
@@ -60,7 +109,7 @@
<field name="company_name"/>
<field name="email"/>
<field name="message_bounce"/>
- <field name="opt_out"/>
+ <field name="is_blacklisted"/>
</tree>
</field>
</record>
@@ -126,24 +175,38 @@
</div>
<group>
<group>
- <field name="email"/>
+ <field name="is_blacklisted" invisible="1"/>
+ <label for="email" class="oe_inline"/>
+ <div class="o_row o_row_readonly">
+ <i class="fa fa-ban" style="color: red;" role="img" title="This email is blacklisted for mass mailing"
+ aria-label="Blacklisted" attrs="{'invisible': [('is_blacklisted', '=', False)]}"></i>
+ <field name="email" widget="email"/>
+ </div>
<field name="title_id"/>
<field name="company_name"/>
<field name="country_id"/>
</group>
<group>
<field name="create_date" readonly="1"/>
- <field name="unsubscription_date"/>
<label for="message_bounce"/>
<div class="o_row">
<field name="message_bounce"/>
<span> times</span>
</div>
- <field name="opt_out"/>
</group>
</group>
- <field name="list_ids"/>
+ <field name="subscription_list_ids">
+ <tree editable="bottom">
+ <field name="list_id"/>
+ <field name="unsubscription_date"/>
+ <field name="opt_out"/>
+ </tree>
+ </field>
</sheet>
+ <div class="oe_chatter">
+ <field name="message_follower_ids" widget="mail_followers"/>
+ <field name="message_ids" widget="mail_thread"/>
+ </div>
</form>
</field>
</record>
@@ -175,7 +238,7 @@
<field name="res_model">mail.mass_mailing.contact</field>
<field name="view_type">form</field>
<field name="view_mode">tree,kanban,form,graph,pivot</field>
- <field name="context">{'search_default_not_opt_out': 1}</field>
+ <field name="context">{'search_default_not_blacklisted': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a contact in your address book
@@ -187,11 +250,11 @@
<record model="ir.actions.act_window" id="action_view_mass_mailing_contacts_from_list">
<field name="name">Recipients</field>
- <field name="res_model">mail.mass_mailing.contact</field>
+ <field name="res_model">mail.mass_mailing.list_contact_rel</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,graph</field>
- <field name="domain">[('list_ids', 'in', [active_id])]</field>
- <field name="context">{'search_default_not_opt_out': 1, 'default_list_ids': [(4,active_id)]}</field>
+ <field name="domain">[('list_id', 'in', [active_id])]</field>
+ <field name="context">{'search_default_valid_recipients': 1, 'default_list_id': active_id}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new recipient
@@ -211,6 +274,7 @@
<search string="Mailing Lists">
<field name="name"/>
<field name="create_date"/>
+ <field name="is_public"/>
<filter name="inactive" string="Archived" domain="[('active','=',False)]"/>
<group expand="0" string="Group By">
<filter string="Creation Month" name="group_create_date"
@@ -228,6 +292,7 @@
<tree string="Mailing Lists">
<field name="name"/>
<field name="create_date"/>
+ <field name="is_public"/>
<field name="contact_nbr"/>
</tree>
</field>
@@ -256,6 +321,9 @@
<field name="name" placeholder="e.g. Consumer Newsletter"/>
</h1>
</div>
+ <group>
+ <field name="is_public"/>
+ </group>
</sheet>
</form>
</field>
@@ -276,6 +344,9 @@
</div>
</group>
</group>
+ <group>
+ <field name="is_public"/>
+ </group>
<footer>
<button string="Create" name="close_dialog" type="object" class="btn-primary"/>
<button string="Discard" class="btn-secondary" special="cancel"/>
@@ -416,7 +487,20 @@
<field name="state" readonly="1" widget="statusbar"/>
</header>
- <div class="oe_form_box_info bg-info oe_text_center" attrs="{'invisible': [('scheduled', '=', 0)]}">
+ <div class="oe_form_box_info bg-info oe_text_center" attrs="{'invisible': [('ignored', '=', 0)]}">
+ <p>
+ <button class="btn-link"
+ name="%(action_view_mail_mail_statistics_mailing_list)d"
+ type="action"
+ context="{'search_default_filter_ignored': True}">
+ <strong>
+ <field name="ignored" class="oe_inline"/>
+ email(s) have been ignored and will not be sent.
+ </strong>
+ </button>
+ </p>
+ </div>
+ <div class="oe_form_box_info bg-info oe_text_center" attrs="{'invisible': ['|', ('scheduled', '=', 0), ('ignored','!=',0)]}">
<p>
<button class="btn-link"
name="%(action_view_mail_mail_statistics_mailing_list)d"
@@ -505,8 +589,7 @@
<field name="mailing_model_real" invisible="1"/>
<field name="mailing_domain" widget="domain"
attrs="{'invisible': [('mailing_model_name', '=', 'mail.mass_mailing.list')]}"
- options="{'model': 'mailing_model_real'}"
- context="{'search_default_not_opt_out':1}">
+ options="{'model': 'mailing_model_real'}">
</field>
<div attrs="{'invisible': [('mailing_model_name', '!=', 'mail.mass_mailing.list')]}">
<label for="contact_list_ids" string="Select mailing lists:" class="oe_edit_only"/>
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 @@
</div>
</div>
</div>
+ <div class="col-md-6 o_setting_box col-xs-12">
+ <div class="o_setting_left_pane" title="Allow the recipient to manage himself his state in the blacklist via the unsubscription page.
+ If the option is active, the 'Blacklist Me' button is hidden on the unsubscription page.
+ The 'come Back' button will always be visible in any case to allow leads and partners to re-subscribe.">
+ <field name="show_blacklist_buttons"/>
+ </div>
+ <div class="o_setting_right_pane">
+ <label for="show_blacklist_buttons"/>
+ <div class="text-muted">
+ Allow the recipient to manage himself his state in the blacklist via the unsubscription page.
+ </div>
+ </div>
+ </div>
</div>
<h2>Mailing Lists</h2>
<div id="mailing_list" class="row mt16 o_settings_container"/>
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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <template id="unsubscribe">
+ <div class="container o_unsubscribe_form">
+ <div class="row">
+ <form action="/mail/mailing/unsubscribe" method="POST" id="unsubscribe_form">
+ <input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
+ <input type="hidden" name="email" t-att-value="email"/>
+ <input type="hidden" name="mailing_id" t-att-value="mailing_id"/>
+ <input type="hidden" name="res_id" t-att-value="res_id"/>
+ <input type="hidden" name="unsubscribed_list" t-att-value="unsubscribed_list"/>
+
+ <div class="col-lg-6 offset-lg-3 mt32">
+ <t t-if="contacts">
+ <div id="info_state" class="alert alert-success" role="status">
+ <div id="subscription_info"></div>
+ <div id="div_feedback">
+ <p>We would appreciate if you provide feedback about why you unsubscribed</p>
+ <textarea class="form-control" name="opt_out_feedback" cols="60" rows="3"></textarea>
+ <br/>
+ <div class="btn btn-primary text-left" id="button_feedback">Send</div>
+ </div>
+ </div>
+
+ <h1 class="page-header">Mailing Subscriptions</h1>
+ <p>Choose your mailing subscriptions</p>
+ <div id="div_opt_out">
+ <ul class="list-group">
+ <t t-foreach="list_ids" t-as="list_id">
+ <t t-if="list_id.is_public == True">
+ <li class="list-group-item">
+ <input type="checkbox" class="mail_list_checkbox" name="contact_ids"
+ t-att-value="list_id['id']" t-att-checked="None if list_id['id'] in opt_out_list_ids else 'checked'"/>
+ <t t-esc="list_id.name"/>
+ </li>
+ </t>
+ </t>
+ </ul>
+
+ <div class="mb64">
+ <div t-if="show_blacklist_button">
+ <div class="btn btn-default pull-right" id="button_add_blacklist" style="display:none">Blacklist Me</div>
+ </div>
+ <div class="btn btn-default pull-right" id="button_remove_blacklist" style="display:none">Come Back</div>
+ <input type="submit" id="send_form" class="btn btn-primary" value="Update my subscriptions"/>
+ </div>
+ </div>
+
+ </t>
+
+ <t t-if="contacts == False">
+ <div class="alert alert-info text-center" role="status">
+ <p>You are not subscribed to any of our mailing list.</p>
+ </div>
+ </t>
+ </div>
+ </form>
+ </div>
+ </div>
+ </template>
+
+ <template id="unsubscribed">
+ <div class="container o_unsubscribe_form">
+ <div class="row">
+ <input type="hidden" name="email" t-att-value="email"/>
+ <input type="hidden" name="mailing_id" t-att-value="mailing_id"/>
+ <input type="hidden" name="res_id" t-att-value="res_id"/>
+ <div id="div_blacklist" class="col-lg-6 offset-lg-3">
+ <h1 class="page-header">Mailing Subscriptions</h1>
+
+ <div id="subscription_info" class="alert alert-success text-center" role="status">
+ <p>You have been successfully <strong>unsubscribed</strong>!</p>
+ </div>
+
+ <div t-if="show_blacklist_button" class="mb64">
+ <div class="btn btn-default pull-right" id="button_add_blacklist" style="display:none">Blacklist Me</div>
+ <div class="btn btn-default pull-right" id="button_remove_blacklist" style="display:none">Unblacklist Me</div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </template>
+
+ <template id="page_unsubscribe" name="Unsubscribe">
+ <t t-call="mass_mailing.layout">
+ <t t-call="mass_mailing.unsubscribe"/>
+ </t>
+ </template>
+
+ <template id="page_unsubscribed" name="Unsubscribed">
+ <t t-call="mass_mailing.layout">
+ <t t-call="mass_mailing.unsubscribed"/>
+ </t>
+ </template>
+
+ <!-- new layout for mass_mailing -->
+ <template id="mass_mailing.layout" name="Mass Mailing Layout">
+ <t t-call="web.layout">
+ <t t-set="head">
+ <t t-call-assets="web.assets_common"/>
+ <t t-call-assets="mass_mailing.assets_backend"/>
+ </t>
+ <body class="o_white_body">
+ <header>
+ <div><title>Odoo</title></div>
+ <div class="text-center">
+ <img t-attf-src="/web/binary/company_logo"/>
+ </div>
+ </header>
+ <div id="wrap" class="oe_structure oe_empty"/>
+ <main>
+ <t t-raw="0"/>
+ </main>
+ </body>
+ <xpath expr="//footer" position="replace">
+ <div class="container mt16 mb8">
+ <div class="pull-right" t-ignore="true" t-if="not editable">
+ Create a <a target="_blank" href="https://www.odoo.com/page/website-builder">free website</a> with
+ <a target="_blank" class="label label-danger" href="https://www.odoo.com/page/website-builder">Odoo</a>
+ </div>
+ <div class="pull-left text-muted" itemscope="itemscope" itemtype="https://schema.org/Organization">
+ Copyright &amp;copy; <span t-field="res_company.name" itemprop="name">Company name</span>
+ </div>
+ </div>
+ </xpath>
+ </t>
+ </template>
+</odoo>
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/<int:mailing_id>/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 @@
-<?xml version="1.0" encoding="utf-8"?>
-<odoo>
- <template id="unsubscribe">
- <div class="container o_unsubscribe_form">
- <div class="row">
- <form action="/mail/mailing/unsubscribe" method="POST" id="unsubscribe_form">
- <input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
- <input type="hidden" name="email" t-att-value="email"/>
- <input type="hidden" name="mailing_id" t-att-value="mailing_id"/>
-
- <div class="col-lg-6 offset-lg-3">
-
- <h1 class="page-header">Mailing Subscriptions</h1>
-
- <t t-if="contacts">
- <div class="alert alert-info text-center" role="status">
- <p>Choose your mailing subscriptions.</p>
- </div>
-
- <ul class="list-group">
- <t t-foreach="contacts" t-as="contact">
- <li class="list-group-item">
- <input type="checkbox" class="mail_list_checkbox" name="contact_ids"
- t-att-value="contact['id']" t-att-checked="'checked' if contact['opt_out'] == False else None"/>
- <t t-foreach="contact.list_ids" t-as="contact_list">
- <t t-esc="contact_list.name"/>
- </t>
- </li>
- </t>
- </ul>
-
- <div class="text-center mb16">
- <input type="submit" id="send_form" class="btn btn-primary" value="Update my subscriptions"/>
- </div>
- </t>
-
- <t t-if="contacts == False">
- <div class="alert alert-info text-center" role="status">
- <p>You are not subscribed to any of our mailing list.</p>
- </div>
- </t>
- </div>
- </form>
- </div>
- </div>
- </template>
-
- <template id="unsubscribed">
- <div class="container mt8">
- <div class="row">
- <div class="col-lg-6 offset-lg-3">
- <h1 class="page-header">Mailing Subscriptions</h1>
-
- <div class="alert alert-success text-center" role="status">
- <p>You have been successfully <strong>unsubscribed</strong>!</p>
- </div>
- </div>
- </div>
- </div>
- </template>
-
- <template id="page_unsubscribe" name="Unsubscribe">
- <t t-call="website.layout">
- <t t-call="website_mass_mailing.unsubscribe"/>
- </t>
- </template>
-
- <template id="page_unsubscribed" name="Unsubscribed">
- <t t-call="website.layout">
- <t t-call="website_mass_mailing.unsubscribed"/>
- </t>
- </template>
-</odoo>