diff --git a/addons/mail/__manifest__.py b/addons/mail/__manifest__.py
index de396abcdd2..c45b4f7598e 100644
--- a/addons/mail/__manifest__.py
+++ b/addons/mail/__manifest__.py
@@ -11,6 +11,7 @@
'data': [
'views/mail_menus.xml',
'wizard/invite_view.xml',
+ 'wizard/mail_blacklist_remove_view.xml',
'wizard/mail_compose_message_view.xml',
'wizard/mail_resend_cancel_views.xml',
'wizard/mail_resend_message_views.xml',
diff --git a/addons/mail/models/mail_blacklist.py b/addons/mail/models/mail_blacklist.py
index abae3f35fbd..b2d27238a2c 100644
--- a/addons/mail/models/mail_blacklist.py
+++ b/addons/mail/models/mail_blacklist.py
@@ -2,7 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, tools, _
-from odoo.exceptions import UserError
+from odoo.exceptions import AccessError, UserError
class MailBlackList(models.Model):
@@ -78,6 +78,13 @@ class MailBlackList(models.Model):
record = self.create({'email': email})
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):
normalized = tools.email_normalize(email)
record = self.env["mail.blacklist"].with_context(active_test=False).search([('email', '=', normalized)])
@@ -87,6 +94,17 @@ class MailBlackList(models.Model):
record = record.create({'email': email, 'active': False})
return record
+ def mail_action_blacklist_remove(self):
+ return {
+ 'name': 'Are you sure you want to unblacklist this Email Address?',
+ 'type': 'ir.actions.act_window',
+ 'view_mode': 'form',
+ 'res_model': 'mail.blacklist.remove',
+ 'target': 'new',
+ }
+
+ def action_add(self):
+ self._add(self.email)
class MailBlackListMixin(models.AbstractModel):
""" Mixin that is inherited by all model with opt out. This mixin stores a normalized
@@ -192,3 +210,18 @@ class MailBlackListMixin(models.AbstractModel):
bounce counter of the record. """
super(MailBlackListMixin, self)._message_reset_bounce(email)
self.write({'message_bounce': 0})
+
+ def mail_action_blacklist_remove(self):
+ # wizard access rights currently not working as expected and allows users without access to
+ # open this wizard, therefore we check to make sure they have access before the wizard opens.
+ can_access = self.env['mail.blacklist'].check_access_rights('write', raise_exception=False)
+ if can_access:
+ return {
+ 'name': 'Are you sure you want to unblacklist this Email Address?',
+ 'type': 'ir.actions.act_window',
+ 'view_mode': 'form',
+ 'res_model': 'mail.blacklist.remove',
+ 'target': 'new',
+ }
+ else:
+ raise AccessError("You do not have the access right to unblacklist emails. Please contact your administrator.")
diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv
index 6a5e71f7e1e..49c90572ebb 100644
--- a/addons/mail/security/ir.model.access.csv
+++ b/addons/mail/security/ir.model.access.csv
@@ -45,3 +45,4 @@ access_mail_resend_cancel,access.mail.resend.cancel,model_mail_resend_cancel,bas
access_mail_resend_message,access.mail.resend.message,model_mail_resend_message,base.group_user,1,1,1,0
access_mail_resend_partner,access.mail.resend.partner,model_mail_resend_partner,base.group_user,1,1,1,0
access_mail_template_preview,access.mail.template.preview,model_mail_template_preview,base.group_user,1,1,1,0
+access_mail_blacklist_remove_system,acesss.mail.blacklist.remove.system,model_mail_blacklist_remove,base.group_system,1,1,1,1
diff --git a/addons/mail/views/mail_blacklist_views.xml b/addons/mail/views/mail_blacklist_views.xml
index 8ae6b2f3e7f..0ee88d2805e 100644
--- a/addons/mail/views/mail_blacklist_views.xml
+++ b/addons/mail/views/mail_blacklist_views.xml
@@ -16,12 +16,21 @@
mail.blacklist
@@ -32,7 +32,7 @@
-
+
@@ -45,11 +45,13 @@
-
+
-
+
diff --git a/addons/phone_validation/__init__.py b/addons/phone_validation/__init__.py
index 3d097a1fcdf..c5e2ac921ad 100644
--- a/addons/phone_validation/__init__.py
+++ b/addons/phone_validation/__init__.py
@@ -3,3 +3,4 @@
from . import tools
from . import models
+from . import wizard
diff --git a/addons/phone_validation/__manifest__.py b/addons/phone_validation/__manifest__.py
index 1d9fce1843b..a87d866101e 100644
--- a/addons/phone_validation/__manifest__.py
+++ b/addons/phone_validation/__manifest__.py
@@ -26,6 +26,7 @@ It adds two mixins :
'data': [
'security/ir.model.access.csv',
'views/phone_blacklist_views.xml',
+ 'wizard/phone_blacklist_remove_view.xml',
],
'depends': [
'base',
diff --git a/addons/phone_validation/models/mail_thread_phone.py b/addons/phone_validation/models/mail_thread_phone.py
index d0e9b5b9254..9b608c8b601 100644
--- a/addons/phone_validation/models/mail_thread_phone.py
+++ b/addons/phone_validation/models/mail_thread_phone.py
@@ -3,7 +3,7 @@
from odoo import api, fields, models, _
from odoo.addons.phone_validation.tools import phone_validation
-from odoo.exceptions import UserError
+from odoo.exceptions import AccessError, UserError
class PhoneMixin(models.AbstractModel):
@@ -30,10 +30,18 @@ class PhoneMixin(models.AbstractModel):
phone_sanitized = fields.Char(
string='Sanitized Number', compute="_compute_phone_sanitized", compute_sudo=True, store=True,
help="Field used to store sanitized phone number. Helps speeding up searches and comparisons.")
+ phone_sanitized_blacklisted = fields.Boolean(
+ string='Phone Blacklisted', compute="_compute_blacklisted", compute_sudo=True, store=False,
+ search="_search_phone_sanitized_blacklisted", groups="base.group_user",
+ help="If the sanitized phone number is on the blacklist, the contact won't receive mass mailing sms anymore, from any list")
phone_blacklisted = fields.Boolean(
- string='Phone Blacklisted', compute="_compute_phone_blacklisted", compute_sudo=True, store=False,
- search="_search_phone_blacklisted", groups="base.group_user",
- help="If the phone number is on the blacklist, the contact won't receive mass mailing sms anymore, from any list")
+ string='Blacklisted Phone is Phone', compute="_compute_blacklisted", compute_sudo=True, store=False, groups="base.group_user",
+ help="Indicates if a blacklisted sanitized phone number is a phone number. Helps distinguish which number is blacklisted \
+ when there is both a mobile and phone field in a model.")
+ mobile_blacklisted = fields.Boolean(
+ string='Blacklisted Phone Is Mobile', compute="_compute_blacklisted", compute_sudo=True, store=False, groups="base.group_user",
+ help="Indicates if a blacklisted sanitized phone number is a mobile number. Helps distinguish which number is blacklisted \
+ when there is both a mobile and phone field in a model.")
@api.depends(lambda self: self._phone_get_number_fields())
def _compute_phone_sanitized(self):
@@ -47,16 +55,26 @@ class PhoneMixin(models.AbstractModel):
record.phone_sanitized = sanitized
@api.depends('phone_sanitized')
- def _compute_phone_blacklisted(self):
+ def _compute_blacklisted(self):
# TODO : Should remove the sudo as compute_sudo defined on methods.
# But if user doesn't have access to mail.blacklist, doen't work without sudo().
blacklist = set(self.env['phone.blacklist'].sudo().search([
('number', 'in', self.mapped('phone_sanitized'))]).mapped('number'))
+ number_fields = self._phone_get_number_fields()
for record in self:
- record.phone_blacklisted = record.phone_sanitized in blacklist
+ record.phone_sanitized_blacklisted = record.phone_sanitized in blacklist
+ # This is a bit of a hack. Assume that any "mobile" numbers will have the word 'mobile'
+ # in them due to varying field names and assume all others are just "phone" numbers.
+ # Note that the limitation of only having 1 phone_sanitized value means that a phone/mobile number
+ # may not be calculated as blacklisted even though it is if both field values exist in a model.
+ for number_field in number_fields:
+ if 'mobile' in number_field:
+ record.mobile_blacklisted = record.phone_sanitized_blacklisted and record.phone_get_sanitized_number(number_fname=number_field) == record.phone_sanitized
+ else:
+ record.phone_blacklisted = record.phone_sanitized_blacklisted and record.phone_get_sanitized_number(number_fname=number_field) == record.phone_sanitized
@api.model
- def _search_phone_blacklisted(self, operator, value):
+ def _search_phone_sanitized_blacklisted(self, operator, value):
# Assumes operator is '=' or '!=' and value is True or False
self._assert_phone_field()
if operator != '=':
@@ -121,3 +139,18 @@ class PhoneMixin(models.AbstractModel):
def _phone_reset_blacklisted(self):
return self.env['phone.blacklist'].sudo()._remove([r.phone_sanitized for r in self])
+
+ def phone_action_blacklist_remove(self):
+ # wizard access rights currently not working as expected and allows users without access to
+ # open this wizard, therefore we check to make sure they have access before the wizard opens.
+ can_access = self.env['phone.blacklist'].check_access_rights('write', raise_exception=False)
+ if can_access:
+ return {
+ 'name': 'Are you sure you want to unblacklist this Phone Number?',
+ 'type': 'ir.actions.act_window',
+ 'view_mode': 'form',
+ 'res_model': 'phone.blacklist.remove',
+ 'target': 'new',
+ }
+ else:
+ raise AccessError("You do not have the access right to unblacklist phone numbers. Please contact your administrator.")
diff --git a/addons/phone_validation/models/phone_blacklist.py b/addons/phone_validation/models/phone_blacklist.py
index 2a6e90b6cb5..3fd1c4b4bcc 100644
--- a/addons/phone_validation/models/phone_blacklist.py
+++ b/addons/phone_validation/models/phone_blacklist.py
@@ -31,9 +31,10 @@ class PhoneBlackList(models.Model):
done = set()
for value in values:
number = value['number']
- sanitized = phone_validation.phone_sanitize_numbers_w_record([number], self.env.user)[number]['sanitized']
+ sanitized_values = phone_validation.phone_sanitize_numbers_w_record([number], self.env.user)[number]
+ sanitized = sanitized_values['sanitized']
if not sanitized:
- raise UserError(_('Invalid number %s') % number)
+ raise UserError(sanitized_values['msg'] + _(" Please correct the number and try again."))
if sanitized in done:
continue
done.add(sanitized)
@@ -52,9 +53,10 @@ class PhoneBlackList(models.Model):
def write(self, values):
if 'number' in values:
number = values['number']
- sanitized = phone_validation.phone_sanitize_numbers_w_record([number], self.env.user)[number]['sanitized']
+ sanitized_values = phone_validation.phone_sanitize_numbers_w_record([number], self.env.user)[number]
+ sanitized = sanitized_values['sanitized']
if not sanitized:
- raise UserError(_('Invalid number %s') % number)
+ raise UserError(sanitized_values['msg'] + _(" Please correct the number and try again."))
values['number'] = sanitized
return super(PhoneBlackList, self).write(values)
@@ -92,6 +94,13 @@ class PhoneBlackList(models.Model):
records += self.create([{'number': n} for n in todo])
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):
sanitized = phone_validation.phone_sanitize_numbers_w_record([number], self.env.user)[number]['sanitized']
return self._remove([sanitized])
@@ -107,3 +116,15 @@ class PhoneBlackList(models.Model):
if todo:
records += self.create([{'number': n, 'active': False} for n in todo])
return records
+
+ def phone_action_blacklist_remove(self):
+ return {
+ 'name': 'Are you sure you want to unblacklist this Phone Number?',
+ 'type': 'ir.actions.act_window',
+ 'view_mode': 'form',
+ 'res_model': 'phone.blacklist.remove',
+ 'target': 'new',
+ }
+
+ def action_add(self):
+ self.add(self.number)
diff --git a/addons/phone_validation/security/ir.model.access.csv b/addons/phone_validation/security/ir.model.access.csv
index 53a27a4e6b2..fe442d04d2d 100644
--- a/addons/phone_validation/security/ir.model.access.csv
+++ b/addons/phone_validation/security/ir.model.access.csv
@@ -1,3 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_phone_blacklist_all,access.phone.blacklist.all,model_phone_blacklist,,0,0,0,0
access_phone_blacklist_system,access.phone.blacklist.system,model_phone_blacklist,base.group_system,1,1,1,1
+access_phone_blacklist_remove_system,acesss.phone.blacklist.remove.system,model_phone_blacklist_remove,base.group_system,1,1,1,1
diff --git a/addons/phone_validation/tools/phone_validation.py b/addons/phone_validation/tools/phone_validation.py
index f54edbe301f..97eb7bac05c 100644
--- a/addons/phone_validation/tools/phone_validation.py
+++ b/addons/phone_validation/tools/phone_validation.py
@@ -17,12 +17,12 @@ try:
try:
phone_nbr = phonenumbers.parse(number, region=country_code, keep_raw_input=True)
except phonenumbers.phonenumberutil.NumberParseException as e:
- raise UserError(_('Unable to parse %s: %s') % (number, str(e)))
+ raise UserError(_('Unable to parse %s.: %s') % (number, str(e)))
if not phonenumbers.is_possible_number(phone_nbr):
- raise UserError(_('Impossible number %s: probably invalid number of digits') % number)
+ raise UserError(_('Impossible number %s: probably invalid number of digits.') % number)
if not phonenumbers.is_valid_number(phone_nbr):
- raise UserError(_('Invalid number %s: probably incorrect prefix') % number)
+ raise UserError(_('Invalid number %s: probably incorrect prefix.') % number)
return phone_nbr
diff --git a/addons/phone_validation/views/phone_blacklist_views.xml b/addons/phone_validation/views/phone_blacklist_views.xml
index b04c7352e58..e988c86c087 100644
--- a/addons/phone_validation/views/phone_blacklist_views.xml
+++ b/addons/phone_validation/views/phone_blacklist_views.xml
@@ -16,12 +16,21 @@
phone.blacklist