Browse Source

[MIG][mass_mailing_custom_unsubscribe] Migrate to v10

pull/178/head
Jairo Llopis 7 years ago
parent
commit
5dc6c8749f
  1. 10
      mass_mailing_custom_unsubscribe/README.rst
  2. 5
      mass_mailing_custom_unsubscribe/__manifest__.py
  3. 19
      mass_mailing_custom_unsubscribe/controllers/main.py
  4. 9
      mass_mailing_custom_unsubscribe/models/mail_mail.py
  5. 11
      mass_mailing_custom_unsubscribe/models/mail_mass_mailing.py
  6. 2
      mass_mailing_custom_unsubscribe/security/ir.model.access.csv
  7. 77
      mass_mailing_custom_unsubscribe/static/src/js/contact.tour.js
  8. 49
      mass_mailing_custom_unsubscribe/static/src/js/partner.tour.js
  9. 24
      mass_mailing_custom_unsubscribe/tests/test_ui.py
  10. 2
      mass_mailing_custom_unsubscribe/views/mail_unsubscription_reason_view.xml

10
mass_mailing_custom_unsubscribe/README.rst

@ -10,7 +10,7 @@ This addon extends the unsubscription form to let you:
- Choose which mailing lists are not cross-unsubscriptable when unsubscribing
from a different one.
- Know why and when a contact as been unsubscribed from a mass mailing.
- Know why and when a contact has been unsubscribed from a mass mailing.
Configuration
=============
@ -21,7 +21,7 @@ Unsubscription Reasons
You can customize what reasons will be displayed to your unsubscriptors when
they are going to unsubscribe. To do it:
#. Go to *Marketing > Configuration > Unsubscription Reasons*.
#. Go to *Mass Mailing > Configuration > Unsubscription Reasons*.
#. Create / edit / remove / sort as usual.
#. If *Details required* is enabled, they will have to fill a text area to
continue.
@ -40,7 +40,7 @@ Once configured:
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/205/9.0
:target: https://runbot.odoo-community.org/runbot/205/10.0
Known issues / Roadmap
======================
@ -48,7 +48,9 @@ Known issues / Roadmap
* This module adds a security hash for mass mailing unsubscription URLs, which
disables insecure URLs from mass mailing messages sent before its
installation. This can be a problem, but anyway you'd get that problem in
Odoo 11.0, so at least this addon will be forward-compatible with it.
Odoo 11.0, where https://github.com/odoo/odoo/pull/12040 was merged, so at
least this addon will be forward-compatible with it. So, **this feature must
be removed from here when migrating to v11**.
* 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

5
mass_mailing_custom_unsubscribe/__manifest__.py

@ -5,7 +5,7 @@
'name': "Customizable unsubscription process on mass mailing emails",
"summary": "Know unsubscription reasons, track them",
'category': 'Marketing',
'version': '9.0.2.0.0',
'version': '10.0.1.0.0',
'depends': [
'website_mass_mailing',
],
@ -25,8 +25,7 @@
'images': [
'images/form.png',
],
'author': 'Antiun Ingeniería S.L., '
'Tecnativa,'
'author': 'Tecnativa,'
'Odoo Community Association (OCA)',
'website': 'https://www.tecnativa.com',
'license': 'AGPL-3',

19
mass_mailing_custom_unsubscribe/controllers/main.py

@ -29,7 +29,7 @@ class CustomUnsubscribe(MassMailController):
Security token for unsubscriptions.
"""
reasons = request.env["mail.unsubscription.reason"].search([])
return request.website.render(
return request.render(
"mass_mailing_custom_unsubscribe.reason_form",
{
"email": email,
@ -75,11 +75,11 @@ class CustomUnsubscribe(MassMailController):
return self.reason_form(mailing, email, res_id, token)
else:
# Unsubscribe, saving reason and details by context
request.context.update({
"default_reason_id": reason_id,
"default_details": post.get("details") or False,
})
del request.env
request.context = dict(
request.context,
default_reason_id=reason_id,
default_details=post.get("details") or False,
)
# You could get a DetailsRequiredError here, but only if HTML5
# validation fails, which should not happen in modern browsers
return super(CustomUnsubscribe, self).mailing(
@ -91,8 +91,11 @@ class CustomUnsubscribe(MassMailController):
"""Store unsubscription reasons when unsubscribing from RPC."""
# Update request context and reset environment
if reason_id:
request.context["default_reason_id"] = int(reason_id)
request.context["default_details"] = details or False
request.context = dict(
request.context,
default_reason_id=int(reason_id),
default_details=details or False,
)
# FIXME Remove token check in version where this is merged:
# https://github.com/odoo/odoo/pull/14385
mailing = request.env['mail.mass_mailing'].sudo().browse(mailing_id)

9
mass_mailing_custom_unsubscribe/models/mail_mail.py

@ -2,14 +2,13 @@
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp import api, models
from openerp import models
class MailMail(models.Model):
_inherit = 'mail.mail'
@api.model
def _get_unsubscribe_url(self, mail, email_to):
result = super(MailMail, self)._get_unsubscribe_url(mail, email_to)
token = mail.mailing_id._unsubscribe_token(mail.res_id)
def _get_unsubscribe_url(self, email_to):
result = super(MailMail, self)._get_unsubscribe_url(email_to)
token = self.mailing_id._unsubscribe_token(self.res_id)
return "%s&token=%s" % (result, token)

11
mass_mailing_custom_unsubscribe/models/mail_mass_mailing.py

@ -37,18 +37,17 @@ class MailMassMailing(models.Model):
raise AccessDenied()
return token
@api.model
def update_opt_out(self, mailing_id, email, res_ids, value):
def update_opt_out(self, email, res_ids, value):
"""Save unsubscription reason when opting out from mailing."""
mailing = self.browse(mailing_id)
self.ensure_one()
if value and self.env.context.get("default_reason_id"):
for res_id in res_ids:
# reason_id and details are expected from the context
self.env["mail.unsubscription"].create({
"email": email,
"mass_mailing_id": mailing.id,
"mass_mailing_id": self.id,
"unsubscriber_id": "%s,%d" % (
mailing.mailing_model, int(res_id)),
self.mailing_model, int(res_id)),
})
return super(MailMassMailing, self).update_opt_out(
mailing_id, email, res_ids, value)
email, res_ids, value)

2
mass_mailing_custom_unsubscribe/security/ir.model.access.csv

@ -3,4 +3,4 @@ read_unsubscription_reason_public,Public users can read unsubscription reasons,m
read_unsubscription_reason_employee,Employee users can read unsubscription reasons,model_mail_unsubscription_reason,base.group_user,1,0,0,0
write_unsubscription_reason,Mass mailing managers can manage unsubscription reasons,model_mail_unsubscription_reason,mass_mailing.group_mass_mailing_user,1,1,1,1
read_unsubscription,Marketing users can read unsubscriptions,model_mail_unsubscription,mass_mailing.group_mass_mailing_user,1,0,0,0
write_unsubscription,Mass mailing managers can manage unsubscriptions,model_mail_unsubscription,mass_mailing.group_mass_mailing_user,1,1,1,1
write_unsubscription,Admins can change unsubscriptions,model_mail_unsubscription,base.group_system,1,1,1,1

77
mass_mailing_custom_unsubscribe/static/src/js/contact.tour.js

@ -3,75 +3,72 @@
odoo.define("mass_mailing_custom_unsubscribe.contact_tour",
function (require) {
"use strict";
var Tour = require("web.Tour");
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) {
propRequired: function(element, index, matches) {
return $(element).prop("required");
},
},
});
Tour.register({
id: "mass_mailing_custom_unsubscribe_tour_contact",
name: "Mass mailing contact unsubscribes",
mode: "test",
steps: [
tour.register(
"mass_mailing_custom_unsubscribe_tour_contact",
{
tour: true,
wait_for: base.ready(),
},
[
{
title: "Unsubscription reasons are invisible",
waitFor: "#unsubscribe_form .js_unsubscription_reason:hidden",
content: "Unsubscription reasons are invisible",
trigger: "#unsubscribe_form:has(.js_unsubscription_reason:hidden)",
},
{
title: "Uncheck list 0",
element: "li:contains('test list 0') input",
waitFor: "li:contains('test list 0') input:checked",
content: "Uncheck list 0",
trigger: "li:contains('test list 0') input",
// List 2 is not cross unsubscriptable
waitNot: "li:contains('test list 2')",
extra_trigger: "body:not(:has(li:contains('test list 2'))) li:contains('test list 0') input:checked",
},
{
title: "Uncheck list 1",
element: "li:contains('test list 1') input:checked",
waitFor: ".js_unsubscription_reason:visible",
content: "Uncheck list 1",
trigger: "li:contains('test list 1') input:checked",
extra_trigger: ".js_unsubscription_reason:visible",
},
{
title: "Choose other reason",
element: ".radio:contains('Other reason') :radio",
waitFor: ".radio:contains('Other reason') " +
":radio:not(:checked)",
content: "Choose other reason",
trigger: ".radio:contains('Other reason') :radio",
extra_trigger: ".radio:contains('Other reason') " +
":radio:not(:checked)",
},
{
title: "Add details to reason",
element: "[name='details']:visible:propRequired",
sampleText: "I want to unsubscribe because I want. Period.",
waitFor: ".radio:contains('Other reason') :radio: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",
},
{
title: "Update subscriptions 1st time",
element: "#unsubscribe_form :submit",
content: "Update subscriptions 1st time",
trigger: "#unsubscribe_form :submit",
},
{
title: "Subscribe again to list 0",
element: "li:contains('test list 0') input:not(:checked)",
waitFor: ".alert-success",
waitNot: "#unsubscribe_form .js_unsubscription_reason:visible",
onend: function () {
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");
},
},
{
title: "Update subscriptions 2nd time",
element: "#unsubscribe_form :submit",
waitNot: "#unsubscribe_form .js_unsubscription_reason:visible",
content: "Update subscriptions 2nd time",
trigger: "#unsubscribe_form:not(:has(.js_unsubscription_reason:visible)) :submit",
},
{
title: "Resuscription was OK",
waitFor: ".alert-success",
content: "Resuscription was OK",
trigger: ".alert-success",
}
]
});
return Tour.tours.mass_mailing_custom_unsubscribe_tour_contact;
);
});

49
mass_mailing_custom_unsubscribe/static/src/js/partner.tour.js

@ -3,47 +3,44 @@
odoo.define("mass_mailing_custom_unsubscribe.partner_tour",
function (require) {
"use strict";
var Tour = require("web.Tour");
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) {
propRequired: function(element, index, matches) {
return $(element).prop("required");
},
},
});
Tour.register({
id: "mass_mailing_custom_unsubscribe_tour_partner",
name: "Mass mailing partner unsubscribes",
mode: "test",
steps: [
tour.register(
"mass_mailing_custom_unsubscribe_tour_partner",
{
tour: true,
wait_for: base.ready(),
},
[
{
title: "Choose other reason",
element: ".radio:contains('Other reason') " +
":radio:not(:checked)",
waitFor: "#reason_form .js_unsubscription_reason",
content: "Choose other reason",
trigger: ".radio:contains('Other reason') :radio:not(:checked)",
extra_trigger: "#reason_form .js_unsubscription_reason",
},
{
title: "Switch to not interested reason",
element: ".radio:contains(\"I'm not interested\") " +
":radio:not(:checked)",
waitFor: "[name='details']:propRequired",
content: "Switch to not interested reason",
trigger: ".radio:contains(\"I'm not interested\") :radio:not(:checked)",
extra_trigger: "[name='details']:propRequired",
},
{
title: "Unsubscribe",
element: "#reason_form :submit",
waitNot: "[name='details']:propRequired",
content: "Unsubscribe",
trigger: "#reason_form :submit",
extra_trigger: "body:not(:has([name='details']:propRequired))",
},
{
title: "Successfully unsubscribed",
waitFor: ".alert-success:contains(" +
"'Your changes have been saved.')",
waitNot: "#reason_form",
content: "Successfully unsubscribed",
trigger: "body:not(:has(#reason_form)) .alert-success:contains('Your changes have been saved.')",
},
]
});
return Tour.tours.mass_mailing_custom_unsubscribe_tour_partner;
);
});

24
mass_mailing_custom_unsubscribe/tests/test_ui.py

@ -7,8 +7,11 @@ from openerp.tests.common import HttpCase
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(mail, self.email)
url = mail._get_unsubscribe_url(self.email)
self.assertIn("&token=", url)
self.assertTrue(url.startswith(self.domain))
self.url = url.replace(self.domain, "", 1)
@ -20,6 +23,7 @@ class UICase(HttpCase):
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:
@ -37,11 +41,7 @@ class UICase(HttpCase):
"contact_list_ids": [(6, 0, self.lists.ids)],
"reply_to_mode": "thread",
})
self.mailings[n].write(
self.mailings[n].on_change_model_and_list(
self.mailings[n].mailing_model,
self.mailings[n].contact_list_ids.ids,
)["value"])
self.mailings[n]._onchange_model_and_list()
# HACK https://github.com/odoo/odoo/pull/14429
self.mailings[n].body_html = """
<div>
@ -86,13 +86,12 @@ class UICase(HttpCase):
tour = "mass_mailing_custom_unsubscribe_tour_contact"
self.phantom_js(
url_path=self.url,
code=("odoo.__DEBUG__.services['web.Tour']"
".run('%s', 'test')") % tour,
ready="odoo.__DEBUG__.services['web.Tour'].tours.%s" % tour)
code=self._tour_run % tour,
ready=self._tour_ready % tour)
# Check results from running tour
with self.tempenv() as env:
self.assertFalse(self.contacts[0].opt_out)
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([
@ -130,9 +129,8 @@ class UICase(HttpCase):
tour = "mass_mailing_custom_unsubscribe_tour_partner"
self.phantom_js(
url_path=self.url,
code=("odoo.__DEBUG__.services['web.Tour']"
".run('%s', 'test')") % tour,
ready="odoo.__DEBUG__.services['web.Tour'].tours.%s" % tour)
code=self._tour_run % tour,
ready=self._tour_ready % tour)
# Check results from running tour
with self.tempenv() as env:

2
mass_mailing_custom_unsubscribe/views/mail_unsubscription_reason_view.xml

@ -26,9 +26,9 @@
<field name="model">mail.unsubscription.reason</field>
<field name="arch" type="xml">
<tree>
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="details_required"/>
<field name="sequence" invisible="True"/>
</tree>
</field>
</record>

Loading…
Cancel
Save