forms: provide stricter PhoneField validation (#73345)

This commit is contained in:
Paul Marillonnet 2023-01-16 08:54:08 +01:00
parent 5616322fa3
commit 043c7abf6d
7 changed files with 56 additions and 52 deletions

View File

@ -244,5 +244,7 @@ class PhoneField(MultiValueField):
pn = phonenumbers.parse(''.join(data_list), dial)
except phonenumbers.NumberParseException:
raise ValidationError(_('Invalid phone number.'))
if not phonenumbers.is_valid_number(pn):
raise ValidationError(_('Invalid phone number.'))
return phonenumbers.format_number(pn, phonenumbers.PhoneNumberFormat.E164)
return ''

View File

@ -234,11 +234,11 @@ def test_phone_number(db, app, admin, mailoutbox, settings):
form = response.form
form.set('phone_number_0', '33')
form.set('phone_number_1', '12345')
form.set('phone_number_1', '123456789')
form.set('password1', '12345abcdA')
form.set('password2', '12345abcdA')
response = form.submit().follow()
assert qs.get().attributes.phone_number == '+3312345'
assert qs.get().attributes.phone_number == '+33123456789'
qs.delete()
url = register_john()
@ -247,11 +247,11 @@ def test_phone_number(db, app, admin, mailoutbox, settings):
form.set('first_name', 'John')
form.set('last_name', 'Doe')
form.set('phone_number_0', '32')
form.set('phone_number_1', '12345')
form.set('phone_number_1', '081000000')
form.set('password1', '12345abcdA')
form.set('password2', '12345abcdA')
response = form.submit().follow()
assert qs.get().attributes.phone_number == '+3212345'
assert qs.get().attributes.phone_number == '+3281000000'
qs.delete()
url = register_john()
@ -273,11 +273,11 @@ def test_phone_number(db, app, admin, mailoutbox, settings):
form.set('first_name', 'John')
form.set('last_name', 'Doe')
form.set('phone_number_0', '33')
form.set('phone_number_1', '1 2 3 4 5')
form.set('phone_number_1', '1 234 5678 9')
form.set('password1', '12345abcdA')
form.set('password2', '12345abcdA')
response = form.submit().follow()
assert qs.get().attributes.phone_number == '+3312345'
assert qs.get().attributes.phone_number == '+33123456789'
qs.delete()

View File

@ -191,8 +191,8 @@ def test_bom_character(profile, user_csv_importer_factory):
def test_run(profile, user_csv_importer_factory):
assert User.objects.count() == 0
content = '''email key,first_name,last_name,phone update
tnoel@entrouvert.com,Thomas,Noël,1234
fpeters@entrouvert.com,Frédéric,Péters,5678
tnoel@entrouvert.com,Thomas,Noël,0123456789
fpeters@entrouvert.com,Frédéric,Péters,+3281005678
x,x,x,x'''
importer = user_csv_importer_factory(content)
@ -226,7 +226,7 @@ x,x,x,x'''
assert thomas.last_name == 'Noël'
assert thomas.attributes.last_name == 'Noël'
# phonenumbers' e.164 representation from a settings.DEFAULT_COUNTRY_CODE dial:
assert thomas.attributes.phone == '+331234'
assert thomas.attributes.phone == '+33123456789'
assert thomas.password
fpeters = User.objects.get(email='fpeters@entrouvert.com')
@ -237,14 +237,14 @@ x,x,x,x'''
assert fpeters.last_name == 'Péters'
assert fpeters.attributes.last_name == 'Péters'
# phonenumbers' e.164 representation from a settings.DEFAULT_COUNTRY_CODE dial:
assert fpeters.attributes.phone == '+335678'
assert fpeters.attributes.phone == '+3281005678'
def test_simulate(profile, user_csv_importer_factory):
assert User.objects.count() == 0
content = '''email key,first_name,last_name,phone update
tnoel@entrouvert.com,Thomas,Noël,1234
fpeters@entrouvert.com,Frédéric,Péters,5678
tnoel@entrouvert.com,Thomas,Noël,0123456789
fpeters@entrouvert.com,Frédéric,Péters,+3281005678
x,x,x,x'''
importer = user_csv_importer_factory(content)
@ -275,11 +275,11 @@ x,x,x,x'''
def test_create_unique_error(profile, user_csv_importer_factory):
content = '''email key verified,first_name,last_name,phone unique
tnoel@entrouvert.com,Thomas,Noël,1234'''
tnoel@entrouvert.com,Thomas,Noël,0123456789'''
importer = user_csv_importer_factory(content)
user = User.objects.create(ou=get_default_ou())
user.attributes.phone = '+331234'
user.attributes.phone = '+33123456789'
assert importer.run()
@ -296,11 +296,11 @@ tnoel@entrouvert.com,Thomas,Noël,1234'''
def test_create_unique_in_ou(profile, user_csv_importer_factory):
content = '''email key verified,first_name,last_name,phone unique
tnoel@entrouvert.com,Thomas,Noël,1234'''
tnoel@entrouvert.com,Thomas,Noël,0123456789'''
importer = user_csv_importer_factory(content)
user = User.objects.create()
user.attributes.phone = '+331234'
user.attributes.phone = '+33123456789'
assert importer.run()
@ -316,11 +316,11 @@ tnoel@entrouvert.com,Thomas,Noël,1234'''
def test_create_unique_globally_error(profile, user_csv_importer_factory):
content = '''email key verified,first_name,last_name,phone globally-unique
tnoel@entrouvert.com,Thomas,Noël,1234'''
tnoel@entrouvert.com,Thomas,Noël,0123456789'''
importer = user_csv_importer_factory(content)
user = User.objects.create()
user.attributes.phone = '+331234'
user.attributes.phone = '+33123456789'
assert importer.run()
@ -336,8 +336,8 @@ tnoel@entrouvert.com,Thomas,Noël,1234'''
def test_create_key_self_reference_error(profile, user_csv_importer_factory):
content = '''email key,first_name,last_name,phone
tnoel@entrouvert.com,Thomas,Noël,1234
tnoel@entrouvert.com,Frédéric,Péters,1234'''
tnoel@entrouvert.com,Thomas,Noël,0606060606
tnoel@entrouvert.com,Frédéric,Péters,+3281123456'''
importer = user_csv_importer_factory(content)
assert importer.run()
@ -354,11 +354,11 @@ tnoel@entrouvert.com,Frédéric,Péters,1234'''
def test_update_unique_error(profile, user_csv_importer_factory):
content = '''email key verified,first_name,last_name,phone unique update
tnoel@entrouvert.com,Thomas,Noël,1234'''
tnoel@entrouvert.com,Thomas,Noël,0123456789'''
importer = user_csv_importer_factory(content)
user = User.objects.create(ou=get_default_ou())
user.attributes.phone = '+331234'
user.attributes.phone = '+33123456789'
user = User.objects.create(email='tnoel@entrouvert.com', ou=get_default_ou())
@ -376,11 +376,11 @@ tnoel@entrouvert.com,Thomas,Noël,1234'''
def test_update_unique_globally_error(profile, user_csv_importer_factory):
content = '''email key verified,first_name,last_name,phone globally-unique update
tnoel@entrouvert.com,Thomas,Noël,1234'''
tnoel@entrouvert.com,Thomas,Noël,0123456789'''
importer = user_csv_importer_factory(content)
user = User.objects.create()
user.attributes.phone = '+331234'
user.attributes.phone = '+33123456789'
User.objects.create(email='tnoel@entrouvert.com', ou=get_default_ou())
@ -398,11 +398,11 @@ tnoel@entrouvert.com,Thomas,Noël,1234'''
def test_update_unique_globally(profile, user_csv_importer_factory):
content = '''email key verified no-update,first_name no-update,last_name no-update,phone unique update
tnoel@entrouvert.com,Thomas,Noël,1234'''
tnoel@entrouvert.com,Thomas,Noël,0123456789'''
importer = user_csv_importer_factory(content)
user = User.objects.create()
user.attributes.phone = '+331234'
user.attributes.phone = '+33123456789'
thomas = User.objects.create(email='tnoel@entrouvert.com', ou=get_default_ou())
@ -420,14 +420,14 @@ tnoel@entrouvert.com,Thomas,Noël,1234'''
thomas.refresh_from_db()
assert not thomas.first_name
assert not thomas.last_name
assert thomas.attributes.phone == '+331234'
assert thomas.attributes.phone == '+33123456789'
def test_external_id(profile, user_csv_importer_factory):
assert User.objects.count() == 0
content = '''_source_name,_source_id,email,first_name,last_name,phone
app1,1,tnoel@entrouvert.com,Thomas,Noël,1234
app1,2,tnoel@entrouvert.com,Thomas,Noël,1234
app1,1,tnoel@entrouvert.com,Thomas,Noël,0606060606
app1,2,tnoel@entrouvert.com,Thomas,Noël,0606060606
'''
importer = user_csv_importer_factory(content)
@ -451,7 +451,7 @@ app1,2,tnoel@entrouvert.com,Thomas,Noël,1234
assert thomas.attributes.first_name == 'Thomas'
assert thomas.last_name == 'Noël'
assert thomas.attributes.last_name == 'Noël'
assert thomas.attributes.phone == '+331234'
assert thomas.attributes.phone == '+33606060606'
importer = user_csv_importer_factory(content)
assert importer.run(), importer.errors
@ -464,7 +464,7 @@ def test_user_roles_csv(profile, user_csv_importer_factory):
role = Role.objects.create(name=role_name, slug=role_slug, ou=get_default_ou())
role2 = Role.objects.create(name='test2', ou=get_default_ou())
base_header = 'email key,first_name,last_name,phone,'
base_user = 'tnoel@entrouvert.com,Thomas,Noël,1234,'
base_user = 'tnoel@entrouvert.com,Thomas,Noël,0123456789,'
content_name_add = '\n'.join((base_header + '_role_name', base_user + role_name))
importer = user_csv_importer_factory(content_name_add)

View File

@ -28,9 +28,9 @@ def test_phonenumber_field(settings):
positive = [
{'input': ['33', '01 01 01 01 01'], 'output': '+33101010101'},
# undialable numbers are still parsed and usable as identifiers
{'input': ['33', '01 01 01'], 'output': '+33010101'},
{'input': ['33', '01 01 01 010101'], 'output': '+3310101010101'},
{'input': ['33', '0101010101'], 'output': '+33101010101'},
{'input': ['33', '0666666666'], 'output': '+33666666666'},
{'input': ['32', '081 00 0000'], 'output': '+3281000000'},
]
# positive
for value in positive:
@ -43,6 +43,8 @@ def test_phonenumber_field(settings):
['33', '+01 01 01 01 01'],
['33', ' + 01/01.01-01.01'],
['33', '+01/01.01-01.01'],
['33', '01 01 01'],
['33', '01 01 01 010101'],
]:
with pytest.raises(ValidationError):
field.clean(value)

View File

@ -31,8 +31,8 @@ def profile(transactional_db):
def test_user_import(transactional_db, profile):
content = '''email key verified,first_name,last_name,phone no-create
tnoel@entrouvert.com,Thomas,Noël,1234
fpeters@entrouvert.com,Frédéric,Péters,5678
tnoel@entrouvert.com,Thomas,Noël,0123456789
fpeters@entrouvert.com,Frédéric,Péters,+3281123456
x,x,x,x'''
fd = io.BytesIO(content.encode('utf-8'))

View File

@ -46,7 +46,7 @@ def test_account_edit_view(app, simple_user):
)
resp = old_resp = app.get(url, status=200)
resp.form['phone_1'] = '1234'
resp.form['phone_1'] = '123456789'
assert resp.form['phone_1'].attrs['type'] == 'text'
resp.form['title'] = 'Mrs'
resp.form['agreement'] = False
@ -56,7 +56,7 @@ def test_account_edit_view(app, simple_user):
resp = resp.form.submit()
# verify that missing next_url in POST is ok
assert resp['Location'].endswith(reverse('account_management'))
assert phone.get_value(simple_user) == '+331234'
assert phone.get_value(simple_user) == '+33123456789'
assert title.get_value(simple_user) == 'Mrs'
assert agreement.get_value(simple_user) is False
assert language.get_value(simple_user) == 'fr'
@ -70,7 +70,7 @@ def test_account_edit_view(app, simple_user):
('First name', 'Jôhn'),
('Last name', 'Dôe'),
('Email address', 'user@example.net'),
('Phone', '+331234'),
('Phone', '+33123456789'),
('Title', 'Mrs'),
('Language', 'French'),
]

View File

@ -500,9 +500,9 @@ def test_user_import(encoding, transactional_db, app, admin, ou1, admin_ou1):
Upload(
'users.csv',
'''email key verified,first_name,last_name,phone
tnoel@entrouvert.com,Thomas,Noël,1234
fpeters@entrouvert.com,Frédéric,Péters,+325678
john.doe@entrouvert.com,John,Doe,9101112
tnoel@entrouvert.com,Thomas,Noël,0123456789
fpeters@entrouvert.com,Frédéric,Péters,+3281123456
john.doe@entrouvert.com,John,Doe,0910111213
x,x,x,x'''.encode(
encoding
),
@ -582,7 +582,7 @@ x,x,x,x'''.encode(
email='tnoel@entrouvert.com',
first_name='Thomas',
last_name='Noël',
attribute_values__content='+331234',
attribute_values__content='+33123456789',
).count()
== 1
)
@ -591,7 +591,7 @@ x,x,x,x'''.encode(
email='fpeters@entrouvert.com',
first_name='Frédéric',
last_name='Péters',
attribute_values__content='+325678',
attribute_values__content='+3281123456',
).count()
== 1
)
@ -600,7 +600,7 @@ x,x,x,x'''.encode(
email='john.doe@entrouvert.com',
first_name='John',
last_name='Doe',
attribute_values__content='+339101112',
attribute_values__content='+33910111213',
).count()
== 1
)
@ -623,9 +623,9 @@ def test_user_import_legacy_encoding(transactional_db, app, admin, ou1, admin_ou
Upload(
'users.csv',
'''email key verified,first_name,last_name,phone
tnoel@entrouvert.com,Thomas,Noël,1234
fpeters@entrouvert.com,Frédéric,Péters,5678
john.doe@entrouvert.com,John,Doe,9101112
tnoel@entrouvert.com,Thomas,Noël,0123456789
fpeters@entrouvert.com,Frédéric,Péters,+3281123456
john.doe@entrouvert.com,John,Doe,0910111213
x,x,x,x'''.encode(),
'application/octet-stream',
),
@ -720,7 +720,7 @@ def test_user_import_attributes(transactional_db, app, admin):
csv_lines = [
"email key verified,first_name,last_name,more,title,bike,saintsday,birthdate,zip,phone",
"elliot@universalpictures.com,Elliott,Thomas,petit,Mr,True,2019-7-20,1972-05-26,75014,1234",
"elliot@universalpictures.com,Elliott,Thomas,petit,Mr,True,2019-7-20,1972-05-26,75014,0123456789",
"et@universalpictures.com,ET,the Extra-Terrestrial,long,??,False,1/2/3/4,0002-2-22,42,home",
]
response = import_csv('\n'.join(csv_lines), app)
@ -739,9 +739,9 @@ def test_user_import_attributes(transactional_db, app, admin):
assert elliot.attributes.values['saintsday'].content == '2019-07-20'
assert elliot.attributes.values['birthdate'].content == '1972-05-26'
assert elliot.attributes.values['zip'].content == '75014'
assert elliot.attributes.values['phone'].content == '+331234'
assert elliot.attributes.values['phone'].content == '+33123456789'
csv_lines[2] = "et@universalpictures.com,ET,the Extra-Terrestrial,,,,,,42000,+888 5678"
csv_lines[2] = "et@universalpictures.com,ET,the Extra-Terrestrial,,,,,,42000,+3281123456"
response = import_csv('\n'.join(csv_lines), app)
assert '0 rows have errors' in response.text
@ -753,7 +753,7 @@ def test_user_import_attributes(transactional_db, app, admin):
assert 'saintsday' not in et.attributes.values
assert 'birthdate' not in et.attributes.values
assert et.attributes.values['zip'].content == '42000'
assert et.attributes.values['phone'].content == '+8885678'
assert et.attributes.values['phone'].content == '+3281123456'
def test_detail_view(app, admin, simple_user):