MA-2050: add optional user profile details in GET thread & comment

This commit is contained in:
wajeeha-khalid
2016-05-11 18:21:44 +05:00
parent c681c56768
commit f19af52054
15 changed files with 615 additions and 100 deletions

View File

@@ -41,7 +41,7 @@ visible_fields = _visible_fields
@intercept_errors(UserAPIInternalError, ignore_errors=[UserAPIRequestError])
def get_account_settings(request, username=None, configuration=None, view=None):
def get_account_settings(request, usernames=None, configuration=None, view=None):
"""Returns account information for a user serialized as JSON.
Note:
@@ -52,8 +52,8 @@ def get_account_settings(request, username=None, configuration=None, view=None):
request (Request): The request object with account information about the requesting user.
Only the user with username `username` or users with "is_staff" privileges can get full
account information. Other users will get the account fields that the user has elected to share.
username (str): Optional username for the desired account information. If not specified,
`request.user.username` is assumed.
usernames (list): Optional list of usernames for the desired account information. If not
specified, `request.user.username` is assumed.
configuration (dict): an optional configuration specifying which fields in the account
can be shared, and the default visibility settings. If not present, the setting value with
key ACCOUNT_VISIBILITY_CONFIGURATION is used.
@@ -62,7 +62,7 @@ def get_account_settings(request, username=None, configuration=None, view=None):
"shared", only shared account information will be returned, regardless of `request.user`.
Returns:
A dict containing account fields.
A list of users account details.
Raises:
UserNotFound: no user with username `username` exists (or `request.user.username` if
@@ -70,27 +70,27 @@ def get_account_settings(request, username=None, configuration=None, view=None):
UserAPIInternalError: the operation failed due to an unexpected error.
"""
requesting_user = request.user
usernames = usernames or [requesting_user.username]
if username is None:
username = requesting_user.username
try:
existing_user = User.objects.select_related('profile').get(username=username)
except ObjectDoesNotExist:
requested_users = User.objects.select_related('profile').filter(username__in=usernames)
if not requested_users:
raise UserNotFound()
has_full_access = requesting_user.username == username or requesting_user.is_staff
if has_full_access and view != 'shared':
admin_fields = settings.ACCOUNT_VISIBILITY_CONFIGURATION.get('admin_fields')
else:
admin_fields = None
serialized_users = []
for user in requested_users:
has_full_access = requesting_user.is_staff or requesting_user.username == user.username
if has_full_access and view != 'shared':
admin_fields = settings.ACCOUNT_VISIBILITY_CONFIGURATION.get('admin_fields')
else:
admin_fields = None
serialized_users.append(UserReadOnlySerializer(
user,
configuration=configuration,
custom_fields=admin_fields,
context={'request': request}
).data)
return UserReadOnlySerializer(
existing_user,
configuration=configuration,
custom_fields=admin_fields,
context={'request': request}
).data
return serialized_users
@intercept_errors(UserAPIInternalError, ignore_errors=[UserAPIRequestError])

View File

@@ -57,13 +57,13 @@ class TestAccountApi(UserSettingsEventTestMixin, TestCase):
def test_get_username_provided(self):
"""Test the difference in behavior when a username is supplied to get_account_settings."""
account_settings = get_account_settings(self.default_request)
account_settings = get_account_settings(self.default_request)[0]
self.assertEqual(self.user.username, account_settings["username"])
account_settings = get_account_settings(self.default_request, username=self.user.username)
account_settings = get_account_settings(self.default_request, usernames=[self.user.username])[0]
self.assertEqual(self.user.username, account_settings["username"])
account_settings = get_account_settings(self.default_request, username=self.different_user.username)
account_settings = get_account_settings(self.default_request, usernames=[self.different_user.username])[0]
self.assertEqual(self.different_user.username, account_settings["username"])
def test_get_configuration_provided(self):
@@ -81,20 +81,20 @@ class TestAccountApi(UserSettingsEventTestMixin, TestCase):
}
# With default configuration settings, email is not shared with other (non-staff) users.
account_settings = get_account_settings(self.default_request, self.different_user.username)
account_settings = get_account_settings(self.default_request, [self.different_user.username])[0]
self.assertNotIn("email", account_settings)
account_settings = get_account_settings(
self.default_request,
self.different_user.username,
[self.different_user.username],
configuration=config,
)
)[0]
self.assertEqual(self.different_user.email, account_settings["email"])
def test_get_user_not_found(self):
"""Test that UserNotFound is thrown if there is no user with username."""
with self.assertRaises(UserNotFound):
get_account_settings(self.default_request, username="does_not_exist")
get_account_settings(self.default_request, usernames=["does_not_exist"])
self.user.username = "does_not_exist"
request = self.request_factory.get("/api/user/v1/accounts/")
@@ -105,11 +105,11 @@ class TestAccountApi(UserSettingsEventTestMixin, TestCase):
def test_update_username_provided(self):
"""Test the difference in behavior when a username is supplied to update_account_settings."""
update_account_settings(self.user, {"name": "Mickey Mouse"})
account_settings = get_account_settings(self.default_request)
account_settings = get_account_settings(self.default_request)[0]
self.assertEqual("Mickey Mouse", account_settings["name"])
update_account_settings(self.user, {"name": "Donald Duck"}, username=self.user.username)
account_settings = get_account_settings(self.default_request)
account_settings = get_account_settings(self.default_request)[0]
self.assertEqual("Donald Duck", account_settings["name"])
with self.assertRaises(UserNotAuthorized):
@@ -189,7 +189,7 @@ class TestAccountApi(UserSettingsEventTestMixin, TestCase):
self.assertIn("Error thrown from do_email_change_request", context_manager.exception.developer_message)
# Verify that the name change happened, even though the attempt to send the email failed.
account_settings = get_account_settings(self.default_request)
account_settings = get_account_settings(self.default_request)[0]
self.assertEqual("Mickey Mouse", account_settings["name"])
@patch('openedx.core.djangoapps.user_api.accounts.serializers.AccountUserSerializer.save')
@@ -255,7 +255,7 @@ class AccountSettingsOnCreationTest(TestCase):
user = User.objects.get(username=self.USERNAME)
request = RequestFactory().get("/api/user/v1/accounts/")
request.user = user
account_settings = get_account_settings(request)
account_settings = get_account_settings(request)[0]
# Expect a date joined field but remove it to simplify the following comparison
self.assertIsNotNone(account_settings['date_joined'])
@@ -341,14 +341,14 @@ class AccountCreationActivationAndPasswordChangeTest(TestCase):
request = RequestFactory().get("/api/user/v1/accounts/")
request.user = user
account = get_account_settings(request)
account = get_account_settings(request)[0]
self.assertEqual(self.USERNAME, account["username"])
self.assertEqual(self.EMAIL, account["email"])
self.assertFalse(account["is_active"])
# Activate the account and verify that it is now active
activate_account(activation_key)
account = get_account_settings(request)
account = get_account_settings(request)[0]
self.assertTrue(account['is_active'])
def test_create_account_duplicate_username(self):

View File

@@ -10,6 +10,7 @@ from rest_framework import permissions
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSet
from openedx.core.lib.api.authentication import (
SessionAuthenticationAllowInactiveUser,
@@ -20,7 +21,7 @@ from .api import get_account_settings, update_account_settings
from ..errors import UserNotFound, UserNotAuthorized, AccountUpdateError, AccountValidationError
class AccountView(APIView):
class AccountViewSet(ViewSet):
"""
**Use Cases**
@@ -29,6 +30,7 @@ class AccountView(APIView):
**Example Requests**
GET /api/user/v1/accounts?usernames={username1,username2}[?view=shared]
GET /api/user/v1/accounts/{username}/[?view=shared]
PATCH /api/user/v1/accounts/{username}/{"key":"value"} "application/merge-patch+json"
@@ -146,19 +148,34 @@ class AccountView(APIView):
permission_classes = (permissions.IsAuthenticated,)
parser_classes = (MergePatchParser,)
def get(self, request, username):
def list(self, request):
"""
GET /api/user/v1/accounts/{username}/
GET /api/user/v1/accounts?username={username1,username2}
"""
usernames = request.GET.get('username')
try:
if usernames:
usernames = usernames.strip(',').split(',')
account_settings = get_account_settings(
request, username, view=request.query_params.get('view'))
request, usernames, view=request.query_params.get('view'))
except UserNotFound:
return Response(status=status.HTTP_403_FORBIDDEN if request.user.is_staff else status.HTTP_404_NOT_FOUND)
return Response(account_settings)
def patch(self, request, username):
def retrieve(self, request, username):
"""
GET /api/user/v1/accounts/{username}/
"""
try:
account_settings = get_account_settings(
request, [username], view=request.query_params.get('view'))
except UserNotFound:
return Response(status=status.HTTP_403_FORBIDDEN if request.user.is_staff else status.HTTP_404_NOT_FOUND)
return Response(account_settings[0])
def partial_update(self, request, username):
"""
PATCH /api/user/v1/accounts/{username}/
@@ -169,7 +186,7 @@ class AccountView(APIView):
try:
with transaction.atomic():
update_account_settings(request.user, request.data, username=username)
account_settings = get_account_settings(request, username)
account_settings = get_account_settings(request, [username])[0]
except UserNotAuthorized:
return Response(status=status.HTTP_403_FORBIDDEN if request.user.is_staff else status.HTTP_404_NOT_FOUND)
except UserNotFound:

View File

@@ -1366,7 +1366,7 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
user = User.objects.get(username=self.USERNAME)
request = RequestFactory().get('/url')
request.user = user
account_settings = get_account_settings(request)
account_settings = get_account_settings(request)[0]
self.assertEqual(self.USERNAME, account_settings["username"])
self.assertEqual(self.EMAIL, account_settings["email"])
@@ -1406,7 +1406,7 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
user = User.objects.get(username=self.USERNAME)
request = RequestFactory().get('/url')
request.user = user
account_settings = get_account_settings(request)
account_settings = get_account_settings(request)[0]
self.assertEqual(account_settings["level_of_education"], self.EDUCATION)
self.assertEqual(account_settings["mailing_address"], self.ADDRESS)
@@ -1440,7 +1440,7 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
user = User.objects.get(username=self.USERNAME)
request = RequestFactory().get('/url')
request.user = user
account_settings = get_account_settings(request)
account_settings = get_account_settings(request)[0]
self.assertEqual(self.USERNAME, account_settings["username"])
self.assertEqual(self.EMAIL, account_settings["email"])

View File

@@ -6,16 +6,23 @@ from django.conf import settings
from django.conf.urls import patterns, url
from ..profile_images.views import ProfileImageView
from .accounts.views import AccountView
from .accounts.views import AccountViewSet
from .preferences.views import PreferencesView, PreferencesDetailView
ACCOUNT_LIST = AccountViewSet.as_view({
'get': 'list',
})
ACCOUNT_DETAIL = AccountViewSet.as_view({
'get': 'retrieve',
'patch': 'partial_update',
})
urlpatterns = patterns(
'',
url(
r'^v1/accounts/{}$'.format(settings.USERNAME_PATTERN),
AccountView.as_view(),
name="accounts_api"
),
url(r'^v1/accounts/{}$'.format(settings.USERNAME_PATTERN), ACCOUNT_DETAIL, name='accounts_api'),
url(r'^v1/accounts$', ACCOUNT_LIST, name='accounts_detail_api'),
url(
r'^v1/accounts/{}/image$'.format(settings.USERNAME_PATTERN),
ProfileImageView.as_view(),