diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index e4bfc9398d..42d7a9fbea 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -503,7 +503,7 @@ class UserProfile(models.Model): allow_certificate = models.BooleanField(default=1) bio = models.CharField(blank=True, null=True, max_length=3000, db_index=False) profile_image_uploaded_at = models.DateTimeField(null=True, blank=True) - phone_regex = RegexValidator(regex=r'^\+?1?\d*$', message="Phone number can only contain numbers.") + phone_regex = RegexValidator(regex=r'^\+?\d*$', message="Phone number can only contain numbers.") phone_number = models.CharField(validators=[phone_regex], blank=True, null=True, max_length=50) @property diff --git a/lms/envs/common.py b/lms/envs/common.py index 020fe37e45..8cc8b00b4a 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -3264,6 +3264,7 @@ ACCOUNT_VISIBILITY_CONFIGURATION["admin_fields"] = ( "requires_parental_consent", "secondary_email", "year_of_birth", + "phone_number", ] ) diff --git a/openedx/core/djangoapps/user_api/accounts/serializers.py b/openedx/core/djangoapps/user_api/accounts/serializers.py index e492bc5196..87eb1436cd 100644 --- a/openedx/core/djangoapps/user_api/accounts/serializers.py +++ b/openedx/core/djangoapps/user_api/accounts/serializers.py @@ -140,6 +140,7 @@ class UserReadOnlySerializer(serializers.Serializer): "account_privacy": self.configuration.get('default_visibility'), "social_links": None, "extended_profile_fields": None, + "phone_number": None, } if user_profile: @@ -167,6 +168,7 @@ class UserReadOnlySerializer(serializers.Serializer): user_profile.social_links.all().order_by('platform'), many=True ).data, "extended_profile": get_extended_profile(user_profile), + "phone_number": user_profile.phone_number, } ) @@ -226,7 +228,8 @@ class AccountLegacyProfileSerializer(serializers.HyperlinkedModelSerializer, Rea model = UserProfile fields = ( "name", "gender", "goals", "year_of_birth", "level_of_education", "country", "social_links", - "mailing_address", "bio", "profile_image", "requires_parental_consent", "language_proficiencies" + "mailing_address", "bio", "profile_image", "requires_parental_consent", "language_proficiencies", + "phone_number" ) # Currently no read-only field, but keep this so view code doesn't need to know. read_only_fields = () @@ -292,6 +295,12 @@ class AccountLegacyProfileSerializer(serializers.HyperlinkedModelSerializer, Rea """ return AccountLegacyProfileSerializer.convert_empty_to_None(value) + def transform_phone_number(self, user_profile, value): # pylint: disable=unused-argument + """ + Converts empty string to None, to indicate not set. Replaced by to_representation in version 3. + """ + return AccountLegacyProfileSerializer.convert_empty_to_None(value) + @staticmethod def convert_empty_to_None(value): """ diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_api.py b/openedx/core/djangoapps/user_api/accounts/tests/test_api.py index cea200d797..2fdc51205c 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_api.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/test_api.py @@ -536,6 +536,7 @@ class AccountSettingsOnCreationTest(CreateAccountMixin, TestCase): 'secondary_email': None, 'time_zone': None, 'course_certificates': None, + 'phone_number': None, }) def test_normalize_password(self): diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_views.py b/openedx/core/djangoapps/user_api/accounts/tests/test_views.py index 414ca9e097..bc52cc585c 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_views.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/test_views.py @@ -115,6 +115,7 @@ class UserAPITestCase(APITestCase): legacy_profile.bio = TEST_BIO_VALUE legacy_profile.profile_image_uploaded_at = TEST_PROFILE_IMAGE_UPLOADED_AT legacy_profile.language_proficiencies.create(code=TEST_LANGUAGE_PROFICIENCY_CODE) + legacy_profile.phone_number = "+18005555555" legacy_profile.save() def _verify_profile_image_data(self, data, has_profile_image): @@ -265,7 +266,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase): Verify that all account fields are returned (even those that are not shareable). """ data = response.data - self.assertEqual(22, len(data)) + self.assertEqual(23, len(data)) # public fields (3) expected_account_privacy = ( @@ -474,7 +475,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase): with self.assertNumQueries(queries): response = self.send_get(self.client) data = response.data - self.assertEqual(22, len(data)) + self.assertEqual(23, len(data)) self.assertEqual(self.user.username, data["username"]) self.assertEqual(self.user.first_name + " " + self.user.last_name, data["name"]) for empty_field in ("year_of_birth", "level_of_education", "mailing_address", "bio"): @@ -885,7 +886,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase): response = self.send_get(client) if has_full_access: data = response.data - self.assertEqual(22, len(data)) + self.assertEqual(23, len(data)) self.assertEqual(self.user.username, data["username"]) self.assertEqual(self.user.first_name + " " + self.user.last_name, data["name"]) self.assertEqual(self.user.email, data["email"]) diff --git a/openedx/core/djangoapps/user_api/accounts/views.py b/openedx/core/djangoapps/user_api/accounts/views.py index 2d8eb44b0a..643c3ae22c 100644 --- a/openedx/core/djangoapps/user_api/accounts/views.py +++ b/openedx/core/djangoapps/user_api/accounts/views.py @@ -227,6 +227,9 @@ class AccountViewSet(ViewSet): * accomplishments_shared: Signals whether badges are enabled on the platform and should be fetched. + * phone_number: The phone number for the user. String of numbers with + an optional `+` sign at the start. + For all text fields, plain text instead of HTML is supported. The data is stored exactly as specified. Clients must HTML escape rendered values to avoid script injections.