[MIG] mass_mailing_custom_unsubscribe: Migration to 11.0
This commit is contained in:
parent
0d1b3a499e
commit
2e416ed4d0
@ -1,96 +1 @@
|
|||||||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
|
**This file is going to be generated by oca-gen-addon-readme.**
|
||||||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
|
||||||
:alt: License: AGPL-3
|
|
||||||
|
|
||||||
==========================================================
|
|
||||||
Customizable unsubscription process on mass mailing emails
|
|
||||||
==========================================================
|
|
||||||
|
|
||||||
This addon extends the unsubscription form to let you:
|
|
||||||
|
|
||||||
- Choose which mailing lists are not cross-unsubscriptable when unsubscribing
|
|
||||||
from a different one.
|
|
||||||
- Know why and when a contact has been subscribed or unsubscribed from a
|
|
||||||
mass mailing.
|
|
||||||
- Provide proof on why you are sending mass mailings to a given contact, as
|
|
||||||
required by the GDPR in Europe.
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
=============
|
|
||||||
|
|
||||||
Unsubscription Reasons
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
You can customize what reasons will be displayed to your unsubscriptors when
|
|
||||||
they are going to unsubscribe. To do it:
|
|
||||||
|
|
||||||
#. Go to *Mass Mailing > Configuration > Unsubscription Reasons*.
|
|
||||||
#. Create / edit / remove / sort as usual.
|
|
||||||
#. If *Details required* is enabled, they will have to fill a text area to
|
|
||||||
continue.
|
|
||||||
|
|
||||||
Usage
|
|
||||||
=====
|
|
||||||
|
|
||||||
Once configured:
|
|
||||||
|
|
||||||
#. Go to *Mass Mailing > Mailings > Mass Mailings > Create*.
|
|
||||||
#. Edit your mass mailing at wish, but remember to add a snippet from
|
|
||||||
*Footers*, so people have an *Unsubscribe* link.
|
|
||||||
#. Send it.
|
|
||||||
#. If somebody gets unsubscribed, you will see logs about that under
|
|
||||||
*Mass Mailing > Mailings > 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/10.0
|
|
||||||
|
|
||||||
Known issues / Roadmap
|
|
||||||
======================
|
|
||||||
|
|
||||||
* This module adds a security hash for mass mailing unsubscription URLs, which
|
|
||||||
disables insecure URLs from mass mailing messages sent before its
|
|
||||||
installation. This can be a problem, but anyway you'd get that problem in
|
|
||||||
Odoo 11.0, where https://github.com/odoo/odoo/pull/12040 was merged, so at
|
|
||||||
least this addon will be forward-compatible with it. So, **this feature must
|
|
||||||
be removed from here when migrating to v11**.
|
|
||||||
* This module replaces AJAX submission core implementation from the mailing
|
|
||||||
list management form, because it is impossible to extend it. When
|
|
||||||
https://github.com/odoo/odoo/pull/14386 gets merged (which upstreams most
|
|
||||||
needed changes), this addon will need a refactoring (mostly removing
|
|
||||||
duplicated functionality and depending on it instead of replacing it). In the
|
|
||||||
mean time, there is a little chance that this introduces some
|
|
||||||
incompatibilities with other addons that depend on ``website_mass_mailing``.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Credits
|
|
||||||
=======
|
|
||||||
|
|
||||||
Contributors
|
|
||||||
------------
|
|
||||||
|
|
||||||
* Rafael Blasco <rafael.blasco@tecnativa.com>
|
|
||||||
* Antonio Espinosa <antonio.espinosa@tecnativa.com>
|
|
||||||
* Jairo Llopis <jairo.llopis@tecnativa.com>
|
|
||||||
|
|
||||||
Maintainer
|
|
||||||
----------
|
|
||||||
|
|
||||||
.. image:: https://odoo-community.org/logo.png
|
|
||||||
:alt: Odoo Community Association
|
|
||||||
:target: https://odoo-community.org
|
|
||||||
|
|
||||||
This module is maintained by the OCA.
|
|
||||||
|
|
||||||
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 https://odoo-community.org.
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
|
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
from . import controllers, models
|
from . import controllers, models
|
||||||
|
from .hooks import post_init_hook
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
|
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
|
||||||
|
# Copyright 2018 David Vidal <david.vidal@tecnativa.com>
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
{
|
{
|
||||||
'name': "Customizable unsubscription process on mass mailing emails",
|
'name': 'Customizable unsubscription process on mass mailing emails',
|
||||||
"summary": "Know and track (un)subscription reasons, GDPR compliant",
|
'summary': 'Know and track (un)subscription reasons, GDPR compliant',
|
||||||
'category': 'Marketing',
|
'category': 'Marketing',
|
||||||
'version': '10.0.2.0.0',
|
'version': '11.0.1.0.0',
|
||||||
'depends': [
|
'depends': [
|
||||||
'website_mass_mailing',
|
'website_mass_mailing',
|
||||||
],
|
],
|
||||||
@ -17,9 +17,10 @@
|
|||||||
'views/assets.xml',
|
'views/assets.xml',
|
||||||
'views/mail_unsubscription_reason_view.xml',
|
'views/mail_unsubscription_reason_view.xml',
|
||||||
'views/mail_mass_mailing_list_view.xml',
|
'views/mail_mass_mailing_list_view.xml',
|
||||||
|
'views/mail_mass_mailing_contact_view.xml',
|
||||||
'views/mail_unsubscription_view.xml',
|
'views/mail_unsubscription_view.xml',
|
||||||
],
|
],
|
||||||
"demo": [
|
'demo': [
|
||||||
'demo/assets.xml',
|
'demo/assets.xml',
|
||||||
],
|
],
|
||||||
'images': [
|
'images': [
|
||||||
@ -27,7 +28,8 @@
|
|||||||
],
|
],
|
||||||
'author': 'Tecnativa,'
|
'author': 'Tecnativa,'
|
||||||
'Odoo Community Association (OCA)',
|
'Odoo Community Association (OCA)',
|
||||||
'website': 'https://www.tecnativa.com',
|
'website': 'https://github.com/OCA/social',
|
||||||
'license': 'AGPL-3',
|
'license': 'AGPL-3',
|
||||||
'installable': True,
|
'installable': True,
|
||||||
|
'post_init_hook': 'post_init_hook',
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
|
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2015 Antiun Ingeniería S.L. (http://www.antiun.com)
|
# Copyright 2015 Antiun Ingeniería S.L. (http://www.antiun.com)
|
||||||
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
|
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from openerp.http import request, route
|
from odoo.http import request, route
|
||||||
from openerp.addons.website_mass_mailing.controllers.main \
|
from odoo.addons.website_mass_mailing.controllers.main \
|
||||||
import MassMailController
|
import MassMailController
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
@ -45,24 +44,18 @@ class CustomUnsubscribe(MassMailController):
|
|||||||
_logger.debug(
|
_logger.debug(
|
||||||
"Called `mailing()` with: %r",
|
"Called `mailing()` with: %r",
|
||||||
(mailing_id, email, res_id, token, post))
|
(mailing_id, email, res_id, token, post))
|
||||||
if res_id:
|
|
||||||
res_id = int(res_id)
|
|
||||||
mailing = request.env["mail.mass_mailing"].sudo().browse(mailing_id)
|
mailing = request.env["mail.mass_mailing"].sudo().browse(mailing_id)
|
||||||
mailing._unsubscribe_token(res_id, token)
|
|
||||||
# Mass mailing list contacts are a special case because they have a
|
# Mass mailing list contacts are a special case because they have a
|
||||||
# subscription management form
|
# subscription management form
|
||||||
if mailing.mailing_model == 'mail.mass_mailing.contact':
|
if mailing.mailing_model_real == 'mail.mass_mailing.contact':
|
||||||
result = super(CustomUnsubscribe, self).mailing(
|
result = super(CustomUnsubscribe, self).mailing(
|
||||||
mailing_id, email, res_id, **post)
|
mailing_id, email, res_id, token=token, **post)
|
||||||
# FIXME Remove res_id and token in version where this is merged:
|
|
||||||
# https://github.com/odoo/odoo/pull/14385
|
|
||||||
result.qcontext.update({
|
result.qcontext.update({
|
||||||
"token": token,
|
|
||||||
"res_id": res_id,
|
|
||||||
"contacts": result.qcontext["contacts"].filtered(
|
"contacts": result.qcontext["contacts"].filtered(
|
||||||
lambda contact:
|
lambda contact:
|
||||||
not contact.list_id.not_cross_unsubscriptable or
|
not any(contact.list_ids.mapped(
|
||||||
contact.list_id <= mailing.contact_list_ids
|
'not_cross_unsubscriptable')) or
|
||||||
|
contact.list_ids <= mailing.contact_list_ids
|
||||||
),
|
),
|
||||||
"reasons":
|
"reasons":
|
||||||
request.env["mail.unsubscription.reason"].search([]),
|
request.env["mail.unsubscription.reason"].search([]),
|
||||||
@ -85,7 +78,7 @@ class CustomUnsubscribe(MassMailController):
|
|||||||
# You could get a DetailsRequiredError here, but only if HTML5
|
# You could get a DetailsRequiredError here, but only if HTML5
|
||||||
# validation fails, which should not happen in modern browsers
|
# validation fails, which should not happen in modern browsers
|
||||||
return super(CustomUnsubscribe, self).mailing(
|
return super(CustomUnsubscribe, self).mailing(
|
||||||
mailing_id, email, res_id, **post)
|
mailing_id, email, res_id, token=token, **post)
|
||||||
|
|
||||||
@route()
|
@route()
|
||||||
def unsubscribe(self, mailing_id, opt_in_ids, opt_out_ids, email, res_id,
|
def unsubscribe(self, mailing_id, opt_in_ids, opt_out_ids, email, res_id,
|
||||||
@ -107,10 +100,6 @@ class CustomUnsubscribe(MassMailController):
|
|||||||
if details:
|
if details:
|
||||||
extra_context["default_details"] = details
|
extra_context["default_details"] = details
|
||||||
request.context = dict(request.context, **extra_context)
|
request.context = dict(request.context, **extra_context)
|
||||||
# FIXME Remove token check in version where this is merged:
|
|
||||||
# https://github.com/odoo/odoo/pull/14385
|
|
||||||
mailing = request.env['mail.mass_mailing'].sudo().browse(mailing_id)
|
|
||||||
mailing._unsubscribe_token(res_id, token)
|
|
||||||
_logger.debug(
|
_logger.debug(
|
||||||
"Called `unsubscribe()` with: %r",
|
"Called `unsubscribe()` with: %r",
|
||||||
(mailing_id, opt_in_ids, opt_out_ids, email, res_id, token,
|
(mailing_id, opt_in_ids, opt_out_ids, email, res_id, token,
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
|
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
from openerp import exceptions
|
from odoo import exceptions
|
||||||
|
|
||||||
|
|
||||||
class DetailsRequiredError(exceptions.ValidationError):
|
class DetailsRequiredError(exceptions.ValidationError):
|
||||||
|
17
mass_mailing_custom_unsubscribe/hooks.py
Normal file
17
mass_mailing_custom_unsubscribe/hooks.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Copyright 2018 David Vidal <david.vidal@tecnativa.com>
|
||||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from odoo import api, SUPERUSER_ID
|
||||||
|
|
||||||
|
|
||||||
|
def post_init_hook(cr, registry):
|
||||||
|
"""Ensure all existing contacts are going to work as v10"""
|
||||||
|
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||||
|
contacts = env['mail.mass_mailing.contact'].search([])
|
||||||
|
for contact in contacts:
|
||||||
|
if len(contact.list_ids) <= 1:
|
||||||
|
continue
|
||||||
|
list_1 = contact.list_ids[0]
|
||||||
|
for list_ in contact.list_ids - list_1:
|
||||||
|
contact.copy({"list_ids": [(6, 0, list_.ids)]})
|
||||||
|
contact.list_ids = list_1
|
@ -1,7 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
|
|
||||||
from . import mail_mail
|
|
||||||
from . import mail_mass_mailing
|
from . import mail_mass_mailing
|
||||||
|
from . import mail_mass_mailing_contact
|
||||||
from . import mail_mass_mailing_list
|
from . import mail_mass_mailing_list
|
||||||
from . import mail_unsubscription
|
from . import mail_unsubscription
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
|
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
||||||
|
|
||||||
from openerp import models
|
|
||||||
|
|
||||||
|
|
||||||
class MailMail(models.Model):
|
|
||||||
_inherit = 'mail.mail'
|
|
||||||
|
|
||||||
def _get_unsubscribe_url(self, email_to):
|
|
||||||
result = super(MailMail, self)._get_unsubscribe_url(email_to)
|
|
||||||
token = self.mailing_id._unsubscribe_token(self.res_id)
|
|
||||||
return "%s&token=%s" % (result, token)
|
|
@ -1,52 +1,27 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
|
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
import hmac
|
from odoo import models
|
||||||
import hashlib
|
|
||||||
from openerp import api, models
|
|
||||||
from openerp.exceptions import AccessDenied
|
|
||||||
from openerp.tools import consteq
|
|
||||||
|
|
||||||
|
|
||||||
class MailMassMailing(models.Model):
|
class MailMassMailing(models.Model):
|
||||||
_inherit = "mail.mass_mailing"
|
_inherit = "mail.mass_mailing"
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def _unsubscribe_token(self, res_id, compare=None):
|
|
||||||
"""Generate a secure hash for this mailing list and parameters.
|
|
||||||
This is appended to the unsubscription URL and then checked at
|
|
||||||
unsubscription time to ensure no malicious unsubscriptions are
|
|
||||||
performed.
|
|
||||||
|
|
||||||
:param int res_id:
|
|
||||||
ID of the resource that will be unsubscribed.
|
|
||||||
|
|
||||||
:param str compare:
|
|
||||||
Received token to be compared with the good one.
|
|
||||||
|
|
||||||
:raise AccessDenied:
|
|
||||||
Will happen if you provide :param:`compare` and it does not match
|
|
||||||
the good token.
|
|
||||||
"""
|
|
||||||
secret = self.env["ir.config_parameter"].sudo().get_param(
|
|
||||||
"database.secret")
|
|
||||||
key = (self.env.cr.dbname, self.id, int(res_id))
|
|
||||||
token = hmac.new(str(secret), repr(key), hashlib.sha512).hexdigest()
|
|
||||||
if compare is not None and not consteq(token, str(compare)):
|
|
||||||
raise AccessDenied()
|
|
||||||
return token
|
|
||||||
|
|
||||||
def update_opt_out(self, email, res_ids, value):
|
def update_opt_out(self, email, res_ids, value):
|
||||||
"""Save unsubscription reason when opting out from mailing."""
|
"""Save unsubscription reason when opting out from mailing."""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
|
model = self.env[self.mailing_model_real].with_context(
|
||||||
|
active_test=False)
|
||||||
action = "unsubscription" if value else "subscription"
|
action = "unsubscription" if value else "subscription"
|
||||||
records = self.env[self.mailing_model].browse(res_ids)
|
records = self.env[model._name].browse(res_ids)
|
||||||
previous = self.env["mail.unsubscription"].search(limit=1, args=[
|
previous = self.env["mail.unsubscription"].search(limit=1, args=[
|
||||||
("mass_mailing_id", "=", self.id),
|
("mass_mailing_id", "=", self.id),
|
||||||
("email", "=", email),
|
("email", "=", email),
|
||||||
("action", "=", action),
|
("action", "=", action),
|
||||||
])
|
])
|
||||||
|
if 'opt_out' not in model._fields:
|
||||||
|
return super(MailMassMailing, self).update_opt_out(
|
||||||
|
email, res_ids, value)
|
||||||
for one in records:
|
for one in records:
|
||||||
# Store action only when something changed, or there was no
|
# Store action only when something changed, or there was no
|
||||||
# previous subscription record
|
# previous subscription record
|
||||||
@ -59,5 +34,7 @@ class MailMassMailing(models.Model):
|
|||||||
"unsubscriber_id": "%s,%d" % (one._name, one.id),
|
"unsubscriber_id": "%s,%d" % (one._name, one.id),
|
||||||
"action": action,
|
"action": action,
|
||||||
})
|
})
|
||||||
|
if model._name == 'mail.mass_mailing.contact':
|
||||||
|
pass
|
||||||
return super(MailMassMailing, self).update_opt_out(
|
return super(MailMassMailing, self).update_opt_out(
|
||||||
email, res_ids, value)
|
email, res_ids, value)
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
# Copyright 2018 David Vidal <david.vidal@tecnativa.com>
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class MailMassMailing(models.Model):
|
||||||
|
_inherit = "mail.mass_mailing.contact"
|
||||||
|
|
||||||
|
# Recover the old Many2one field so we can set a contact by list
|
||||||
|
mailing_list_id = fields.Many2one(
|
||||||
|
'mail.mass_mailing.list',
|
||||||
|
string='Mailing List',
|
||||||
|
ondelete='cascade',
|
||||||
|
compute="_compute_mailing_list_id",
|
||||||
|
inverse="_inverse_mailing_list_id",
|
||||||
|
search="_search_mailing_list_id",
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends('list_ids')
|
||||||
|
def _compute_mailing_list_id(self):
|
||||||
|
for contact in self:
|
||||||
|
contact.mailing_list_id = contact.list_ids[:1]
|
||||||
|
|
||||||
|
def _inverse_mailing_list_id(self):
|
||||||
|
for contact in self:
|
||||||
|
contact.list_ids = contact.mailing_list_id
|
||||||
|
|
||||||
|
def _search_mailing_list_id(self, operator, value):
|
||||||
|
return [('list_ids', operator, value)]
|
@ -1,8 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2016 Pedro M. Baeza <pedro.baeza@tecnativa.com>
|
# Copyright 2016 Pedro M. Baeza <pedro.baeza@tecnativa.com>
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
from openerp import fields, models
|
from odoo import fields, models
|
||||||
|
|
||||||
|
|
||||||
class MailMassMailing(models.Model):
|
class MailMassMailing(models.Model):
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
|
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
from odoo import _, api, fields, models
|
from odoo import _, api, fields, models
|
||||||
|
from odoo.addons.mass_mailing.models.mass_mailing import \
|
||||||
|
MASS_MAILING_BUSINESS_MODELS
|
||||||
from .. import exceptions
|
from .. import exceptions
|
||||||
|
|
||||||
|
|
||||||
@ -35,13 +36,14 @@ class MailUnsubscription(models.Model):
|
|||||||
lambda self: self._selection_unsubscriber_id(),
|
lambda self: self._selection_unsubscriber_id(),
|
||||||
"(Un)subscriber",
|
"(Un)subscriber",
|
||||||
help="Who was subscribed or unsubscribed.")
|
help="Who was subscribed or unsubscribed.")
|
||||||
mailing_list_id = fields.Many2one(
|
mailing_list_id = fields.Many2many(
|
||||||
"mail.mass_mailing.list",
|
comodel_name="mail.mass_mailing.list",
|
||||||
"Mailing list",
|
string="Mailing list",
|
||||||
ondelete="set null",
|
ondelete="set null",
|
||||||
compute="_compute_mailing_list_id",
|
compute="_compute_mailing_list_id",
|
||||||
store=True,
|
store=True,
|
||||||
help="(Un)subscribed mass mailing list, if any.",
|
help="(Un)subscribed mass mailing list, if any.",
|
||||||
|
readonly=False,
|
||||||
)
|
)
|
||||||
reason_id = fields.Many2one(
|
reason_id = fields.Many2one(
|
||||||
"mail.unsubscription.reason",
|
"mail.unsubscription.reason",
|
||||||
@ -57,6 +59,15 @@ class MailUnsubscription(models.Model):
|
|||||||
help="HTTP request metadata used when creating this record.",
|
help="HTTP request metadata used when creating this record.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def map_mailing_list_models(self, models):
|
||||||
|
model_mapped = []
|
||||||
|
for model in models:
|
||||||
|
if model == 'mail.mass_mailing.list':
|
||||||
|
model_mapped.append(('mail.mass_mailing.contact', model))
|
||||||
|
else:
|
||||||
|
model_mapped.append((model, model))
|
||||||
|
return model_mapped
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _default_date(self):
|
def _default_date(self):
|
||||||
return fields.Datetime.now()
|
return fields.Datetime.now()
|
||||||
@ -64,7 +75,9 @@ class MailUnsubscription(models.Model):
|
|||||||
@api.model
|
@api.model
|
||||||
def _selection_unsubscriber_id(self):
|
def _selection_unsubscriber_id(self):
|
||||||
"""Models that can be linked to a ``mail.mass_mailing``."""
|
"""Models that can be linked to a ``mail.mass_mailing``."""
|
||||||
return self.env["mail.mass_mailing"]._get_mailing_model()
|
model = self.env['ir.model'].search(
|
||||||
|
[('model', 'in', MASS_MAILING_BUSINESS_MODELS)]).mapped('model')
|
||||||
|
return self.map_mailing_list_models(model)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
@api.constrains("action", "reason_id")
|
@api.constrains("action", "reason_id")
|
||||||
@ -90,7 +103,7 @@ class MailUnsubscription(models.Model):
|
|||||||
"""Get the mass mailing list, if it is possible."""
|
"""Get the mass mailing list, if it is possible."""
|
||||||
for one in self:
|
for one in self:
|
||||||
try:
|
try:
|
||||||
one.mailing_list_id = one.unsubscriber_id.list_id
|
one.mailing_list_id |= one.unsubscriber_id.mailing_list_id
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# Possibly model != mail.mass_mailing.contact; no problem
|
# Possibly model != mail.mass_mailing.contact; no problem
|
||||||
pass
|
pass
|
||||||
|
10
mass_mailing_custom_unsubscribe/readme/CONFIGURE.rst
Normal file
10
mass_mailing_custom_unsubscribe/readme/CONFIGURE.rst
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
Unsubscription Reasons
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
You can customize what reasons will be displayed to your unsubscriptors when
|
||||||
|
they are going to unsubscribe. To do it:
|
||||||
|
|
||||||
|
#. Go to *Mass Mailing > Configuration > Unsubscription Reasons*.
|
||||||
|
#. Create / edit / remove / sort as usual.
|
||||||
|
#. If *Details required* is enabled, they will have to fill a text area to
|
||||||
|
continue.
|
4
mass_mailing_custom_unsubscribe/readme/CONTRIBUTORS.rst
Normal file
4
mass_mailing_custom_unsubscribe/readme/CONTRIBUTORS.rst
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
* Rafael Blasco <rafael.blasco@tecnativa.com>
|
||||||
|
* Antonio Espinosa <antonio.espinosa@tecnativa.com>
|
||||||
|
* Jairo Llopis <jairo.llopis@tecnativa.com>
|
||||||
|
* David Vidal <david.vidal@tecnativa.com>
|
8
mass_mailing_custom_unsubscribe/readme/DESCRIPTION.rst
Normal file
8
mass_mailing_custom_unsubscribe/readme/DESCRIPTION.rst
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
This addon extends the unsubscription form to let you:
|
||||||
|
|
||||||
|
- Choose which mailing lists are not cross-unsubscriptable when unsubscribing
|
||||||
|
from a different one.
|
||||||
|
- Know why and when a contact has been subscribed or unsubscribed from a
|
||||||
|
mass mailing.
|
||||||
|
- Provide proof on why you are sending mass mailings to a given contact, as
|
||||||
|
required by the GDPR in Europe.
|
11
mass_mailing_custom_unsubscribe/readme/ROADMAP.rst
Normal file
11
mass_mailing_custom_unsubscribe/readme/ROADMAP.rst
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
* As version 11 has introduced a new relation type between mailing lists and
|
||||||
|
contacts that has multiple usability issues that are being reworked by Odoo
|
||||||
|
to land in version 12, this module falls back to the version 10 behaviour in
|
||||||
|
which one contact belonged to just one list.
|
||||||
|
* This module replaces AJAX submission core implementation from the mailing
|
||||||
|
list management form, because it is impossible to extend it. When
|
||||||
|
https://github.com/odoo/odoo/pull/14386 gets merged (which upstreams most
|
||||||
|
needed changes), this addon will need a refactoring (mostly removing
|
||||||
|
duplicated functionality and depending on it instead of replacing it). In the
|
||||||
|
mean time, there is a little chance that this introduces some
|
||||||
|
incompatibilities with other addons that depend on ``website_mass_mailing``.
|
8
mass_mailing_custom_unsubscribe/readme/USAGE.rst
Normal file
8
mass_mailing_custom_unsubscribe/readme/USAGE.rst
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
Once configured:
|
||||||
|
|
||||||
|
#. Go to *Mass Mailing > Mailings > Mass Mailings > Create*.
|
||||||
|
#. Edit your mass mailing at wish, but remember to add a snippet from
|
||||||
|
*Footers*, so people have an *Unsubscribe* link.
|
||||||
|
#. Send it.
|
||||||
|
#. If somebody gets unsubscribed, you will see logs about that under
|
||||||
|
*Mass Mailing > Mailings > Unsubscriptions*.
|
@ -39,7 +39,7 @@ odoo.define("mass_mailing_custom_unsubscribe.partner_tour",
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: "Successfully unsubscribed",
|
content: "Successfully unsubscribed",
|
||||||
trigger: "body:not(:has(#reason_form)) .alert-success:contains('Your changes have been saved.')",
|
trigger: "body:not(:has(#reason_form)) .alert-success:contains('You have been successfully unsubscribed!')",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
odoo.define("mass_mailing_custom_unsubscribe.require_details",
|
odoo.define("mass_mailing_custom_unsubscribe.require_details",
|
||||||
function (require) {
|
function (require) {
|
||||||
"use strict";
|
"use strict";
|
||||||
var animation = require("web_editor.snippets.animation");
|
var animation = require("website.content.snippets.animation");
|
||||||
|
|
||||||
animation.registry.mass_mailing_custom_unsubscribe_require_details =
|
animation.registry.mass_mailing_custom_unsubscribe_require_details =
|
||||||
animation.Class.extend({
|
animation.Class.extend({
|
||||||
|
@ -9,7 +9,7 @@ odoo.define("mass_mailing_custom_unsubscribe.unsubscribe", function (require) {
|
|||||||
"use strict";
|
"use strict";
|
||||||
var core = require("web.core");
|
var core = require("web.core");
|
||||||
var ajax = require("web.ajax");
|
var ajax = require("web.ajax");
|
||||||
var animation = require("web_editor.snippets.animation");
|
var animation = require("website.content.snippets.animation");
|
||||||
var _t = core._t;
|
var _t = core._t;
|
||||||
|
|
||||||
animation.registry.mass_mailing_unsubscribe =
|
animation.registry.mass_mailing_unsubscribe =
|
||||||
@ -24,7 +24,7 @@ odoo.define("mass_mailing_custom_unsubscribe.unsubscribe", function (require) {
|
|||||||
this.$token = this.$("input[name='token']");
|
this.$token = this.$("input[name='token']");
|
||||||
this.$res_id = this.$("input[name='res_id']");
|
this.$res_id = this.$("input[name='res_id']");
|
||||||
this.$reasons = this.$(".js_unsubscription_reason");
|
this.$reasons = this.$(".js_unsubscription_reason");
|
||||||
this.$details = this.$reasons.find("[name='details']")
|
this.$details = this.$reasons.find("[name='details']");
|
||||||
this.$el.on("submit", $.proxy(this.submit, this));
|
this.$el.on("submit", $.proxy(this.submit, this));
|
||||||
this.$contacts.on("change", $.proxy(this.toggle_reasons, this));
|
this.$contacts.on("change", $.proxy(this.toggle_reasons, this));
|
||||||
this.toggle_reasons();
|
this.toggle_reasons();
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<!-- Disable core AJAX submission of form, because it is impossible to
|
<!-- Disable core AJAX submission of form, because it is impossible to
|
||||||
extend it as it is designed right now. It is refactored in this addon.
|
extend it as it is designed right now. It is refactored in this addon.
|
||||||
TODO Remove when merged https://github.com/odoo/odoo/pull/14386. -->
|
TODO Remove when merged https://github.com/odoo/odoo/pull/14386. -->
|
||||||
<xpath expr="//div[@class='container o_unsubscribe_form']"
|
<xpath expr="//div[hasclass('container', 'o_unsubscribe_form')]"
|
||||||
position="attributes">
|
position="attributes">
|
||||||
<attribute name="class" value="container o_unsubscribe_form_custom"/>
|
<attribute name="class" value="container o_unsubscribe_form_custom"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
|
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
|
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
import mock
|
import mock
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from openerp.tests.common import HttpCase
|
from odoo.tests.common import HttpCase
|
||||||
|
|
||||||
|
|
||||||
class UICase(HttpCase):
|
class UICase(HttpCase):
|
||||||
@ -37,7 +36,7 @@ class UICase(HttpCase):
|
|||||||
})
|
})
|
||||||
self.mailings += Mailing.create({
|
self.mailings += Mailing.create({
|
||||||
"name": "test mailing %d" % n,
|
"name": "test mailing %d" % n,
|
||||||
"mailing_model": "mail.mass_mailing.contact",
|
"mailing_model_id": self.env["mail.mass_mailing.contact"],
|
||||||
"contact_list_ids": [(6, 0, self.lists.ids)],
|
"contact_list_ids": [(6, 0, self.lists.ids)],
|
||||||
"reply_to_mode": "thread",
|
"reply_to_mode": "thread",
|
||||||
})
|
})
|
||||||
@ -53,7 +52,7 @@ class UICase(HttpCase):
|
|||||||
self.contacts += Contact.create({
|
self.contacts += Contact.create({
|
||||||
"name": "test contact %d" % n,
|
"name": "test contact %d" % n,
|
||||||
"email": self.email,
|
"email": self.email,
|
||||||
"list_id": self.lists[n].id,
|
"mailing_list_id": self.lists[n].id,
|
||||||
})
|
})
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
@ -117,7 +116,8 @@ class UICase(HttpCase):
|
|||||||
# Change mailing to be sent to partner
|
# Change mailing to be sent to partner
|
||||||
partner_id = env["res.partner"].name_create(
|
partner_id = env["res.partner"].name_create(
|
||||||
"Demo Partner <%s>" % self.email)[0]
|
"Demo Partner <%s>" % self.email)[0]
|
||||||
self.mailings[0].mailing_model = "res.partner"
|
self.mailings[0].mailing_model_id = self.env.ref(
|
||||||
|
"base.model_res_partner")
|
||||||
self.mailings[0].mailing_domain = repr([
|
self.mailings[0].mailing_domain = repr([
|
||||||
('opt_out', '=', False),
|
('opt_out', '=', False),
|
||||||
('id', '=', partner_id),
|
('id', '=', partner_id),
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
|
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
from openerp.tests.common import SavepointCase
|
from odoo.tests.common import SavepointCase
|
||||||
from .. import exceptions
|
from .. import exceptions
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright 2018 David Vidal <david.vidal@tecnativa.com>
|
||||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
|
||||||
|
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="view_mail_mass_mailing_contact_form" model="ir.ui.view">
|
||||||
|
<field name="model">mail.mass_mailing.contact</field>
|
||||||
|
<field name="inherit_id" ref="mass_mailing.view_mail_mass_mailing_contact_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="list_ids" position="attributes">
|
||||||
|
<attribute name="invisible">1</attribute>
|
||||||
|
</field>
|
||||||
|
<field name="email" position="after">
|
||||||
|
<field name="mailing_list_id"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_mail_mass_mailing_contact_tree" model="ir.ui.view">
|
||||||
|
<field name="model">mail.mass_mailing.contact</field>
|
||||||
|
<field name="inherit_id" ref="mass_mailing.view_mail_mass_mailing_contact_tree"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="email" position="before">
|
||||||
|
<field name="mailing_list_id"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
@ -14,7 +14,7 @@
|
|||||||
<field name="date"/>
|
<field name="date"/>
|
||||||
<field name="mass_mailing_id"/>
|
<field name="mass_mailing_id"/>
|
||||||
<field name="unsubscriber_id"/>
|
<field name="unsubscriber_id"/>
|
||||||
<field name="mailing_list_id"/>
|
<field name="mailing_list_id" widget="many2many_tags"/>
|
||||||
<field name="email"/>
|
<field name="email"/>
|
||||||
<field name="action"/>
|
<field name="action"/>
|
||||||
<field name="reason_id"
|
<field name="reason_id"
|
||||||
@ -44,7 +44,7 @@
|
|||||||
<field name="date"/>
|
<field name="date"/>
|
||||||
<field name="mass_mailing_id"/>
|
<field name="mass_mailing_id"/>
|
||||||
<field name="unsubscriber_id"/>
|
<field name="unsubscriber_id"/>
|
||||||
<field name="mailing_list_id"/>
|
<field name="mailing_list_id" widget="many2many_tags"/>
|
||||||
<field name="email" invisible="True"/>
|
<field name="email" invisible="True"/>
|
||||||
<field name="action"/>
|
<field name="action"/>
|
||||||
<field name="reason_id"/>
|
<field name="reason_id"/>
|
||||||
|
Loading…
Reference in New Issue
Block a user