summaryrefslogtreecommitdiff
path: root/addons/phone_validation
diff options
context:
space:
mode:
authorTiffany Chang (tic) <tic@odoo.com>2020-02-13 12:59:34 +0000
committerTiffany Chang (tic) <tic@odoo.com>2020-03-26 10:15:18 +0000
commitf9ff21a200b0c5553ba6d1314e4d9f54d3ebdd7d (patch)
treeaf7ff5f3eb86d2a0ee79a23aac6397d75820f524 /addons/phone_validation
parent3a112c7b78d04479e7f8d27adaeb0753bdb0111c (diff)
[IMP] mass_mailing_(sms): Add unblacklisting button
Previously a blacklisted phone number or email address could only be removed from their respective blacklist by going to the corresponding blacklist view [in mass_mailing_(sms)] and manually archiving/deactiviting the entry. All users could see that an email was blacklisted via an fa-ban icon added next to their corresponding field in the modules: crm, mass_mailing, mass_mailing_sms, and contacts (via extension). This commit adds additional fa-ban icons next to blacklisted phone numbers and makes all instances of these icons clickable to remove them from their corresponding blacklist using a wizard. There are several limitations to this implementation including: 1. If both a mobile and phone number field appear within a crm lead instance, then only the mobile field will indicate if it is blacklisted (due to current implementation of PhoneMixin which only checks 1 phone value against the blacklist and "sms" which returns mobile numbers first). I.e. if a phone field value is blacklisted, but a value is typed into the mobile field, then the phone field will never indicate that it is blacklisted. 2. If someone clicks on a unblacklisting icon after changing the corresponding field value without clicking off the field input, then the latest typed in value will be the one submitted for unblacklisting, which may not actually be blacklisted (due to "is_blacklisted" flag being a computed field and it not having a chance to re-compute to hide the button). 3. If someone changes values in the blacklist, then someone who already has a form open will not see icon disappear until something triggers a re-compute of the "phone_sanitized_blacklisted" flag (this was already the case with the icon, but now a user may try to unblacklist a value that is already unblacklisted.) 4. Since the icon needs to be visible by everyone to indicate whether or not a phone/email is blacklisted, users without corresponding unblacklist permissions will be able to click on the icon. When they click on it, they will be informed they do not have permission to unblacklist. A wizard was determined to be the best way to implement this unblacklisting for the following reasons: - A "Unblacklisting Reason" is needed and will not be stored as a field. - A field widget is unable to do this due to security settings + inability to refresh view after unblacklisting with a "Unblacklisting Reason" without wiping unsaved changes. Additionally, to better align with GDPR, the corresponding form view for the phone/email blacklist has been updated to use the same wizard to track "Reason for unblacklisting". Unfortunately there is no straightforward way to prevent direct "Archive" action, so users are still able to bypass unblacklisting without being asked for a "reason for unblacklisting". Supports task: 2117635 Upgrade PR: odoo/upgrade#939 COM PR: odoo/odoo#45315
Diffstat (limited to 'addons/phone_validation')
-rw-r--r--addons/phone_validation/__init__.py1
-rw-r--r--addons/phone_validation/__manifest__.py1
-rw-r--r--addons/phone_validation/models/mail_thread_phone.py47
-rw-r--r--addons/phone_validation/models/phone_blacklist.py29
-rw-r--r--addons/phone_validation/security/ir.model.access.csv1
-rw-r--r--addons/phone_validation/tools/phone_validation.py6
-rw-r--r--addons/phone_validation/views/phone_blacklist_views.xml11
-rw-r--r--addons/phone_validation/wizard/__init__.py4
-rw-r--r--addons/phone_validation/wizard/phone_blacklist_remove.py14
-rw-r--r--addons/phone_validation/wizard/phone_blacklist_remove_view.xml19
10 files changed, 118 insertions, 15 deletions
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 @@
<field name="model">phone.blacklist</field>
<field name="arch" type="xml">
<form string="Phone Blacklist" duplicate="false" edit="false">
+ <header>
+ <button name="phone_action_blacklist_remove" string="Unblacklist"
+ type="object" class="oe_highlight" context="{'default_phone': number}"
+ attrs="{'invisible': ['|', ('active', '=', False), ('number', '=', False)]}"/>
+ <button name="action_add" string="Blacklist"
+ type="object" class="oe_highlight"
+ attrs="{'invisible': ['|', ('active', '=', True), ('number', '=', False)]}"/>
+ </header>
<sheet>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
<group>
<group>
<field name="number"/>
- <field name="active" widget="boolean_toggle"/>
+ <field name="active" readonly="1"/>
+ <br/>
</group>
</group>
</sheet>
diff --git a/addons/phone_validation/wizard/__init__.py b/addons/phone_validation/wizard/__init__.py
new file mode 100644
index 00000000000..7d23a79178e
--- /dev/null
+++ b/addons/phone_validation/wizard/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import phone_blacklist_remove
diff --git a/addons/phone_validation/wizard/phone_blacklist_remove.py b/addons/phone_validation/wizard/phone_blacklist_remove.py
new file mode 100644
index 00000000000..8dff728f9f8
--- /dev/null
+++ b/addons/phone_validation/wizard/phone_blacklist_remove.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+
+from odoo import fields, models
+
+
+class PhoneBlacklistRemove(models.TransientModel):
+ _name = 'phone.blacklist.remove'
+ _description = 'Remove phone from blacklist'
+
+ phone = fields.Char(string="Phone Number", readonly=True, required=True)
+ reason = fields.Char(name="Reason")
+
+ def action_unblacklist_apply(self):
+ return self.env['phone.blacklist'].action_remove_with_reason(self.phone, self.reason)
diff --git a/addons/phone_validation/wizard/phone_blacklist_remove_view.xml b/addons/phone_validation/wizard/phone_blacklist_remove_view.xml
new file mode 100644
index 00000000000..c41db2c23f8
--- /dev/null
+++ b/addons/phone_validation/wizard/phone_blacklist_remove_view.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<odoo>
+ <record id="phone_blacklist_remove_view_form" model="ir.ui.view">
+ <field name="name">phone.blacklist.remove.form</field>
+ <field name="model">phone.blacklist.remove</field>
+ <field name="arch" type="xml">
+ <form string="phone_blacklist_removal">
+ <group class="oe_title">
+ <field name="phone" string="Phone Number"/>
+ <field name="reason" string="Reason"/>
+ </group>
+ <footer>
+ <button name="action_unblacklist_apply" string="Confirm" type="object" class="btn-primary"/>
+ <button string="Discard" class="btn-secondary" special="cancel"/>
+ </footer>
+ </form>
+ </field>
+ </record>
+</odoo>