MA-2050: add optional user profile details in GET thread & comment
This commit is contained in:
@@ -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])
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user