[MERGE][IMP] mass_mailing: improve logging, fix assets, cleanup code bits
PURPOSE
Fix assets in mass mailing (unsubscription page is broken and assets usage is
a complete mess). Improve blocklist logging. Update tools methods, controllers
and templates naming to ease future improvements and refactoring.
SPECIFICATIONS
[FIX] mass_mailing: use frontend assets in subscription page
Unsubscribe page uses a custom layout allowing a portal-like behavior for
mass mailing without any link to website or portal. However it currently
uses the complete backend assets. This leads to a lot of unnecessary css
and js being loaded.
When having frontend pages like the one used in mass mailing it should rely
on frontend assets with some custom assets if necessary for the page to
be correctly displayed.
In this commit we choose to use frontend assets like other portal-like pages.
Mailing layout now uses the frontend layout defined in web and adds its own
subset of assets (mailing_assets). Some custom css is moved or added as well
as few tweaks to ensure layouting is globally the same as before this commit.
[IMP] mail, phone_validation: allow to log a message when (un)blacklisting
Add possibility to add a message when adding or removing a blacklist entry
for both mail and phone_validation (used for SMS). This replaces the
``action_remove_with_reason`` method.
When updating active flag this is added as a tracking note to avoid having
several messages. Indeed message is concatenated with the tracking itself
instead of adding message for tracking + a message for the log itself. When
a new record is created, a note is logged.
Use it in mass mailing.
[REF] mass_mailing: rename templates and controller methods
Rename templates to better understand their purpose. Current naming is not
crystal clear.
Rename controller methods in order to be clearer and more aligned with current
guidelines. Notably namespace by mailing, try to be clearer about their
purpose, ...
Add a helper field to know when a mailing runs on mailing lists as it triggers
some specific behavior.
In this commit we rename methods generating tokens (for recipients and report
management). Otherwise it is hard to distinguish them as naming is not really
convenient to use
* _unsubscribe_token -> was actually generating token for a given recipient
on a given mailing;
* _get_unsubscribe_token -> was actually generating token for mailing
users to deactivate D+1 KPI reporting;
Hash for mail tracking is also moved in its own method to avoid having the
same code twice. Better delegate it to a tool sub method.
Rename main routes, and keep a legacy for the main unsubscribe route to
keep retro compatibility for existing mailings.
[IMP] mass_mailing: show blacklist button by default on portal
We now consider being able to blacklist their email is a default behavior for
users being redirected to the subscription management portal page.
LINKS
Task-3082546 (Mass Mailing: Fix assets and unsubscribe page)
Task-2710804 (Mail: Clean MailThread API)
Prepares Task-2669037 (Mass Mailing: Subscription page code cleaning)
Prepares Task-2150462 (Mass Mailing: Unsubscribe flow refactoring)
closes odoo/odoo#106568
Related: odoo/upgrade#4077
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
@@ -69,29 +69,36 @@ class MailBlackList(models.Model):
|
||||
new_args = args
|
||||
return super(MailBlackList, self)._search(new_args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid)
|
||||
|
||||
def _add(self, email):
|
||||
def _add(self, email, message=None):
|
||||
normalized = tools.email_normalize(email)
|
||||
record = self.env["mail.blacklist"].with_context(active_test=False).search([('email', '=', normalized)])
|
||||
if len(record) > 0:
|
||||
if message:
|
||||
record._track_set_log_message(message)
|
||||
record.action_unarchive()
|
||||
else:
|
||||
record = self.create({'email': email})
|
||||
if message:
|
||||
record.with_context(mail_create_nosubscribe=True).message_post(
|
||||
body=message,
|
||||
subtype_id=self.env.ref('mail.mt_note').id,
|
||||
)
|
||||
return record
|
||||
|
||||
def action_remove_with_reason(self, email, reason=None):
|
||||
record = self._remove(email)
|
||||
if reason:
|
||||
record.message_post(body=_("Unblacklisting Reason: %s", reason))
|
||||
|
||||
return record
|
||||
|
||||
def _remove(self, email):
|
||||
def _remove(self, email, message=None):
|
||||
normalized = tools.email_normalize(email)
|
||||
record = self.env["mail.blacklist"].with_context(active_test=False).search([('email', '=', normalized)])
|
||||
if len(record) > 0:
|
||||
if message:
|
||||
record._track_set_log_message(message)
|
||||
record.action_archive()
|
||||
else:
|
||||
record = record.create({'email': email, 'active': False})
|
||||
if message:
|
||||
record.with_context(mail_create_nosubscribe=True).message_post(
|
||||
body=message,
|
||||
subtype_id=self.env.ref('mail.mt_note').id,
|
||||
)
|
||||
return record
|
||||
|
||||
def mail_action_blacklist_remove(self):
|
||||
|
||||
@@ -189,11 +189,11 @@ class Users(models.Model):
|
||||
super(Users, self)._deactivate_portal_user(**post)
|
||||
|
||||
for user in users_to_blacklist:
|
||||
blacklist = self.env['mail.blacklist']._add(user.email)
|
||||
blacklist._message_log(
|
||||
body=_('Blocked by deletion of portal account %(portal_user_name)s by %(user_name)s (#%(user_id)s)',
|
||||
user_name=current_user.name, user_id=current_user.id,
|
||||
portal_user_name=user.name),
|
||||
self.env['mail.blacklist']._add(
|
||||
user.email,
|
||||
message=_('Blocked by deletion of portal account %(portal_user_name)s by %(user_name)s (#%(user_id)s)',
|
||||
user_name=current_user.name, user_id=current_user.id,
|
||||
portal_user_name=user.name)
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import fields, models
|
||||
from markupsafe import Markup
|
||||
from odoo import fields, models, _
|
||||
|
||||
|
||||
class MailBlacklistRemove(models.TransientModel):
|
||||
@@ -11,4 +12,11 @@ class MailBlacklistRemove(models.TransientModel):
|
||||
reason = fields.Char(name="Reason")
|
||||
|
||||
def action_unblacklist_apply(self):
|
||||
return self.env['mail.blacklist'].action_remove_with_reason(self.email, self.reason)
|
||||
if self.reason:
|
||||
message = Markup('<p>%s</p>') % _("Unblock Reason: %(reason)s", reason=self.reason)
|
||||
else:
|
||||
message = None
|
||||
return self.env['mail.blacklist']._remove(
|
||||
self.email,
|
||||
message=message,
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
{
|
||||
'name': 'Email Marketing',
|
||||
'summary': 'Design, send and track emails',
|
||||
'version': '2.5',
|
||||
'version': '2.6',
|
||||
'sequence': 60,
|
||||
'website': 'https://www.odoo.com/app/email-marketing',
|
||||
'category': 'Marketing/Email Marketing',
|
||||
@@ -85,6 +85,10 @@
|
||||
],
|
||||
'application': True,
|
||||
'assets': {
|
||||
'mass_mailing.mailing_assets': [
|
||||
'mass_mailing/static/src/scss/mailing_portal.scss',
|
||||
'mass_mailing/static/src/js/mailing_portal.js',
|
||||
],
|
||||
'web.assets_backend': [
|
||||
'mass_mailing/static/src/scss/mailing_filter_widget.scss',
|
||||
'mass_mailing/static/src/scss/mass_mailing.scss',
|
||||
@@ -98,7 +102,6 @@
|
||||
'mass_mailing/static/src/js/mass_mailing_mobile_preview.js',
|
||||
'mass_mailing/static/src/js/mass_mailing_html_field.js',
|
||||
'mass_mailing/static/src/js/mailing_mailing_view_form_full_width.js',
|
||||
'mass_mailing/static/src/js/mailing_portal.js',
|
||||
'mass_mailing/static/src/xml/mailing_filter_widget.xml',
|
||||
'mass_mailing/static/src/xml/mass_mailing.xml',
|
||||
'mass_mailing/static/src/views/*.xml',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import legacy
|
||||
from . import main
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
from odoo.addons.base.models.ir_qweb import keep_query
|
||||
|
||||
class MailingLegacy(http.Controller):
|
||||
""" Retro compatibility layer for legacy endpoint"""
|
||||
|
||||
@http.route(['/mail/mailing/<int:mailing_id>/unsubscribe'], type='http', website=True, auth='public')
|
||||
def mailing_unsubscribe(self, mailing_id, **post):
|
||||
return request.redirect(
|
||||
f'/mailing/{mailing_id}/unsubscribe?{keep_query("*")}'
|
||||
)
|
||||
@@ -7,32 +7,29 @@ import werkzeug
|
||||
from odoo import _, exceptions, http, tools
|
||||
from odoo.http import request
|
||||
from odoo.tools import consteq
|
||||
|
||||
from markupsafe import Markup, escape
|
||||
from werkzeug.exceptions import BadRequest, NotFound
|
||||
|
||||
|
||||
class MassMailController(http.Controller):
|
||||
|
||||
def _valid_unsubscribe_token(self, mailing_id, res_id, email, token):
|
||||
def _check_mailing_email_token(self, mailing_id, res_id, email, token):
|
||||
if not (mailing_id and res_id and email and token):
|
||||
return False
|
||||
mailing = request.env['mailing.mailing'].sudo().browse(mailing_id)
|
||||
return consteq(mailing._unsubscribe_token(res_id, email), token)
|
||||
|
||||
def _log_blacklist_action(self, blacklist_entry, mailing_id, description):
|
||||
mailing = request.env['mailing.mailing'].sudo().browse(mailing_id)
|
||||
model_display = mailing.mailing_model_id.display_name
|
||||
blacklist_entry._message_log(body=description + " ({})".format(model_display))
|
||||
return consteq(mailing._generate_mailing_recipient_token(res_id, email), token)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# SUBSCRIPTION MANAGEMENT
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@http.route(['/mail/mailing/<int:mailing_id>/unsubscribe'], type='http', website=True, auth='public')
|
||||
def mailing(self, mailing_id, email=None, res_id=None, token="", **post):
|
||||
@http.route(['/mailing/<int:mailing_id>/unsubscribe'], type='http', website=True, auth='public')
|
||||
def mailing_unsubscribe(self, mailing_id, email=None, res_id=None, token="", **post):
|
||||
mailing = request.env['mailing.mailing'].sudo().browse(mailing_id)
|
||||
if mailing.exists():
|
||||
res_id = res_id and int(res_id)
|
||||
if not self._valid_unsubscribe_token(mailing_id, res_id, email, str(token)):
|
||||
if not self._check_mailing_email_token(mailing_id, res_id, email, str(token)):
|
||||
raise exceptions.AccessDenied()
|
||||
|
||||
if mailing.mailing_model_real == 'mailing.contact':
|
||||
@@ -51,7 +48,7 @@ class MassMailController(http.Controller):
|
||||
unique_list_ids = set([list.list_id.id for list in subscription_list_ids])
|
||||
list_ids = request.env['mailing.list'].sudo().browse(unique_list_ids)
|
||||
unsubscribed_list = ', '.join(str(list.name) for list in mailing.contact_list_ids if list.is_public)
|
||||
return request.render('mass_mailing.page_unsubscribe', {
|
||||
return request.render('mass_mailing.page_mailing_unsubscribe', {
|
||||
'contacts': contacts,
|
||||
'list_ids': list_ids,
|
||||
'opt_out_list_ids': opt_out_list_ids,
|
||||
@@ -66,11 +63,16 @@ class MassMailController(http.Controller):
|
||||
('contact_id.email_normalized', '=', email),
|
||||
('opt_out', '=', False)
|
||||
]).mapped('list_id')
|
||||
blacklist_rec = request.env['mail.blacklist'].sudo()._add(email)
|
||||
self._log_blacklist_action(
|
||||
blacklist_rec, mailing_id,
|
||||
_("""Requested blacklisting via unsubscribe link."""))
|
||||
return request.render('mass_mailing.page_unsubscribed', {
|
||||
|
||||
message = Markup('<p>%s</p>') % Markup(
|
||||
_(
|
||||
'Blocklist request from unsubscribe link of mailing %(mailing_link)s (document %(record_link)s)',
|
||||
**self._format_bl_request(mailing, res_id)
|
||||
)
|
||||
)
|
||||
_blacklist_rec = request.env['mail.blacklist'].sudo()._add(email, message=message)
|
||||
|
||||
return request.render('mass_mailing.page_mailing_unsubscribe_done', {
|
||||
'email': email,
|
||||
'mailing_id': mailing_id,
|
||||
'res_id': res_id,
|
||||
@@ -80,11 +82,11 @@ class MassMailController(http.Controller):
|
||||
})
|
||||
return request.redirect('/web')
|
||||
|
||||
@http.route('/mail/mailing/unsubscribe', type='json', auth='public')
|
||||
def unsubscribe(self, mailing_id, opt_in_ids, opt_out_ids, email, res_id, token):
|
||||
@http.route('/mailing/list/update', type='json', auth='public')
|
||||
def mailing_update_list_subscription(self, mailing_id, opt_in_ids, opt_out_ids, email, res_id, token):
|
||||
mailing = request.env['mailing.mailing'].sudo().browse(mailing_id)
|
||||
if mailing.exists():
|
||||
if not self._valid_unsubscribe_token(mailing_id, res_id, email, token):
|
||||
if not self._check_mailing_email_token(mailing_id, res_id, email, token):
|
||||
return 'unauthorized'
|
||||
mailing.update_opt_out(email, opt_in_ids, False)
|
||||
mailing.update_opt_out(email, opt_out_ids, True)
|
||||
@@ -92,10 +94,10 @@ class MassMailController(http.Controller):
|
||||
return 'error'
|
||||
|
||||
@http.route('/mailing/feedback', type='json', auth='public')
|
||||
def send_feedback(self, mailing_id, res_id, email, feedback, token):
|
||||
def mailing_send_feedback(self, mailing_id, res_id, email, feedback, token):
|
||||
mailing = request.env['mailing.mailing'].sudo().browse(mailing_id)
|
||||
if mailing.exists() and email:
|
||||
if not self._valid_unsubscribe_token(mailing_id, res_id, email, token):
|
||||
if not self._check_mailing_email_token(mailing_id, res_id, email, token):
|
||||
return 'unauthorized'
|
||||
model = request.env[mailing.mailing_model_real]
|
||||
records = model.sudo().search([('email_normalized', '=', tools.email_normalize(email))])
|
||||
@@ -105,7 +107,7 @@ class MassMailController(http.Controller):
|
||||
return 'error'
|
||||
|
||||
@http.route(['/unsubscribe_from_list'], type='http', website=True, multilang=False, auth='public', sitemap=False)
|
||||
def unsubscribe_placeholder_link(self, **post):
|
||||
def mailing_unsubscribe_placeholder_link(self, **post):
|
||||
"""Dummy route so placeholder is not prefixed by language, MUST have multilang=False"""
|
||||
raise werkzeug.exceptions.NotFound()
|
||||
|
||||
@@ -116,7 +118,8 @@ class MassMailController(http.Controller):
|
||||
@http.route('/mail/track/<int:mail_id>/<string:token>/blank.gif', type='http', auth='public')
|
||||
def track_mail_open(self, mail_id, token, **post):
|
||||
""" Email tracking. """
|
||||
if not consteq(token, tools.hmac(request.env(su=True), 'mass_mailing-mail_mail-open', mail_id)):
|
||||
mail = request.env['mail.mail'].sudo().browse(mail_id).exists()
|
||||
if not mail or not consteq(token, mail._generate_mail_recipient_token()):
|
||||
raise BadRequest()
|
||||
|
||||
request.env['mailing.trace'].sudo().set_opened(domain=[('mail_mail_id_int', 'in', [mail_id])])
|
||||
@@ -145,11 +148,11 @@ class MassMailController(http.Controller):
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@http.route('/mailing/report/unsubscribe', type='http', website=True, auth='public')
|
||||
def turn_off_mailing_reports(self, token, user_id):
|
||||
def mailing_report_deactivate(self, token, user_id):
|
||||
if not token or not user_id:
|
||||
raise werkzeug.exceptions.NotFound()
|
||||
user_id = int(user_id)
|
||||
correct_token = consteq(token, request.env['mailing.mailing']._get_unsubscribe_token(user_id))
|
||||
correct_token = consteq(token, request.env['mailing.mailing']._generate_mailing_report_token(user_id))
|
||||
user = request.env['res.users'].sudo().browse(user_id)
|
||||
if correct_token and user.has_group('mass_mailing.group_mass_mailing_user'):
|
||||
request.env['ir.config_parameter'].sudo().set_param('mass_mailing.mass_mailing_reports', False)
|
||||
@@ -160,11 +163,11 @@ class MassMailController(http.Controller):
|
||||
raise werkzeug.exceptions.NotFound()
|
||||
|
||||
@http.route(['/mailing/<int:mailing_id>/view'], type='http', website=True, auth='public')
|
||||
def view(self, mailing_id, email=None, res_id=None, token=""):
|
||||
def mailing_view_in_browser(self, mailing_id, email=None, res_id=None, token=""):
|
||||
mailing = request.env['mailing.mailing'].sudo().browse(mailing_id)
|
||||
if mailing.exists():
|
||||
res_id = int(res_id) if res_id else False
|
||||
if not self._valid_unsubscribe_token(mailing_id, res_id, email, str(token)) and not request.env.user.has_group('mass_mailing.group_mass_mailing_user'):
|
||||
if not self._check_mailing_email_token(mailing_id, res_id, email, str(token)) and not request.env.user.has_group('mass_mailing.group_mass_mailing_user'):
|
||||
raise exceptions.AccessDenied()
|
||||
|
||||
# do not force lang, will simply use user context
|
||||
@@ -178,7 +181,7 @@ class MassMailController(http.Controller):
|
||||
html_markupsafe = html_markupsafe.replace('/unsubscribe_from_list',
|
||||
mailing._get_unsubscribe_url(email, res_id))
|
||||
|
||||
return request.render('mass_mailing.view', {
|
||||
return request.render('mass_mailing.mailing_view', {
|
||||
'body': html_markupsafe,
|
||||
})
|
||||
|
||||
@@ -189,8 +192,8 @@ class MassMailController(http.Controller):
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@http.route('/mailing/blacklist/check', type='json', auth='public')
|
||||
def blacklist_check(self, mailing_id, res_id, email, token):
|
||||
if not self._valid_unsubscribe_token(mailing_id, res_id, email, token):
|
||||
def mail_blacklist_check(self, mailing_id, res_id, email, token):
|
||||
if not self._check_mailing_email_token(mailing_id, res_id, email, token):
|
||||
return 'unauthorized'
|
||||
if email:
|
||||
record = request.env['mail.blacklist'].sudo().with_context(active_test=False).search([('email', '=', tools.email_normalize(email))])
|
||||
@@ -200,29 +203,52 @@ class MassMailController(http.Controller):
|
||||
return 'error'
|
||||
|
||||
@http.route('/mailing/blacklist/add', type='json', auth='public')
|
||||
def blacklist_add(self, mailing_id, res_id, email, token):
|
||||
if not self._valid_unsubscribe_token(mailing_id, res_id, email, token):
|
||||
def mail_blacklist_add(self, mailing_id, res_id, email, token):
|
||||
if not self._check_mailing_email_token(mailing_id, res_id, email, token):
|
||||
return 'unauthorized'
|
||||
if email:
|
||||
blacklist_rec = request.env['mail.blacklist'].sudo()._add(email)
|
||||
self._log_blacklist_action(
|
||||
blacklist_rec, mailing_id,
|
||||
_("""Requested blacklisting via unsubscription page."""))
|
||||
if mailing_id and res_id:
|
||||
mailing_sudo = request.env['mailing.mailing'].sudo().browse(mailing_id)
|
||||
message = Markup('<p>%s</p>') % Markup(
|
||||
_(
|
||||
'Blocklist request from portal of mailing %(mailing_link)s (document %(record_link)s)',
|
||||
**self._format_bl_request(mailing_sudo, res_id)
|
||||
)
|
||||
)
|
||||
else:
|
||||
message = Markup('<p>%s</p>') % _('Blocklist request from portal')
|
||||
|
||||
_blacklist_rec = request.env['mail.blacklist'].sudo()._add(email, message=message)
|
||||
return True
|
||||
return 'error'
|
||||
|
||||
@http.route('/mailing/blacklist/remove', type='json', auth='public')
|
||||
def blacklist_remove(self, mailing_id, res_id, email, token):
|
||||
if not self._valid_unsubscribe_token(mailing_id, res_id, email, token):
|
||||
def mail_blacklist_remove(self, mailing_id, res_id, email, token):
|
||||
if not self._check_mailing_email_token(mailing_id, res_id, email, token):
|
||||
return 'unauthorized'
|
||||
if email:
|
||||
blacklist_rec = request.env['mail.blacklist'].sudo()._remove(email)
|
||||
self._log_blacklist_action(
|
||||
blacklist_rec, mailing_id,
|
||||
_("""Requested de-blacklisting via unsubscription page."""))
|
||||
if mailing_id and res_id:
|
||||
mailing_sudo = request.env['mailing.mailing'].sudo().browse(mailing_id)
|
||||
message = Markup('<p>%s</p>') % Markup(
|
||||
_(
|
||||
'Blocklist removal request from portal of mailing %(mailing_link)s (document %(record_link)s)',
|
||||
**self._format_bl_request(mailing_sudo, res_id)
|
||||
)
|
||||
)
|
||||
else:
|
||||
message = Markup('<p>%s</p>') % _('Blocklist removal request from portal')
|
||||
|
||||
_blacklist_rec = request.env['mail.blacklist'].sudo()._remove(email, message=message)
|
||||
return True
|
||||
return 'error'
|
||||
|
||||
def _format_bl_request(self, mailing, document_id):
|
||||
mailing_model_name = request.env['ir.model']._get(mailing.mailing_model_real).display_name
|
||||
return {
|
||||
'mailing_link': Markup(f'<a href="#" data-oe-model="mailing.mailing" data-oe-id="{mailing.id}">{escape(mailing.subject)}</a>'),
|
||||
'record_link': Markup(f'<a href="#" data-oe-model="{escape(mailing.mailing_model_real)}" data-oe-id="{int(document_id)}">{escape(mailing_model_name)}</a>'),
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# MISCELLANEOUS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@@ -5,5 +5,9 @@
|
||||
<function model="ir.config_parameter"
|
||||
name="set_param"
|
||||
eval="('mass_mailing.mass_mailing_reports', 'True')"/>
|
||||
<!-- Display blacklist button to customers on portal page -->
|
||||
<function model="ir.config_parameter"
|
||||
name="set_param"
|
||||
eval="('mass_mailing.show_blacklist_buttons', True)"/>
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -25,8 +25,15 @@ class MailMail(models.Model):
|
||||
return mails
|
||||
|
||||
def _get_tracking_url(self):
|
||||
token = tools.hmac(self.env(su=True), 'mass_mailing-mail_mail-open', self.id)
|
||||
return werkzeug.urls.url_join(self.get_base_url(), 'mail/track/%s/%s/blank.gif' % (self.id, token))
|
||||
token = self._generate_mail_recipient_token()
|
||||
return werkzeug.urls.url_join(
|
||||
self.get_base_url(),
|
||||
f'mail/track/{self.id}/{token}/blank.gif'
|
||||
)
|
||||
|
||||
def _generate_mail_recipient_token(self):
|
||||
self.ensure_one()
|
||||
return tools.hmac(self.env(su=True), 'mass_mailing-mail_mail-open', self.id)
|
||||
|
||||
def _send_prepare_body(self):
|
||||
""" Override to add the tracking URL to the body and to add
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import datetime
|
||||
from markupsafe import Markup
|
||||
|
||||
from odoo import api, models, fields, tools
|
||||
from odoo import api, models, fields, tools, _
|
||||
|
||||
BLACKLIST_MAX_BOUNCED_LIMIT = 5
|
||||
|
||||
@@ -59,9 +60,10 @@ class MailThread(models.AbstractModel):
|
||||
stats = self.env['mailing.trace'].search(['&', '&', ('trace_status', '=', 'bounce'), ('write_date', '>', three_months_ago), ('email', '=ilike', bounced_email)]).mapped('write_date')
|
||||
if len(stats) >= BLACKLIST_MAX_BOUNCED_LIMIT and (not bounced_partner or any(p.message_bounce >= BLACKLIST_MAX_BOUNCED_LIMIT for p in bounced_partner)):
|
||||
if max(stats) > min(stats) + datetime.timedelta(weeks=1):
|
||||
blacklist_rec = self.env['mail.blacklist'].sudo()._add(bounced_email)
|
||||
blacklist_rec._message_log(
|
||||
body='This email has been automatically blacklisted because of too much bounced.')
|
||||
self.env['mail.blacklist'].sudo()._add(
|
||||
bounced_email,
|
||||
message=Markup('<p>%s</p>') % _('This email has been automatically added in blocklist because of too much bounced.')
|
||||
)
|
||||
|
||||
@api.model
|
||||
def message_new(self, msg_dict, custom_values=None):
|
||||
|
||||
@@ -53,7 +53,7 @@ class MassMailing(models.Model):
|
||||
})
|
||||
|
||||
if 'contact_list_ids' in fields_list and not vals.get('contact_list_ids') and vals.get('mailing_model_id'):
|
||||
if vals.get('mailing_model_id') == self.env['ir.model']._get('mailing.list').id:
|
||||
if vals.get('mailing_model_id') == self.env['ir.model']._get_id('mailing.list'):
|
||||
mailing_list = self.env['mailing.list'].search([], limit=2)
|
||||
if len(mailing_list) == 1:
|
||||
vals['contact_list_ids'] = [(6, 0, [mailing_list.id])]
|
||||
@@ -154,6 +154,9 @@ class MassMailing(models.Model):
|
||||
mailing_model_name = fields.Char(
|
||||
string='Recipients Model Name',
|
||||
related='mailing_model_id.model', readonly=True, related_sudo=True)
|
||||
mailing_on_mailing_list = fields.Boolean(
|
||||
string='Based on Mailing Lists',
|
||||
compute='_compute_mailing_on_mailing_list')
|
||||
mailing_domain = fields.Char(
|
||||
string='Domain',
|
||||
compute='_compute_mailing_domain', readonly=False, store=True)
|
||||
@@ -383,6 +386,12 @@ class MassMailing(models.Model):
|
||||
for mailing in self:
|
||||
mailing.mailing_model_real = 'mailing.contact' if mailing.mailing_model_id.model == 'mailing.list' else mailing.mailing_model_id.model
|
||||
|
||||
@api.depends('mailing_model_id')
|
||||
def _compute_mailing_on_mailing_list(self):
|
||||
mailing_list_model_id = self.env['ir.model']._get('mailing.list')
|
||||
self.mailing_on_mailing_list = False
|
||||
self.filtered(lambda m: m.mailing_model_id == mailing_list_model_id).mailing_on_mailing_list = True
|
||||
|
||||
@api.depends('mailing_model_id', 'contact_list_ids', 'mailing_type', 'mailing_filter_id')
|
||||
def _compute_mailing_domain(self):
|
||||
for mailing in self:
|
||||
@@ -1008,12 +1017,12 @@ class MassMailing(models.Model):
|
||||
|
||||
def _get_unsubscribe_url(self, email_to, res_id):
|
||||
url = werkzeug.urls.url_join(
|
||||
self.get_base_url(), 'mail/mailing/%(mailing_id)s/unsubscribe?%(params)s' % {
|
||||
self.get_base_url(), 'mailing/%(mailing_id)s/unsubscribe?%(params)s' % {
|
||||
'mailing_id': self.id,
|
||||
'params': werkzeug.urls.url_encode({
|
||||
'res_id': res_id,
|
||||
'email': email_to,
|
||||
'token': self._unsubscribe_token(res_id, email_to),
|
||||
'token': self._generate_mailing_recipient_token(res_id, email_to),
|
||||
}),
|
||||
}
|
||||
)
|
||||
@@ -1026,7 +1035,7 @@ class MassMailing(models.Model):
|
||||
'params': werkzeug.urls.url_encode({
|
||||
'res_id': res_id,
|
||||
'email': email_to,
|
||||
'token': self._unsubscribe_token(res_id, email_to),
|
||||
'token': self._generate_mailing_recipient_token(res_id, email_to),
|
||||
}),
|
||||
}
|
||||
)
|
||||
@@ -1158,7 +1167,7 @@ class MassMailing(models.Model):
|
||||
** mailing._prepare_statistics_email_values(),
|
||||
}
|
||||
if mail_user.has_group('mass_mailing.group_mass_mailing_user'):
|
||||
rendering_data['mailing_report_token'] = self._get_unsubscribe_token(mail_user.id)
|
||||
rendering_data['mailing_report_token'] = self._generate_mailing_report_token(mail_user.id)
|
||||
rendering_data['user_id'] = mail_user.id
|
||||
|
||||
rendered_body = self.env['ir.qweb']._render(
|
||||
@@ -1258,8 +1267,8 @@ class MassMailing(models.Model):
|
||||
def _get_pretty_mailing_type(self):
|
||||
return _('Emails')
|
||||
|
||||
def _get_unsubscribe_token(self, user_id):
|
||||
"""Generate a secure hash for this user. It allows to opt out from
|
||||
def _generate_mailing_report_token(self, user_id):
|
||||
"""Generate a secure token for this user. It allows to opt out from
|
||||
mailing reports while keeping some security in that process. """
|
||||
return tools.hmac(self.env(su=True), 'mailing-report-deactivated', user_id)
|
||||
|
||||
@@ -1285,21 +1294,18 @@ class MassMailing(models.Model):
|
||||
mailing_domain = [('id', 'in', [])]
|
||||
return mailing_domain
|
||||
|
||||
def _unsubscribe_token(self, res_id, email):
|
||||
"""Generate a secure hash for this mailing list and parameters.
|
||||
def _generate_mailing_recipient_token(self, document_id, email):
|
||||
"""Generate a secure token for a given mailing and recipient (based on
|
||||
their email). This allows notably to unsubscribe from the mailing or
|
||||
to blacklist their email entirely without need of a user account.
|
||||
|
||||
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 email:
|
||||
Email of the resource that will be unsubscribed.
|
||||
:param int document_id: ID of the business document on which mailing
|
||||
is performed;
|
||||
:param str email: recipient email, used to unsubscribe / blacklist;
|
||||
"""
|
||||
self.ensure_one()
|
||||
secret = self.env["ir.config_parameter"].sudo().get_param("database.secret")
|
||||
token = (self.env.cr.dbname, self.id, int(res_id), tools.ustr(email))
|
||||
token = (self.env.cr.dbname, self.id, int(document_id), tools.ustr(email))
|
||||
return hmac.new(secret.encode('utf-8'), repr(token).encode('utf-8'), hashlib.sha512).hexdigest()
|
||||
|
||||
def _convert_inline_images_to_urls(self, body_html):
|
||||
|
||||
@@ -71,7 +71,7 @@ odoo.define('mass_mailing.unsubscribe', function (require) {
|
||||
unchecked_ids[i] = parseInt($(this).val());
|
||||
});
|
||||
|
||||
ajax.jsonRpc('/mail/mailing/unsubscribe', 'call', {'opt_in_ids': checked_ids, 'opt_out_ids': unchecked_ids, 'email': email, 'mailing_id': mailing_id, 'res_id': res_id, 'token': token})
|
||||
ajax.jsonRpc('/mailing/list/update', 'call', {'opt_in_ids': checked_ids, 'opt_out_ids': unchecked_ids, 'email': email, 'mailing_id': mailing_id, 'res_id': res_id, 'token': token})
|
||||
.then(function (result) {
|
||||
if (result == 'unauthorized'){
|
||||
$('#subscription_info').text(_t('You are not authorized to do this!'));
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
.o_mailing_portal_body {
|
||||
// Taken from webclient.scss to copy backend styling
|
||||
// Uppercase primary and secondary except when secondary is used as a dropdown
|
||||
// toggler.
|
||||
.btn-primary,
|
||||
.btn-secondary:not(.dropdown-toggle):not(.dropdown-item),
|
||||
.btn-secondary.o_arrow_button:not(.dropdown-item) {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.o_mailing_portal_list_unsubscribed {
|
||||
margin-left: 20px;
|
||||
color: #005326;
|
||||
font-size: 90%;
|
||||
}
|
||||
}
|
||||
@@ -210,15 +210,6 @@
|
||||
bottom: 55px;
|
||||
}
|
||||
}
|
||||
.o_white_body {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.o_mass_mailing_unsubscribed {
|
||||
margin-left: 20px;
|
||||
color: #005326;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
|
||||
.o_mailing_contact_import_list {
|
||||
|
||||
@@ -194,14 +194,14 @@
|
||||
<field name="mailing_model_id" options="{'no_open': True, 'no_create': True}"
|
||||
attrs="{'readonly': [('state', 'in', ('sending', 'done'))]}"/>
|
||||
</div>
|
||||
<div attrs="{'invisible': [('mailing_model_name', '!=', 'mailing.list')]}" class="o_mass_mailing_contact_list_ids pt-1">
|
||||
<div attrs="{'invisible': [('mailing_on_mailing_list', '=', False)]}" class="o_mass_mailing_contact_list_ids pt-1">
|
||||
<label for="contact_list_ids" string="Select mailing lists:" class="oe_edit_only"/>
|
||||
<div class="d-inline-flex flex-row align-items-center">
|
||||
<field name="contact_list_ids" widget="many2many_tags"
|
||||
placeholder="Select mailing lists..." class="oe_inline mb-0"
|
||||
context="{'form_view_ref': 'mass_mailing.mailing_list_view_form_simplified'}"
|
||||
attrs="{
|
||||
'required':[('mailing_model_name','=','mailing.list')],
|
||||
'required':[('mailing_on_mailing_list', '=', True)],
|
||||
'readonly': [('state', 'in', ('sending', 'done'))]
|
||||
}"/>
|
||||
<button icon="fa-user-plus" type="object" class="btn btn-secondary py-0 px-1 ms-1"
|
||||
@@ -209,7 +209,7 @@
|
||||
name="action_view_mailing_contacts" title="Add Mailing Contacts"/>
|
||||
</div>
|
||||
</div>
|
||||
<div attrs="{'invisible': [('mailing_model_name', '=', 'mailing.list')]}" class="o_td_label">
|
||||
<div attrs="{'invisible': [('mailing_on_mailing_list', '=', True)]}" class="o_td_label">
|
||||
<!-- We don't want to display label in edit mode, unless mailing is in sending or done state (where filter will be readonly) -->
|
||||
<label for="mailing_filter_id" string="Filter" class="oe_read_only me-4"
|
||||
attrs="{'invisible': ['|', ('state', 'in', ('sending', 'done')), ('mailing_filter_id', '=', False)]}"/>
|
||||
@@ -225,8 +225,9 @@
|
||||
</div>
|
||||
|
||||
<field name="mailing_model_name" invisible="1"/>
|
||||
<field name="mailing_on_mailing_list" invisible="1"/>
|
||||
<field name="mailing_model_real" invisible="1"/>
|
||||
<div attrs="{'invisible': [('mailing_model_name', '=', 'mailing.list')]}">
|
||||
<div attrs="{'invisible': [('mailing_on_mailing_list', '=', True)]}">
|
||||
<field name="mailing_domain" widget="domain" options="{'model': 'mailing_model_real'}"
|
||||
attrs="{'readonly': [('state', 'in', ('sending', 'done'))]}"/>
|
||||
</div>
|
||||
@@ -483,7 +484,7 @@
|
||||
<field name='failed'/>
|
||||
<field name='total'/>
|
||||
<field name='mailing_model_id'/>
|
||||
<field name='mailing_model_name'/>
|
||||
<field name='mailing_on_mailing_list'/>
|
||||
<field name='sent_date'/>
|
||||
<field name='schedule_date'/>
|
||||
<field name='next_departure'/>
|
||||
@@ -536,8 +537,8 @@
|
||||
<div>
|
||||
<span attrs="{'invisible': [('sent_date', '=', False)]}" class="me-1"><b><field name="delivered"/> / <field name="expected"/></b> Delivered to</span>
|
||||
<span attrs="{'invisible': [('sent_date', '!=', False)]}" class="me-1"><b><field name='total'/></b></span>
|
||||
<field name='mailing_model_id' attrs="{'invisible': [('mailing_model_name','=','mailing.list')]}"/>
|
||||
<span attrs="{'invisible': [('mailing_model_name','!=','mailing.list')]}">Mailing Contact</span>
|
||||
<field name='mailing_model_id' attrs="{'invisible': [('mailing_on_mailing_list', '=', True)]}"/>
|
||||
<span attrs="{'invisible': [('mailing_on_mailing_list', '=', False)]}">Mailing Contact</span>
|
||||
</div>
|
||||
<div attrs="{'invisible': [('sent_date', '=', False)]}" class="d-flex justify-content-between">
|
||||
<div name="stat_opened">
|
||||
@@ -567,8 +568,8 @@
|
||||
<span attrs="{'invisible': ['|', '|', ('sent_date', '!=', False), ('schedule_date', '!=', False), ('state', '=', 'in_queue')]}"
|
||||
class="clearfix">
|
||||
<b><field name='total' class="me-1"/></b>
|
||||
<field name='mailing_model_id' attrs="{'invisible': [('mailing_model_name','=','mailing.list')]}"/>
|
||||
<span attrs="{'invisible': [('mailing_model_name','!=','mailing.list')]}">Mailing Contact</span>
|
||||
<field name='mailing_model_id' attrs="{'invisible': [('mailing_on_mailing_list', '=', True)]}"/>
|
||||
<span attrs="{'invisible': [('mailing_on_mailing_list', '=', False)]}">Mailing Contact</span>
|
||||
</span>
|
||||
<span attrs="{'invisible': ['|', '|', ('schedule_date', '!=', False), ('state', '!=', 'in_queue'), ('next_departure', '=', False)]}"
|
||||
t-attf-title="Scheduled on #{record.next_departure.value}" class="d-inline-flex">
|
||||
|
||||
@@ -2,18 +2,8 @@
|
||||
<odoo>
|
||||
<!-- new layout for mass_mailing -->
|
||||
<template id="mass_mailing.layout" name="Mass Mailing Layout">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="head">
|
||||
<t t-if="request.httprequest.cookies.get('color_scheme') == 'dark'">
|
||||
<t t-call-assets="web.dark_mode_assets_common"/>
|
||||
<t t-call-assets="web.dark_mode_assets_backend"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-call-assets="web.assets_common"/>
|
||||
<t t-call-assets="web.assets_backend"/>
|
||||
</t>
|
||||
</t>
|
||||
<body class="o_white_body">
|
||||
<t t-call="web.frontend_layout">
|
||||
<body class="bg-white o_mailing_portal_body">
|
||||
<header>
|
||||
<div><title>Odoo</title></div>
|
||||
<div class="text-center">
|
||||
@@ -25,16 +15,11 @@
|
||||
<t t-out="0"/>
|
||||
</main>
|
||||
</body>
|
||||
<xpath expr="//footer" position="replace">
|
||||
<div class="container mt16 mb8">
|
||||
<div class="float-end" t-ignore="true" t-if="not editable">
|
||||
Create a <a target="_blank" href="https://www.odoo.com/app/website">free website</a> with
|
||||
<a target="_blank" class="label label-danger" href="https://www.odoo.com/app/website">Odoo</a>
|
||||
</div>
|
||||
<div class="float-start text-muted" itemscope="itemscope" itemtype="https://schema.org/Organization">
|
||||
Copyright &copy; <span t-field="res_company.name" itemprop="name">Company name</span>
|
||||
</div>
|
||||
</div>
|
||||
<xpath expr="//head/t[@t-call-assets][last()]" position="after">
|
||||
<t t-call-assets="mass_mailing.mailing_assets" lazy_load="True"/>
|
||||
</xpath>
|
||||
<xpath expr="//header" position="before">
|
||||
<t t-set="no_header" t-value="True"/>
|
||||
</xpath>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</template>
|
||||
|
||||
<!-- Dummy layout to "view" a template content (html) -->
|
||||
<template id="view" name="Browser View">
|
||||
<template id="mailing_view" name="Browser View">
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="unsubscribe">
|
||||
<template id="page_mailing_unsubscribe" name="Unsubscribe">
|
||||
<t t-call="mass_mailing.layout">
|
||||
<t t-call="mass_mailing.unsubscribe_form"/>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="unsubscribe_form">
|
||||
<div class="container o_unsubscribe_form">
|
||||
<div class="row">
|
||||
<form action="/mail/mailing/unsubscribe" method="POST" id="unsubscribe_form" class="col-lg-6 offset-lg-3 mt-4">
|
||||
<form action="/mailing/list/update" method="POST" id="unsubscribe_form" class="col-lg-6 offset-lg-3 mt-4">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="email" t-att-value="email"/>
|
||||
<input type="hidden" name="mailing_id" t-att-value="mailing_id"/>
|
||||
@@ -29,11 +35,11 @@
|
||||
<t t-foreach="list_ids" t-as="list_id">
|
||||
<t t-if="list_id.is_public == True">
|
||||
<li class="list-group-item">
|
||||
<input type="checkbox" class="mail_list_checkbox" name="contact_ids"
|
||||
<input type="checkbox" name="contact_ids"
|
||||
t-att-value="list_id['id']" t-att-checked="None if list_id['id'] in opt_out_list_ids else 'checked'"/>
|
||||
<t t-esc="list_id.name"/>
|
||||
<span t-if="list_id['id'] in opt_out_list_ids"
|
||||
class="o_mass_mailing_unsubscribed">
|
||||
class="o_mailing_portal_list_unsubscribed">
|
||||
Unsubscribed
|
||||
</span>
|
||||
</li>
|
||||
@@ -42,11 +48,15 @@
|
||||
</ul>
|
||||
|
||||
<div class="mb64 pt-3">
|
||||
<div t-if="show_blacklist_button">
|
||||
<div class="btn btn-secondary float-end" id="button_add_blacklist" style="display:none">Blacklist Me</div>
|
||||
</div>
|
||||
<div class="btn btn-secondary float-end" id="button_remove_blacklist" style="display:none">Come Back</div>
|
||||
<button type="submit" id="send_form" class="btn btn-primary">Update my subscriptions</button>
|
||||
<div class="btn btn-link float-end pe-0 text-uppercase"
|
||||
t-if="show_blacklist_button"
|
||||
id="button_add_blacklist"
|
||||
style="display:none">Blacklist Me</div>
|
||||
<div class="btn btn-link float-end pe-0 text-uppercase"
|
||||
id="button_remove_blacklist"
|
||||
style="display:none">Come Back</div>
|
||||
<button type="submit" id="send_form"
|
||||
class="btn btn-primary">Update my subscriptions</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -62,7 +72,13 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="unsubscribed">
|
||||
<template id="page_mailing_unsubscribe_done" name="Unsubscribed">
|
||||
<t t-call="mass_mailing.layout">
|
||||
<t t-call="mass_mailing.unsubscribe_done"/>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="unsubscribe_done">
|
||||
<div class="container o_unsubscribe_form">
|
||||
<div class="row">
|
||||
<input type="hidden" name="email" t-att-value="email"/>
|
||||
@@ -89,23 +105,15 @@
|
||||
</div>
|
||||
|
||||
<div t-if="show_blacklist_button" class="mb64">
|
||||
<div class="btn btn-secondary float-end" id="button_add_blacklist" style="display:none">Blacklist Me</div>
|
||||
<div class="btn btn-secondary float-end" id="button_remove_blacklist" style="display:none">Come Back</div>
|
||||
<div class="btn btn-link float-end"
|
||||
id="button_add_blacklist"
|
||||
style="display:none">Blacklist Me</div>
|
||||
<div class="btn btn-link float-end"
|
||||
id="button_remove_blacklist"
|
||||
style="display:none">Come Back</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="page_unsubscribe" name="Unsubscribe">
|
||||
<t t-call="mass_mailing.layout">
|
||||
<t t-call="mass_mailing.unsubscribe"/>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="page_unsubscribed" name="Unsubscribed">
|
||||
<t t-call="mass_mailing.layout">
|
||||
<t t-call="mass_mailing.unsubscribed"/>
|
||||
</t>
|
||||
</template>
|
||||
</odoo>
|
||||
|
||||
@@ -78,43 +78,54 @@ class PhoneBlackList(models.Model):
|
||||
new_args = args
|
||||
return super(PhoneBlackList, self)._search(new_args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid)
|
||||
|
||||
def add(self, number):
|
||||
def add(self, number, message=None):
|
||||
sanitized = phone_validation.phone_sanitize_numbers_w_record([number], self.env.user)[number]['sanitized']
|
||||
return self._add([sanitized])
|
||||
return self._add([sanitized], message=message)
|
||||
|
||||
def _add(self, numbers):
|
||||
def _add(self, numbers, message=None):
|
||||
""" Add or re activate a phone blacklist entry.
|
||||
|
||||
:param numbers: list of sanitized numbers """
|
||||
records = self.env["phone.blacklist"].with_context(active_test=False).search([('number', 'in', numbers)])
|
||||
todo = [n for n in numbers if n not in records.mapped('number')]
|
||||
if records:
|
||||
if message:
|
||||
records._track_set_log_message(message)
|
||||
records.action_unarchive()
|
||||
if todo:
|
||||
records += self.create([{'number': n} for n in todo])
|
||||
new_records = self.create([{'number': n} for n in todo])
|
||||
if message:
|
||||
for record in new_records:
|
||||
record.with_context(mail_create_nosubscribe=True).message_post(
|
||||
body=message,
|
||||
subtype_id=self.env.ref('mail.mt_note').id,
|
||||
)
|
||||
records += new_records
|
||||
return records
|
||||
|
||||
def action_remove_with_reason(self, number, reason=None):
|
||||
records = self.remove(number)
|
||||
if reason:
|
||||
for record in records:
|
||||
record.message_post(body=_("Unblacklisting Reason: %s", reason))
|
||||
return records
|
||||
|
||||
def remove(self, number):
|
||||
def remove(self, number, message=None):
|
||||
sanitized = phone_validation.phone_sanitize_numbers_w_record([number], self.env.user)[number]['sanitized']
|
||||
return self._remove([sanitized])
|
||||
return self._remove([sanitized], message=message)
|
||||
|
||||
def _remove(self, numbers):
|
||||
def _remove(self, numbers, message=None):
|
||||
""" Add de-activated or de-activate a phone blacklist entry.
|
||||
|
||||
:param numbers: list of sanitized numbers """
|
||||
records = self.env["phone.blacklist"].with_context(active_test=False).search([('number', 'in', numbers)])
|
||||
todo = [n for n in numbers if n not in records.mapped('number')]
|
||||
if records:
|
||||
if message:
|
||||
records._track_set_log_message(message)
|
||||
records.action_archive()
|
||||
if todo:
|
||||
records += self.create([{'number': n, 'active': False} for n in todo])
|
||||
new_records = self.create([{'number': n, 'active': False} for n in todo])
|
||||
if message:
|
||||
for record in new_records:
|
||||
record.with_context(mail_create_nosubscribe=True).message_post(
|
||||
body=message,
|
||||
subtype_id=self.env.ref('mail.mt_note').id,
|
||||
)
|
||||
records += new_records
|
||||
return records
|
||||
|
||||
def phone_action_blacklist_remove(self):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import fields, models
|
||||
from markupsafe import Markup
|
||||
from odoo import fields, models, _
|
||||
|
||||
|
||||
class PhoneBlacklistRemove(models.TransientModel):
|
||||
@@ -11,4 +12,11 @@ class PhoneBlacklistRemove(models.TransientModel):
|
||||
reason = fields.Char(name="Reason")
|
||||
|
||||
def action_unblacklist_apply(self):
|
||||
return self.env['phone.blacklist'].action_remove_with_reason(self.phone, self.reason)
|
||||
if self.reason:
|
||||
message = Markup('<p>%s</p>') % _("Unblock Reason: %(reason)s", reason=self.reason)
|
||||
else:
|
||||
message = None
|
||||
return self.env['phone.blacklist']._remove(
|
||||
self.phone,
|
||||
message=message,
|
||||
)
|
||||
|
||||
@@ -74,7 +74,7 @@ class TestMassMailing(TestMailFullCommon):
|
||||
email['body'])
|
||||
# rendered unsubscribe
|
||||
self.assertIn(
|
||||
'%s/mail/mailing/%s/unsubscribe' % (mailing.get_base_url(), mailing.id),
|
||||
'%s/mailing/%s/unsubscribe' % (mailing.get_base_url(), mailing.id),
|
||||
email['body'])
|
||||
unsubscribe_href = self._get_href_from_anchor_id(email['body'], "url6")
|
||||
unsubscribe_url = werkzeug.urls.url_parse(unsubscribe_href)
|
||||
@@ -82,7 +82,7 @@ class TestMassMailing(TestMailFullCommon):
|
||||
self.assertEqual(int(unsubscribe_params['res_id']), recipient.id)
|
||||
self.assertEqual(unsubscribe_params['email'], recipient.email_normalized)
|
||||
self.assertEqual(
|
||||
mailing._unsubscribe_token(unsubscribe_params['res_id'], (unsubscribe_params['email'])),
|
||||
mailing._generate_mailing_recipient_token(unsubscribe_params['res_id'], (unsubscribe_params['email'])),
|
||||
unsubscribe_params['token']
|
||||
)
|
||||
# rendered view
|
||||
@@ -95,7 +95,7 @@ class TestMassMailing(TestMailFullCommon):
|
||||
self.assertEqual(int(view_params['res_id']), recipient.id)
|
||||
self.assertEqual(view_params['email'], recipient.email_normalized)
|
||||
self.assertEqual(
|
||||
mailing._unsubscribe_token(view_params['res_id'], (view_params['email'])),
|
||||
mailing._generate_mailing_recipient_token(view_params['res_id'], (view_params['email'])),
|
||||
view_params['token']
|
||||
)
|
||||
|
||||
|
||||
@@ -229,8 +229,8 @@ class TestMassMailing(TestMassMailCommon):
|
||||
self.env['mail.blacklist'].create({'email': recipients[4].email_normalized})
|
||||
|
||||
# unblacklist record 2
|
||||
self.env['mail.blacklist'].action_remove_with_reason(
|
||||
recipients[2].email_normalized, "human error"
|
||||
self.env['mail.blacklist']._remove(
|
||||
recipients[2].email_normalized, message="human error"
|
||||
)
|
||||
self.env['mail.blacklist'].flush_model(['active'])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user