Consolidate PUT and POST on the RESTful Layer. Change URLs for API Test cleanup. Adding a course details URL to the enrollment API. Change student to user Change to v1, remove feature flag from API URLs Updating student to user in tests Re-ordering redirect urls to be evaluated last. Adding pagination and testing. Adding Django REST settings for pagination. Revert "Re-ordering redirect urls to be evaluated last." This reverts commit 4c9502daa383e49b46f8abec5456c271e5e24ccb. Re-ordering redirect urls to be evaluated last. Conflicts: common/djangoapps/enrollment/urls.py Revert "Adding Django REST settings for pagination." This reverts commit 9f8a54c41f34caa24818c88f1e75ac59f6ce5259. Conflicts: common/djangoapps/enrollment/urls.py Revert "Adding pagination and testing." This reverts commit 0b2d46262abb78f5ad170700205e7fd28b6af942. Additional testing, logging, and error messages.
201 lines
8.4 KiB
Python
201 lines
8.4 KiB
Python
"""
|
|
The Enrollment API Views should be simple, lean HTTP endpoints for API access. This should
|
|
consist primarily of authentication, request validation, and serialization.
|
|
|
|
"""
|
|
from rest_framework import status
|
|
from rest_framework.authentication import OAuth2Authentication
|
|
from rest_framework import permissions
|
|
from rest_framework.response import Response
|
|
from rest_framework.throttling import UserRateThrottle
|
|
from rest_framework.views import APIView
|
|
from enrollment import api
|
|
from enrollment.errors import CourseNotFoundError, CourseEnrollmentError, CourseModeNotFoundError
|
|
from util.authentication import SessionAuthenticationAllowInactiveUser
|
|
|
|
|
|
class EnrollmentUserThrottle(UserRateThrottle):
|
|
"""Limit the number of requests users can make to the enrollment API."""
|
|
# TODO Limit significantly after performance testing. # pylint: disable=fixme
|
|
rate = '50/second'
|
|
|
|
|
|
class EnrollmentView(APIView):
|
|
""" Enrollment API View for creating, updating, and viewing course enrollments. """
|
|
|
|
authentication_classes = OAuth2Authentication, SessionAuthenticationAllowInactiveUser
|
|
permission_classes = permissions.IsAuthenticated,
|
|
throttle_classes = EnrollmentUserThrottle,
|
|
|
|
def get(self, request, course_id=None, user=None):
|
|
"""Create, read, or update enrollment information for a user.
|
|
|
|
HTTP Endpoint for all CRUD operations for a user course enrollment. Allows creation, reading, and
|
|
updates of the current enrollment for a particular course.
|
|
|
|
Args:
|
|
request (Request): To get current course enrollment information, a GET request will return
|
|
information for the current user and the specified course.
|
|
course_id (str): URI element specifying the course location. Enrollment information will be
|
|
returned, created, or updated for this particular course.
|
|
user (str): The user username associated with this enrollment request.
|
|
|
|
Return:
|
|
A JSON serialized representation of the course enrollment.
|
|
|
|
"""
|
|
if request.user.username != user:
|
|
# Return a 404 instead of a 403 (Unauthorized). If one user is looking up
|
|
# other users, do not let them deduce the existence of an enrollment.
|
|
return Response(status=status.HTTP_404_NOT_FOUND)
|
|
try:
|
|
return Response(api.get_enrollment(user, course_id))
|
|
except CourseEnrollmentError:
|
|
return Response(
|
|
status=status.HTTP_400_BAD_REQUEST,
|
|
data={
|
|
"message": (
|
|
u"An error occurred while retrieving enrollments for user "
|
|
u"'{user}' in course '{course_id}'"
|
|
).format(user=user, course_id=course_id)
|
|
}
|
|
)
|
|
|
|
|
|
class EnrollmentCourseDetailView(APIView):
|
|
""" Enrollment API View for viewing course enrollment details. """
|
|
|
|
authentication_classes = []
|
|
permission_classes = []
|
|
throttle_classes = EnrollmentUserThrottle,
|
|
|
|
def get(self, request, course_id=None):
|
|
"""Read enrollment information for a particular course.
|
|
|
|
HTTP Endpoint for retrieving course level enrollment information.
|
|
|
|
Args:
|
|
request (Request): To get current course enrollment information, a GET request will return
|
|
information for the specified course.
|
|
course_id (str): URI element specifying the course location. Enrollment information will be
|
|
returned.
|
|
|
|
Return:
|
|
A JSON serialized representation of the course enrollment details.
|
|
|
|
"""
|
|
try:
|
|
return Response(api.get_course_enrollment_details(course_id))
|
|
except CourseNotFoundError:
|
|
return Response(
|
|
status=status.HTTP_400_BAD_REQUEST,
|
|
data={
|
|
"message": (
|
|
u"No course found for course ID '{course_id}'"
|
|
).format(course_id=course_id)
|
|
}
|
|
)
|
|
|
|
|
|
class EnrollmentListView(APIView):
|
|
""" Enrollment API List View for viewing all course enrollments for a user. """
|
|
|
|
authentication_classes = OAuth2Authentication, SessionAuthenticationAllowInactiveUser
|
|
permission_classes = permissions.IsAuthenticated,
|
|
throttle_classes = EnrollmentUserThrottle,
|
|
|
|
def get(self, request):
|
|
"""List out all the enrollments for the current user
|
|
|
|
Returns a JSON response with all the course enrollments for the current user.
|
|
|
|
Args:
|
|
request (Request): The GET request for course enrollment listings.
|
|
user (str): Get all enrollments for the specified user's username.
|
|
|
|
Returns:
|
|
A JSON serialized representation of the user's course enrollments.
|
|
|
|
"""
|
|
user = request.GET.get('user', request.user.username)
|
|
if request.user.username != user:
|
|
# Return a 404 instead of a 403 (Unauthorized). If one user is looking up
|
|
# other users, do not let them deduce the existence of an enrollment.
|
|
return Response(status=status.HTTP_404_NOT_FOUND)
|
|
try:
|
|
return Response(api.get_enrollments(user))
|
|
except CourseEnrollmentError:
|
|
return Response(
|
|
status=status.HTTP_400_BAD_REQUEST,
|
|
data={
|
|
"message": (
|
|
u"An error occurred while retrieving enrollments for user '{user}'"
|
|
).format(user=user)
|
|
}
|
|
)
|
|
|
|
def post(self, request):
|
|
"""Create a new enrollment
|
|
|
|
Creates a new enrollment based on the data posted. Currently all that can be specified is
|
|
the course id. All other attributes will be determined by the server, and cannot be updated
|
|
through this endpoint.
|
|
|
|
By default, this will attempt to create the enrollment with course mode 'honor'. If the course
|
|
does not have an 'honor' course mode, it will fail as a bad request and list the available
|
|
course modes.
|
|
|
|
Args:
|
|
request (Request): The POST request to create a new enrollment. POST DATA should contain
|
|
'course_details' with an attribute 'course_id' to identify which course to enroll in.
|
|
'user' may be specified as well, but must match the username of the authenticated user.
|
|
Ex. {'user': 'Bob', 'course_details': { 'course_id': 'edx/demo/T2014' } }
|
|
|
|
Returns:
|
|
A JSON serialized representation of the user's new course enrollment.
|
|
|
|
"""
|
|
user = request.DATA.get('user', request.user.username)
|
|
if not user:
|
|
user = request.user.username
|
|
if user != request.user.username:
|
|
# Return a 404 instead of a 403 (Unauthorized). If one user is looking up
|
|
# other users, do not let them deduce the existence of an enrollment.
|
|
return Response(status=status.HTTP_404_NOT_FOUND)
|
|
|
|
if 'course_details' not in request.DATA or 'course_id' not in request.DATA['course_details']:
|
|
return Response(
|
|
status=status.HTTP_400_BAD_REQUEST,
|
|
data={"message": u"Course ID must be specified to create a new enrollment."}
|
|
)
|
|
course_id = request.DATA['course_details']['course_id']
|
|
|
|
try:
|
|
return Response(api.add_enrollment(user, course_id))
|
|
except CourseModeNotFoundError as error:
|
|
return Response(
|
|
status=status.HTTP_400_BAD_REQUEST,
|
|
data={
|
|
"message": (
|
|
u"The course mode '{mode}' is not available for course '{course_id}'."
|
|
).format(mode="honor", course_id=course_id),
|
|
"course_details": error.data
|
|
})
|
|
except CourseNotFoundError:
|
|
return Response(
|
|
status=status.HTTP_400_BAD_REQUEST,
|
|
data={
|
|
"message": u"No course '{course_id}' found for enrollment".format(course_id=course_id)
|
|
}
|
|
)
|
|
except CourseEnrollmentError:
|
|
return Response(
|
|
status=status.HTTP_400_BAD_REQUEST,
|
|
data={
|
|
"message": (
|
|
u"An error occurred while creating the new course enrollment for user "
|
|
u"'{user}' in course '{course_id}'"
|
|
).format(user=user, course_id=course_id)
|
|
}
|
|
)
|