diff --git a/lms/envs/common.py b/lms/envs/common.py index d77ddfcac51a0ce8824deb6e4d3e5e657b6d5995..83d30b3b8a85d771464226c1f8cb02285ea2aa20 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -3872,6 +3872,7 @@ ACCOUNT_VISIBILITY_CONFIGURATION["custom_shareable_fields"] = ( ACCOUNT_VISIBILITY_CONFIGURATION["admin_fields"] = ( ACCOUNT_VISIBILITY_CONFIGURATION["custom_shareable_fields"] + [ "email", + "id", "extended_profile", "gender", "state", diff --git a/openedx/core/djangoapps/user_api/accounts/serializers.py b/openedx/core/djangoapps/user_api/accounts/serializers.py index 213cda2c76bd13938bcc4cf1e43dc33a423657d0..695b48306e70c6e9dbf4abe204a5090f4efe007f 100644 --- a/openedx/core/djangoapps/user_api/accounts/serializers.py +++ b/openedx/core/djangoapps/user_api/accounts/serializers.py @@ -43,6 +43,7 @@ class PhoneNumberSerializer(serializers.BaseSerializer): # lint-amnesty, pylint """ Class to serialize phone number into a digit only representation """ + def to_internal_value(self, data): """Remove all non numeric characters in phone number""" return re.sub("[^0-9]", "", data) or None @@ -94,6 +95,7 @@ class UserReadOnlySerializer(serializers.Serializer): # lint-amnesty, pylint: d """ Class that serializes the User model and UserProfile model together. """ + def __init__(self, *args, **kwargs): # Don't pass the 'configuration' arg up to the superclass self.configuration = kwargs.pop('configuration', None) @@ -129,6 +131,7 @@ class UserReadOnlySerializer(serializers.Serializer): # lint-amnesty, pylint: d reverse('accounts_api', kwargs={'username': user.username}) ), "email": user.email, + "id": user.id, # For backwards compatibility: Tables created after the upgrade to Django 1.8 will save microseconds. # However, mobile apps are not expecting microsecond in the serialized value. If we set it to zero the # DRF JSONEncoder will not include it in the serialized 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 ec1f1cd1d6e5a5323f54bc31ed7c24ae91ea7414..d7a6c6860b850a43eceeea7b54513eb3da62aa07 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_api.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/test_api.py @@ -203,7 +203,7 @@ class TestAccountApi(UserSettingsEventTestMixin, EmailTemplateTagMixin, CreateAc account_settings = get_account_settings(self.default_request)[0] assert account_settings['social_links'] == \ - sorted((original_social_links + extra_social_links), key=(lambda s: s['platform'])) + sorted((original_social_links + extra_social_links), key=(lambda s: s['platform'])) def test_replace_social_links(self): original_facebook_link = dict(platform="facebook", social_link="https://www.facebook.com/myself") @@ -505,12 +505,14 @@ class AccountSettingsOnCreationTest(CreateAccountMixin, TestCase): USERNAME = u'frank-underwood' PASSWORD = u'ṕáśśẃőŕd' EMAIL = u'frank+underwood@example.com' + ID = -1 def test_create_account(self): # Create a new account, which should have empty account settings by default. self.create_account(self.USERNAME, self.PASSWORD, self.EMAIL) # Retrieve the account settings user = User.objects.get(username=self.USERNAME) + self.ID = user.id request = RequestFactory().get("/api/user/v1/accounts/") request.user = user account_settings = get_account_settings(request)[0] @@ -522,32 +524,36 @@ class AccountSettingsOnCreationTest(CreateAccountMixin, TestCase): del account_settings['last_login'] # Expect all the values to be defaulted - assert account_settings ==\ - {'username': self.USERNAME, - 'email': self.EMAIL, - 'name': self.USERNAME, - 'gender': None, 'goals': u'', - 'is_active': False, - 'level_of_education': None, - 'mailing_address': u'', - 'year_of_birth': None, - 'country': None, - 'state': None, - 'social_links': [], - 'bio': None, - 'profile_image': {'has_image': False, - 'image_url_full': request.build_absolute_uri('/static/default_50.png'), - 'image_url_small': request.build_absolute_uri('/static/default_10.png')}, - 'requires_parental_consent': True, - 'language_proficiencies': [], - 'account_privacy': PRIVATE_VISIBILITY, - 'accomplishments_shared': False, - 'extended_profile': [], - 'secondary_email': None, - 'secondary_email_enabled': None, - 'time_zone': None, - 'course_certificates': None, - 'phone_number': None} + assert account_settings == { + 'username': self.USERNAME, + 'email': self.EMAIL, + 'id': self.ID, + 'name': self.USERNAME, + 'gender': None, 'goals': u'', + 'is_active': False, + 'level_of_education': None, + 'mailing_address': u'', + 'year_of_birth': None, + 'country': None, + 'state': None, + 'social_links': [], + 'bio': None, + 'profile_image': { + 'has_image': False, + 'image_url_full': request.build_absolute_uri('/static/default_50.png'), + 'image_url_small': request.build_absolute_uri('/static/default_10.png') + }, + 'requires_parental_consent': True, + 'language_proficiencies': [], + 'account_privacy': PRIVATE_VISIBILITY, + 'accomplishments_shared': False, + 'extended_profile': [], + 'secondary_email': None, + 'secondary_email_enabled': 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 35f0f5fded2da740df3e4f8bc6fc09dae17ba781..c3e2fa9dd87141366766f5e512e094da36ab18d6 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_views.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/test_views.py @@ -262,7 +262,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase): Verify that all account fields are returned (even those that are not shareable). """ data = response.data - assert 26 == len(data) + assert 27 == len(data) # public fields (3) expected_account_privacy = ( @@ -285,8 +285,9 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase): assert data['accomplishments_shared'] is not None assert ((self.user.first_name + ' ') + self.user.last_name) == data['name'] - # additional admin fields (10) + # additional admin fields (11) assert self.user.email == data['email'] + assert self.user.id == data['id'] assert data['extended_profile'] is not None assert 'MA' == data['state'] assert 'f' == data['gender'] @@ -493,7 +494,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase): with self.assertNumQueries(queries): response = self.send_get(self.client) data = response.data - assert 26 == len(data) + assert 27 == len(data) assert self.user.username == data['username'] assert ((self.user.first_name + ' ') + self.user.last_name) == data['name'] for empty_field in ("year_of_birth", "level_of_education", "mailing_address", "bio"): @@ -503,6 +504,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase): assert 'm' == data['gender'] assert 'Learn a lot' == data['goals'] assert self.user.email == data['email'] + assert self.user.id == data['id'] assert data['date_joined'] is not None assert data['last_login'] is not None assert self.user.is_active == data['is_active'] @@ -846,7 +848,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase): client = self.login_client("client", "user") response = self.send_patch(client, {"language_proficiencies": patch_value}, expected_status=400) assert response.data['field_errors']['language_proficiencies']['developer_message'] == \ - f"Value '{patch_value}' is not valid for field 'language_proficiencies': {expected_error_message}" + f"Value '{patch_value}' is not valid for field 'language_proficiencies': {expected_error_message}" @mock.patch('openedx.core.djangoapps.user_api.accounts.serializers.AccountUserSerializer.save') def test_patch_serializer_save_fails(self, serializer_save): @@ -869,8 +871,8 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase): self.client.login(username=self.user.username, password=TEST_PASSWORD) response = self.send_get(self.client) assert response.data['profile_image'] ==\ - {'has_image': False, - 'image_url_full': 'http://testserver/static/default_50.png', + {'has_image': False, + 'image_url_full': 'http://testserver/static/default_50.png', 'image_url_small': 'http://testserver/static/default_10.png'} @ddt.data( @@ -892,10 +894,11 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase): response = self.send_get(client) if has_full_access: data = response.data - assert 26 == len(data) + assert 27 == len(data) assert self.user.username == data['username'] assert ((self.user.first_name + ' ') + self.user.last_name) == data['name'] assert self.user.email == data['email'] + assert self.user.id == data['id'] assert year_of_birth == data['year_of_birth'] for empty_field in ("country", "level_of_education", "mailing_address", "bio", "state",): assert data[empty_field] is None diff --git a/openedx/core/djangoapps/user_api/accounts/views.py b/openedx/core/djangoapps/user_api/accounts/views.py index 651e2e51214cc23ac03f918c2f20aacdc4a32fcb..00182aef7acffa38b0a3cc1854a62178abecfdde 100644 --- a/openedx/core/djangoapps/user_api/accounts/views.py +++ b/openedx/core/djangoapps/user_api/accounts/views.py @@ -164,6 +164,7 @@ class AccountViewSet(ViewSet): "OK" response is returned. The response contains the following values. + * id: numerical lms user id in db * bio: null or textual representation of user biographical information ("about me"). * country: An ISO 3166 country code or null.