[8.0][IMP][mass_mailing_custom_unsubscribe] Get reasons for unsubscription (#58)
* [8.0][IMP][mass_mailing_custom_unsubscribe] Get reasons for unsubscription.
This commit is contained in:
parent
1b72b82749
commit
aa7e32c2b1
@ -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
|
||||
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
|
||||
=============
|
||||
|
||||
Unsubscription Message In Mail Footer
|
||||
-------------------------------------
|
||||
|
||||
To configure unsubscribe label go to *Settings > Technical > Parameters >
|
||||
System parameters* and add a ``mass_mailing.unsubscribe.label`` parameter
|
||||
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
|
||||
terms*.
|
||||
|
||||
Also your unsubscriptors will recieve a beautier goodbye page. You can
|
||||
customize it clicking here **after installing the module**:
|
||||
Unsubscription Reasons
|
||||
----------------------
|
||||
|
||||
* `Unsubscription successful </page/mass_mail_unsubscription_success>`_.
|
||||
* `Unsubscription failed </page/mass_mail_unsubscription_failure>`_.
|
||||
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
|
||||
------------------------------
|
||||
|
||||
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
|
||||
=====
|
||||
|
||||
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
|
||||
:alt: Try me on Runbot
|
||||
:target: https://runbot.odoo-community.org/runbot/205/8.0
|
||||
|
||||
|
||||
Known issues / Roadmap
|
||||
======================
|
||||
|
||||
* This needs tests.
|
||||
* This custom HTML is not translatable, so as a suggestion, you can define
|
||||
the same text in several languages in several lines.
|
||||
|
||||
For example:
|
||||
For example:
|
||||
|
||||
.. code:: html
|
||||
|
||||
<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>
|
||||
|
||||
* 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
|
||||
===========
|
||||
@ -63,8 +99,10 @@ Bug Tracker
|
||||
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.
|
||||
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
|
||||
=======
|
||||
@ -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
|
||||
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.
|
||||
|
@ -23,15 +23,28 @@
|
||||
{
|
||||
'name': "Customizable unsubscription process on mass mailing emails",
|
||||
'category': 'Marketing',
|
||||
'version': '8.0.1.1.0',
|
||||
'version': '8.0.2.0.0',
|
||||
'depends': [
|
||||
'mass_mailing',
|
||||
'website_crm',
|
||||
],
|
||||
'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',
|
||||
],
|
||||
'images': [
|
||||
'images/failure.png',
|
||||
'images/form.png',
|
||||
'images/success.png',
|
||||
],
|
||||
'author': 'Antiun Ingeniería S.L., '
|
||||
'Tecnativa,'
|
||||
'Odoo Community Association (OCA)',
|
||||
'website': 'http://www.antiun.com',
|
||||
'license': 'AGPL-3',
|
||||
|
@ -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
Normal file
5
mass_mailing_custom_unsubscribe/controllers/__init__.py
Normal file
@ -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
Normal file
237
mass_mailing_custom_unsubscribe/controllers/main.py
Normal file
@ -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
Normal file
11
mass_mailing_custom_unsubscribe/data/install_salt.xml
Normal file
@ -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>
|
@ -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
Normal file
9
mass_mailing_custom_unsubscribe/exceptions.py
Normal file
@ -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
|
@ -1,29 +1,381 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * mass_mailing_custom_unsubscribe
|
||||
#
|
||||
# Translators:
|
||||
# * mass_mailing_custom_unsubscribe
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: social (8.0)\n"
|
||||
"Project-Id-Version: Odoo Server 8.0\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"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Language: es\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. 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
|
||||
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
|
||||
#: model:ir.model,name:mass_mailing_custom_unsubscribe.model_mail_mail
|
||||
msgid "Outgoing Mails"
|
||||
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
Normal file
BIN
mass_mailing_custom_unsubscribe/images/failure.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
BIN
mass_mailing_custom_unsubscribe/images/form.png
Normal file
BIN
mass_mailing_custom_unsubscribe/images/form.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
BIN
mass_mailing_custom_unsubscribe/images/success.png
Normal file
BIN
mass_mailing_custom_unsubscribe/images/success.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
@ -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")))
|
@ -1,7 +1,7 @@
|
||||
# -*- 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_mass_mailing
|
||||
from . import mail_mass_mailing_list
|
||||
from . import mail_unsubscription
|
||||
|
@ -6,28 +6,37 @@
|
||||
|
||||
import urlparse
|
||||
import urllib
|
||||
|
||||
from openerp import models
|
||||
from openerp import api, models
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
||||
class MailMail(models.Model):
|
||||
_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(
|
||||
base_url, 'mail/mailing/%(mailing_id)s/unsubscribe?%(params)s' % {
|
||||
'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 = ''
|
||||
|
35
mass_mailing_custom_unsubscribe/models/mail_mass_mailing.py
Normal file
35
mass_mailing_custom_unsubscribe/models/mail_mass_mailing.py
Normal file
@ -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()
|
@ -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?'")
|
@ -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.")
|
@ -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
|
|
@ -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
Normal file
7
mass_mailing_custom_unsubscribe/tests/__init__.py
Normal file
@ -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
Normal file
111
mass_mailing_custom_unsubscribe/tests/test_controller.py
Normal file
@ -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
Normal file
97
mass_mailing_custom_unsubscribe/tests/test_mail_mail.py
Normal file
@ -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
Normal file
21
mass_mailing_custom_unsubscribe/tests/test_unsubscription.py
Normal file
@ -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
Normal file
17
mass_mailing_custom_unsubscribe/views/assets.xml
Normal file
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -3,7 +3,7 @@
|
||||
<data>
|
||||
|
||||
<template name="Unsubscription worked"
|
||||
id="website.mass_mail_unsubscription_success"
|
||||
id="success"
|
||||
page="True">
|
||||
<t t-call="website.layout">
|
||||
<div id="wrap" class="oe_structure oe_empty">
|
||||
@ -18,12 +18,11 @@
|
||||
something, let it go.
|
||||
</h3>
|
||||
<p>
|
||||
However, we are open to suggestions. Please tell us
|
||||
why you left.
|
||||
Is there anything else you want to tell us?
|
||||
</p>
|
||||
<p>
|
||||
<a class="btn btn-primary btn-lg"
|
||||
href="/page/website.contactus">Contact us</a>
|
||||
href="/page/contactus">Contact us</a>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
@ -32,7 +31,7 @@
|
||||
</template>
|
||||
|
||||
<template name="Unsubscription failed"
|
||||
id="website.mass_mail_unsubscription_failure"
|
||||
id="failure"
|
||||
page="True">
|
||||
<t t-call="website.layout">
|
||||
<div id="wrap" class="oe_structure oe_empty">
|
||||
@ -49,7 +48,7 @@
|
||||
<p>Thanks for your patience.</p>
|
||||
<p>
|
||||
<a class="btn btn-primary btn-lg"
|
||||
href="/page/website.contactus">Contact us</a>
|
||||
href="/page/contactus">Contact us</a>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
@ -57,5 +56,100 @@
|
||||
</t>
|
||||
</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>
|
||||
</openerp>
|
||||
|
Loading…
Reference in New Issue
Block a user