[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
|
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
|
Unsubscription Reasons
|
||||||
customize it clicking here **after installing the module**:
|
----------------------
|
||||||
|
|
||||||
* `Unsubscription successful </page/mass_mail_unsubscription_success>`_.
|
You can customize what reasons will be displayed to your unsubscriptors when
|
||||||
* `Unsubscription failed </page/mass_mail_unsubscription_failure>`_.
|
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
|
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.
|
||||||
|
@ -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',
|
||||||
|
@ -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.
|
# 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"
|
"POT-Creation-Date: 2016-05-23 14:21+0000\n"
|
||||||
"PO-Revision-Date: 2015-09-04 14:43+0000\n"
|
"PO-Revision-Date: 2016-05-23 14:21+0000\n"
|
||||||
"Last-Translator: OCA Transbot <transbot@odoo-community.org>\n"
|
"Last-Translator: <>\n"
|
||||||
"Language-Team: Spanish (http://www.transifex.com/oca/OCA-social-8-0/language/es/)\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: \n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\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
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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Python source code encoding : https://www.python.org/dev/peps/pep-0263/
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
|
||||||
##############################################################################
|
|
||||||
# For copyright and license notices, see __openerp__.py file in root directory
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
from . import mail_mail
|
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 urlparse
|
||||||
import urllib
|
import urllib
|
||||||
|
from openerp import api, models
|
||||||
from openerp import 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,
|
@api.model
|
||||||
msg=None, context=None):
|
def _get_unsubscribe_url(self, mail, email_to, msg=None):
|
||||||
m_config = self.pool.get('ir.config_parameter')
|
m_config = self.env['ir.config_parameter']
|
||||||
base_url = m_config.get_param(cr, uid, 'web.base.url')
|
base_url = m_config.get_param('web.base.url')
|
||||||
config_msg = m_config.get_param(cr, uid,
|
config_msg = m_config.get_param('mass_mailing.unsubscribe.label')
|
||||||
'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({
|
'params': urllib.urlencode(params),
|
||||||
'db': cr.dbname,
|
|
||||||
'res_id': mail.res_id,
|
|
||||||
'email': email_to
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
html = ''
|
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>
|
<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
|
Is there anything else you want to tell us?
|
||||||
why you left.
|
|
||||||
</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…
x
Reference in New Issue
Block a user