Skip to content

Commit c370028

Browse files
authored
feat: modify PhoneNumberSerializer regex to allow plus symbol at the … (#35117)
* feat: modify PhoneNumberSerializer regex to allow plus symbol at the beginning * chore: update PhonenumberSerializer docstring
1 parent 58de096 commit c370028

4 files changed

Lines changed: 78 additions & 24 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Generated by Django 4.2.14 on 2024-07-16 22:21
2+
3+
import django.core.validators
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('student', '0045_auto_20230808_0944'),
11+
]
12+
13+
operations = [
14+
migrations.AlterField(
15+
model_name='userprofile',
16+
name='phone_number',
17+
field=models.CharField(blank=True, max_length=50, null=True, validators=[django.core.validators.RegexValidator(message="Phone number must start with '+' (optional) followed by digits (0-9) only.", regex='^\\+?1?\\d*$')]),
18+
),
19+
]

common/djangoapps/student/models/user.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,10 @@ class Meta:
552552
goals = models.TextField(blank=True, null=True)
553553
bio = models.CharField(blank=True, null=True, max_length=3000, db_index=False)
554554
profile_image_uploaded_at = models.DateTimeField(null=True, blank=True)
555-
phone_regex = RegexValidator(regex=r'^\+?1?\d*$', message="Phone number can only contain numbers.")
555+
phone_regex = RegexValidator(
556+
regex=r'^\+?1?\d*$',
557+
message="Phone number must start with '+' (optional) followed by digits (0-9) only.",
558+
)
556559
phone_number = models.CharField(validators=[phone_regex], blank=True, null=True, max_length=50)
557560

558561
@property

common/djangoapps/student/tests/test_user_profile_properties.py

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -107,23 +107,43 @@ def test_invalidate_cache_user_profile_country_updated(self):
107107
assert cache.get(cache_key) != country
108108
assert cache.get(cache_key) is None
109109

110-
def test_phone_number_can_only_contain_digits(self):
111-
# validating the profile will fail, because there are letters
112-
# in the phone number
113-
self.profile.phone_number = 'abc'
114-
pytest.raises(ValidationError, self.profile.full_clean)
115-
# fail if mixed digits/letters
116-
self.profile.phone_number = '1234gb'
117-
pytest.raises(ValidationError, self.profile.full_clean)
118-
# fail if whitespace
119-
self.profile.phone_number = ' 123'
120-
pytest.raises(ValidationError, self.profile.full_clean)
121-
# fail with special characters
122-
self.profile.phone_number = '123!@#$%^&*'
123-
pytest.raises(ValidationError, self.profile.full_clean)
124-
# valid phone number
125-
self.profile.phone_number = '123456789'
126-
try:
127-
self.profile.full_clean()
128-
except ValidationError:
129-
self.fail("This phone number should be valid.")
110+
def test_valid_phone_numbers(self):
111+
"""
112+
Test that valid phone numbers are accepted.
113+
114+
Expected behavior:
115+
- The phone number '+123456789' should be considered valid.
116+
- The phone number '123456789' (without '+') should also be valid.
117+
118+
This test verifies that valid phone numbers are accepted by the profile model validation.
119+
"""
120+
valid_numbers = ['+123456789', '123456789']
121+
122+
for number in valid_numbers:
123+
self.profile.phone_number = number
124+
125+
try:
126+
self.profile.full_clean()
127+
except ValidationError:
128+
self.fail("This phone number should be valid.")
129+
130+
def test_invalid_phone_numbers(self):
131+
"""
132+
Test that invalid phone numbers raise ValidationError.
133+
134+
Expected behavior:
135+
- Phone numbers with letters, mixed digits/letters, whitespace,
136+
or special characters should raise a ValidationError.
137+
138+
This test verifies that invalid phone numbers are rejected by the profile model validation.
139+
"""
140+
invalid_phone_numbers = [
141+
'abc', # Letters in the phone number
142+
'1234gb', # Mixed digits and letters
143+
' 123', # Whitespace
144+
'123!@#$%^&*' # Special characters
145+
]
146+
147+
for number in invalid_phone_numbers:
148+
self.profile.phone_number = number
149+
pytest.raises(ValidationError, self.profile.full_clean)

openedx/core/djangoapps/user_api/accounts/serializers.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,24 @@
4747

4848
class PhoneNumberSerializer(serializers.BaseSerializer): # lint-amnesty, pylint: disable=abstract-method
4949
"""
50-
Class to serialize phone number into a digit only representation
50+
Class to serialize phone number into a digit only representation.
51+
52+
This serializer removes all non-numeric characters from the phone number,
53+
allowing '+' only at the beginning of the number.
5154
"""
5255

5356
def to_internal_value(self, data):
54-
"""Remove all non numeric characters in phone number"""
55-
return re.sub("[^0-9]", "", data) or None
57+
"""
58+
Remove all non-numeric characters from the phone number.
59+
60+
Args:
61+
data (str): The input phone number string.
62+
63+
Returns:
64+
str or None: The cleaned phone number string containing only digits,
65+
with an optional '+' at the beginning.
66+
"""
67+
return re.sub(r'(?!^)\+|[^0-9+]', "", data) or None
5668

5769

5870
class LanguageProficiencySerializer(serializers.ModelSerializer):

0 commit comments

Comments
 (0)