ernesto
6 years ago
30 changed files with 502 additions and 780 deletions
-
40mass_mailing_custom_unsubscribe/README.rst
-
4mass_mailing_custom_unsubscribe/__init__.py
-
10mass_mailing_custom_unsubscribe/__manifest__.py
-
76mass_mailing_custom_unsubscribe/controllers/main.py
-
17mass_mailing_custom_unsubscribe/demo/assets.xml
-
17mass_mailing_custom_unsubscribe/hooks.py
-
BINmass_mailing_custom_unsubscribe/images/form.png
-
3mass_mailing_custom_unsubscribe/models/__init__.py
-
36mass_mailing_custom_unsubscribe/models/mail_blacklist.py
-
34mass_mailing_custom_unsubscribe/models/mail_mass_mailing.py
-
30mass_mailing_custom_unsubscribe/models/mail_mass_mailing_contact.py
-
14mass_mailing_custom_unsubscribe/models/mail_mass_mailing_list.py
-
27mass_mailing_custom_unsubscribe/models/mail_unsubscription.py
-
2mass_mailing_custom_unsubscribe/readme/CONFIGURE.rst
-
11mass_mailing_custom_unsubscribe/readme/CONTRIBUTORS.rst
-
13mass_mailing_custom_unsubscribe/readme/ROADMAP.rst
-
4mass_mailing_custom_unsubscribe/readme/USAGE.rst
-
37mass_mailing_custom_unsubscribe/static/description/index.html
-
74mass_mailing_custom_unsubscribe/static/src/js/contact.tour.js
-
46mass_mailing_custom_unsubscribe/static/src/js/partner.tour.js
-
28mass_mailing_custom_unsubscribe/static/src/js/require_details.js
-
332mass_mailing_custom_unsubscribe/static/src/js/unsubscribe.js
-
65mass_mailing_custom_unsubscribe/templates/general_reason_form.xml
-
40mass_mailing_custom_unsubscribe/templates/mass_mailing_contact_reason.xml
-
1mass_mailing_custom_unsubscribe/tests/__init__.py
-
149mass_mailing_custom_unsubscribe/tests/test_ui.py
-
6mass_mailing_custom_unsubscribe/views/assets.xml
-
30mass_mailing_custom_unsubscribe/views/mail_mass_mailing_contact_view.xml
-
19mass_mailing_custom_unsubscribe/views/mail_mass_mailing_list_view.xml
-
31mass_mailing_custom_unsubscribe/views/mail_unsubscription_view.xml
@ -1,4 +1,4 @@ |
|||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
from . import controllers, models |
|
||||
from .hooks import post_init_hook |
|
||||
|
from . import controllers |
||||
|
from . import models |
@ -1,17 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<!-- Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com> |
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> |
|
||||
|
|
||||
<odoo> |
|
||||
|
|
||||
<template id="assets_frontend_demo" |
|
||||
inherit_id="website.assets_frontend"> |
|
||||
<xpath expr="."> |
|
||||
<script type="text/javascript" |
|
||||
src="/mass_mailing_custom_unsubscribe/static/src/js/contact.tour.js"/> |
|
||||
<script type="text/javascript" |
|
||||
src="/mass_mailing_custom_unsubscribe/static/src/js/partner.tour.js"/> |
|
||||
</xpath> |
|
||||
</template> |
|
||||
|
|
||||
</odoo> |
|
@ -1,17 +0,0 @@ |
|||||
# Copyright 2018 David Vidal <david.vidal@tecnativa.com> |
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). |
|
||||
|
|
||||
from odoo import api, SUPERUSER_ID |
|
||||
|
|
||||
|
|
||||
def post_init_hook(cr, registry): |
|
||||
"""Ensure all existing contacts are going to work as v10""" |
|
||||
env = api.Environment(cr, SUPERUSER_ID, {}) |
|
||||
contacts = env['mail.mass_mailing.contact'].search([]) |
|
||||
for contact in contacts: |
|
||||
if len(contact.list_ids) <= 1: |
|
||||
continue |
|
||||
list_1 = contact.list_ids[0] |
|
||||
for list_ in contact.list_ids - list_1: |
|
||||
contact.copy({"list_ids": [(6, 0, list_.ids)]}) |
|
||||
contact.list_ids = list_1 |
|
Before Width: 1004 | Height: 506 | Size: 36 KiB |
@ -1,6 +1,5 @@ |
|||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
|
from . import mail_blacklist |
||||
from . import mail_mass_mailing |
from . import mail_mass_mailing |
||||
from . import mail_mass_mailing_contact |
|
||||
from . import mail_mass_mailing_list |
|
||||
from . import mail_unsubscription |
from . import mail_unsubscription |
@ -0,0 +1,36 @@ |
|||||
|
# Copyright 2019 Tecnativa - Ernesto Tejeda |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
from odoo import models |
||||
|
|
||||
|
|
||||
|
class MailBlackList(models.Model): |
||||
|
_inherit = 'mail.blacklist' |
||||
|
|
||||
|
def _add(self, email): |
||||
|
mailing_id = self.env.context.get('mailing_id') |
||||
|
if mailing_id: |
||||
|
mailing = self.env['mail.mass_mailing'].browse(mailing_id) |
||||
|
model_name = mailing.mailing_model_real |
||||
|
res_id = self.env.context.get('res_id') |
||||
|
self.env["mail.unsubscription"].create({ |
||||
|
"email": email, |
||||
|
"mass_mailing_id": mailing_id, |
||||
|
"unsubscriber_id": "%s,%d" % (model_name, res_id), |
||||
|
"action": "blacklisting", |
||||
|
}) |
||||
|
return super(MailBlackList, self)._add(email) |
||||
|
|
||||
|
def _remove(self, email): |
||||
|
mailing_id = self.env.context.get('mailing_id') |
||||
|
if mailing_id: |
||||
|
mailing = self.env['mail.mass_mailing'].browse(mailing_id) |
||||
|
model_name = mailing.mailing_model_real |
||||
|
res_id = self.env.context.get('res_id') |
||||
|
self.env["mail.unsubscription"].create({ |
||||
|
"email": email, |
||||
|
"mass_mailing_id": mailing_id, |
||||
|
"unsubscriber_id": "%s,%d" % (model_name, res_id), |
||||
|
"action": "de_blacklisting", |
||||
|
}) |
||||
|
return super(MailBlackList, self)._remove(email) |
@ -1,30 +0,0 @@ |
|||||
# Copyright 2018 David Vidal <david.vidal@tecnativa.com> |
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|
||||
|
|
||||
from odoo import api, fields, models |
|
||||
|
|
||||
|
|
||||
class MailMassMailing(models.Model): |
|
||||
_inherit = "mail.mass_mailing.contact" |
|
||||
|
|
||||
# Recover the old Many2one field so we can set a contact by list |
|
||||
mailing_list_id = fields.Many2one( |
|
||||
'mail.mass_mailing.list', |
|
||||
string='Mailing List', |
|
||||
ondelete='cascade', |
|
||||
compute="_compute_mailing_list_id", |
|
||||
inverse="_inverse_mailing_list_id", |
|
||||
search="_search_mailing_list_id", |
|
||||
) |
|
||||
|
|
||||
@api.depends('list_ids') |
|
||||
def _compute_mailing_list_id(self): |
|
||||
for contact in self: |
|
||||
contact.mailing_list_id = contact.list_ids[:1] |
|
||||
|
|
||||
def _inverse_mailing_list_id(self): |
|
||||
for contact in self: |
|
||||
contact.list_ids = contact.mailing_list_id |
|
||||
|
|
||||
def _search_mailing_list_id(self, operator, value): |
|
||||
return [('list_ids', operator, value)] |
|
@ -1,14 +0,0 @@ |
|||||
# Copyright 2016 Pedro M. Baeza <pedro.baeza@tecnativa.com> |
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|
||||
|
|
||||
from odoo import fields, models |
|
||||
|
|
||||
|
|
||||
class MailMassMailing(models.Model): |
|
||||
_inherit = "mail.mass_mailing.list" |
|
||||
|
|
||||
not_cross_unsubscriptable = fields.Boolean( |
|
||||
string="Don't show this list in the other unsubscriptions", |
|
||||
help="If you mark this field, this list won't be shown when " |
|
||||
"unsubscribing from other mailing list, in the section: " |
|
||||
"'Is there any other mailing list you want to leave?'") |
|
@ -1,7 +1,7 @@ |
|||||
You can customize what reasons will be displayed to your unsubscriptors when |
You can customize what reasons will be displayed to your unsubscriptors when |
||||
they are going to unsubscribe. To do it: |
they are going to unsubscribe. To do it: |
||||
|
|
||||
#. Go to *Mass Mailing > Configuration > Unsubscription Reasons*. |
|
||||
|
#. Go to *Email Marketing > Configuration > Unsubscription Reasons*. |
||||
#. Create / edit / remove / sort as usual. |
#. Create / edit / remove / sort as usual. |
||||
#. If *Details required* is enabled, they will have to fill a text area to |
#. If *Details required* is enabled, they will have to fill a text area to |
||||
continue. |
continue. |
@ -1,4 +1,7 @@ |
|||||
* Rafael Blasco <rafael.blasco@tecnativa.com> |
|
||||
* Antonio Espinosa <antonio.espinosa@tecnativa.com> |
|
||||
* Jairo Llopis <jairo.llopis@tecnativa.com> |
|
||||
* David Vidal <david.vidal@tecnativa.com> |
|
||||
|
* `Tecnativa <https://www.tecnativa.com>`_: |
||||
|
|
||||
|
* Rafael Blasco |
||||
|
* Antonio Espinosa |
||||
|
* Jairo Llopis |
||||
|
* David Vidal |
||||
|
* Ernesto Tejeda |
@ -1,11 +1,4 @@ |
|||||
* As version 11 has introduced a new relation type between mailing lists and |
|
||||
contacts that has multiple usability issues that are being reworked by Odoo |
|
||||
to land in version 12, this module falls back to the version 10 behaviour in |
|
||||
which one contact belonged to just one list. |
|
||||
* This module replaces AJAX submission core implementation from the mailing |
* This module replaces AJAX submission core implementation from the mailing |
||||
list management form, because it is impossible to extend it. When |
|
||||
https://github.com/odoo/odoo/pull/14386 gets merged (which upstreams most |
|
||||
needed changes), this addon will need a refactoring (mostly removing |
|
||||
duplicated functionality and depending on it instead of replacing it). In the |
|
||||
mean time, there is a little chance that this introduces some |
|
||||
incompatibilities with other addons that depend on ``website_mass_mailing``. |
|
||||
|
list management form, because it is impossible to extend it. When this is |
||||
|
fixed, this addon will need a refactoring (mostly removing |
||||
|
duplicated functionality and depending on it instead of replacing it). |
@ -1,8 +1,8 @@ |
|||||
Once configured: |
Once configured: |
||||
|
|
||||
#. Go to *Mass Mailing > Mailings > Mass Mailings > Create*. |
|
||||
|
#. Go to *Email Marketing > Mailings > Create*. |
||||
#. Edit your mass mailing at wish, but remember to add a snippet from |
#. Edit your mass mailing at wish, but remember to add a snippet from |
||||
*Footers*, so people have an *Unsubscribe* link. |
*Footers*, so people have an *Unsubscribe* link. |
||||
#. Send it. |
#. Send it. |
||||
#. If somebody gets unsubscribed, you will see logs about that under |
#. If somebody gets unsubscribed, you will see logs about that under |
||||
*Mass Mailing > Mailings > Unsubscriptions*. |
|
||||
|
*Email Marketing > Unsubscriptions*. |
@ -1,74 +0,0 @@ |
|||||
/* Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com> |
|
||||
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
|
|
||||
odoo.define("mass_mailing_custom_unsubscribe.contact_tour", |
|
||||
function (require) { |
|
||||
"use strict"; |
|
||||
var base = require("web_editor.base"); |
|
||||
var tour = require("web_tour.tour"); |
|
||||
require("mass_mailing_custom_unsubscribe.require_details"); |
|
||||
require("mass_mailing_custom_unsubscribe.unsubscribe"); |
|
||||
|
|
||||
// Allow to know if an element is required
|
|
||||
$.extend($.expr[':'], { |
|
||||
propRequired: function(element, index, matches) { |
|
||||
return $(element).prop("required"); |
|
||||
}, |
|
||||
}); |
|
||||
|
|
||||
tour.register( |
|
||||
"mass_mailing_custom_unsubscribe_tour_contact", |
|
||||
{ |
|
||||
tour: true, |
|
||||
wait_for: base.ready(), |
|
||||
}, |
|
||||
[ |
|
||||
{ |
|
||||
content: "Unsubscription reasons are invisible", |
|
||||
trigger: "#unsubscribe_form:has(.js_unsubscription_reason:hidden)", |
|
||||
}, |
|
||||
{ |
|
||||
content: "Uncheck list 0", |
|
||||
trigger: "li:contains('test list 0') input", |
|
||||
// List 2 is not cross unsubscriptable
|
|
||||
extra_trigger: "body:not(:has(li:contains('test list 2'))) li:contains('test list 0') input:checked", |
|
||||
}, |
|
||||
{ |
|
||||
content: "Uncheck list 1", |
|
||||
trigger: "li:contains('test list 1') input:checked", |
|
||||
extra_trigger: ".js_unsubscription_reason:visible", |
|
||||
}, |
|
||||
{ |
|
||||
content: "Choose other reason", |
|
||||
trigger: ".radio:contains('Other reason') :radio", |
|
||||
extra_trigger: ".radio:contains('Other reason') " + |
|
||||
":radio:not(:checked)", |
|
||||
}, |
|
||||
{ |
|
||||
content: "Add details to reason", |
|
||||
trigger: "[name='details']:visible:propRequired", |
|
||||
run: "text I want to unsubscribe because I want. Period.", |
|
||||
extra_trigger: ".radio:contains('Other reason') :radio:checked", |
|
||||
}, |
|
||||
{ |
|
||||
content: "Update subscriptions 1st time", |
|
||||
trigger: "#unsubscribe_form :submit", |
|
||||
}, |
|
||||
{ |
|
||||
content: "Subscribe again to list 0", |
|
||||
trigger: "body:not(:has(#unsubscribe_form .js_unsubscription_reason:visible)):has(.alert-success, li:contains('test list 0') input:not(:checked))", |
|
||||
run: function () { |
|
||||
// This one will get the success again after next step
|
|
||||
$(".alert-success").removeClass("alert-success"); |
|
||||
}, |
|
||||
}, |
|
||||
{ |
|
||||
content: "Update subscriptions 2nd time", |
|
||||
trigger: "#unsubscribe_form:not(:has(.js_unsubscription_reason:visible)) :submit", |
|
||||
}, |
|
||||
{ |
|
||||
content: "Resuscription was OK", |
|
||||
trigger: ".alert-success", |
|
||||
} |
|
||||
] |
|
||||
); |
|
||||
}); |
|
@ -1,46 +0,0 @@ |
|||||
/* Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com> |
|
||||
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
|
|
||||
odoo.define("mass_mailing_custom_unsubscribe.partner_tour", |
|
||||
function (require) { |
|
||||
"use strict"; |
|
||||
var base = require("web_editor.base"); |
|
||||
var tour = require("web_tour.tour"); |
|
||||
require("mass_mailing_custom_unsubscribe.require_details"); |
|
||||
require("mass_mailing_custom_unsubscribe.unsubscribe"); |
|
||||
|
|
||||
// Allow to know if an element is required
|
|
||||
$.extend($.expr[':'], { |
|
||||
propRequired: function(element, index, matches) { |
|
||||
return $(element).prop("required"); |
|
||||
}, |
|
||||
}); |
|
||||
|
|
||||
tour.register( |
|
||||
"mass_mailing_custom_unsubscribe_tour_partner", |
|
||||
{ |
|
||||
tour: true, |
|
||||
wait_for: base.ready(), |
|
||||
}, |
|
||||
[ |
|
||||
{ |
|
||||
content: "Choose other reason", |
|
||||
trigger: ".radio:contains('Other reason') :radio:not(:checked)", |
|
||||
extra_trigger: "#reason_form .js_unsubscription_reason", |
|
||||
}, |
|
||||
{ |
|
||||
content: "Switch to not interested reason", |
|
||||
trigger: ".radio:contains(\"I'm not interested\") :radio:not(:checked)", |
|
||||
extra_trigger: "[name='details']:propRequired", |
|
||||
}, |
|
||||
{ |
|
||||
content: "Unsubscribe", |
|
||||
trigger: "#reason_form :submit", |
|
||||
extra_trigger: "body:not(:has([name='details']:propRequired))", |
|
||||
}, |
|
||||
{ |
|
||||
content: "Successfully unsubscribed", |
|
||||
trigger: "body:not(:has(#reason_form)) .alert-success:contains('You have been successfully unsubscribed!')", |
|
||||
}, |
|
||||
] |
|
||||
); |
|
||||
}); |
|
@ -1,28 +0,0 @@ |
|||||
/* Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com> |
|
||||
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
|
|
||||
odoo.define("mass_mailing_custom_unsubscribe.require_details", |
|
||||
function (require) { |
|
||||
"use strict"; |
|
||||
var animation = require("website.content.snippets.animation"); |
|
||||
|
|
||||
animation.registry.mass_mailing_custom_unsubscribe_require_details = |
|
||||
animation.Class.extend({ |
|
||||
selector: ".js_unsubscription_reason", |
|
||||
|
|
||||
start: function () { |
|
||||
this.$radio = this.$(":radio"); |
|
||||
this.$details = this.$("[name=details]"); |
|
||||
this.$radio.on("change click", $.proxy(this.toggle, this)); |
|
||||
this.$radio.filter(":checked").trigger("change"); |
|
||||
}, |
|
||||
|
|
||||
toggle: function (event) { |
|
||||
this.$details.prop( |
|
||||
"required", |
|
||||
$(event.target).is("[data-details-required]") && |
|
||||
$(event.target).is(":visible")); |
|
||||
}, |
|
||||
}); |
|
||||
|
|
||||
return animation.registry.mass_mailing_custom_unsubscribe_require_details; |
|
||||
}); |
|
@ -1,121 +1,279 @@ |
|||||
/* Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com> |
/* Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com> |
||||
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
|
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
|
||||
|
|
||||
/* TODO This JS module replaces core AJAX submission because it is impossible |
|
||||
* to extend it as it is currently designed. Most of this code has been |
|
||||
* upstreamed in https://github.com/odoo/odoo/pull/14386, so we should extend
|
|
||||
* that when it gets merged, and remove most of this file. */ |
|
||||
odoo.define("mass_mailing_custom_unsubscribe.unsubscribe", function (require) { |
|
||||
"use strict"; |
|
||||
var core = require("web.core"); |
|
||||
var ajax = require("web.ajax"); |
|
||||
var animation = require("website.content.snippets.animation"); |
|
||||
|
/* This JS module replaces core AJAX submission because it is impossible |
||||
|
* to extend it as it is currently designed. */ |
||||
|
odoo.define('mass_mailing_custom_unsubscribe.unsubscribe', function (require) { |
||||
|
'use strict'; |
||||
|
|
||||
|
var ajax = require('web.ajax'); |
||||
|
var core = require('web.core'); |
||||
|
require('web.dom_ready'); |
||||
|
|
||||
var _t = core._t; |
var _t = core._t; |
||||
|
|
||||
animation.registry.mass_mailing_unsubscribe = |
|
||||
animation.Class.extend({ |
|
||||
selector: "#unsubscribe_form", |
|
||||
start: function () { |
|
||||
this.controller = '/mail/mailing/unsubscribe'; |
|
||||
this.$alert = this.$(".alert"); |
|
||||
this.$email = this.$("input[name='email']"); |
|
||||
this.$contacts = this.$("input[name='contact_ids']"); |
|
||||
this.$mailing_id = this.$("input[name='mailing_id']"); |
|
||||
this.$token = this.$("input[name='token']"); |
|
||||
this.$res_id = this.$("input[name='res_id']"); |
|
||||
this.$reasons = this.$(".js_unsubscription_reason"); |
|
||||
this.$details = this.$reasons.find("[name='details']"); |
|
||||
this.$el.on("submit", $.proxy(this.submit, this)); |
|
||||
this.$contacts.on("change", $.proxy(this.toggle_reasons, this)); |
|
||||
this.toggle_reasons(); |
|
||||
}, |
|
||||
|
|
||||
// Helper to get list ids, to use in this.$contacts.map()
|
|
||||
int_val: function (index, element) { |
|
||||
return parseInt($(element).val(), 10); |
|
||||
}, |
|
||||
|
|
||||
// Get a filtered array of integer IDs of matching lists
|
|
||||
contact_ids: function (checked) { |
|
||||
var filter = checked ? ":checked" : ":not(:checked)"; |
|
||||
return this.$contacts.filter(filter).map(this.int_val).get(); |
|
||||
}, |
|
||||
|
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]; |
||||
|
var $mailing_lists = $("input[name='contact_ids']"); |
||||
|
var $reasons = $("#custom_div_feedback"); |
||||
|
var $details = $("textarea[name='details']"); |
||||
|
var $radio = $(":radio"); |
||||
|
var $info_state = $("#info_state, #custom_div_feedback"); |
||||
|
|
||||
|
$radio.on('change click', function (e) { |
||||
|
$details.prop( |
||||
|
"required", |
||||
|
$(event.target).is("[data-details-required]") && $(event.target).is(":visible") |
||||
|
); |
||||
|
}); |
||||
|
|
||||
// Display reasons form only if there are unsubscriptions
|
// Display reasons form only if there are unsubscriptions
|
||||
toggle_reasons: function () { |
|
||||
|
var toggle_reasons = function () { |
||||
// Find contacts that were checked and now are unchecked
|
// Find contacts that were checked and now are unchecked
|
||||
var $disabled = this.$contacts.filter(function () { |
|
||||
|
var $disabled = $mailing_lists.filter(function () { |
||||
var $this = $(this); |
var $this = $(this); |
||||
return !$this.prop("checked") && $this.attr("checked"); |
return !$this.prop("checked") && $this.attr("checked"); |
||||
}); |
}); |
||||
// Hide reasons form if you are only subscribing
|
// Hide reasons form if you are only subscribing
|
||||
this.$reasons.toggleClass("hidden", !$disabled.length); |
|
||||
var $radios = this.$reasons.find(":radio"); |
|
||||
if (this.$reasons.is(":hidden")) { |
|
||||
|
$reasons.toggleClass("d-none", !$disabled.length); |
||||
|
var $radios = $reasons.find(":radio"); |
||||
|
if ($reasons.is(":hidden")) { |
||||
// Uncheck chosen reason
|
// Uncheck chosen reason
|
||||
$radios.prop("checked", false) |
$radios.prop("checked", false) |
||||
// Unrequire specifying a reason
|
// Unrequire specifying a reason
|
||||
.prop("required", false) |
.prop("required", false) |
||||
// Remove possible constraints for details
|
// Remove possible constraints for details
|
||||
.trigger("change"); |
.trigger("change"); |
||||
|
// Clear textarea
|
||||
|
$details.val(""); |
||||
} else { |
} else { |
||||
// Require specifying a reason
|
// Require specifying a reason
|
||||
$radios.prop("required", true); |
$radios.prop("required", true); |
||||
} |
} |
||||
}, |
|
||||
|
|
||||
// Get values to send
|
|
||||
values: function () { |
|
||||
var result = { |
|
||||
email: this.$email.val(), |
|
||||
mailing_id: parseInt(this.$mailing_id.val(), 10), |
|
||||
opt_in_ids: this.contact_ids(true), |
|
||||
opt_out_ids: this.contact_ids(false), |
|
||||
res_id: parseInt(this.$res_id.val(), 10), |
|
||||
token: this.$token.val(), |
|
||||
|
}; |
||||
|
|
||||
|
$mailing_lists.change(function (e) { |
||||
|
toggle_reasons(); |
||||
|
$('#info_state').addClass('invisible'); |
||||
|
}); |
||||
|
|
||||
|
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()); |
||||
|
}); |
||||
|
|
||||
|
var values = { |
||||
|
'opt_in_ids': checked_ids, |
||||
|
'opt_out_ids': unchecked_ids, |
||||
|
'email': email, |
||||
|
'mailing_id': mailing_id, |
||||
|
'res_id': res_id, |
||||
|
'token': token |
||||
}; |
}; |
||||
// Only send reason and details if an unsubscription was found
|
// Only send reason and details if an unsubscription was found
|
||||
if (this.$reasons.is(":visible")) { |
|
||||
result.reason_id = parseInt( |
|
||||
this.$reasons.find("[name='reason_id']:checked").val(), |
|
||||
|
if ($reasons.is(":visible")) { |
||||
|
values.reason_id = parseInt( |
||||
|
$reasons.find("[name='reason_id']:checked").val(), |
||||
10 |
10 |
||||
); |
); |
||||
result.details = this.$details.val(); |
|
||||
} |
|
||||
return result; |
|
||||
}, |
|
||||
|
|
||||
// Submit by ajax
|
|
||||
submit: function (event) { |
|
||||
event.preventDefault(); |
|
||||
return ajax.jsonRpc(this.controller, "call", this.values()) |
|
||||
.done($.proxy(this.success, this)) |
|
||||
.fail($.proxy(this.failure, this)); |
|
||||
}, |
|
||||
|
|
||||
// When you successfully saved the new subscriptions status
|
|
||||
success: function () { |
|
||||
this.$alert |
|
||||
.html(_t('Your changes have been saved.')) |
|
||||
.removeClass("alert-info alert-warning") |
|
||||
.addClass("alert-success"); |
|
||||
|
values.details = $details.val(); |
||||
|
} |
||||
|
|
||||
|
ajax.jsonRpc('/mail/mailing/unsubscribe', 'call', values) |
||||
|
.then(function (result) { |
||||
|
if (result == 'unauthorized') { |
||||
|
$('#info_state').removeClass('invisible'); |
||||
|
$('#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) { |
||||
|
$('#info_state').removeClass('invisible'); |
||||
|
$('#subscription_info').html(_t('Your changes have been saved.')); |
||||
|
$info_state.removeClass('alert-info').addClass('alert-success'); |
||||
// Store checked status, to enable further changes
|
// Store checked status, to enable further changes
|
||||
this.$contacts.each(function () { |
|
||||
|
$mailing_lists.each(function () { |
||||
var $this = $(this); |
var $this = $(this); |
||||
$this.attr("checked", $this.prop("checked")); |
$this.attr("checked", $this.prop("checked")); |
||||
}); |
}); |
||||
this.toggle_reasons(); |
|
||||
}, |
|
||||
|
|
||||
// When you fail to save the new subscriptions status
|
|
||||
failure: function () { |
|
||||
this.$alert |
|
||||
.html(_t('Your changes have not been saved, try again later.')) |
|
||||
.removeClass("alert-info alert-success") |
|
||||
.addClass("alert-warning"); |
|
||||
}, |
|
||||
|
toggle_reasons(); |
||||
|
} |
||||
|
else { |
||||
|
$('#info_state').removeClass('invisible'); |
||||
|
$('#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 () { |
||||
|
$('#info_state').removeClass('invisible'); |
||||
|
$('#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(); |
||||
|
|
||||
|
if ($reasons.is(":hidden")) { |
||||
|
$reasons.toggleClass("d-none", false); |
||||
|
$reasons.find(":radio").prop("required", true); |
||||
|
} |
||||
|
if (!$("#unsubscribe_form")[0].reportValidity()) |
||||
|
return; |
||||
|
|
||||
|
ajax.jsonRpc('/mailing/blacklist/add', 'call', { |
||||
|
'email': email, |
||||
|
'mailing_id': mailing_id, |
||||
|
'res_id': res_id, |
||||
|
'token': token, |
||||
|
'reason_id': parseInt( |
||||
|
$reasons.find("[name='reason_id']:checked").val()), |
||||
|
'details': $details.val(), |
||||
|
}) |
||||
|
.then(function (result) { |
||||
|
if (result == 'unauthorized') { |
||||
|
$('#info_state').removeClass('invisible'); |
||||
|
$('#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) { |
||||
|
$('#info_state').removeClass('invisible'); |
||||
|
$('#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); |
||||
|
// set mailing lists checkboxes to previous state
|
||||
|
$mailing_lists.each(function () { |
||||
|
var $this = $(this); |
||||
|
$this.prop("checked", $(this)[0].hasAttribute("checked")); |
||||
|
}); |
||||
|
// Hide reasons and reset reason fields
|
||||
|
$reasons.toggleClass("d-none", true) |
||||
|
.find(":radio").prop("checked", false); |
||||
|
$details.val("").prop("required", false); |
||||
|
} |
||||
|
else { |
||||
|
$('#info_state').removeClass('invisible'); |
||||
|
$('#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'); |
||||
|
}); |
||||
}); |
}); |
||||
|
|
||||
return animation.registry.mass_mailing_unsubscribe; |
|
||||
|
$('#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') { |
||||
|
$('#info_state').removeClass('invisible'); |
||||
|
$('#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) { |
||||
|
$('#info_state').removeClass('invisible'); |
||||
|
$('#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 { |
||||
|
$('#info_state').removeClass('invisible'); |
||||
|
$('#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 () { |
||||
|
$('#info_state').removeClass('invisible'); |
||||
|
$('#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'); |
||||
|
}); |
||||
|
}); |
||||
}); |
}); |
||||
|
|
||||
|
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); |
||||
|
$("#custom_div_feedback").find('*').attr('disabled', false); |
||||
|
if (value) { |
||||
|
$('[name="button_subscription"]').addClass('clickable'); |
||||
|
} |
||||
|
else { |
||||
|
$('[name="button_subscription"]').removeClass('clickable'); |
||||
|
} |
||||
|
} |
@ -1,149 +0,0 @@ |
|||||
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com> |
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
|
||||
import mock |
|
||||
from contextlib import contextmanager |
|
||||
from odoo.tests.common import HttpCase |
|
||||
from werkzeug import urls |
|
||||
|
|
||||
|
|
||||
class UICase(HttpCase): |
|
||||
_tour_run = "odoo.__DEBUG__.services['web_tour.tour'].run('%s')" |
|
||||
_tour_ready = "odoo.__DEBUG__.services['web_tour.tour'].tours.%s.ready" |
|
||||
|
|
||||
def extract_url(self, mail, *args, **kwargs): |
|
||||
url = mail._get_unsubscribe_url(self.email) |
|
||||
self.assertTrue(urls.url_parse(url).decode_query().get('token')) |
|
||||
self.assertTrue(url.startswith(self.domain)) |
|
||||
self.url = url.replace(self.domain, "", 1) |
|
||||
return True |
|
||||
|
|
||||
def setUp(self): |
|
||||
super(UICase, self).setUp() |
|
||||
self.email = "test.contact@example.com" |
|
||||
self.mail_postprocess_patch = mock.patch( |
|
||||
"openerp.addons.mass_mailing.models.mail_mail.MailMail." |
|
||||
"_postprocess_sent_message", |
|
||||
autospec=True, |
|
||||
side_effect=self.extract_url, |
|
||||
) |
|
||||
with self.tempenv() as env: |
|
||||
self.domain = env["ir.config_parameter"].get_param('web.base.url') |
|
||||
List = self.lists = env["mail.mass_mailing.list"] |
|
||||
Mailing = self.mailings = env["mail.mass_mailing"] |
|
||||
Contact = self.contacts = env["mail.mass_mailing.contact"] |
|
||||
for n in range(3): |
|
||||
self.lists += List.create({ |
|
||||
"name": "test list %d" % n, |
|
||||
}) |
|
||||
self.mailings += Mailing.create({ |
|
||||
"name": "test mailing %d" % n, |
|
||||
"mailing_model_id": self.env["mail.mass_mailing.contact"], |
|
||||
"contact_list_ids": [(6, 0, self.lists.ids)], |
|
||||
"reply_to_mode": "thread", |
|
||||
}) |
|
||||
self.mailings[n]._onchange_model_and_list() |
|
||||
# HACK https://github.com/odoo/odoo/pull/14429 |
|
||||
self.mailings[n].body_html = """ |
|
||||
<div> |
|
||||
<a href="/unsubscribe_from_list"> |
|
||||
This link should get the unsubscription URL |
|
||||
</a> |
|
||||
</div> |
|
||||
""" |
|
||||
self.contacts += Contact.create({ |
|
||||
"name": "test contact %d" % n, |
|
||||
"email": self.email, |
|
||||
"mailing_list_id": self.lists[n].id, |
|
||||
}) |
|
||||
|
|
||||
def tearDown(self): |
|
||||
del self.email, self.lists, self.contacts, self.mailings, self.url |
|
||||
super(UICase, self).tearDown() |
|
||||
|
|
||||
@contextmanager |
|
||||
def tempenv(self): |
|
||||
with self.cursor() as cr: |
|
||||
env = self.env(cr) |
|
||||
try: |
|
||||
self.lists = self.lists.with_env(env) |
|
||||
self.contacts = self.contacts.with_env(env) |
|
||||
self.mailings = self.mailings.with_env(env) |
|
||||
except AttributeError: |
|
||||
pass # We are in :meth:`~.setUp` |
|
||||
yield env |
|
||||
|
|
||||
def test_contact_unsubscription(self): |
|
||||
"""Test a mass mailing contact that wants to unsubscribe.""" |
|
||||
with self.tempenv() as env: |
|
||||
# This list we are unsubscribing from, should appear always in UI |
|
||||
self.lists[0].not_cross_unsubscriptable = True |
|
||||
# This another list should not appear in UI |
|
||||
self.lists[2].not_cross_unsubscriptable = True |
|
||||
# Extract the unsubscription link from the message body |
|
||||
with self.mail_postprocess_patch: |
|
||||
self.mailings[0].send_mail() |
|
||||
|
|
||||
tour = "mass_mailing_custom_unsubscribe_tour_contact" |
|
||||
self.phantom_js( |
|
||||
url_path=self.url, |
|
||||
code=self._tour_run % tour, |
|
||||
ready=self._tour_ready % tour) |
|
||||
|
|
||||
# Check results from running tour |
|
||||
with self.tempenv() as env: |
|
||||
self.assertTrue(self.contacts[0].opt_out) |
|
||||
self.assertTrue(self.contacts[1].opt_out) |
|
||||
self.assertFalse(self.contacts[2].opt_out) |
|
||||
unsubscriptions = env["mail.unsubscription"].search([ |
|
||||
("mass_mailing_id", "=", self.mailings[0].id), |
|
||||
("email", "=", self.email), |
|
||||
("unsubscriber_id", "in", |
|
||||
["%s,%d" % (cnt._name, cnt.id) |
|
||||
for cnt in self.contacts]), |
|
||||
("details", "=", |
|
||||
"I want to unsubscribe because I want. Period."), |
|
||||
("reason_id", "=", |
|
||||
env.ref("mass_mailing_custom_unsubscribe.reason_other").id), |
|
||||
]) |
|
||||
try: |
|
||||
self.assertEqual(2, len(unsubscriptions)) |
|
||||
except AssertionError: |
|
||||
# HACK This works locally but fails on travis, undo in v10 |
|
||||
pass |
|
||||
|
|
||||
def test_partner_unsubscription(self): |
|
||||
"""Test a partner that wants to unsubscribe.""" |
|
||||
with self.tempenv() as env: |
|
||||
# Change mailing to be sent to partner |
|
||||
partner_id = env["res.partner"].name_create( |
|
||||
"Demo Partner <%s>" % self.email)[0] |
|
||||
self.mailings[0].mailing_model_id = self.env.ref( |
|
||||
"base.model_res_partner") |
|
||||
self.mailings[0].mailing_domain = repr([ |
|
||||
('opt_out', '=', False), |
|
||||
('id', '=', partner_id), |
|
||||
]) |
|
||||
# Extract the unsubscription link from the message body |
|
||||
with self.mail_postprocess_patch: |
|
||||
self.mailings[0].send_mail() |
|
||||
|
|
||||
tour = "mass_mailing_custom_unsubscribe_tour_partner" |
|
||||
self.phantom_js( |
|
||||
url_path=self.url, |
|
||||
code=self._tour_run % tour, |
|
||||
ready=self._tour_ready % tour) |
|
||||
|
|
||||
# Check results from running tour |
|
||||
with self.tempenv() as env: |
|
||||
partner = env["res.partner"].browse(partner_id) |
|
||||
self.assertTrue(partner.opt_out) |
|
||||
unsubscriptions = env["mail.unsubscription"].search([ |
|
||||
("mass_mailing_id", "=", self.mailings[0].id), |
|
||||
("email", "=", self.email), |
|
||||
("unsubscriber_id", "=", "res.partner,%d" % partner_id), |
|
||||
("details", "=", False), |
|
||||
("reason_id", "=", |
|
||||
env.ref("mass_mailing_custom_unsubscribe" |
|
||||
".reason_not_interested").id), |
|
||||
]) |
|
||||
self.assertEqual(1, len(unsubscriptions)) |
|
@ -1,30 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<!-- Copyright 2018 David Vidal <david.vidal@tecnativa.com> |
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> |
|
||||
|
|
||||
<odoo> |
|
||||
|
|
||||
<record id="view_mail_mass_mailing_contact_form" model="ir.ui.view"> |
|
||||
<field name="model">mail.mass_mailing.contact</field> |
|
||||
<field name="inherit_id" ref="mass_mailing.view_mail_mass_mailing_contact_form"/> |
|
||||
<field name="arch" type="xml"> |
|
||||
<field name="list_ids" position="attributes"> |
|
||||
<attribute name="invisible">1</attribute> |
|
||||
</field> |
|
||||
<field name="email" position="after"> |
|
||||
<field name="mailing_list_id"/> |
|
||||
</field> |
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
<record id="view_mail_mass_mailing_contact_tree" model="ir.ui.view"> |
|
||||
<field name="model">mail.mass_mailing.contact</field> |
|
||||
<field name="inherit_id" ref="mass_mailing.view_mail_mass_mailing_contact_tree"/> |
|
||||
<field name="arch" type="xml"> |
|
||||
<field name="email" position="before"> |
|
||||
<field name="mailing_list_id"/> |
|
||||
</field> |
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
</odoo> |
|
@ -1,19 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<!-- Copyright 2016 Pedro M. Baeza <pedro.baeza@tecnativa.com> |
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> |
|
||||
|
|
||||
<odoo> |
|
||||
|
|
||||
<record id="view_mail_mass_mailing_list_form" model="ir.ui.view"> |
|
||||
<field name="model">mail.mass_mailing.list</field> |
|
||||
<field name="inherit_id" ref="mass_mailing.view_mail_mass_mailing_list_form"/> |
|
||||
<field name="arch" type="xml"> |
|
||||
<div class="oe_title" position="after"> |
|
||||
<group> |
|
||||
<field name="not_cross_unsubscriptable"/> |
|
||||
</group> |
|
||||
</div> |
|
||||
</field> |
|
||||
</record> |
|
||||
|
|
||||
</odoo> |
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue