From 3b5f9e3eaaa32bbaf8e221de168f8474936f4af3 Mon Sep 17 00:00:00 2001
From: Paul Marillonnet <pmarillonnet@entrouvert.com>
Date: Thu, 5 Aug 2021 11:58:56 +0200
Subject: [PATCH] attribute_kinds: add french phone number type (#45422)

---
 src/authentic2/attribute_kinds.py |  29 +++++++
 tests/test_api.py                 |  29 +++++++
 tests/test_attribute_kinds.py     | 133 ++++++++++++++++++++++++++++++
 3 files changed, 191 insertions(+)

diff --git a/src/authentic2/attribute_kinds.py b/src/authentic2/attribute_kinds.py
index f61de21a1..87ea2a247 100644
--- a/src/authentic2/attribute_kinds.py
+++ b/src/authentic2/attribute_kinds.py
@@ -151,6 +151,10 @@ validate_phone_number = RegexValidator(
     r'^\+?\d{,20}$', message=_('Phone number can start with a + and must contain only digits.')
 )
 
+french_validate_phone_number = RegexValidator(
+    r'^[0][0-9]{9}$', message=_('A french phone number must start with a zero then another nine digits.')
+)
+
 
 def clean_number(number):
     cleaned_number = re.sub(r'[-.\s/]', '', number)
@@ -172,6 +176,21 @@ class PhoneNumberField(forms.CharField):
         return value
 
 
+class FrenchPhoneNumberField(forms.CharField):
+    widget = widgets.PhoneNumberInput
+
+    def __init__(self, *args, **kwargs):
+        kwargs['validators'] = (french_validate_phone_number,)
+        kwargs.setdefault('help_text', _('ex.: 0699999999, 01 23 45 67 89, 09.87.65.43.21'))
+        super().__init__(*args, **kwargs)
+
+    def clean(self, value):
+        if value not in self.empty_values:
+            value = clean_number(value)
+        value = super().clean(value)
+        return value
+
+
 class PhoneNumberDRFField(serializers.CharField):
     default_validators = [validate_phone_number]
 
@@ -179,6 +198,10 @@ class PhoneNumberDRFField(serializers.CharField):
         return clean_number(super().to_internal_value(data))
 
 
+class FrenchPhoneNumberDRFField(PhoneNumberDRFField):
+    default_validators = [french_validate_phone_number]
+
+
 validate_fr_postcode = RegexValidator(r'^\d{5}$', message=_('The value must be a valid french postcode'))
 
 
@@ -340,6 +363,12 @@ DEFAULT_ATTRIBUTE_KINDS = [
         'field_class': PhoneNumberField,
         'rest_framework_field_class': PhoneNumberDRFField,
     },
+    {
+        'label': _('french phone number'),
+        'name': 'fr_phone_number',
+        'field_class': FrenchPhoneNumberField,
+        'rest_framework_field_class': FrenchPhoneNumberDRFField,
+    },
     {
         'label': _('profile image'),
         'name': 'profile_image',
diff --git a/tests/test_api.py b/tests/test_api.py
index 82440a275..b25dd92d3 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -2211,6 +2211,35 @@ def test_phone_normalization_nok(settings, app, admin):
     app.post_json('/api/users/', headers=headers, params=payload, status=400)
 
 
+def test_fr_phone_normalization_ok(settings, app, admin):
+    headers = basic_authorization_header(admin)
+    Attribute.objects.create(name='phone', label='phone', kind='fr_phone_number')
+    payload = {
+        'username': 'janedoe',
+        'phone': ' 04-99 98.56/43',
+        'first_name': 'Jane',
+        'last_name': 'Doe',
+    }
+    resp = app.post_json('/api/users/', headers=headers, params=payload, status=201)
+    assert resp.json['phone'] == '0499985643'
+    assert User.objects.get(username='janedoe').attributes.phone == '0499985643'
+
+
+def test_fr_phone_normalization_nok(settings, app, admin):
+    headers = basic_authorization_header(admin)
+    Attribute.objects.create(name='phone', label='phone', kind='fr_phone_number')
+    payload = {
+        'username': 'janedoe',
+        'phone': '+33499985643',
+        'first_name': 'Jane',
+        'last_name': 'Doe',
+    }
+    app.post_json('/api/users/', headers=headers, params=payload, status=400)
+
+    payload['phone'] = '1#2'
+    app.post_json('/api/users/', headers=headers, params=payload, status=400)
+
+
 def test_api_users_create_user_delete(app, settings, admin):
     email = 'foo@example.net'
     user1 = User.objects.create(username='foo', email=email)
diff --git a/tests/test_attribute_kinds.py b/tests/test_attribute_kinds.py
index 4890afb72..1f1d79acf 100644
--- a/tests/test_attribute_kinds.py
+++ b/tests/test_attribute_kinds.py
@@ -267,6 +267,139 @@ def test_phone_number(db, app, admin, mailoutbox, settings):
     qs.delete()
 
 
+def test_french_phone_number(db, app, admin, mailoutbox, settings):
+    settings.A2_EMAILS_ADDRESS_RATELIMIT = None
+
+    def register_john():
+        response = app.get('/accounts/register/')
+        form = response.form
+        form.set('email', 'john.doe@example.com')
+        response = form.submit().follow()
+        assert 'john.doe@example.com' in response
+        return get_link_from_mail(mailoutbox[-1])
+
+    Attribute.objects.create(
+        name='phone_number', label='phone', kind='fr_phone_number', asked_on_registration=True
+    )
+    qs = User.objects.filter(first_name='John')
+
+    url = register_john()
+    response = app.get(url)
+    form = response.form
+    form.set('first_name', 'John')
+    form.set('last_name', 'Doe')
+    form.set('phone_number', 'abc')
+    form.set('password1', '12345abcdA')
+    form.set('password2', '12345abcdA')
+    response = form.submit()
+    assert response.pyquery.find('.form-field-error #id_phone_number')
+
+    form = response.form
+    form.set('phone_number', '1234512345' * 10)
+    form.set('password1', '12345abcdA')
+    form.set('password2', '12345abcdA')
+    response = form.submit()
+    assert response.pyquery.find('.form-field-error #id_phone_number')
+
+    form = response.form
+    form.set('phone_number', '12345')
+    form.set('password1', '12345abcdA')
+    form.set('password2', '12345abcdA')
+    response = form.submit()
+    assert response.pyquery.find('.form-field-error #id_phone_number')
+
+    form = response.form
+    form.set('first_name', 'John')
+    form.set('last_name', 'Doe')
+    form.set('phone_number', '+12345')
+    form.set('password1', '12345abcdA')
+    form.set('password2', '12345abcdA')
+    response = form.submit()
+    assert response.pyquery.find('.form-field-error #id_phone_number')
+
+    form = response.form
+    form.set('phone_number', ' +  1.2-3  4 5 ')
+    form.set('password1', '12345abcdA')
+    form.set('password2', '12345abcdA')
+    response = form.submit()
+    assert response.pyquery.find('.form-field-error #id_phone_number')
+
+    form = response.form
+    form.set('phone_number', '1234567890')
+    form.set('password1', '12345abcdA')
+    form.set('password2', '12345abcdA')
+    response = form.submit()
+    assert response.pyquery.find('.form-field-error #id_phone_number')
+
+    form = response.form
+    form.set('phone_number', '')
+    form.set('password1', '12345abcdA')
+    form.set('password2', '12345abcdA')
+    response = form.submit().follow()
+    assert qs.get().attributes.phone_number == ''
+    qs.delete()
+
+    url = register_john()
+    response = app.get(url)
+    form = response.form
+    form.set('first_name', 'John')
+    form.set('last_name', 'Doe')
+    form.set('phone_number', '0123456789')
+    form.set('password1', '12345abcdA')
+    form.set('password2', '12345abcdA')
+    response = form.submit().follow()
+    assert qs.get().attributes.phone_number == '0123456789'
+    qs.delete()
+
+    url = register_john()
+    response = app.get(url)
+    form = response.form
+    form.set('first_name', 'John')
+    form.set('last_name', 'Doe')
+    form.set('phone_number', '01 23 45 67 89')
+    form.set('password1', '12345abcdA')
+    form.set('password2', '12345abcdA')
+    response = form.submit().follow()
+    assert qs.get().attributes.phone_number == '0123456789'
+    qs.delete()
+
+    url = register_john()
+    response = app.get(url)
+    form = response.form
+    form.set('first_name', 'John')
+    form.set('last_name', 'Doe')
+    form.set('phone_number', '01-23-45-67-89')
+    form.set('password1', '12345abcdA')
+    form.set('password2', '12345abcdA')
+    response = form.submit().follow()
+    assert qs.get().attributes.phone_number == '0123456789'
+    qs.delete()
+
+    url = register_john()
+    response = app.get(url)
+    form = response.form
+    form.set('first_name', 'John')
+    form.set('last_name', 'Doe')
+    form.set('phone_number', '06.99.99.99.99')
+    form.set('password1', '12345abcdA')
+    form.set('password2', '12345abcdA')
+    response = form.submit().follow()
+    assert qs.get().attributes.phone_number == '0699999999'
+    qs.delete()
+
+    url = register_john()
+    response = app.get(url)
+    form = response.form
+    form.set('first_name', 'John')
+    form.set('last_name', 'Doe')
+    form.set('phone_number', '0699999999')
+    form.set('password1', '12345abcdA')
+    form.set('password2', '12345abcdA')
+    response = form.submit().follow()
+    assert qs.get().attributes.phone_number == '0699999999'
+    qs.delete()
+
+
 def test_birthdate(db, app, admin, mailoutbox, freezer):
     def register_john():
         response = app.get('/accounts/register/')
-- 
2.33.0

