[IMP] phone_validation: add a phone formatting tool on model
RATIONALE Simplify field management for mail / phone / sms flows. Make it working out of the box, easier to use and tweak. SPECIFICATIONS Add a '_phone_format' tool method on BaseModel. It allows to format a number either directly, either from a field available on the model. It allows to ease number formatting. It uses available helpers to find numbers using '_phone_get_number_fields' and '_phone_get_country_field'. With default generic behavior this allows to simplify most calls to phone number formatting. Having it available at BaseModel level allows to remove some custom code, calls to phone_validation API, ... Task-3422449 (Mail, Phone: Move and improve field helpers) Part-of: odoo/odoo#130468
This commit is contained in:
@@ -633,12 +633,12 @@ class Lead(models.Model):
|
||||
@api.onchange('phone', 'country_id', 'company_id')
|
||||
def _onchange_phone_validation(self):
|
||||
if self.phone:
|
||||
self.phone = self.phone_get_sanitized_number(number_fname='phone', force_format='INTERNATIONAL') or self.phone
|
||||
self.phone = self._phone_format(fname='phone', force_format='INTERNATIONAL') or self.phone
|
||||
|
||||
@api.onchange('mobile', 'country_id', 'company_id')
|
||||
def _onchange_mobile_validation(self):
|
||||
if self.mobile:
|
||||
self.mobile = self.phone_get_sanitized_number(number_fname='mobile', force_format='INTERNATIONAL') or self.mobile
|
||||
self.mobile = self._phone_format(fname='mobile', force_format='INTERNATIONAL') or self.mobile
|
||||
|
||||
def _prepare_values_from_partner(self, partner):
|
||||
""" Get a dictionary with values coming from partner information to
|
||||
@@ -706,8 +706,8 @@ class Lead(models.Model):
|
||||
"""
|
||||
self.ensure_one()
|
||||
if self.partner_id and self.phone != self.partner_id.phone:
|
||||
lead_phone_formatted = self.phone_get_sanitized_number(number_fname='phone') or self.phone or False
|
||||
partner_phone_formatted = self.partner_id.phone_get_sanitized_number(number_fname='phone') or self.partner_id.phone or False
|
||||
lead_phone_formatted = self._phone_format(fname='phone') or self.phone or False
|
||||
partner_phone_formatted = self.partner_id._phone_format(fname='phone') or self.partner_id.phone or False
|
||||
return lead_phone_formatted != partner_phone_formatted
|
||||
return False
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ Key Features
|
||||
* Manage your Events and Registrations
|
||||
* Use emails to automatically confirm and send acknowledgments for any event registration
|
||||
""",
|
||||
'depends': ['base_setup', 'mail', 'portal', 'utm'],
|
||||
'depends': ['base_setup', 'mail', 'phone_validation', 'portal', 'utm'],
|
||||
'data': [
|
||||
'security/event_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
|
||||
@@ -7,15 +7,6 @@ from odoo import _, api, fields, models, SUPERUSER_ID
|
||||
from odoo.tools import format_date
|
||||
from odoo.exceptions import AccessError, ValidationError
|
||||
|
||||
# phone_validation is not officially in the depends of event, but we would like
|
||||
# to have the formatting available in event, not in event_sms -> do a conditional
|
||||
# import just to be sure
|
||||
try:
|
||||
from odoo.addons.phone_validation.tools.phone_validation import phone_format
|
||||
except ImportError:
|
||||
def phone_format(number, country_code, country_phone_code, force_format='INTERNATIONAL', raise_exception=True):
|
||||
return number
|
||||
|
||||
|
||||
class EventRegistration(models.Model):
|
||||
_name = 'event.registration'
|
||||
@@ -134,13 +125,13 @@ class EventRegistration(models.Model):
|
||||
def _onchange_phone_validation(self):
|
||||
if self.phone:
|
||||
country = self.partner_id.country_id or self.event_id.country_id or self.env.company.country_id
|
||||
self.phone = self._phone_format(self.phone, country)
|
||||
self.phone = self._phone_format(fname='phone', country=country) or self.phone
|
||||
|
||||
@api.onchange('mobile', 'event_id', 'partner_id')
|
||||
def _onchange_mobile_validation(self):
|
||||
if self.mobile:
|
||||
country = self.partner_id.country_id or self.event_id.country_id or self.env.company.country_id
|
||||
self.mobile = self._phone_format(self.mobile, country)
|
||||
self.mobile = self._phone_format(fname='mobile', country=country)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# CRUD
|
||||
@@ -165,7 +156,7 @@ class EventRegistration(models.Model):
|
||||
|
||||
for fname in {'mobile', 'phone'}:
|
||||
if values.get(fname):
|
||||
values[fname] = self._phone_format(values[fname], related_country)
|
||||
values[fname] = self._phone_format(number=values[fname], country=related_country) or values[fname]
|
||||
|
||||
registrations = super(EventRegistration, self).create(vals_list)
|
||||
|
||||
@@ -221,20 +212,6 @@ class EventRegistration(models.Model):
|
||||
""" Checks that all registrations are for `auto-confirm` events. """
|
||||
return all(event.auto_confirm for event in self.event_id)
|
||||
|
||||
def _phone_format(self, number, country):
|
||||
""" Call phone_validation formatting tool function. Returns original
|
||||
number in case formatting cannot be done (no country, wrong info, ...) """
|
||||
if not number or not country:
|
||||
return number
|
||||
new_number = phone_format(
|
||||
number,
|
||||
country.code,
|
||||
country.phone_code,
|
||||
force_format='E164',
|
||||
raise_exception=False,
|
||||
)
|
||||
return new_number if new_number else number
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# ACTIONS / BUSINESS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@@ -208,14 +208,8 @@ class EventRegistration(models.Model):
|
||||
|
||||
# compare phone, taking into account formatting
|
||||
if valid_partner and self.phone and valid_partner.phone:
|
||||
phone_formatted = phone_validation.phone_format(
|
||||
self.phone,
|
||||
valid_partner.country_id.code or None,
|
||||
valid_partner.country_id.phone_code or None,
|
||||
force_format='E164',
|
||||
raise_exception=False
|
||||
)
|
||||
partner_phone_formatted = valid_partner._phone_format(valid_partner.phone)
|
||||
phone_formatted = self._phone_format(fname='phone', country=valid_partner.country_id)
|
||||
partner_phone_formatted = valid_partner._phone_format(fname='phone')
|
||||
if phone_formatted and partner_phone_formatted and phone_formatted != partner_phone_formatted:
|
||||
valid_partner = self.env['res.partner']
|
||||
if (not phone_formatted or not partner_phone_formatted) and self.phone != valid_partner.phone:
|
||||
|
||||
@@ -168,16 +168,16 @@ class TestEventSale(TestEventSaleCommon):
|
||||
)
|
||||
self.assertEqual(
|
||||
set(ticket1_new_reg.mapped('phone')),
|
||||
set(['+32456111111', self.event_customer._phone_format(self.event_customer.phone)])
|
||||
set(['+32456111111', self.event_customer._phone_format(fname='phone')])
|
||||
)
|
||||
self.assertEqual(
|
||||
set(ticket1_new_reg.mapped('mobile')),
|
||||
set(['+32456222222', self.event_customer._phone_format(self.event_customer.mobile)])
|
||||
set(['+32456222222', self.event_customer._phone_format(fname='mobile')])
|
||||
)
|
||||
for field in ['name', 'email']:
|
||||
self.assertEqual(ticket2_new_reg[field], self.event_customer[field])
|
||||
for field in ['phone', 'mobile']:
|
||||
self.assertEqual(ticket2_new_reg[field], self.event_customer._phone_format(self.event_customer[field]))
|
||||
self.assertEqual(ticket2_new_reg[field], self.event_customer._phone_format(fname=field))
|
||||
|
||||
# ADDING MANUAL LINES ON SO
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@@ -43,17 +43,21 @@ class MailingSMSController(http.Controller):
|
||||
return request.redirect('/web')
|
||||
# parse and validate number
|
||||
sms_number = post.get('sms_number', '').strip(' ')
|
||||
sanitize_res = phone_validation.phone_sanitize_numbers([sms_number], request.geoip.country_code, None)[sms_number]
|
||||
tocheck_number = sanitize_res['sanitized'] or sms_number
|
||||
|
||||
trace = check_res['trace'].filtered(lambda r: r.sms_number == tocheck_number)[:1]
|
||||
mailing_list_ids = trace.mass_mailing_id.contact_list_ids
|
||||
sanitized = phone_validation.phone_format(
|
||||
sms_number,
|
||||
request.geoip.country_code,
|
||||
force_format='E164',
|
||||
raise_exception=False,
|
||||
)
|
||||
tocheck_number = sanitized or sms_number
|
||||
|
||||
trace = check_res['trace'].filtered(lambda r: r.sms_number == tocheck_number)[:1] if tocheck_number else False
|
||||
# compute opt-out / blacklist information
|
||||
lists_optout = request.env['mailing.list'].sudo()
|
||||
lists_optin = request.env['mailing.list'].sudo()
|
||||
unsubscribe_error = False
|
||||
if tocheck_number and trace:
|
||||
mailing_list_ids = trace.mass_mailing_id.contact_list_ids
|
||||
if mailing_list_ids:
|
||||
subscriptions = request.env['mailing.contact.subscription'].sudo().search([
|
||||
('list_id', 'in', mailing_list_ids.ids),
|
||||
@@ -74,7 +78,7 @@ class MailingSMSController(http.Controller):
|
||||
elif tocheck_number:
|
||||
unsubscribe_error = _('Number %s not found', tocheck_number)
|
||||
else:
|
||||
unsubscribe_error = sanitize_res['msg']
|
||||
unsubscribe_error = _('Invalid number %s', post.get('sms_number', ''))
|
||||
|
||||
return request.render('mass_mailing_sms.blacklist_number', {
|
||||
'mailing_id': mailing_id,
|
||||
|
||||
@@ -21,9 +21,8 @@ class MassSMSTest(models.TransientModel):
|
||||
self.ensure_one()
|
||||
|
||||
numbers = [number.strip() for number in self.numbers.splitlines()]
|
||||
sanitize_res = phone_validation.phone_sanitize_numbers_w_record(numbers, self.env.user)
|
||||
sanitized_numbers = [info['sanitized'] for info in sanitize_res.values() if info['sanitized']]
|
||||
invalid_numbers = [number for number, info in sanitize_res.items() if info['code']]
|
||||
sanitized_numbers = [self.env.user._phone_format(number=number) for number in numbers]
|
||||
invalid_numbers = [number for sanitized, number in zip(sanitized_numbers, numbers) if not sanitized]
|
||||
|
||||
record = self.env[self.mailing_id.mailing_model_real].search([], limit=1)
|
||||
body = self.mailing_id.body_plaintext
|
||||
|
||||
@@ -11,7 +11,6 @@ from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.addons.payment import utils as payment_utils
|
||||
from odoo.addons.payment_razorpay.const import PAYMENT_STATUS_MAPPING
|
||||
from odoo.addons.payment_razorpay.controllers.main import RazorpayController
|
||||
from odoo.addons.phone_validation.tools.phone_validation import phone_sanitize_numbers
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -52,16 +51,14 @@ class PaymentTransaction(models.Model):
|
||||
return_url_params = {'reference': self.reference}
|
||||
|
||||
phone = self.partner_phone
|
||||
error_message = _("The phone number is missing.")
|
||||
if phone:
|
||||
# sanitize partner phone
|
||||
country_code = self.partner_country_id.code
|
||||
country_phone_code = self.partner_country_id.phone_code
|
||||
phone_info = phone_sanitize_numbers([phone], country_code, country_phone_code)
|
||||
phone = phone_info[self.partner_phone]['sanitized']
|
||||
error_message = phone_info[self.partner_phone]['msg']
|
||||
if not phone:
|
||||
raise ValidationError("Razorpay: " + error_message)
|
||||
try:
|
||||
phone = self._phone_format(number=phone, country=self.partner_country_id, raise_exception=True)
|
||||
except Exception as err:
|
||||
raise ValidationError("Razorpay: " + str(err)) from err
|
||||
else:
|
||||
raise ValidationError("Razorpay: " + _("The phone number is missing."))
|
||||
|
||||
rendering_values = {
|
||||
'key_id': self.provider_id.razorpay_key_id,
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import re
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.addons.phone_validation.tools import phone_validation
|
||||
from odoo.exceptions import AccessError, UserError
|
||||
from odoo.osv import expression
|
||||
|
||||
@@ -127,7 +126,7 @@ class PhoneMixin(models.AbstractModel):
|
||||
number_fields = self._phone_get_number_fields()
|
||||
for record in self:
|
||||
for fname in number_fields:
|
||||
sanitized = record.phone_get_sanitized_number(number_fname=fname)
|
||||
sanitized = record._phone_format(fname=fname)
|
||||
if sanitized:
|
||||
break
|
||||
record.phone_sanitized = sanitized
|
||||
@@ -148,9 +147,9 @@ class PhoneMixin(models.AbstractModel):
|
||||
# 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:
|
||||
mobile_blacklisted = record.phone_sanitized_blacklisted and record.phone_get_sanitized_number(number_fname=number_field) == record.phone_sanitized
|
||||
mobile_blacklisted = record.phone_sanitized_blacklisted and record._phone_format(fname=number_field) == record.phone_sanitized
|
||||
else:
|
||||
phone_blacklisted = record.phone_sanitized_blacklisted and record.phone_get_sanitized_number(number_fname=number_field) == record.phone_sanitized
|
||||
phone_blacklisted = record.phone_sanitized_blacklisted and record._phone_format(fname=number_field) == record.phone_sanitized
|
||||
record.mobile_blacklisted = mobile_blacklisted
|
||||
record.phone_blacklisted = phone_blacklisted
|
||||
|
||||
@@ -196,20 +195,6 @@ class PhoneMixin(models.AbstractModel):
|
||||
res = [self._phone_get_country_field()] if self._phone_get_country_field() else []
|
||||
return res + self._phone_get_number_fields()
|
||||
|
||||
def phone_get_sanitized_numbers(self, number_fname='mobile', force_format='E164'):
|
||||
res = dict.fromkeys(self.ids, False)
|
||||
country_fname = self._phone_get_country_field()
|
||||
for record in self:
|
||||
number = record[number_fname]
|
||||
res[record.id] = phone_validation.phone_sanitize_numbers_w_record([number], record, record_country_fname=country_fname, force_format=force_format)[number]['sanitized']
|
||||
return res
|
||||
|
||||
def phone_get_sanitized_number(self, number_fname='mobile', force_format='E164'):
|
||||
self.ensure_one()
|
||||
country_fname = self._phone_get_country_field()
|
||||
number = self[number_fname]
|
||||
return phone_validation.phone_sanitize_numbers_w_record([number], self, record_country_fname=country_fname, force_format=force_format)[number]['sanitized']
|
||||
|
||||
def _phone_set_blacklisted(self):
|
||||
return self.env['phone.blacklist'].sudo()._add([r.phone_sanitized for r in self])
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, models
|
||||
from odoo import api, exceptions, models
|
||||
from odoo.addons.phone_validation.tools import phone_validation
|
||||
|
||||
|
||||
class BaseModel(models.AbstractModel):
|
||||
@@ -24,3 +25,66 @@ class BaseModel(models.AbstractModel):
|
||||
if 'country_id' in self:
|
||||
return 'country_id'
|
||||
return False
|
||||
|
||||
def _phone_format(self, fname=False, number=False, country=False, force_format='E164', raise_exception=False):
|
||||
""" Format and return number. This number can be found using a field
|
||||
(in which case self should be a singleton recordet), or directly given
|
||||
if the formatting itself is what matter. Field name can be found
|
||||
automatically using '_phone_get_number_fields'
|
||||
|
||||
:param str fname: if number is not given, fname indicates the field to
|
||||
use to find the number; otherwise use '_phone_get_number_fields';
|
||||
:param str number: number to format (in which case fields-based computation
|
||||
is skipped);
|
||||
:param <res.country> country: country used for formatting number; otherwise
|
||||
it is fetched based on record, using '_phone_get_country_field';
|
||||
:param str force_format: stringified version of format globals; should be
|
||||
one of 'E164', 'INTERNATIONAL', 'NATIONAL' or 'RFC3966';
|
||||
:param bool raise_exception: raise if formatting is not possible (notably
|
||||
wrong formatting, invalid country information, ...). Otherwise False
|
||||
is returned;
|
||||
|
||||
:return str: formatted number. If formatting is not possible False is
|
||||
returned.
|
||||
"""
|
||||
if not number:
|
||||
# if no number is given, having a singletong recordset is mandatory to
|
||||
# always have a number as input
|
||||
self.ensure_one()
|
||||
fnames = self._phone_get_number_fields() if not fname else [fname]
|
||||
number = next((self[fname] for fname in fnames if fname in self and self[fname]), False)
|
||||
if not number:
|
||||
return False
|
||||
|
||||
# fetch country info only if self is a singleton recordset allowing to
|
||||
# effectively try to find a country
|
||||
if not country and self:
|
||||
self.ensure_one()
|
||||
country_fname = self._phone_get_country_field()
|
||||
country = self[country_fname] if country_fname and country_fname in self else self.env['res.country']
|
||||
if not country:
|
||||
country = self.env.company.country_id
|
||||
if not country:
|
||||
return number
|
||||
|
||||
return self._phone_format_number(number, country=country, force_format=force_format, raise_exception=raise_exception)
|
||||
|
||||
def _phone_format_number(self, number, country, force_format='E164', raise_exception=False):
|
||||
""" Format and return number according to the asked format. This is
|
||||
mainly a small helper around 'phone_validation.phone_format'."""
|
||||
if not number or not country:
|
||||
return False
|
||||
|
||||
try:
|
||||
number = phone_validation.phone_format(
|
||||
number,
|
||||
country.code,
|
||||
country.phone_code,
|
||||
force_format=force_format,
|
||||
raise_exception=True, # do not get original number returned
|
||||
)
|
||||
except exceptions.UserError:
|
||||
if raise_exception:
|
||||
raise
|
||||
number = False
|
||||
return number
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import logging
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.addons.phone_validation.tools import phone_validation
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -26,21 +25,20 @@ class PhoneBlackList(models.Model):
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, values):
|
||||
# First of all, extract values to ensure emails are really unique (and don't modify values in place)
|
||||
# First of all, extract values to ensure numbers are really unique (and don't modify values in place)
|
||||
to_create = []
|
||||
done = set()
|
||||
for value in values:
|
||||
number = value['number']
|
||||
sanitized_values = phone_validation.phone_sanitize_numbers_w_record([number], self.env.user)[number]
|
||||
sanitized = sanitized_values['sanitized']
|
||||
if not sanitized:
|
||||
raise UserError(sanitized_values['msg'] + _(" Please correct the number and try again."))
|
||||
if sanitized in done:
|
||||
try:
|
||||
sanitized_value = self.env.user._phone_format(number=value['number'], raise_exception=True)
|
||||
except UserError as err:
|
||||
raise UserError(str(err) + _(" Please correct the number and try again.")) from err
|
||||
if sanitized_value in done:
|
||||
continue
|
||||
done.add(sanitized)
|
||||
to_create.append(dict(value, number=sanitized))
|
||||
done.add(sanitized_value)
|
||||
to_create.append(dict(value, number=sanitized_value))
|
||||
|
||||
""" To avoid crash during import due to unique email, return the existing records if any """
|
||||
# To avoid crash during import due to unique number, return the existing records if any
|
||||
bl_entries = {}
|
||||
if to_create:
|
||||
sql = '''SELECT number, id FROM phone_blacklist WHERE number = ANY(%s)'''
|
||||
@@ -54,11 +52,10 @@ class PhoneBlackList(models.Model):
|
||||
|
||||
def write(self, values):
|
||||
if 'number' in values:
|
||||
number = values['number']
|
||||
sanitized_values = phone_validation.phone_sanitize_numbers_w_record([number], self.env.user)[number]
|
||||
sanitized = sanitized_values['sanitized']
|
||||
if not sanitized:
|
||||
raise UserError(sanitized_values['msg'] + _(" Please correct the number and try again."))
|
||||
try:
|
||||
sanitized = self.env.user._phone_format(number=values['number'], raise_exception=True)
|
||||
except UserError as err:
|
||||
raise UserError(str(err) + _(" Please correct the number and try again.")) from err
|
||||
values['number'] = sanitized
|
||||
return super(PhoneBlackList, self).write(values)
|
||||
|
||||
@@ -67,7 +64,7 @@ class PhoneBlackList(models.Model):
|
||||
def sanitize_number(arg):
|
||||
if isinstance(arg, (list, tuple)) and arg[0] == 'number' and isinstance(arg[2], str):
|
||||
number = arg[2]
|
||||
sanitized = phone_validation.phone_sanitize_numbers_w_record([number], self.env.user)[number]['sanitized']
|
||||
sanitized = self.env.user._phone_format(number=number)
|
||||
if sanitized:
|
||||
return (arg[0], arg[1], sanitized)
|
||||
return arg
|
||||
@@ -76,7 +73,7 @@ class PhoneBlackList(models.Model):
|
||||
return super()._search(domain, offset, limit, order, access_rights_uid)
|
||||
|
||||
def add(self, number, message=None):
|
||||
sanitized = phone_validation.phone_sanitize_numbers_w_record([number], self.env.user)[number]['sanitized']
|
||||
sanitized = self.env.user._phone_format(number=number)
|
||||
return self._add([sanitized], message=message)
|
||||
|
||||
def _add(self, numbers, message=None):
|
||||
@@ -101,7 +98,7 @@ class PhoneBlackList(models.Model):
|
||||
return records
|
||||
|
||||
def remove(self, number, message=None):
|
||||
sanitized = phone_validation.phone_sanitize_numbers_w_record([number], self.env.user)[number]['sanitized']
|
||||
sanitized = self.env.user._phone_format(number=number)
|
||||
return self._remove([sanitized], message=message)
|
||||
|
||||
def _remove(self, numbers, message=None):
|
||||
|
||||
@@ -11,29 +11,9 @@ class Partner(models.Model):
|
||||
@api.onchange('phone', 'country_id', 'company_id')
|
||||
def _onchange_phone_validation(self):
|
||||
if self.phone:
|
||||
self.phone = self._phone_format(self.phone, force_format='INTERNATIONAL')
|
||||
self.phone = self._phone_format(fname='phone', force_format='INTERNATIONAL') or self.phone
|
||||
|
||||
@api.onchange('mobile', 'country_id', 'company_id')
|
||||
def _onchange_mobile_validation(self):
|
||||
if self.mobile:
|
||||
self.mobile = self._phone_format(self.mobile, force_format='INTERNATIONAL')
|
||||
|
||||
def _phone_format(self, number, country=None, company=None, force_format='E164'):
|
||||
country = country or self.country_id or self.env.company.country_id
|
||||
if not country or not number:
|
||||
return number
|
||||
return phone_validation.phone_format(
|
||||
number,
|
||||
country.code if country else None,
|
||||
country.phone_code if country else None,
|
||||
force_format=force_format,
|
||||
raise_exception=False
|
||||
)
|
||||
|
||||
def phone_get_sanitized_number(self, number_fname='mobile', force_format='E164'):
|
||||
""" Stand alone version, allowing to use it on partner model without
|
||||
having any dependency on sms module. To cleanup in master (15.3 +)."""
|
||||
self.ensure_one()
|
||||
country_fname = 'country_id'
|
||||
number = self[number_fname]
|
||||
return phone_validation.phone_sanitize_numbers_w_record([number], self, record_country_fname=country_fname, force_format=force_format)[number]['sanitized']
|
||||
self.mobile = self._phone_format(fname='mobile', force_format='INTERNATIONAL') or self.mobile
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import _, models
|
||||
from odoo.addons.phone_validation.tools import phone_validation
|
||||
|
||||
|
||||
class Users(models.Model):
|
||||
@@ -13,13 +12,10 @@ class Users(models.Model):
|
||||
numbers_to_blacklist = {} # numbers to blacklist and the related user
|
||||
if post.get('request_blacklist'):
|
||||
for user in self:
|
||||
sanitized = phone_validation.phone_sanitize_numbers_w_record([user.phone, user.mobile], user)
|
||||
user_phone = sanitized[user.phone]['sanitized']
|
||||
user_mobile = sanitized[user.mobile]['sanitized']
|
||||
if user_phone:
|
||||
numbers_to_blacklist[user_phone] = user
|
||||
if user_mobile:
|
||||
numbers_to_blacklist[user_mobile] = user
|
||||
for fname in self._phone_get_number_fields():
|
||||
number = user._phone_format(fname=fname)
|
||||
if number:
|
||||
numbers_to_blacklist[number] = user
|
||||
|
||||
super(Users, self)._deactivate_portal_user(**post)
|
||||
|
||||
|
||||
@@ -17,7 +17,9 @@ try:
|
||||
try:
|
||||
phone_nbr = phonenumbers.parse(number, region=country_code or None, keep_raw_input=True)
|
||||
except phonenumbers.phonenumberutil.NumberParseException as e:
|
||||
raise UserError(_('Unable to parse %(phone)s: %(error)s', phone=number, error=str(e)))
|
||||
raise UserError(
|
||||
_('Unable to parse %(phone)s: %(error)s', phone=number, error=str(e))
|
||||
) from e
|
||||
|
||||
if not phonenumbers.is_possible_number(phone_nbr):
|
||||
raise UserError(_('Impossible number %s: probably invalid number of digits.', number))
|
||||
@@ -44,11 +46,10 @@ try:
|
||||
"""
|
||||
try:
|
||||
phone_nbr = phone_parse(number, country_code)
|
||||
except (phonenumbers.phonenumberutil.NumberParseException, UserError) as e:
|
||||
except UserError:
|
||||
if raise_exception:
|
||||
raise
|
||||
else:
|
||||
return number
|
||||
return number
|
||||
if force_format == 'E164':
|
||||
phone_fmt = phonenumbers.PhoneNumberFormat.E164
|
||||
elif force_format == 'RFC3966':
|
||||
@@ -73,44 +74,3 @@ except ImportError:
|
||||
)
|
||||
_phonenumbers_lib_warning = True
|
||||
return number
|
||||
|
||||
|
||||
def phone_sanitize_numbers(numbers, country_code, country_phone_code, force_format='E164'):
|
||||
""" Given a list of numbers, return parsezd and sanitized information
|
||||
|
||||
:return dict: {number: {
|
||||
'sanitized': sanitized and formated number or False (if cannot format)
|
||||
'code': 'empty' (number was a void string), 'invalid' (error) or False (sanitize ok)
|
||||
'msg': error message when 'invalid'
|
||||
}}
|
||||
"""
|
||||
if not isinstance(numbers, (list)):
|
||||
raise NotImplementedError()
|
||||
result = dict.fromkeys(numbers, False)
|
||||
for number in numbers:
|
||||
if not number:
|
||||
result[number] = {'sanitized': False, 'code': 'empty', 'msg': False}
|
||||
continue
|
||||
try:
|
||||
stripped = number.strip()
|
||||
sanitized = phone_format(
|
||||
stripped, country_code, country_phone_code,
|
||||
force_format=force_format, raise_exception=True)
|
||||
except Exception as e:
|
||||
result[number] = {'sanitized': False, 'code': 'invalid', 'msg': str(e)}
|
||||
else:
|
||||
result[number] = {'sanitized': sanitized, 'code': False, 'msg': False}
|
||||
return result
|
||||
|
||||
|
||||
def phone_sanitize_numbers_w_record(numbers, record, country=False, record_country_fname='country_id', force_format='E164'):
|
||||
if not isinstance(numbers, (list)):
|
||||
raise NotImplementedError()
|
||||
if not country:
|
||||
if record and record_country_fname and hasattr(record, record_country_fname) and record[record_country_fname]:
|
||||
country = record[record_country_fname]
|
||||
elif record:
|
||||
country = record.env.company.country_id
|
||||
country_code = country.code if country else None
|
||||
country_phone_code = country.phone_code if country else None
|
||||
return phone_sanitize_numbers(numbers, country_code, country_phone_code, force_format=force_format)
|
||||
|
||||
@@ -88,7 +88,7 @@ class MailThread(models.AbstractModel):
|
||||
|
||||
valid_number = False
|
||||
for fname in [f for f in tocheck_fields if f in record]:
|
||||
valid_number = phone_validation.phone_sanitize_numbers_w_record([record[fname]], record)[record[fname]]['sanitized']
|
||||
valid_number = record._phone_format(fname=fname)
|
||||
if valid_number:
|
||||
break
|
||||
|
||||
@@ -104,7 +104,7 @@ class MailThread(models.AbstractModel):
|
||||
partner = self.env['res.partner']
|
||||
for partner in all_partners:
|
||||
for fname in self.env['res.partner']._phone_get_number_fields():
|
||||
valid_number = phone_validation.phone_sanitize_numbers_w_record([partner[fname]], record)[partner[fname]]['sanitized']
|
||||
valid_number = partner._phone_format(fname=fname)
|
||||
if valid_number:
|
||||
break
|
||||
|
||||
@@ -271,20 +271,17 @@ class MailThread(models.AbstractModel):
|
||||
if partner_ids:
|
||||
for partner in self.env['res.partner'].sudo().browse(partner_ids):
|
||||
number = sms_pid_to_number.get(partner.id) or partner.mobile or partner.phone
|
||||
sanitize_res = phone_validation.phone_sanitize_numbers_w_record([number], partner)[number]
|
||||
number = sanitize_res['sanitized'] or number
|
||||
sms_create_vals.append(dict(
|
||||
sms_base_vals,
|
||||
partner_id=partner.id,
|
||||
number=number
|
||||
number=partner._phone_format(number=number) or number,
|
||||
))
|
||||
|
||||
# notify from additional numbers
|
||||
if sms_numbers:
|
||||
sanitized = phone_validation.phone_sanitize_numbers_w_record(sms_numbers, self)
|
||||
tocreate_numbers = [
|
||||
value['sanitized'] or original
|
||||
for original, value in sanitized.items()
|
||||
self._phone_format(number=sms_number) or sms_number
|
||||
for sms_number in sms_numbers
|
||||
]
|
||||
sms_create_vals += [dict(
|
||||
sms_base_vals,
|
||||
|
||||
@@ -93,7 +93,7 @@ class SMSCase(MockSMS):
|
||||
|
||||
def _find_sms_sent(self, partner, number):
|
||||
if number is None and partner:
|
||||
number = partner.phone_get_sanitized_number()
|
||||
number = partner._phone_format()
|
||||
sent_sms = next((sms for sms in self._sms if sms['number'] == number), None)
|
||||
if not sent_sms:
|
||||
raise AssertionError('sent sms not found for %s (number: %s)' % (partner, number))
|
||||
@@ -101,7 +101,7 @@ class SMSCase(MockSMS):
|
||||
|
||||
def _find_sms_sms(self, partner, number, status):
|
||||
if number is None and partner:
|
||||
number = partner.phone_get_sanitized_number()
|
||||
number = partner._phone_format()
|
||||
domain = [('id', 'in', self._new_sms.ids),
|
||||
('partner_id', '=', partner.id),
|
||||
('number', '=', number)]
|
||||
@@ -214,7 +214,7 @@ class SMSCase(MockSMS):
|
||||
number = recipient_info.get('number')
|
||||
state = recipient_info.get('state', 'sent')
|
||||
if number is None and partner:
|
||||
number = partner.phone_get_sanitized_number()
|
||||
number = partner._phone_format()
|
||||
|
||||
notif = notifications.filtered(lambda n: n.res_partner_id == partner and n.sms_number == number and n.notification_status == state)
|
||||
self.assertTrue(notif, 'SMS: not found notification for %s (number: %s, state: %s)' % (partner, number, state))
|
||||
|
||||
@@ -136,8 +136,7 @@ class SendSMS(models.TransientModel):
|
||||
value = composer.recipient_single_number_itf or composer.recipient_single_number
|
||||
if value:
|
||||
records = composer._get_records()
|
||||
sanitized = phone_validation.phone_sanitize_numbers_w_record([value], records)[value]['sanitized']
|
||||
composer.recipient_single_valid = bool(sanitized)
|
||||
composer.recipient_single_valid = bool(records._phone_format(number=value)) if len(records) == 1 else False
|
||||
else:
|
||||
composer.recipient_single_valid = False
|
||||
|
||||
@@ -147,9 +146,8 @@ class SendSMS(models.TransientModel):
|
||||
if composer.numbers:
|
||||
record = composer._get_records() if composer.res_model and composer.res_id else self.env.user
|
||||
numbers = [number.strip() for number in composer.numbers.split(',')]
|
||||
sanitize_res = phone_validation.phone_sanitize_numbers_w_record(numbers, record)
|
||||
sanitized_numbers = [info['sanitized'] for info in sanitize_res.values() if info['sanitized']]
|
||||
invalid_numbers = [number for number, info in sanitize_res.items() if info['code']]
|
||||
sanitized_numbers = [record._phone_format(number=number) for number in numbers]
|
||||
invalid_numbers = [number for sanitized, number in zip(sanitized_numbers, numbers) if not sanitized]
|
||||
if invalid_numbers:
|
||||
raise UserError(_('Following numbers are not correctly encoded: %s', repr(invalid_numbers)))
|
||||
composer.sanitized_numbers = ','.join(sanitized_numbers)
|
||||
|
||||
@@ -573,7 +573,7 @@ class TestSMSComposerMass(SMSCommon):
|
||||
with self.mockSMSGateway():
|
||||
messages = composer._action_send_sms()
|
||||
|
||||
number = self.partners[2].phone_get_sanitized_number()
|
||||
number = self.partners[2]._phone_format()
|
||||
self.assertSMSNotification(
|
||||
[{'partner': test_record_2.customer_id, 'number': number}],
|
||||
"Hello %s ceci est en français." % test_record_2.display_name, messages
|
||||
|
||||
@@ -329,7 +329,7 @@ class TestSMSPostException(SMSCommon, TestSMSRecipients):
|
||||
], self._test_body, messages)
|
||||
|
||||
def test_message_sms_crash_credit_single(self):
|
||||
with self.with_user('employee'), self.mockSMSGateway(nbr_t_error={self.partner_2.phone_get_sanitized_number(): 'credit'}):
|
||||
with self.with_user('employee'), self.mockSMSGateway(nbr_t_error={self.partner_2._phone_format(): 'credit'}):
|
||||
test_record = self.env['mail.test.sms'].browse(self.test_record.id)
|
||||
messages = test_record._message_sms(self._test_body, partner_ids=(self.partner_1 | self.partner_2 | self.partner_3).ids)
|
||||
|
||||
@@ -361,7 +361,7 @@ class TestSMSPostException(SMSCommon, TestSMSRecipients):
|
||||
], self._test_body, messages)
|
||||
|
||||
def test_message_sms_crash_unregistered_single(self):
|
||||
with self.with_user('employee'), self.mockSMSGateway(nbr_t_error={self.partner_2.phone_get_sanitized_number(): 'unregistered'}):
|
||||
with self.with_user('employee'), self.mockSMSGateway(nbr_t_error={self.partner_2._phone_format(): 'unregistered'}):
|
||||
test_record = self.env['mail.test.sms'].browse(self.test_record.id)
|
||||
messages = test_record._message_sms(self._test_body, partner_ids=(self.partner_1 | self.partner_2 | self.partner_3).ids)
|
||||
|
||||
@@ -382,7 +382,7 @@ class TestSMSPostException(SMSCommon, TestSMSRecipients):
|
||||
], self._test_body, messages)
|
||||
|
||||
def test_message_sms_crash_wrong_number_single(self):
|
||||
with self.with_user('employee'), self.mockSMSGateway(nbr_t_error={self.partner_2.phone_get_sanitized_number(): 'wrong_number_format'}):
|
||||
with self.with_user('employee'), self.mockSMSGateway(nbr_t_error={self.partner_2._phone_format(): 'wrong_number_format'}):
|
||||
test_record = self.env['mail.test.sms'].browse(self.test_record.id)
|
||||
messages = test_record._message_sms(self._test_body, partner_ids=(self.partner_1 | self.partner_2 | self.partner_3).ids)
|
||||
|
||||
|
||||
@@ -69,7 +69,9 @@ class WebsiteForm(form.WebsiteForm):
|
||||
# or if both numbers (after formating) are the same. This way we get additional phone
|
||||
# if possible, without modifying an existing one. (see inverse function on model crm.lead)
|
||||
if values_phone and visitor_partner.phone:
|
||||
if visitor_partner._phone_format(visitor_partner.phone) == values_phone:
|
||||
if values_phone == visitor_partner.phone:
|
||||
values['partner_id'] = visitor_partner.id
|
||||
elif (visitor_partner._phone_format('phone') or visitor_partner.phone) == values_phone:
|
||||
values['partner_id'] = visitor_partner.id
|
||||
else:
|
||||
values['partner_id'] = visitor_partner.id
|
||||
|
||||
Reference in New Issue
Block a user