Browse Source

[8.0][IMP][mass_mailing_custom_unsubscribe] Get reasons for unsubscription (#58)

* [8.0][IMP][mass_mailing_custom_unsubscribe] Get reasons for unsubscription.
pull/178/head
Yajo 8 years ago
committed by Jairo Llopis
parent
commit
97566e1c47
  1. 56
      mass_mailing_custom_unsubscribe/README.rst
  2. 15
      mass_mailing_custom_unsubscribe/__manifest__.py
  3. 15
      mass_mailing_custom_unsubscribe/controllers.py
  4. 5
      mass_mailing_custom_unsubscribe/controllers/__init__.py
  5. 237
      mass_mailing_custom_unsubscribe/controllers/main.py
  6. 11
      mass_mailing_custom_unsubscribe/data/install_salt.xml
  7. 5
      mass_mailing_custom_unsubscribe/data/mail.unsubscription.reason.csv
  8. 9
      mass_mailing_custom_unsubscribe/exceptions.py
  9. 374
      mass_mailing_custom_unsubscribe/i18n/es.po
  10. BIN
      mass_mailing_custom_unsubscribe/images/failure.png
  11. BIN
      mass_mailing_custom_unsubscribe/images/form.png
  12. BIN
      mass_mailing_custom_unsubscribe/images/success.png
  13. 27
      mass_mailing_custom_unsubscribe/migrations/8.0.2.0.0/pre-migrate.py
  14. 8
      mass_mailing_custom_unsubscribe/models/__init__.py
  15. 35
      mass_mailing_custom_unsubscribe/models/mail_mail.py
  16. 35
      mass_mailing_custom_unsubscribe/models/mail_mass_mailing.py
  17. 15
      mass_mailing_custom_unsubscribe/models/mail_mass_mailing_list.py
  18. 74
      mass_mailing_custom_unsubscribe/models/mail_unsubscription.py
  19. 6
      mass_mailing_custom_unsubscribe/security/ir.model.access.csv
  20. 13
      mass_mailing_custom_unsubscribe/static/src/js/require_details.js
  21. 7
      mass_mailing_custom_unsubscribe/tests/__init__.py
  22. 111
      mass_mailing_custom_unsubscribe/tests/test_controller.py
  23. 97
      mass_mailing_custom_unsubscribe/tests/test_mail_mail.py
  24. 21
      mass_mailing_custom_unsubscribe/tests/test_unsubscription.py
  25. 17
      mass_mailing_custom_unsubscribe/views/assets.xml
  26. 21
      mass_mailing_custom_unsubscribe/views/mail_mass_mailing_list_view.xml
  27. 60
      mass_mailing_custom_unsubscribe/views/mail_unsubscription_reason_view.xml
  28. 89
      mass_mailing_custom_unsubscribe/views/mail_unsubscription_view.xml
  29. 106
      mass_mailing_custom_unsubscribe/views/pages.xml

56
mass_mailing_custom_unsubscribe/README.rst

@ -8,10 +8,17 @@ Customizable unsubscription process on mass mailing emails
With this module you can set a custom unsubscribe link appended at the bottom With this module you can set a custom unsubscribe link appended at the bottom
of mass mailing emails. of mass mailing emails.
It also displays a beautiful and simple unsubscription form when somebody
unsubscribes, to let you know why and let the user unsubscribe form another
mailing lists at the same time; and then displays a beautiful and customizable
goodbye message.
Configuration Configuration
============= =============
Unsubscription Message In Mail Footer
-------------------------------------
To configure unsubscribe label go to *Settings > Technical > Parameters > To configure unsubscribe label go to *Settings > Technical > Parameters >
System parameters* and add a ``mass_mailing.unsubscribe.label`` parameter System parameters* and add a ``mass_mailing.unsubscribe.label`` parameter
with HTML to set at the bottom of mass emailing emails. Including ``%(url)s`` with HTML to set at the bottom of mass emailing emails. Including ``%(url)s``
@ -28,34 +35,63 @@ default 'Click to unsubscribe' link will appear, with the advantage that it is
translatable via *Settings > Translations > Application Terms > Translated translatable via *Settings > Translations > Application Terms > Translated
terms*. terms*.
Also your unsubscriptors will recieve a beautier goodbye page. You can
customize it clicking here **after installing the module**:
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*.
#. Create / edit / remove / sort as usual.
#. If *Details required* is enabled, they will have to fill a text area to
continue.
Unsubscription Goodbye Message
------------------------------
* `Unsubscription successful </page/mass_mail_unsubscription_success>`_.
* `Unsubscription failed </page/mass_mail_unsubscription_failure>`_.
Your unsubscriptors will receive a beautier goodbye page. You can customize it
with these links **after installing the module**:
* `Unsubscription successful </page/mass_mailing_custom_unsubscribe.successs>`_.
* `Unsubscription failed </page/mass_mailing_custom_unsubscribe.failure>`_.
Usage Usage
===== =====
Once configured, just send mass mailings as usual.
If somebody gets unsubscribed, you will see logs about that under
*Marketing > Mass Mailing > Unsubscriptions*.
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot :alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/205/8.0 :target: https://runbot.odoo-community.org/runbot/205/8.0
Known issues / Roadmap Known issues / Roadmap
====================== ======================
* This needs tests.
* This custom HTML is not translatable, so as a suggestion, you can define * This custom HTML is not translatable, so as a suggestion, you can define
the same text in several languages in several lines. the same text in several languages in several lines.
For example:
For example:
.. code:: html .. code:: html
<small>[EN] You can unsubscribe <a href="%(url)s">here</a></small><br/> <small>[EN] You can unsubscribe <a href="%(url)s">here</a></small><br/>
<small>[ES] Puedes darte de baja <a href="%(url)s">aquí</a></small> <small>[ES] Puedes darte de baja <a href="%(url)s">aquí</a></small>
* If you use the ``website_multi`` module, you will probably find that the
views are not visible by default.
* This module adds a security hash for mass mailing unsubscription URLs, which
makes to not work anymore URLs of mass mailing messages sent before its
installation. If you need backwards compatibility, disable this security
feature by removing the ``mass_mailing.salt`` system parameter. To avoid
breaking current installations, you will not get a salt if you are upgrading
the addon. If you want a salt, create the above system parameter and assign a
random value to it.
* Security should be patched upstream. Remove security features in the version
where https://github.com/odoo/odoo/pull/12040 gets merged (if it does).
Bug Tracker Bug Tracker
=========== ===========
@ -63,8 +99,10 @@ Bug Tracker
Bugs are tracked on `GitHub Issues <https://github.com/OCA/social/issues>`_. Bugs are tracked on `GitHub Issues <https://github.com/OCA/social/issues>`_.
In case of trouble, please check there if your issue has already been reported. In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback If you spotted it first, help us smashing it by providing a detailed and welcomed feedback
`here <https://github.com/OCA/social/issues/new?body=module:%20mass_mailing_custom_unsubscribe%0Aversion:%208.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
`here <https://github.com/OCA/
social/issues/new?body=module:%20
mass_mailing_custom_unsubscribe%0Aversion:%20
8.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Credits Credits
======= =======
@ -89,4 +127,4 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and mission is to support the collaborative development of Odoo features and
promote its widespread use. promote its widespread use.
To contribute to this module, please visit http://odoo-community.org.
To contribute to this module, please visit https://odoo-community.org.

15
mass_mailing_custom_unsubscribe/__manifest__.py

@ -23,15 +23,28 @@
{ {
'name': "Customizable unsubscription process on mass mailing emails", 'name': "Customizable unsubscription process on mass mailing emails",
'category': 'Marketing', 'category': 'Marketing',
'version': '8.0.1.1.0',
'version': '8.0.2.0.0',
'depends': [ 'depends': [
'mass_mailing', 'mass_mailing',
'website_crm', 'website_crm',
], ],
'data': [ 'data': [
'security/ir.model.access.csv',
'data/install_salt.xml',
'data/mail.unsubscription.reason.csv',
'views/assets.xml',
'views/mail_unsubscription_reason_view.xml',
'views/mail_mass_mailing_list_view.xml',
'views/mail_unsubscription_view.xml',
'views/pages.xml', 'views/pages.xml',
], ],
'images': [
'images/failure.png',
'images/form.png',
'images/success.png',
],
'author': 'Antiun Ingeniería S.L., ' 'author': 'Antiun Ingeniería S.L., '
'Tecnativa,'
'Odoo Community Association (OCA)', 'Odoo Community Association (OCA)',
'website': 'http://www.antiun.com', 'website': 'http://www.antiun.com',
'license': 'AGPL-3', 'license': 'AGPL-3',

15
mass_mailing_custom_unsubscribe/controllers.py

@ -1,15 +0,0 @@
# -*- coding: utf-8 -*-
# © 2015 Antiun Ingeniería S.L. (http://www.antiun.com)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from openerp import http
from openerp.addons.mass_mailing.controllers.main import MassMailController
class CustomUnsuscribe(MassMailController):
@http.route()
def mailing(self, *args, **kwargs):
path = "/page/mass_mail_unsubscription_%s"
result = super(CustomUnsuscribe, self).mailing(*args, **kwargs)
return http.local_redirect(
path % ("success" if result.data == "OK" else "failure"))

5
mass_mailing_custom_unsubscribe/controllers/__init__.py

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# © 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import main

237
mass_mailing_custom_unsubscribe/controllers/main.py

@ -0,0 +1,237 @@
# -*- coding: utf-8 -*-
# © 2015 Antiun Ingeniería S.L. (http://www.antiun.com)
# © 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from openerp import exceptions
from openerp.http import local_redirect, request, route
from openerp.addons.mass_mailing.controllers.main import MassMailController
from .. import exceptions as _ex
class CustomUnsubscribe(MassMailController):
def _mailing_list_contacts_by_email(self, email):
"""Gets the mailing list contacts by email.
This should not be displayed to the final user if security validations
have not been matched.
"""
return request.env["mail.mass_mailing.contact"].sudo().search([
("email", "=", email),
("opt_out", "=", False),
("list_id.not_cross_unsubscriptable", "=", False),
])
def unsubscription_reason(self, mailing_id, email, res_id, token,
qcontext_extra=None):
"""Get the unsubscription reason form.
:param mail.mass_mailing mailing_id:
Mailing where the unsubscription is being processed.
:param str email:
Email to be unsubscribed.
:param int res_id:
ID of the unsubscriber.
:param dict qcontext_extra:
Additional dictionary to pass to the view.
"""
values = self.unsubscription_qcontext(mailing_id, email, res_id, token)
values.update(qcontext_extra or dict())
return request.website.render(
"mass_mailing_custom_unsubscribe.reason_form",
values)
def unsubscription_qcontext(self, mailing_id, email, res_id, token):
"""Get rendering context for unsubscription form.
:param mail.mass_mailing mailing_id:
Mailing where the unsubscription is being processed.
:param str email:
Email to be unsubscribed.
:param int res_id:
ID of the unsubscriber.
"""
email_fname = origin_name = None
domain = [("id", "=", res_id)]
record_ids = request.env[mailing_id.mailing_model].sudo()
if "email_from" in record_ids._fields:
email_fname = "email_from"
elif "email" in record_ids._fields:
email_fname = "email"
if not (email_fname and email):
# Trying to unsubscribe without email? Bad boy...
raise exceptions.AccessDenied()
domain.append((email_fname, "ilike", email))
# Search additional mailing lists for the unsubscriber
additional_contacts = self._mailing_list_contacts_by_email(email)
if record_ids._name == "mail.mass_mailing.contact":
domain.append(
("list_id", "in", mailing_id.contact_list_ids.ids))
# Unsubscription targets
record_ids = record_ids.search(domain)
if record_ids._name == "mail.mass_mailing.contact":
additional_contacts -= record_ids
if not record_ids:
# Trying to unsubscribe with fake criteria? Bad boy...
raise exceptions.AccessDenied()
# Get data to identify the source of the unsubscription
fnames = self.unsubscription_special_fnames(record_ids._name)
first = record_ids[:1]
contact_name = first[fnames.get("contact", "name")]
origin_model_name = request.env["ir.model"].search(
[("model", "=", first._name)]).name
try:
first = first[fnames["related"]]
except KeyError:
pass
try:
origin_name = first[fnames["origin"]]
except KeyError:
pass
# Get available reasons
reason_ids = (
request.env["mail.unsubscription.reason"].search([]))
return {
"additional_contact_ids": additional_contacts,
"contact_name": contact_name,
"email": email,
"mailing_id": mailing_id,
"origin_model_name": origin_model_name,
"origin_name": origin_name,
"reason_ids": reason_ids,
"record_ids": record_ids,
"res_id": res_id,
"token": token,
}
def unsubscription_special_fnames(self, model):
"""Define special field names to generate the unsubscription qcontext.
:return dict:
Special fields will depend on the model, so this method should
return something like::
{
"related": "parent_id",
"origin": "display_name",
"contact": "contact_name",
}
Where:
- ``model.name`` is the technical name of the model.
- ``related`` indicates the name of a field in ``model.name`` that
contains a :class:`openerp.fields.Many2one` field which is
considered what the user is unsubscribing from.
- ``origin``: is the name of the field that contains the name of
what the user is unsubscribing from.
- ``contact`` is the name of the field that contains the name of
the user that is unsubscribing.
Missing keys will mean that nothing special is required for that
model and it will use the default values.
"""
specials = {
"mail.mass_mailing.contact": {
"related": "list_id",
"origin": "display_name",
},
"crm.lead": {
"origin": "name",
"contact": "contact_name",
},
"hr.applicant": {
"related": "job_id",
"origin": "name",
},
# In case you install OCA's event_registration_mass_mailing
"event.registration": {
"related": "event_id",
"origin": "name",
},
}
return specials.get(model, dict())
@route(auth="public", website=True)
def mailing(self, mailing_id, email=None, res_id=None, **post):
"""Display a confirmation form to get the unsubscription reason."""
mailing = request.env["mail.mass_mailing"]
path = "/page/mass_mailing_custom_unsubscribe.%s"
good_token = mailing.hash_create(mailing_id, res_id, email)
# Trying to unsubscribe with fake hash? Bad boy...
if good_token and post.get("token") != good_token:
return local_redirect(path % "failure")
mailing = mailing.sudo().browse(mailing_id)
contact = request.env["mail.mass_mailing.contact"].sudo()
unsubscription = request.env["mail.unsubscription"].sudo()
if not post.get("reason_id"):
# We need to know why you leave, get to the form
return self.unsubscription_reason(
mailing, email, res_id, post.get("token"))
# Save reason and details
try:
with request.env.cr.savepoint():
records = unsubscription.create({
"email": email,
"unsubscriber_id": ",".join(
(mailing.mailing_model, res_id)),
"reason_id": int(post["reason_id"]),
"details": post.get("details", False),
"mass_mailing_id": mailing_id,
})
# Should provide details, go back to form
except _ex.DetailsRequiredError:
return self.unsubscription_reason(
mailing, email, res_id, post.get("token"),
{"error_details_required": True})
# Unsubscribe from additional lists
for key, value in post.iteritems():
try:
label, list_id = key.split(",")
if label != "list_id":
raise ValueError
list_id = int(list_id)
except ValueError:
pass
else:
contact_id = contact.browse(int(value))
if contact_id.list_id.id == list_id:
contact_id.opt_out = True
records += unsubscription.create({
"email": email,
"unsubscriber_id": ",".join((contact._name, value)),
"reason_id": int(post["reason_id"]),
"details": post.get("details", False),
"mass_mailing_id": mailing_id,
})
# All is OK, unsubscribe
result = super(CustomUnsubscribe, self).mailing(
mailing_id, email, res_id, **post)
records.write({"success": result.data == "OK"})
# Redirect to the result
return local_redirect(path % ("success" if result.data == "OK"
else "failure"))

11
mass_mailing_custom_unsubscribe/data/install_salt.xml

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- © 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<openerp>
<data noupdate="1">
<function model="mail.mass_mailing" name="_init_salt_create"/>
</data>
</openerp>

5
mass_mailing_custom_unsubscribe/data/mail.unsubscription.reason.csv

@ -0,0 +1,5 @@
"id","name","sequence","details_required"
"reason_not_interested","I'm not interested",10,"False"
"reason_not_requested","I did not request this",20,"False"
"reason_too_many","I get too many emails",30,"False"
"reason_other","Other reason",100,"True"

9
mass_mailing_custom_unsubscribe/exceptions.py

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# © 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp import exceptions
class DetailsRequiredError(exceptions.ValidationError):
pass

374
mass_mailing_custom_unsubscribe/i18n/es.po

@ -1,29 +1,381 @@
# Translation of Odoo Server. # Translation of Odoo Server.
# This file contains the translation of the following modules: # This file contains the translation of the following modules:
# * mass_mailing_custom_unsubscribe
# * mass_mailing_custom_unsubscribe
# #
# Translators:
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: social (8.0)\n"
"Project-Id-Version: Odoo Server 8.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-09-04 14:42+0000\n"
"PO-Revision-Date: 2015-09-04 14:43+0000\n"
"Last-Translator: OCA Transbot <transbot@odoo-community.org>\n"
"Language-Team: Spanish (http://www.transifex.com/oca/OCA-social-8-0/language/es/)\n"
"POT-Creation-Date: 2016-05-23 14:21+0000\n"
"PO-Revision-Date: 2016-05-23 14:21+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n" "Content-Transfer-Encoding: \n"
"Language: es\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Plural-Forms: \n"
#. module: mass_mailing_custom_unsubscribe #. module: mass_mailing_custom_unsubscribe
#: code:addons/mass_mailing_custom_unsubscribe/models/mail_mail.py:37
#: view:website:mass_mailing_custom_unsubscribe.reason_form
msgid "Anything else you want to say before you leave?"
msgstr "¿Algo más que quiera decir antes de irse?"
#. module: mass_mailing_custom_unsubscribe
#: view:website:mass_mailing_custom_unsubscribe.reason_form
msgid "But before continuing, could you please tell us why do you want to unsubscribe?"
msgstr "Pero antes de continuar, ¿podría decirnos por qué quiere darse de baja?"
#. module: mass_mailing_custom_unsubscribe
#: help:mail.unsubscription,details_required:0
#: help:mail.unsubscription.reason,details_required:0
msgid "Check to ask for more details when this reason is selected."
msgstr "Marcar para pedir más detalles cuando está razón se selecciona."
#. module: mass_mailing_custom_unsubscribe
#: code:addons/mass_mailing_custom_unsubscribe/models/mail_mail.py:39
#, python-format #, python-format
msgid "Click to unsubscribe" msgid "Click to unsubscribe"
msgstr "Haz click para darte de baja"
msgstr "Haga click para darse de baja"
#. module: mass_mailing_custom_unsubscribe
#: view:website:mass_mailing_custom_unsubscribe.failure
#: view:website:mass_mailing_custom_unsubscribe.success
msgid "Contact us"
msgstr "Contáctenos"
#. module: mass_mailing_custom_unsubscribe
#: field:mail.unsubscription,create_uid:0
#: field:mail.unsubscription.reason,create_uid:0
msgid "Created by"
msgstr "Creado por"
#. module: mass_mailing_custom_unsubscribe
#: field:mail.unsubscription,create_date:0
#: field:mail.unsubscription.reason,create_date:0
msgid "Created on"
msgstr "Creado en"
#. module: mass_mailing_custom_unsubscribe
#: field:mail.unsubscription,date:0
msgid "Date"
msgstr "Fecha"
#. module: mass_mailing_custom_unsubscribe
#: help:mail.unsubscription,message_last_post:0
msgid "Date of the last message posted on the record."
msgstr "Fecha del último mensaje publicado en el registro."
#. module: mass_mailing_custom_unsubscribe
#: field:mail.unsubscription,details:0
msgid "Details"
msgstr "Detalles"
#. module: mass_mailing_custom_unsubscribe
#: field:mail.unsubscription,details_required:0
#: field:mail.unsubscription.reason,details_required:0
msgid "Details required"
msgstr "Detalles requeridos"
#. module: mass_mailing_custom_unsubscribe
#: field:mail.unsubscription,display_name:0
#: field:mail.unsubscription.reason,display_name:0
msgid "Display Name"
msgstr "Nombre mostrado"
#. module: mass_mailing_custom_unsubscribe
#: field:mail.mass_mailing.list,not_cross_unsubscriptable:0
msgid "Don't show this list in the other unsubscriptions"
msgstr "No mostrar esta lista en las otras desuscripciones"
#. module: mass_mailing_custom_unsubscribe
#: field:mail.unsubscription,email:0
msgid "Email"
msgstr "Correo electrónico"
#. module: mass_mailing_custom_unsubscribe
#: model:ir.model,name:mass_mailing_custom_unsubscribe.model_mail_unsubscription
msgid "Email Thread"
msgstr "Hilo de mensajes"
#. module: mass_mailing_custom_unsubscribe
#: field:mail.unsubscription,message_follower_ids:0
msgid "Followers"
msgstr "Seguidores"
#. module: mass_mailing_custom_unsubscribe
#: view:mail.unsubscription:mass_mailing_custom_unsubscribe.mail_unsubscription_view_search
msgid "Group by"
msgstr "Agrupar por"
#. module: mass_mailing_custom_unsubscribe
#: view:website:mass_mailing_custom_unsubscribe.reason_form
msgid "Hello,"
msgstr "Hola,"
#. module: mass_mailing_custom_unsubscribe
#: help:mail.unsubscription,message_summary:0
msgid "Holds the Chatter summary (number of messages, ...). This summary is directly in html format in order to be inserted in kanban views."
msgstr "Contiene el resumen del chatter (nº de mensajes, ...). Este resumen está directamente en formato html para ser insertado en vistas kanban."
#. module: mass_mailing_custom_unsubscribe
#: model:mail.unsubscription.reason,name:mass_mailing_custom_unsubscribe.reason_not_requested
msgid "I did not request this"
msgstr "No lo solicité"
#. module: mass_mailing_custom_unsubscribe
#: model:mail.unsubscription.reason,name:mass_mailing_custom_unsubscribe.reason_too_many
msgid "I get too many emails"
msgstr "Tengo demasiados correos electrónicos"
#. module: mass_mailing_custom_unsubscribe
#: model:mail.unsubscription.reason,name:mass_mailing_custom_unsubscribe.reason_not_interested
msgid "I'm not interested"
msgstr "No estoy interesado"
#. module: mass_mailing_custom_unsubscribe
#: field:mail.unsubscription,id:0
#: field:mail.unsubscription.reason,id:0
msgid "ID"
msgstr "ID"
#. module: mass_mailing_custom_unsubscribe
#: help:mail.unsubscription,message_unread:0
msgid "If checked new messages require your attention."
msgstr "Si está marcado, hay nuevos mensajes que requieren de su atención."
#. module: mass_mailing_custom_unsubscribe
#: help:mail.unsubscription,success:0
msgid "If this is unchecked, it indicates some failure happened in the unsubscription process."
msgstr "Si no está marcado, indica que algún fallo ha ocurrido en el proceso de desuscripción."
#. module: mass_mailing_custom_unsubscribe
#: help:mail.mass_mailing.list,not_cross_unsubscriptable:0
msgid "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?'"
msgstr "Si marca esta casilla, esta lista no será mostrada cuando se dé de baja de otra lista de correo, en la sección: '¿Hay alguna otra lista de correo que quiera dejar?'"
#. module: mass_mailing_custom_unsubscribe
#: field:mail.unsubscription,message_is_follower:0
msgid "Is a Follower"
msgstr "Es un seguidor"
#. module: mass_mailing_custom_unsubscribe
#: view:website:mass_mailing_custom_unsubscribe.reason_form
msgid "Is there any other mailing list you want to leave?"
msgstr "¿Hay alguna otra lista de correo que quiera dejar?"
#. module: mass_mailing_custom_unsubscribe
#: view:website:mass_mailing_custom_unsubscribe.success
msgid "Is there anything else you want to tell us?"
msgstr "¿Hay algo más que quiera decirnos?"
#. module: mass_mailing_custom_unsubscribe
#: view:website:mass_mailing_custom_unsubscribe.success
msgid "It's sad to see you go, but if you love\n"
" something, let it go."
msgstr "Es triste verlo partir, pero si quiere a algo, debe dejarlo marchar."
#. module: mass_mailing_custom_unsubscribe
#: field:mail.unsubscription,message_last_post:0
msgid "Last Message Date"
msgstr "Fecha del último mensaje"
#. module: mass_mailing_custom_unsubscribe
#: field:mail.unsubscription,__last_update:0
#: field:mail.unsubscription.reason,__last_update:0
msgid "Last Modified on"
msgstr "Última modificación en"
#. module: mass_mailing_custom_unsubscribe
#: field:mail.unsubscription,write_uid:0
#: field:mail.unsubscription.reason,write_uid:0
msgid "Last Updated by"
msgstr "Última actualización por"
#. module: mass_mailing_custom_unsubscribe
#: field:mail.unsubscription,write_date:0
#: field:mail.unsubscription.reason,write_date:0
msgid "Last Updated on"
msgstr "Última actualización en"
#. module: mass_mailing_custom_unsubscribe
#: model:ir.model,name:mass_mailing_custom_unsubscribe.model_mail_mass_mailing_list
msgid "Mailing List"
msgstr "Lista de correo"
#. module: mass_mailing_custom_unsubscribe
#: model:ir.model,name:mass_mailing_custom_unsubscribe.model_mail_mass_mailing
msgid "Mass Mailing"
msgstr "Envío masivo"
#. module: mass_mailing_custom_unsubscribe
#: view:mail.unsubscription:mass_mailing_custom_unsubscribe.mail_unsubscription_view_search
#: field:mail.unsubscription,mass_mailing_id:0
msgid "Mass mailing"
msgstr "Envío masivo"
#. module: mass_mailing_custom_unsubscribe
#: help:mail.unsubscription,mass_mailing_id:0
msgid "Mass mailing from which he was unsubscribed."
msgstr "Envío masivo del que ha sido dado de baja."
#. module: mass_mailing_custom_unsubscribe
#: field:mail.unsubscription,message_ids:0
msgid "Messages"
msgstr "Mensajes"
#. module: mass_mailing_custom_unsubscribe
#: help:mail.unsubscription,message_ids:0
msgid "Messages and communication history"
msgstr "Mensajes e historial de comunicación"
#. module: mass_mailing_custom_unsubscribe
#: view:mail.unsubscription:mass_mailing_custom_unsubscribe.mail_unsubscription_view_search
msgid "Month"
msgstr "Mes"
#. module: mass_mailing_custom_unsubscribe
#: help:mail.unsubscription,details:0
msgid "More details on why the unsubscription was made."
msgstr "Más detalles de por qué se dio de baja."
#. module: mass_mailing_custom_unsubscribe
#: field:mail.unsubscription.reason,name:0
msgid "Name"
msgstr "Nombre"
#. module: mass_mailing_custom_unsubscribe
#: model:mail.unsubscription.reason,name:mass_mailing_custom_unsubscribe.reason_other
msgid "Other reason"
msgstr "Otra razón"
#. module: mass_mailing_custom_unsubscribe #. module: mass_mailing_custom_unsubscribe
#: model:ir.model,name:mass_mailing_custom_unsubscribe.model_mail_mail #: model:ir.model,name:mass_mailing_custom_unsubscribe.model_mail_mail
msgid "Outgoing Mails" msgid "Outgoing Mails"
msgstr "Correos salientes" msgstr "Correos salientes"
#. module: mass_mailing_custom_unsubscribe
#: help:mail.unsubscription.reason,sequence:0
msgid "Position of the reason in the list."
msgstr "Posición de la razón en la lista."
#. module: mass_mailing_custom_unsubscribe
#: view:mail.unsubscription:mass_mailing_custom_unsubscribe.mail_unsubscription_view_search
#: field:mail.unsubscription,reason_id:0
msgid "Reason"
msgstr "Razón"
#. module: mass_mailing_custom_unsubscribe
#: field:mail.unsubscription.reason,sequence:0
msgid "Sequence"
msgstr "Secuencia"
#. module: mass_mailing_custom_unsubscribe
#: view:mail.unsubscription:mass_mailing_custom_unsubscribe.mail_unsubscription_view_search
#: field:mail.unsubscription,success:0
msgid "Success"
msgstr "Éxito"
#. module: mass_mailing_custom_unsubscribe
#: field:mail.unsubscription,message_summary:0
msgid "Summary"
msgstr "Resumen"
#. module: mass_mailing_custom_unsubscribe
#: view:website:mass_mailing_custom_unsubscribe.reason_form
msgid "Thank you!"
msgstr "¡Gracias!"
#. module: mass_mailing_custom_unsubscribe
#: view:website:mass_mailing_custom_unsubscribe.failure
msgid "Thanks for your patience."
msgstr "Gracias por su paciencia."
#. module: mass_mailing_custom_unsubscribe
#: view:website:mass_mailing_custom_unsubscribe.failure
msgid "There was an error processing your unsubscription\n"
" request."
msgstr "There was an error processing your unsubscription\n"
" request."
#. module: mass_mailing_custom_unsubscribe
#: code:addons/mass_mailing_custom_unsubscribe/models/mail_unsubscription.py:59
#, python-format
msgid "This reason requires an explanation."
msgstr "Esta razón requiere rellenar la explicación."
#. module: mass_mailing_custom_unsubscribe
#: field:mail.unsubscription,message_unread:0
msgid "Unread Messages"
msgstr "Mensajes sin leer"
#. module: mass_mailing_custom_unsubscribe
#: view:website:mass_mailing_custom_unsubscribe.reason_form
msgid "Unsubscribe now"
msgstr "Darse de baja ahora"
#. module: mass_mailing_custom_unsubscribe
#: field:mail.unsubscription,unsubscriber_id:0
msgid "Unsubscriber"
msgstr "Desuscriptor"
#. module: mass_mailing_custom_unsubscribe
#: model:ir.actions.act_window,name:mass_mailing_custom_unsubscribe.mail_unsubscription_reason_action
#: model:ir.ui.menu,name:mass_mailing_custom_unsubscribe.mail_unsubscription_reason_menu
msgid "Unsubscription Reasons"
msgstr "Razones de desuscripción"
#. module: mass_mailing_custom_unsubscribe
#: model:ir.actions.act_window,name:mass_mailing_custom_unsubscribe.mail_unsubscription_action
#: model:ir.ui.menu,name:mass_mailing_custom_unsubscribe.mail_unsubscription_menu
msgid "Unsubscriptions"
msgstr "Desuscripciones"
#. module: mass_mailing_custom_unsubscribe
#: view:website:mass_mailing_custom_unsubscribe.failure
msgid "We apologize for the inconvenience. You can contact us\n"
" and we will handle your unsubscription manually."
msgstr "Lamentamos los inconvenientes. Puede contactarnos\n"
" y realizaremos la desuscripción manualmente."
#. module: mass_mailing_custom_unsubscribe
#: field:mail.unsubscription,website_message_ids:0
msgid "Website Messages"
msgstr "Mensajes del sitio web"
#. module: mass_mailing_custom_unsubscribe
#: help:mail.unsubscription,website_message_ids:0
msgid "Website communication history"
msgstr "Historial de comunicación del sitio web"
#. module: mass_mailing_custom_unsubscribe
#: help:mail.unsubscription,unsubscriber_id:0
msgid "Who was unsubscribed."
msgstr "Quién se dio de baja."
#. module: mass_mailing_custom_unsubscribe
#: help:mail.unsubscription,reason_id:0
msgid "Why the unsubscription was made."
msgstr "Por qué se dio de baja."
#. module: mass_mailing_custom_unsubscribe
#: view:mail.unsubscription:mass_mailing_custom_unsubscribe.mail_unsubscription_view_search
msgid "Year"
msgstr "Año"
#. module: mass_mailing_custom_unsubscribe
#: view:website:mass_mailing_custom_unsubscribe.reason_form
msgid "You are trying to unsubscribe from all massive mailings"
msgstr "Está intentando darse de baja de todos los envíos masivos"
#. module: mass_mailing_custom_unsubscribe
#: view:website:mass_mailing_custom_unsubscribe.success
msgid "You were successfully unsubscribed from our\n"
" mailing list."
msgstr "Fue dado de baja correctamente de nuestra\n"
" lista de correo."
#. module: mass_mailing_custom_unsubscribe
#: view:website:mass_mailing_custom_unsubscribe.reason_form
msgid "sent to followers of"
msgstr "enviados a los seguidores de"

BIN
mass_mailing_custom_unsubscribe/images/failure.png

After

Width: 696  |  Height: 411  |  Size: 41 KiB

BIN
mass_mailing_custom_unsubscribe/images/form.png

After

Width: 1004  |  Height: 506  |  Size: 36 KiB

BIN
mass_mailing_custom_unsubscribe/images/success.png

After

Width: 676  |  Height: 376  |  Size: 35 KiB

27
mass_mailing_custom_unsubscribe/migrations/8.0.2.0.0/pre-migrate.py

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# © 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
try:
from openupgradelib.openupgrade import rename_xmlids
except ImportError:
# Simplified version mostly copied from openupgradelib
def rename_xmlids(cr, xmlids_spec):
for (old, new) in xmlids_spec:
if '.' not in old or '.' not in new:
raise Exception(
'Cannot rename XMLID %s to %s: need the module '
'reference to be specified in the IDs' % (old, new))
else:
query = ("UPDATE ir_model_data SET module = %s, name = %s "
"WHERE module = %s and name = %s")
cr.execute(query, tuple(new.split('.') + old.split('.')))
def migrate(cr, version):
"""Update database from previous versions, before updating module."""
rename_xmlids(
cr,
(("website.mass_mail_unsubscription_" + r,
"mass_mailing_custom_unsubscribe." + r)
for r in ("success", "failure")))

8
mass_mailing_custom_unsubscribe/models/__init__.py

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Python source code encoding : https://www.python.org/dev/peps/pep-0263/
##############################################################################
# For copyright and license notices, see __openerp__.py file in root directory
##############################################################################
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from . import mail_mail from . import mail_mail
from . import mail_mass_mailing
from . import mail_mass_mailing_list
from . import mail_unsubscription

35
mass_mailing_custom_unsubscribe/models/mail_mail.py

@ -6,28 +6,37 @@
import urlparse import urlparse
import urllib import urllib
from openerp import models
from openerp import api, models
from openerp.tools.translate import _ from openerp.tools.translate import _
class MailMail(models.Model): class MailMail(models.Model):
_inherit = 'mail.mail' _inherit = 'mail.mail'
def _get_unsubscribe_url(self, cr, uid, mail, email_to,
msg=None, context=None):
m_config = self.pool.get('ir.config_parameter')
base_url = m_config.get_param(cr, uid, 'web.base.url')
config_msg = m_config.get_param(cr, uid,
'mass_mailing.unsubscribe.label')
@api.model
def _get_unsubscribe_url(self, mail, email_to, msg=None):
m_config = self.env['ir.config_parameter']
base_url = m_config.get_param('web.base.url')
config_msg = m_config.get_param('mass_mailing.unsubscribe.label')
params = {
'db': self.env.cr.dbname,
'res_id': mail.res_id,
'email': email_to,
'token': self.env["mail.mass_mailing"].hash_create(
mail.mailing_id.id,
mail.res_id,
email_to),
}
# Avoid `token=None` in URL
if not params["token"]:
del params["token"]
# Generate URL
url = urlparse.urljoin( url = urlparse.urljoin(
base_url, 'mail/mailing/%(mailing_id)s/unsubscribe?%(params)s' % { base_url, 'mail/mailing/%(mailing_id)s/unsubscribe?%(params)s' % {
'mailing_id': mail.mailing_id.id, 'mailing_id': mail.mailing_id.id,
'params': urllib.urlencode({
'db': cr.dbname,
'res_id': mail.res_id,
'email': email_to
})
'params': urllib.urlencode(params),
} }
) )
html = '' html = ''

35
mass_mailing_custom_unsubscribe/models/mail_mass_mailing.py

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# © 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from hashlib import sha256
from uuid import uuid4
from openerp import api, models
class MailMassMailing(models.Model):
_inherit = "mail.mass_mailing"
@api.model
def _init_salt_create(self):
"""Create a salt to secure the unsubscription URLs."""
icp = self.env["ir.config_parameter"]
key = "mass_mailing.salt"
salt = icp.get_param(key)
if salt is False:
salt = str(uuid4())
icp.set_param(key, salt, ["base.group_erp_manager"])
@api.model
def hash_create(self, mailing_id, res_id, email):
"""Create a secure hash to know if the unsubscription is trusted.
:return None/str:
Secure hash, or ``None`` if the system parameter is empty.
"""
salt = self.env["ir.config_parameter"].sudo().get_param(
"mass_mailing.salt")
if not salt:
return None
source = (self.env.cr.dbname, mailing_id, res_id, email, salt)
return sha256(",".join(map(unicode, source))).hexdigest()

15
mass_mailing_custom_unsubscribe/models/mail_mass_mailing_list.py

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
# © 2016 Pedro M. Baeza <pedro.baeza@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp 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?'")

74
mass_mailing_custom_unsubscribe/models/mail_unsubscription.py

@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
# © 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp import _, api, fields, models
from .. import exceptions
class MailUnsubscription(models.Model):
_name = "mail.unsubscription"
_inherit = "mail.thread"
_rec_name = "date"
date = fields.Datetime(
default=lambda self: self._default_date(),
required=True)
email = fields.Char(
required=True)
mass_mailing_id = fields.Many2one(
"mail.mass_mailing",
"Mass mailing",
required=True,
help="Mass mailing from which he was unsubscribed.")
unsubscriber_id = fields.Reference(
lambda self: self._selection_unsubscriber_id(),
"Unsubscriber",
required=True,
help="Who was unsubscribed.")
reason_id = fields.Many2one(
"mail.unsubscription.reason",
"Reason",
ondelete="restrict",
required=True,
help="Why the unsubscription was made.")
details = fields.Char(
help="More details on why the unsubscription was made.")
details_required = fields.Boolean(
related="reason_id.details_required")
success = fields.Boolean(
help="If this is unchecked, it indicates some failure happened in the "
"unsubscription process.")
@api.model
def _default_date(self):
return fields.Datetime.now()
@api.model
def _selection_unsubscriber_id(self):
"""Models that can be linked to a ``mail.mass_mailing``."""
return self.env["mail.mass_mailing"]._get_mailing_model()
@api.multi
@api.constrains("details", "reason_id")
def _check_details_needed(self):
"""Ensure details are given if required."""
for s in self:
if not s.details and s.details_required:
raise exceptions.DetailsRequiredError(
_("This reason requires an explanation."))
class MailUnsubscriptionReason(models.Model):
_name = "mail.unsubscription.reason"
_order = "sequence, name"
name = fields.Char(
index=True,
translate=True,
required=True)
details_required = fields.Boolean(
help="Check to ask for more details when this reason is selected.")
sequence = fields.Integer(
index=True,
help="Position of the reason in the list.")

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

@ -0,0 +1,6 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"read_unsubscription_reason_public","Public users can read unsubscription reasons","model_mail_unsubscription_reason","base.group_public",1,0,0,0
"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_campaign",1,1,1,1
"read_unsubscription","Marketing users can read unsubscriptions","model_mail_unsubscription","marketing.group_marketing_user",1,0,0,0
"write_unsubscription","Mass mailing managers can manage unsubscriptions","model_mail_unsubscription","mass_mailing.group_mass_mailing_campaign",1,1,1,1

13
mass_mailing_custom_unsubscribe/static/src/js/require_details.js

@ -0,0 +1,13 @@
/* © 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
"use strict";
(function ($) {
$("#reason_form :radio").change(function(event) {
$("textarea[name=details]").attr(
"required",
$(event.target).is("[data-details-required]")
);
});
$("#reason_form :radio:checked").change();
})(jQuery);

7
mass_mailing_custom_unsubscribe/tests/__init__.py

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
# © 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import test_unsubscription
from . import test_mail_mail
from . import test_controller

111
mass_mailing_custom_unsubscribe/tests/test_controller.py

@ -0,0 +1,111 @@
# -*- coding: utf-8 -*-
# © 2016 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import mock
from contextlib import contextmanager
from openerp.tests.common import TransactionCase
from openerp.addons.mass_mailing_custom_unsubscribe.controllers.main import (
CustomUnsubscribe
)
model = 'openerp.addons.mass_mailing_custom_unsubscribe.controllers.main'
@contextmanager
def mock_assets():
""" Mock & yield controller assets """
with mock.patch('%s.request' % model) as request:
yield {
'request': request,
}
class EndTestException(Exception):
pass
class TestController(TransactionCase):
def setUp(self):
super(TestController, self).setUp()
self.controller = CustomUnsubscribe()
def _default_domain(self):
return [
('opt_out', '=', False),
('list_id.not_cross_unsubscriptable', '=', False),
]
def test_mailing_list_contacts_by_email_search(self):
""" It should search for contacts """
expect = 'email'
with mock_assets() as mk:
self.controller._mailing_list_contacts_by_email(expect)
model_obj = mk['request'].env['mail.mass_mailing.contact'].sudo()
model_obj.search.assert_called_once_with(
[('email', '=', expect)] + self._default_domain()
)
def test_mailing_list_contacts_by_email_return(self):
""" It should return result of search """
expect = 'email'
with mock_assets() as mk:
res = self.controller._mailing_list_contacts_by_email(expect)
model_obj = mk['request'].env['mail.mass_mailing.contact'].sudo()
self.assertEqual(
model_obj.search(), res,
)
def test_unsubscription_reason_gets_context(self):
""" It should retrieve unsub qcontext """
expect = 'mailing_id', 'email', 'res_id', 'token'
with mock_assets():
with mock.patch.object(
self.controller, 'unsubscription_qcontext'
) as unsub:
unsub.side_effect = EndTestException
with self.assertRaises(EndTestException):
self.controller.unsubscription_reason(*expect)
unsub.assert_called_once_with(*expect)
def test_unsubscription_updates_with_extra_context(self):
""" It should update qcontext with provided vals """
expect = 'mailing_id', 'email', 'res_id', 'token'
qcontext = {'context': 'test'}
with mock_assets():
with mock.patch.object(
self.controller, 'unsubscription_qcontext'
) as unsub:
self.controller.unsubscription_reason(
*expect, qcontext_extra=qcontext
)
unsub().update.assert_called_once_with(qcontext)
def test_unsubscription_updates_rendered_correctly(self):
""" It should correctly render website """
expect = 'mailing_id', 'email', 'res_id', 'token'
with mock_assets() as mk:
with mock.patch.object(
self.controller, 'unsubscription_qcontext'
) as unsub:
self.controller.unsubscription_reason(*expect)
mk['request'].website.render.assert_called_once_with(
"mass_mailing_custom_unsubscribe.reason_form",
unsub(),
)
def test_unsubscription_updates_returns_site(self):
""" It should return website """
expect = 'mailing_id', 'email', 'res_id', 'token'
with mock_assets() as mk:
with mock.patch.object(
self.controller, 'unsubscription_qcontext'
):
res = self.controller.unsubscription_reason(*expect)
self.assertEqual(
mk['request'].website.render(), res
)

97
mass_mailing_custom_unsubscribe/tests/test_mail_mail.py

@ -0,0 +1,97 @@
# -*- coding: utf-8 -*-
# © 2016 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import mock
from openerp.tests.common import TransactionCase
model = 'openerp.addons.mass_mailing_custom_unsubscribe.models.mail_mail'
class EndTestException(Exception):
pass
class TestMailMail(TransactionCase):
def setUp(self):
super(TestMailMail, self).setUp()
self.Model = self.env['mail.mail']
param_obj = self.env['ir.config_parameter']
self.base_url = param_obj.get_param('web.base.url')
self.config_msg = param_obj.get_param(
'mass_mailing.unsubscribe.label'
)
@mock.patch('%s.urlparse' % model)
@mock.patch('%s.urllib' % model)
def test_get_unsubscribe_url_proper_url(self, urllib, urlparse):
""" It should join the URL w/ proper args """
urlparse.urljoin.side_effect = EndTestException
expect = mock.MagicMock(), 'email', 'msg'
with self.assertRaises(EndTestException):
self.Model._get_unsubscribe_url(*expect)
urlparse.urljoin.assert_called_once_with(
self.base_url,
'mail/mailing/%(mailing_id)s/unsubscribe?%(params)s' % {
'mailing_id': expect[0].mailing_id.id,
'params': urllib.urlencode(),
}
)
@mock.patch('%s.urlparse' % model)
@mock.patch('%s.urllib' % model)
def test_get_unsubscribe_url_correct_params(self, urllib, urlparse):
""" It should create URL params w/ proper data """
urlparse.urljoin.side_effect = EndTestException
expect = mock.MagicMock(), 'email', 'msg'
with self.assertRaises(EndTestException):
self.Model._get_unsubscribe_url(*expect)
urllib.urlencode.assert_called_once_with(dict(
db=self.env.cr.dbname,
res_id=expect[0].res_id,
email=expect[1],
token=self.env['mail.mass_mailing'].hash_create(
expect[0].mailing_id.id,
expect[0].res_id,
expect[1],
)
))
@mock.patch('%s.urlparse' % model)
@mock.patch('%s.urllib' % model)
def test_get_unsubscribe_url_false_config_msg(self, urllib, urlparse):
""" It should return default config msg when none supplied """
expects = ['uri', False]
urlparse.urljoin.return_value = expects[0]
with mock.patch.object(self.Model, 'env') as env:
env['ir.config_paramater'].get_param.side_effect = expects
res = self.Model._get_unsubscribe_url(
mock.MagicMock(), 'email', 'msg'
)
self.assertIn(
expects[0], res,
'Did not include URI in default message'
)
self.assertIn(
'msg', res,
'Did not include input msg in default message'
)
@mock.patch('%s.urlparse' % model)
@mock.patch('%s.urllib' % model)
def test_get_unsubscribe_url_with_config_msg(self, urllib, urlparse):
""" It should return config message w/ URL formatted """
expects = ['uri', 'test %(url)s']
urlparse.urljoin.return_value = expects[0]
with mock.patch.object(self.Model, 'env') as env:
env['ir.config_paramater'].get_param.side_effect = expects
res = self.Model._get_unsubscribe_url(
mock.MagicMock(), 'email', 'msg'
)
self.assertEqual(
expects[1] % {'url': expects[0]}, res,
'Did not return proper config message'
)

21
mass_mailing_custom_unsubscribe/tests/test_unsubscription.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# © 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp.tests.common import TransactionCase
from .. import exceptions
class UnsubscriptionCase(TransactionCase):
def test_details_required(self):
"""Cannot create unsubscription without details when required."""
with self.assertRaises(exceptions.DetailsRequiredError):
self.env["mail.unsubscription"].create({
"email": "axelor@yourcompany.example.com",
"mass_mailing_id": self.env.ref("mass_mailing.mass_mail_1").id,
"unsubscriber_id":
"res.partner,%d" % self.env.ref("base.res_partner_13").id,
"reason_id":
self.env.ref(
"mass_mailing_custom_unsubscribe.reason_other").id,
})

17
mass_mailing_custom_unsubscribe/views/assets.xml

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- © 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<openerp>
<data>
<template id="assets_frontend"
inherit_id="website.assets_frontend">
<xpath expr=".">
<script type="text/javascript"
src="/mass_mailing_custom_unsubscribe/static/src/js/require_details.js"/>
</xpath>
</template>
</data>
</openerp>

21
mass_mailing_custom_unsubscribe/views/mail_mass_mailing_list_view.xml

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- © 2016 Pedro M. Baeza <pedro.baeza@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<openerp>
<data>
<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>
</data>
</openerp>

60
mass_mailing_custom_unsubscribe/views/mail_unsubscription_reason_view.xml

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- © 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<openerp>
<data>
<record id="mail_unsubscription_reason_view_form" model="ir.ui.view">
<field name="name">Mail Unsubscription Reason Form</field>
<field name="model">mail.unsubscription.reason</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="name"/>
<field name="details_required"/>
<field name="sequence"/>
</group>
<div class="oe_chatter"/>
</sheet>
</form>
</field>
</record>
<record id="mail_unsubscription_reason_view_tree" model="ir.ui.view">
<field name="name">Mail Unsubscription Reason Tree</field>
<field name="model">mail.unsubscription.reason</field>
<field name="arch" type="xml">
<tree>
<field name="name"/>
<field name="details_required"/>
<field name="sequence" invisible="True"/>
</tree>
</field>
</record>
<record id="mail_unsubscription_reason_view_search" model="ir.ui.view">
<field name="name">Mail Unsubscription Reason Search</field>
<field name="model">mail.unsubscription.reason</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<field name="details_required"/>
</search>
</field>
</record>
<act_window
id="mail_unsubscription_reason_action"
name="Unsubscription Reasons"
res_model="mail.unsubscription.reason"/>
<menuitem
id="mail_unsubscription_reason_menu"
parent="mass_mailing.marketing_configuration"
groups="mass_mailing.group_mass_mailing_campaign"
action="mail_unsubscription_reason_action"/>
</data>
</openerp>

89
mass_mailing_custom_unsubscribe/views/mail_unsubscription_view.xml

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- © 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<openerp>
<data>
<record id="mail_unsubscription_view_form" model="ir.ui.view">
<field name="name">Mail Unsubscription Form</field>
<field name="model">mail.unsubscription</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="date"/>
<field name="mass_mailing_id"/>
<field name="unsubscriber_id"/>
<field name="email"/>
<field name="success"/>
<field name="reason_id"/>
<field name="details"
attrs="{'required': [('details_required', '=', True)]}"/>
<field name="details_required" invisible="True"/>
</group>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids"
widget="mail_followers"
groups="base.group_user"/>
<field name="message_ids"
widget="mail_thread"/>
</div>
</form>
</field>
</record>
<record id="mail_unsubscription_view_tree" model="ir.ui.view">
<field name="name">Mail Unsubscription Tree</field>
<field name="model">mail.unsubscription</field>
<field name="arch" type="xml">
<tree>
<field name="date"/>
<field name="mass_mailing_id"/>
<field name="unsubscriber_id"/>
<field name="email" invisible="True"/>
<field name="reason_id"/>
<field name="details" invisible="True"/>
</tree>
</field>
</record>
<record id="mail_unsubscription_view_search" model="ir.ui.view">
<field name="name">Mail Unsubscription Search</field>
<field name="model">mail.unsubscription</field>
<field name="arch" type="xml">
<search>
<field name="mass_mailing_id"/>
<field name="unsubscriber_id"/>
<field name="email"/>
<field name="success"/>
<field name="reason_id"/>
<field name="details"/>
<separator/>
<group string="Group by">
<filter string="Month"
context="{'group_by': 'date:month'}"/>
<filter string="Year"
context="{'group_by': 'date:year'}"/>
<filter string="Reason"
context="{'group_by': 'reason_id'}"/>
<filter string="Mass mailing"
context="{'group_by': 'mass_mailing_id'}"/>
<filter string="Success"
context="{'group_by': 'success'}"/>
</group>
</search>
</field>
</record>
<act_window id="mail_unsubscription_action"
name="Unsubscriptions"
res_model="mail.unsubscription"/>
<menuitem id="mail_unsubscription_menu"
parent="mass_mailing.mass_mailing_campaign"
action="mail_unsubscription_action"/>
</data>
</openerp>

106
mass_mailing_custom_unsubscribe/views/pages.xml

@ -3,7 +3,7 @@
<data> <data>
<template name="Unsubscription worked" <template name="Unsubscription worked"
id="website.mass_mail_unsubscription_success"
id="success"
page="True"> page="True">
<t t-call="website.layout"> <t t-call="website.layout">
<div id="wrap" class="oe_structure oe_empty"> <div id="wrap" class="oe_structure oe_empty">
@ -18,12 +18,11 @@
something, let it go. something, let it go.
</h3> </h3>
<p> <p>
However, we are open to suggestions. Please tell us
why you left.
Is there anything else you want to tell us?
</p> </p>
<p> <p>
<a class="btn btn-primary btn-lg" <a class="btn btn-primary btn-lg"
href="/page/website.contactus">Contact us</a>
href="/page/contactus">Contact us</a>
</p> </p>
</div> </div>
</section> </section>
@ -32,7 +31,7 @@
</template> </template>
<template name="Unsubscription failed" <template name="Unsubscription failed"
id="website.mass_mail_unsubscription_failure"
id="failure"
page="True"> page="True">
<t t-call="website.layout"> <t t-call="website.layout">
<div id="wrap" class="oe_structure oe_empty"> <div id="wrap" class="oe_structure oe_empty">
@ -49,7 +48,7 @@
<p>Thanks for your patience.</p> <p>Thanks for your patience.</p>
<p> <p>
<a class="btn btn-primary btn-lg" <a class="btn btn-primary btn-lg"
href="/page/website.contactus">Contact us</a>
href="/page/contactus">Contact us</a>
</p> </p>
</div> </div>
</section> </section>
@ -57,5 +56,100 @@
</t> </t>
</template> </template>
<template id="reason_form"
name="Unsubscription Reason Form">
<t t-call="website.layout">
<div id="wrap" class="oe_structure oe_empty">
<section class="mt16 mb16">
<form
id="reason_form"
class="container"
t-attf-action="/mail/mailing/#{mailing_id.id}/unsubscribe"
method="post">
<div class="row">
<div class="col-md-12 text-center mt16 mb32">
<h2>
Hello,
<t t-esc="contact_name"/>
</h2>
<h3 class="text-muted">
You are trying to unsubscribe from all massive mailings
<t t-if="origin_name">
sent to followers of
<br/>
<br/>
<i><span>"</span><t t-esc="origin_name"/><span>"</span></i>
</t>
</h3>
</div>
<div t-if="additional_contact_ids"
class="col-md-12 mt16">
Is there any other mailing list you want to leave?
<t t-foreach="additional_contact_ids"
t-as="contact">
<div class="checkbox">
<label>
<input
t-attf-name="list_id,#{contact.list_id.id}"
type="checkbox"
t-att-value="contact.id"/>
<t t-esc="contact.list_id.display_name"/>
</label>
</div>
</t>
</div>
<div class="col-md-12 mt16">
But before continuing, could you please tell us why do you want to unsubscribe?
</div>
<div class="col-md-12 mb16">
<input
type="hidden"
name="db"
t-att-value="env.cr.dbname"/>
<input
type="hidden"
name="res_id"
t-att-value="res_id"/>
<input
type="hidden"
name="email"
t-att-value="email"/>
<input
type="hidden"
name="token"
t-att-value="token"/>
<t t-foreach="reason_ids" t-as="reason">
<div class="radio">
<label>
<input
type="radio"
name="reason_id"
t-att-data-details-required="reason.details_required"
t-att-value="reason.id"/>
<t t-esc="reason.display_name"/>
</label>
</div>
</t>
<div t-attf-class="form-group #{error_details_required and 'has-error' or ''}">
<textarea
name="details"
class="form-control"
placeholder="Anything else you want to say before you leave?"
rows="3"/>
</div>
<div class="form-group mb16 mt16">
<button type="submit" class="btn btn-danger">
Unsubscribe now
</button>
<p class="help-block">Thank you!</p>
</div>
</div>
</div>
</form>
</section>
</div>
</t>
</template>
</data> </data>
</openerp> </openerp>
Loading…
Cancel
Save