Commit 3c64c86e by Matt Drayer Committed by Jonathan Piacenti

mattdrayer/api-userscleanup: Added docstrings and changed to fail-fast style

parent 2a923bdc
...@@ -126,7 +126,7 @@ class UsersApiTests(TestCase): ...@@ -126,7 +126,7 @@ class UsersApiTests(TestCase):
response = self.do_post(test_uri, data) response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 409) self.assertEqual(response.status_code, 409)
self.assertGreater(response.data['message'], 0) self.assertGreater(response.data['message'], 0)
self.assertEqual(response.data['field_conflict'], 'username') self.assertEqual(response.data['field_conflict'], 'username or email')
def test_user_detail_get(self): def test_user_detail_get(self):
test_uri = '/api/users' test_uri = '/api/users'
...@@ -582,7 +582,7 @@ class UsersApiTests(TestCase): ...@@ -582,7 +582,7 @@ class UsersApiTests(TestCase):
display_name="Chapter 1" display_name="Chapter 1"
) )
user_id = 2342334 user_id = 2342334
course_id = 'asdfa9sd8fasdf' course_id = 'asd/fa/9sd8fasdf'
test_uri = '/api/users/{}/courses/{}'.format(str(user_id), course_id) test_uri = '/api/users/{}/courses/{}'.format(str(user_id), course_id)
position_data = { position_data = {
'position': { 'position': {
...@@ -661,7 +661,7 @@ class UsersApiTests(TestCase): ...@@ -661,7 +661,7 @@ class UsersApiTests(TestCase):
self.assertEqual(response.data['position'], chapter1.id) self.assertEqual(response.data['position'], chapter1.id)
def test_user_courses_detail_get_undefined_user(self): def test_user_courses_detail_get_undefined_user(self):
test_uri = '/api/users/2134234/courses/a8df7asvd98' test_uri = '/api/users/2134234/courses/a8df7/asv/d98'
response = self.do_get(test_uri) response = self.do_get(test_uri)
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
......
...@@ -112,11 +112,48 @@ def _save_content_position(request, user, course_id, course_descriptor, position ...@@ -112,11 +112,48 @@ def _save_content_position(request, user, course_id, course_descriptor, position
class UsersList(SecureAPIView): class UsersList(SecureAPIView):
""" Inherit with SecureAPIView """ """
### The UsersList view allows clients to retrieve/append a list of User entities
- URI: ```/api/users/```
- POST: Provides the ability to append to the User entity set
* email: __required__, The unique email address for the User being created
* username: __required__, The unique username for the User being created
* password: __required__, String which matches enabled formatting constraints
* first_name
* last_name
* is_active, Boolean flag controlling the User's account activation status
* is_staff, Boolean flag controlling the User's administrative access/permissions
* city
* country, Two-character country code
* level_of_education
* year_of_birth, Four-digit integer value
* gender, Single-character value (M/F)
- POST Example:
{
"email" : "honor@edx.org",
"username" : "honor",
"password" : "edx!@#",
"first_name" : "Honor",
"last_name" : "Student",
"is_active" : False,
"is_staff" : False,
"city" : "Boston",
"country" : "US",
"level_of_education" : "hs",
"year_of_birth" : "1996",
"gender" : "F",
}
### Use Cases/Notes:
* GET requests for _all_ users are not currently allowed via the API
* Password formatting policies can be enabled through the "ENFORCE_PASSWORD_POLICY" feature flag
* The first_name and last_name fields are additionally concatenated and stored in the 'name' field of UserProfile
* Values for level_of_education can be found in the LEVEL_OF_EDUCATION_CHOICES enum, located in common/student/models.py
"""
def post(self, request): def post(self, request):
""" """
POST creates a new user in the system POST /api/users/
""" """
response_data = {} response_data = {}
base_uri = _generate_base_uri(request) base_uri = _generate_base_uri(request)
...@@ -139,29 +176,28 @@ class UsersList(SecureAPIView): ...@@ -139,29 +176,28 @@ class UsersList(SecureAPIView):
validate_password_complexity(password) validate_password_complexity(password)
validate_password_dictionary(password) validate_password_dictionary(password)
except ValidationError, err: except ValidationError, err:
status_code = status.HTTP_400_BAD_REQUEST
response_data['message'] = _('Password: ') + '; '.join(err.messages) response_data['message'] = _('Password: ') + '; '.join(err.messages)
return Response(response_data, status=status_code) return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
try: try:
validate_email(email) validate_email(email)
except ValidationError: except ValidationError:
status_code = status.HTTP_400_BAD_REQUEST
response_data['message'] = _('Valid e-mail is required.') response_data['message'] = _('Valid e-mail is required.')
return Response(response_data, status=status_code) return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
try: try:
validate_slug(username) validate_slug(username)
except ValidationError: except ValidationError:
status_code = status.HTTP_400_BAD_REQUEST
response_data['message'] = _('Username should only consist of A-Z and 0-9, with no spaces.') response_data['message'] = _('Username should only consist of A-Z and 0-9, with no spaces.')
return Response(response_data, status=status_code) return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
# Create the User, UserProfile, and UserPreference records # Create the User, UserProfile, and UserPreference records
try: try:
user = User.objects.create(email=email, username=username, is_staff=is_staff) user = User.objects.create(email=email, username=username, is_staff=is_staff)
except IntegrityError: except IntegrityError:
user = None response_data['message'] = "User '%s' already exists" % (username)
else: response_data['field_conflict'] = "username or email"
return Response(response_data, status=status.HTTP_409_CONFLICT)
user.set_password(password) user.set_password(password)
user.first_name = first_name user.first_name = first_name
user.last_name = last_name user.last_name = last_name
...@@ -200,28 +236,66 @@ class UsersList(SecureAPIView): ...@@ -200,28 +236,66 @@ class UsersList(SecureAPIView):
# CDODGE: @TODO: We will have to extend this to look in the CourseEnrollmentAllowed table and # CDODGE: @TODO: We will have to extend this to look in the CourseEnrollmentAllowed table and
# auto-enroll students when they create a new account. Also be sure to remove from # auto-enroll students when they create a new account. Also be sure to remove from
# the CourseEnrollmentAllow table after the auto-registration has taken place # the CourseEnrollmentAllow table after the auto-registration has taken place
if user:
status_code = status.HTTP_201_CREATED
response_data = _serialize_user(response_data, user) response_data = _serialize_user(response_data, user)
response_data['uri'] = '{}/{}'.format(base_uri, str(user.id)) response_data['uri'] = '{}/{}'.format(base_uri, str(user.id))
else: return Response(response_data, status=status.HTTP_201_CREATED)
status_code = status.HTTP_409_CONFLICT
response_data['message'] = "User '%s' already exists" % (username)
response_data['field_conflict'] = "username"
return Response(response_data, status=status_code)
class UsersDetail(SecureAPIView): class UsersDetail(SecureAPIView):
""" Inherit with SecureAPIView """ """
### The UsersDetail view allows clients to interact with a specific User entity
- URI: ```/api/users/{user_id}```
- GET: Returns a JSON representation of the specified User entity
- POST: Provides the ability to modify specific fields for this User entity
* email: __required__, The unique email address for the User being created
* username: __required__, The unique username for the User being created
* password: __required__, String which matches enabled formatting constraints
* first_name
* last_name
* is_active, Boolean flag controlling the User's account activation status
* is_staff, Boolean flag controlling the User's administrative access/permissions
* city
* country, Two-character country code
* level_of_education
* year_of_birth, Four-digit integer value
* gender, Single-character value (M/F)
- POST Example:
{
"email" : "honor@edx.org",
"username" : "honor",
"password" : "edx!@#",
"first_name" : "Honor",
"last_name" : "Student",
"is_active" : False,
"is_staff" : False,
"city" : "Boston",
"country" : "US",
"level_of_education" : "hs",
"year_of_birth" : "1996",
"gender" : "F",
}
### Use Cases/Notes:
* Use the UsersDetail view to obtain the current state for a specific User
* For POSTs, you may include only those parameters you wish to modify, for example:
** Modifying the 'city' without changing the 'level_of_education' field
** New passwords will be subject to both format and history checks, if enabled
* A GET response will additionally include a list of URIs to available sub-resources:
** Related Courses (/api/users/{user_id}/courses)
** Related Groups(/api/users/{user_id}/groups)
"""
def get(self, request, user_id): def get(self, request, user_id):
""" """
GET retrieves an existing user from the system GET /api/users/{user_id}
""" """
response_data = {} response_data = {}
base_uri = _generate_base_uri(request) base_uri = _generate_base_uri(request)
try: try:
existing_user = User.objects.get(id=user_id) existing_user = User.objects.get(id=user_id)
except ObjectDoesNotExist:
return Response(response_data, status=status.HTTP_404_NOT_FOUND)
_serialize_user(response_data, existing_user) _serialize_user(response_data, existing_user)
response_data['uri'] = base_uri response_data['uri'] = base_uri
response_data['resources'] = [] response_data['resources'] = []
...@@ -235,12 +309,10 @@ class UsersDetail(SecureAPIView): ...@@ -235,12 +309,10 @@ class UsersDetail(SecureAPIView):
_serialize_user_profile(response_data, existing_user_profile) _serialize_user_profile(response_data, existing_user_profile)
return Response(response_data, status=status.HTTP_200_OK) return Response(response_data, status=status.HTTP_200_OK)
except ObjectDoesNotExist:
return Response(response_data, status=status.HTTP_404_NOT_FOUND)
def post(self, request, user_id): def post(self, request, user_id):
""" """
POST provides the ability to update information about an existing user POST /api/users/{user_id}
""" """
response_data = {} response_data = {}
base_uri = _generate_base_uri(request) base_uri = _generate_base_uri(request)
...@@ -281,16 +353,14 @@ class UsersDetail(SecureAPIView): ...@@ -281,16 +353,14 @@ class UsersDetail(SecureAPIView):
try: try:
validate_slug(username) validate_slug(username)
except ValidationError: except ValidationError:
status_code = status.HTTP_400_BAD_REQUEST
response_data['message'] = _('Username should only consist of A-Z and 0-9, with no spaces.') response_data['message'] = _('Username should only consist of A-Z and 0-9, with no spaces.')
return Response(response_data, status=status_code) return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
existing_username = User.objects.filter(username=username).filter(~Q(id=user_id)) existing_username = User.objects.filter(username=username).filter(~Q(id=user_id))
if existing_username: if existing_username:
status_code = status.HTTP_409_CONFLICT
response_data['message'] = "User '%s' already exists" % (username) response_data['message'] = "User '%s' already exists" % (username)
response_data['field_conflict'] = "username" response_data['field_conflict'] = "username"
return Response(response_data, status=status_code) return Response(response_data, status=status.HTTP_409_CONFLICT)
existing_user.username = username existing_user.username = username
response_data['username'] = existing_user.username response_data['username'] = existing_user.username
...@@ -308,9 +378,8 @@ class UsersDetail(SecureAPIView): ...@@ -308,9 +378,8 @@ class UsersDetail(SecureAPIView):
except ValidationError, err: except ValidationError, err:
# bad user? tick the rate limiter counter # bad user? tick the rate limiter counter
AUDIT_LOG.warning("API::Bad password in password_reset.") AUDIT_LOG.warning("API::Bad password in password_reset.")
status_code = status.HTTP_400_BAD_REQUEST
response_data['message'] = _('Password: ') + '; '.join(err.messages) response_data['message'] = _('Password: ') + '; '.join(err.messages)
return Response(response_data, status=status_code) return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
# also, check the password reuse policy # also, check the password reuse policy
err_msg = None err_msg = None
if not PasswordHistory.is_allowable_password_reuse(existing_user, password): if not PasswordHistory.is_allowable_password_reuse(existing_user, password):
...@@ -378,10 +447,27 @@ class UsersDetail(SecureAPIView): ...@@ -378,10 +447,27 @@ class UsersDetail(SecureAPIView):
class UsersGroupsList(SecureAPIView): class UsersGroupsList(SecureAPIView):
""" Inherit with SecureAPIView """ """
### The UsersGroupsList view allows clients to interact with the set of Group entities related to the specified User
- URI: ```/api/users/{user_id}/groups/```
- GET: Returns a JSON representation (array) of the set of related Group entities
* type: Set filtering parameter
- POST: Append a Group entity to the set of related Group entities for the specified User
* group_id: __required__, The identifier for the Group being added
- POST Example:
{
"group_id" : 123
}
### Use Cases/Notes:
* Use the UsersGroupsList view to manage Group membership for a specific User
* For example, you could display a list of all of a User's groups in a dashboard or administrative view
* Optionally include the 'type' parameter to retrieve a subset of groups with a matching 'group_type' value
"""
def post(self, request, user_id): def post(self, request, user_id):
""" """
POST creates a new user-group relationship in the system POST /api/users/{user_id}/groups
""" """
response_data = {} response_data = {}
group_id = request.DATA['group_id'] group_id = request.DATA['group_id']
...@@ -391,30 +477,22 @@ class UsersGroupsList(SecureAPIView): ...@@ -391,30 +477,22 @@ class UsersGroupsList(SecureAPIView):
existing_user = User.objects.get(id=user_id) existing_user = User.objects.get(id=user_id)
existing_group = Group.objects.get(id=group_id) existing_group = Group.objects.get(id=group_id)
except ObjectDoesNotExist: except ObjectDoesNotExist:
existing_user = None return Response({}, status=status.HTTP_404_NOT_FOUND)
existing_group = None
if existing_user and existing_group:
try: try:
existing_relationship = existing_user.groups.get(id=existing_group.id) existing_relationship = existing_user.groups.get(id=existing_group.id)
response_data['uri'] = '{}/{}'.format(base_uri, existing_group.id)
response_data['message'] = "Relationship already exists."
return Response(response_data, status=status.HTTP_409_CONFLICT)
except ObjectDoesNotExist: except ObjectDoesNotExist:
existing_relationship = None
if existing_relationship is None:
existing_user.groups.add(existing_group.id) existing_user.groups.add(existing_group.id)
response_data['uri'] = '{}/{}'.format(base_uri, existing_user.id) response_data['uri'] = '{}/{}'.format(base_uri, existing_user.id)
response_data['group_id'] = str(existing_group.id) response_data['group_id'] = str(existing_group.id)
response_data['user_id'] = str(existing_user.id) response_data['user_id'] = str(existing_user.id)
response_status = status.HTTP_201_CREATED return Response(response_data, status=status.HTTP_201_CREATED)
else:
response_data['uri'] = '{}/{}'.format(base_uri, existing_group.id)
response_data['message'] = "Relationship already exists."
response_status = status.HTTP_409_CONFLICT
else:
response_status = status.HTTP_404_NOT_FOUND
return Response(response_data, status=response_status)
def get(self, request, user_id): def get(self, request, user_id):
""" """
GET retrieves the list of groups related to the specified user GET /api/users/{user_id}/groups?type=workgroup
""" """
try: try:
existing_user = User.objects.get(id=user_id) existing_user = User.objects.get(id=user_id)
...@@ -434,35 +512,38 @@ class UsersGroupsList(SecureAPIView): ...@@ -434,35 +512,38 @@ class UsersGroupsList(SecureAPIView):
group_data['id'] = group.id group_data['id'] = group.id
group_data['name'] = group_profile.name group_data['name'] = group_profile.name
response_data['groups'].append(group_data) response_data['groups'].append(group_data)
response_status = status.HTTP_200_OK return Response(response_data, status=status.HTTP_200_OK)
return Response(response_data, status=response_status)
class UsersGroupsDetail(SecureAPIView): class UsersGroupsDetail(SecureAPIView):
""" Inherit with SecureAPIView """ """
### The UsersGroupsDetail view allows clients to interact with a specific User-Group relationship
- URI: ```/api/users/{user_id}/groups/{group_id}```
- GET: Returns a JSON representation of the specified User-Group relationship
- DELETE: Removes an existing User-Group relationship
### Use Cases/Notes:
* Use the UsersGroupsDetail to validate that a User is a member of a specific Group
* Cancelling a User's membership in a Group is as simple as calling DELETE on the URI
"""
def get(self, request, user_id, group_id): def get(self, request, user_id, group_id):
""" """
GET retrieves an existing user-group relationship from the system GET /api/users/{user_id}/groups/{group_id}
""" """
response_data = {} response_data = {}
try: try:
existing_user = User.objects.get(id=user_id, is_active=True) existing_user = User.objects.get(id=user_id, is_active=True)
existing_relationship = existing_user.groups.get(id=group_id) existing_relationship = existing_user.groups.get(id=group_id)
except ObjectDoesNotExist: except ObjectDoesNotExist:
existing_user = None return Response({}, status=status.HTTP_404_NOT_FOUND)
existing_relationship = None
if existing_user and existing_relationship:
response_data['user_id'] = existing_user.id response_data['user_id'] = existing_user.id
response_data['group_id'] = existing_relationship.id response_data['group_id'] = existing_relationship.id
response_data['uri'] = _generate_base_uri(request) response_data['uri'] = _generate_base_uri(request)
response_status = status.HTTP_200_OK return Response(response_data, status=status.HTTP_200_OK)
else:
response_status = status.HTTP_404_NOT_FOUND
return Response(response_data, status=response_status)
def delete(self, request, user_id, group_id): def delete(self, request, user_id, group_id):
""" """
DELETE removes/inactivates/etc. an existing user-group relationship DELETE /api/users/{user_id}/groups/{group_id}
""" """
existing_user = User.objects.get(id=user_id, is_active=True) existing_user = User.objects.get(id=user_id, is_active=True)
existing_user.groups.remove(group_id) existing_user.groups.remove(group_id)
...@@ -471,10 +552,25 @@ class UsersGroupsDetail(SecureAPIView): ...@@ -471,10 +552,25 @@ class UsersGroupsDetail(SecureAPIView):
class UsersCoursesList(SecureAPIView): class UsersCoursesList(SecureAPIView):
""" Inherit with SecureAPIView """ """
### The UsersCoursesList view allows clients to interact with the set of Course entities related to the specified User
- URI: ```/api/users/{user_id}/courses/```
- GET: Returns a JSON representation (array) of the set of related Course entities
- POST: Append a Group entity to the set of related Group entities for the specified User
* course_id: __required__, The identifier (aka, location/key) for the Course being added
- POST Example:
{
"course_id" : "edx/demo/course"
}
### Use Cases/Notes:
* POST to the UsersCoursesList view to create a new Course enrollment for the specified User (aka, Student)
* Perform a GET to generate a list of all active Course enrollments for the specified User
"""
def post(self, request, user_id): def post(self, request, user_id):
""" """
POST creates a new course enrollment for a user POST /api/users/{user_id}/courses/
""" """
store = modulestore() store = modulestore()
response_data = {} response_data = {}
...@@ -484,33 +580,27 @@ class UsersCoursesList(SecureAPIView): ...@@ -484,33 +580,27 @@ class UsersCoursesList(SecureAPIView):
user = User.objects.get(id=user_id) user = User.objects.get(id=user_id)
course_descriptor = store.get_course(course_id) course_descriptor = store.get_course(course_id)
except (ObjectDoesNotExist, ValueError): except (ObjectDoesNotExist, ValueError):
user = None return Response({}, status=status.HTTP_404_NOT_FOUND)
course_descriptor = None
if user and course_descriptor:
base_uri = _generate_base_uri(request) base_uri = _generate_base_uri(request)
course_enrollment = CourseEnrollment.enroll(user, course_id) course_enrollment = CourseEnrollment.enroll(user, course_id)
response_data['uri'] = '{}/{}'.format(base_uri, course_id) response_data['uri'] = '{}/{}'.format(base_uri, course_id)
response_data['id'] = course_id response_data['id'] = course_id
response_data['name'] = course_descriptor.display_name response_data['name'] = course_descriptor.display_name
response_data['is_active'] = course_enrollment.is_active response_data['is_active'] = course_enrollment.is_active
status_code = status.HTTP_201_CREATED return Response(response_data, status=status.HTTP_201_CREATED)
else:
status_code = status.HTTP_404_NOT_FOUND
return Response(response_data, status=status_code)
def get(self, request, user_id): def get(self, request, user_id):
""" """
GET creates the list of enrolled courses for a user GET /api/users/{user_id}/courses/
""" """
store = modulestore() store = modulestore()
response_data = []
base_uri = _generate_base_uri(request) base_uri = _generate_base_uri(request)
try: try:
user = User.objects.get(id=user_id) user = User.objects.get(id=user_id)
except ObjectDoesNotExist: except ObjectDoesNotExist:
user = None return Response({}, status=status.HTTP_404_NOT_FOUND)
if user:
enrollments = CourseEnrollment.enrollments_for_user(user=user) enrollments = CourseEnrollment.enrollments_for_user(user=user)
response_data = []
for enrollment in enrollments: for enrollment in enrollments:
descriptor = store.get_course(enrollment.course_id) descriptor = store.get_course(enrollment.course_id)
course_data = { course_data = {
...@@ -521,16 +611,37 @@ class UsersCoursesList(SecureAPIView): ...@@ -521,16 +611,37 @@ class UsersCoursesList(SecureAPIView):
} }
response_data.append(course_data) response_data.append(course_data)
return Response(response_data, status=status.HTTP_200_OK) return Response(response_data, status=status.HTTP_200_OK)
else:
status_code = status.HTTP_404_NOT_FOUND
return Response(response_data, status=status_code)
class UsersCoursesDetail(SecureAPIView): class UsersCoursesDetail(SecureAPIView):
""" Inherit with SecureAPIView """ """
### The UsersCoursesDetail view allows clients to interact with a specific User-Course relationship (aka, enrollment)
- URI: ```/api/users/{user_id}/courses/{course_id}```
- POST: Stores the last-known location for the Course, for the specified User
* position: The parent-child identifier set for the Content being set as the last-known position, consisting of:
** parent_content_id, normally the Course identifier
** child_content_id, normally the Chapter identifier
- POST Example:
{
"position" : {
"parent_content_id" : "edX/Open_DemoX/edx_demo_course",
"child_content_id" : "i4x://edX/Open_DemoX/chapter/d8a6192ade314473a78242dfeedfbf5b"
}
}
- GET: Returns a JSON representation of the specified User-Course relationship
- DELETE: Inactivates (but does not remove) a Course relationship for the specified User
### Use Cases/Notes:
* Use the UsersCoursesDetail view to manage EXISTING Course enrollments
* Use GET to confirm that a User is actively enrolled in a particular course
* Use DELETE to unenroll a User from a Course (inactivates the enrollment)
* Use POST to record the last-known position within a Course (essentially, a bookmark)
* Note: To create a new Course enrollment, see UsersCoursesList
"""
def post(self, request, user_id, course_id): def post(self, request, user_id, course_id):
""" """
POST creates an ACTIVE course enrollment for the specified user POST /api/users/{user_id}/courses/{course_id}
""" """
store = modulestore() store = modulestore()
base_uri = _generate_base_uri(request) base_uri = _generate_base_uri(request)
...@@ -540,12 +651,9 @@ class UsersCoursesDetail(SecureAPIView): ...@@ -540,12 +651,9 @@ class UsersCoursesDetail(SecureAPIView):
user = User.objects.get(id=user_id) user = User.objects.get(id=user_id)
course_descriptor = store.get_course(course_id) course_descriptor = store.get_course(course_id)
except (ObjectDoesNotExist, ValueError): except (ObjectDoesNotExist, ValueError):
user = None return Response(response_data, status=status.HTTP_404_NOT_FOUND)
course_descriptor = None
if user and course_descriptor:
response_data['user_id'] = user.id response_data['user_id'] = user.id
response_data['course_id'] = course_id response_data['course_id'] = course_id
response_status = status.HTTP_201_CREATED
if request.DATA['position']: if request.DATA['position']:
response_data['position'] = _save_content_position( response_data['position'] = _save_content_position(
request, request,
...@@ -554,13 +662,11 @@ class UsersCoursesDetail(SecureAPIView): ...@@ -554,13 +662,11 @@ class UsersCoursesDetail(SecureAPIView):
course_descriptor, course_descriptor,
request.DATA['position'] request.DATA['position']
) )
else: return Response(response_data, status=status.HTTP_200_OK)
response_status = status.HTTP_404_NOT_FOUND
return Response(response_data, status=response_status)
def get(self, request, user_id, course_id): def get(self, request, user_id, course_id):
""" """
GET identifies an ACTIVE course enrollment for the specified user GET /api/users/{user_id}/courses/{course_id}
""" """
store = modulestore() store = modulestore()
response_data = {} response_data = {}
...@@ -569,9 +675,9 @@ class UsersCoursesDetail(SecureAPIView): ...@@ -569,9 +675,9 @@ class UsersCoursesDetail(SecureAPIView):
user = User.objects.get(id=user_id, is_active=True) user = User.objects.get(id=user_id, is_active=True)
course_descriptor = store.get_course(course_id) course_descriptor = store.get_course(course_id)
except (ObjectDoesNotExist, ValueError): except (ObjectDoesNotExist, ValueError):
user = None return Response({}, status=status.HTTP_404_NOT_FOUND)
course_descriptor = None if not CourseEnrollment.is_enrolled(user, course_id):
if user and CourseEnrollment.is_enrolled(user, course_id): return Response({}, status=status.HTTP_404_NOT_FOUND)
response_data['user_id'] = user.id response_data['user_id'] = user.id
response_data['course_id'] = course_id response_data['course_id'] = course_id
response_data['uri'] = base_uri response_data['uri'] = base_uri
...@@ -583,29 +689,33 @@ class UsersCoursesDetail(SecureAPIView): ...@@ -583,29 +689,33 @@ class UsersCoursesDetail(SecureAPIView):
field_data_cache, field_data_cache,
course_id) course_id)
response_data['position'] = course_content.position response_data['position'] = course_content.position
response_status = status.HTTP_200_OK return Response(response_data, status=status.HTTP_200_OK)
else:
response_status = status.HTTP_404_NOT_FOUND
return Response(response_data, status=response_status)
def delete(self, request, user_id, course_id): def delete(self, request, user_id, course_id):
""" """
DELETE unenrolls the specified user from a course DELETE /api/users/{user_id}/courses/{course_id}
""" """
try: try:
user = User.objects.get(id=user_id, is_active=True) user = User.objects.get(id=user_id, is_active=True)
except ObjectDoesNotExist: except ObjectDoesNotExist:
user = None return Response({}, status=status.HTTP_204_NO_CONTENT)
if user:
CourseEnrollment.unenroll(user, course_id) CourseEnrollment.unenroll(user, course_id)
return Response({}, status=status.HTTP_204_NO_CONTENT) return Response({}, status=status.HTTP_204_NO_CONTENT)
class UsersCoursesGradesDetail(SecureAPIView): class UsersCoursesGradesDetail(SecureAPIView):
""" Inherit with SecureAPIView """ """
### The UsersCoursesGradesDetail view allows clients to interact with the User's gradebook for a particular Course
- URI: ```/api/users/{user_id}/courses/{course_id}/grades```
- GET: Returns a JSON representation of the specified Course gradebook
### Use Cases/Notes:
* Use the UsersCoursesDetail view to manage the User's gradebook for a Course enrollment
* Use GET to retrieve the Course gradebook for the specified User
"""
def get(self, request, user_id, course_id): def get(self, request, user_id, course_id):
""" """
GET returns the current gradebook for the user in a course GET /api/users/{user_id}/courses/{course_id}/grades
""" """
# @TODO: Add authorization check here once we get caller identity # @TODO: Add authorization check here once we get caller identity
...@@ -639,7 +749,22 @@ class UsersCoursesGradesDetail(SecureAPIView): ...@@ -639,7 +749,22 @@ class UsersCoursesGradesDetail(SecureAPIView):
class UsersPreferences(SecureAPIView): class UsersPreferences(SecureAPIView):
""" Inherit with SecureAPIView """ """
### The UsersPreferences view allows clients to interact with the set of Preference key-value pairs related to the specified User
- URI: ```/api/users/{user_id}/preferences/```
- GET: Returns a JSON representation (dict) of the set of User preferences
- POST: Append a new UserPreference key-value pair to the set of preferences for the specified User
* "keyname": __required__, The identifier (aka, key) for the UserPreference being added. Values must be strings
- POST Example:
{
"favorite_color" : "blue"
}
### Use Cases/Notes:
* POSTing a non-string preference value will result in a 400 Bad Request response from the server
* POSTing a duplicate preference will cause the existing preference to be overwritten (effectively a PUT operation)
"""
def get(self, request, user_id): # pylint: disable=W0613 def get(self, request, user_id): # pylint: disable=W0613
""" """
GET returns the preferences for the specified user GET returns the preferences for the specified user
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment