Merge pull request #5606 from edx/sanchez/create-enrollment-api
WIP Enrollment API -- Merge to Reg-Login Form branch only
This commit is contained in:
0
common/djangoapps/enrollment/__init__.py
Normal file
0
common/djangoapps/enrollment/__init__.py
Normal file
223
common/djangoapps/enrollment/api.py
Normal file
223
common/djangoapps/enrollment/api.py
Normal file
@@ -0,0 +1,223 @@
|
||||
"""
|
||||
Enrollment API for creating, updating, and deleting enrollments. Also provides access to enrollment information at a
|
||||
course level, such as available course modes.
|
||||
|
||||
"""
|
||||
from enrollment import data
|
||||
|
||||
|
||||
class CourseEnrollmentError(Exception):
|
||||
""" Generic Course Enrollment Error.
|
||||
|
||||
Describes any error that may occur when reading or updating enrollment information for a student or a course.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def get_enrollments(student_id):
|
||||
""" Retrieves all the courses a student is enrolled in.
|
||||
|
||||
Takes a student and retrieves all relative enrollments. Includes information regarding how the student is enrolled
|
||||
in the the course.
|
||||
|
||||
Args:
|
||||
student_id (str): The ID of the student we want to retrieve course enrollment information for.
|
||||
|
||||
Returns:
|
||||
A list of enrollment information for the given student.
|
||||
|
||||
Examples:
|
||||
>>> get_enrollments("Bob")
|
||||
[
|
||||
{
|
||||
course_id: "edX/DemoX/2014T2",
|
||||
is_active: True,
|
||||
mode: "honor",
|
||||
student: "Bob",
|
||||
course_modes: [
|
||||
"audit",
|
||||
"honor"
|
||||
],
|
||||
enrollment_start: 2014-04-07,
|
||||
enrollment_end: 2014-06-07,
|
||||
invite_only: False
|
||||
},
|
||||
{
|
||||
course_id: "edX/edX-Insider/2014T2",
|
||||
is_active: True,
|
||||
mode: "honor",
|
||||
student: "Bob",
|
||||
course_modes: [
|
||||
"audit",
|
||||
"honor",
|
||||
"verified"
|
||||
],
|
||||
enrollment_start: 2014-05-01,
|
||||
enrollment_end: 2014-06-01,
|
||||
invite_only: True
|
||||
},
|
||||
]
|
||||
|
||||
"""
|
||||
return data.get_course_enrollments(student_id)
|
||||
|
||||
|
||||
def get_enrollment(student_id, course_id):
|
||||
""" Retrieves all enrollment information for the student in respect to a specific course.
|
||||
|
||||
Gets all the course enrollment information specific to a student in a course.
|
||||
|
||||
Args:
|
||||
student_id (str): The student to get course enrollment information for.
|
||||
course_id (str): The course to get enrollment information for.
|
||||
|
||||
Returns:
|
||||
A serializable dictionary of the course enrollment.
|
||||
|
||||
Example:
|
||||
>>> add_enrollment("Bob", "edX/DemoX/2014T2")
|
||||
{
|
||||
course_id: "edX/DemoX/2014T2",
|
||||
is_active: True,
|
||||
mode: "honor",
|
||||
student: "Bob",
|
||||
course_modes: [
|
||||
"audit",
|
||||
"honor"
|
||||
],
|
||||
enrollment_start: 2014-04-07,
|
||||
enrollment_end: 2014-06-07,
|
||||
invite_only: False
|
||||
}
|
||||
|
||||
"""
|
||||
return data.get_course_enrollment(student_id, course_id)
|
||||
|
||||
|
||||
def add_enrollment(student_id, course_id, mode='honor', is_active=True):
|
||||
""" Enrolls a student in a course.
|
||||
|
||||
Enrolls a student in a course. If the mode is not specified, this will default to 'honor'.
|
||||
|
||||
Args:
|
||||
student_id (str): The student to enroll.
|
||||
course_id (str): The course to enroll the student in.
|
||||
mode (str): Optional argument for the type of enrollment to create. Ex. 'audit', 'honor', 'verified',
|
||||
'professional'. If not specified, this defaults to 'honor'.
|
||||
is_active (boolean): Optional argument for making the new enrollment inactive. If not specified, is_active
|
||||
defaults to True.
|
||||
|
||||
Returns:
|
||||
A serializable dictionary of the new course enrollment.
|
||||
|
||||
Example:
|
||||
>>> add_enrollment("Bob", "edX/DemoX/2014T2", mode="audit")
|
||||
{
|
||||
course_id: "edX/DemoX/2014T2",
|
||||
is_active: True,
|
||||
mode: "audit",
|
||||
student: "Bob",
|
||||
course_modes: [
|
||||
"audit",
|
||||
"honor"
|
||||
],
|
||||
enrollment_start: 2014-04-07,
|
||||
enrollment_end: 2014-06-07,
|
||||
invite_only: False
|
||||
}
|
||||
"""
|
||||
return data.update_course_enrollment(student_id, course_id, mode=mode, is_active=is_active)
|
||||
|
||||
|
||||
def deactivate_enrollment(student_id, course_id):
|
||||
""" Un-enrolls a student in a course
|
||||
|
||||
Deactivate the enrollment of a student in a course. We will not remove the enrollment data, but simply flag it
|
||||
as inactive.
|
||||
|
||||
Args:
|
||||
student_id (str): The student associated with the deactivated enrollment.
|
||||
course_id (str): The course associated with the deactivated enrollment.
|
||||
|
||||
Returns:
|
||||
A serializable dictionary representing the deactivated course enrollment for the student.
|
||||
|
||||
Example:
|
||||
>>> deactivate_enrollment("Bob", "edX/DemoX/2014T2")
|
||||
{
|
||||
course_id: "edX/DemoX/2014T2",
|
||||
mode: "honor",
|
||||
is_active: False,
|
||||
student: "Bob",
|
||||
course_modes: [
|
||||
"audit",
|
||||
"honor"
|
||||
],
|
||||
enrollment_start: 2014-04-07,
|
||||
enrollment_end: 2014-06-07,
|
||||
invite_only: False
|
||||
}
|
||||
"""
|
||||
return data.update_course_enrollment(student_id, course_id, is_active=False)
|
||||
|
||||
|
||||
def update_enrollment(student_id, course_id, mode):
|
||||
""" Updates the course mode for the enrolled user.
|
||||
|
||||
Update a course enrollment for the given student and course.
|
||||
|
||||
Args:
|
||||
student_id (str): The student associated with the updated enrollment.
|
||||
course_id (str): The course associated with the updated enrollment.
|
||||
mode (str): The new course mode for this enrollment.
|
||||
|
||||
Returns:
|
||||
A serializable dictionary representing the updated enrollment.
|
||||
|
||||
Example:
|
||||
>>> update_enrollment("Bob", "edX/DemoX/2014T2", "honor")
|
||||
{
|
||||
course_id: "edX/DemoX/2014T2",
|
||||
mode: "honor",
|
||||
is_active: True,
|
||||
student: "Bob",
|
||||
course_modes: [
|
||||
"audit",
|
||||
"honor"
|
||||
],
|
||||
enrollment_start: 2014-04-07,
|
||||
enrollment_end: 2014-06-07,
|
||||
invite_only: False
|
||||
}
|
||||
|
||||
"""
|
||||
return data.update_course_enrollment(student_id, course_id, mode)
|
||||
|
||||
|
||||
def get_course_enrollment_details(course_id):
|
||||
""" Get the course modes for course. Also get enrollment start and end date, invite only, etc.
|
||||
|
||||
Given a course_id, return a serializable dictionary of properties describing course enrollment information.
|
||||
|
||||
Args:
|
||||
course_id (str): The Course to get enrollment information for.
|
||||
|
||||
Returns:
|
||||
A serializable dictionary of course enrollment information.
|
||||
|
||||
Example:
|
||||
>>> get_course_enrollment_details("edX/DemoX/2014T2")
|
||||
{
|
||||
course_id: "edX/DemoX/2014T2",
|
||||
course_modes: [
|
||||
"audit",
|
||||
"honor"
|
||||
],
|
||||
enrollment_start: 2014-04-01,
|
||||
enrollment_end: 2014-06-01,
|
||||
invite_only: False
|
||||
}
|
||||
|
||||
"""
|
||||
pass
|
||||
48
common/djangoapps/enrollment/data.py
Normal file
48
common/djangoapps/enrollment/data.py
Normal file
@@ -0,0 +1,48 @@
|
||||
"""
|
||||
Data Aggregation Layer of the Enrollment API. Collects all enrollment specific data into a single
|
||||
source to be used throughout the API.
|
||||
|
||||
"""
|
||||
from django.contrib.auth.models import User
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from enrollment.serializers import CourseEnrollmentSerializer
|
||||
from student.models import CourseEnrollment
|
||||
|
||||
|
||||
def get_course_enrollments(student_id):
|
||||
qset = CourseEnrollment.objects.filter(
|
||||
user__username=student_id, is_active=True
|
||||
).order_by('created')
|
||||
return CourseEnrollmentSerializer(qset).data
|
||||
|
||||
|
||||
def get_course_enrollment(student_id, course_id):
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
try:
|
||||
enrollment = CourseEnrollment.objects.get(
|
||||
user__username=student_id, course_id=course_key
|
||||
)
|
||||
return CourseEnrollmentSerializer(enrollment).data
|
||||
except CourseEnrollment.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
def update_course_enrollment(student_id, course_id, mode=None, is_active=None):
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
student = User.objects.get(username=student_id)
|
||||
if not CourseEnrollment.is_enrolled(student, course_key):
|
||||
enrollment = CourseEnrollment.enroll(student, course_key)
|
||||
else:
|
||||
enrollment = CourseEnrollment.objects.get(user=student, course_id=course_key)
|
||||
|
||||
enrollment.update_enrollment(is_active=is_active, mode=mode)
|
||||
enrollment.save()
|
||||
return CourseEnrollmentSerializer(enrollment).data
|
||||
|
||||
|
||||
def get_course_enrollment_info(course_id):
|
||||
pass
|
||||
|
||||
|
||||
def get_course_enrollments_info(student_id):
|
||||
pass
|
||||
4
common/djangoapps/enrollment/models.py
Normal file
4
common/djangoapps/enrollment/models.py
Normal file
@@ -0,0 +1,4 @@
|
||||
"""
|
||||
A models.py is required to make this an app (until we move to Django 1.7)
|
||||
|
||||
"""
|
||||
47
common/djangoapps/enrollment/serializers.py
Normal file
47
common/djangoapps/enrollment/serializers.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""
|
||||
Serializers for all Course Enrollment related return objects.
|
||||
|
||||
"""
|
||||
from rest_framework import serializers
|
||||
from student.models import CourseEnrollment
|
||||
from course_modes.models import CourseMode
|
||||
|
||||
|
||||
class CourseField(serializers.RelatedField):
|
||||
"""Custom field to wrap a CourseDescriptor object. Read-only."""
|
||||
|
||||
def to_native(self, course):
|
||||
course_id = unicode(course.id)
|
||||
course_modes = ModeSerializer(CourseMode.modes_for_course(course.id)).data
|
||||
|
||||
return {
|
||||
"course_id": course_id,
|
||||
"enrollment_start": course.enrollment_start,
|
||||
"enrollment_end": course.enrollment_end,
|
||||
"invite_only": course.invitation_only,
|
||||
"course_modes": course_modes,
|
||||
}
|
||||
|
||||
|
||||
class CourseEnrollmentSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Serializes CourseEnrollment models
|
||||
|
||||
"""
|
||||
course = CourseField()
|
||||
|
||||
class Meta: # pylint: disable=C0111
|
||||
model = CourseEnrollment
|
||||
fields = ('created', 'mode', 'is_active', 'course')
|
||||
lookup_field = 'username'
|
||||
|
||||
|
||||
class ModeSerializer(serializers.Serializer):
|
||||
"""Serializes a course's 'Mode' tuples"""
|
||||
slug = serializers.CharField(max_length=100)
|
||||
name = serializers.CharField(max_length=255)
|
||||
min_price = serializers.IntegerField()
|
||||
suggested_prices = serializers.CharField(max_length=255)
|
||||
currency = serializers.CharField(max_length=8)
|
||||
expiration_datetime = serializers.DateTimeField()
|
||||
description = serializers.CharField()
|
||||
0
common/djangoapps/enrollment/tests/__init__.py
Normal file
0
common/djangoapps/enrollment/tests/__init__.py
Normal file
18
common/djangoapps/enrollment/urls.py
Normal file
18
common/djangoapps/enrollment/urls.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
URLs for the Enrollment API
|
||||
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
from .views import get_course_enrollment, list_student_enrollments
|
||||
|
||||
urlpatterns = patterns(
|
||||
'enrollment.views',
|
||||
url(r'^student$', list_student_enrollments, name='courseenrollments'),
|
||||
url(
|
||||
r'^course/{course_key}$'.format(course_key=settings.COURSE_ID_PATTERN),
|
||||
get_course_enrollment,
|
||||
name='courseenrollment'
|
||||
),
|
||||
)
|
||||
38
common/djangoapps/enrollment/views.py
Normal file
38
common/djangoapps/enrollment/views.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""
|
||||
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.authentication import OAuth2Authentication, SessionAuthentication
|
||||
from rest_framework.decorators import api_view, authentication_classes, permission_classes, throttle_classes
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.throttling import UserRateThrottle
|
||||
from enrollment import api
|
||||
|
||||
|
||||
class EnrollmentUserThrottle(UserRateThrottle):
|
||||
rate = '50/second' # TODO Limit significantly after performance testing.
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
@authentication_classes((OAuth2Authentication, SessionAuthentication))
|
||||
@permission_classes((IsAuthenticated,))
|
||||
@throttle_classes([EnrollmentUserThrottle])
|
||||
def list_student_enrollments(request):
|
||||
return Response(api.get_enrollments(request.user.username))
|
||||
|
||||
|
||||
@api_view(['GET', 'POST'])
|
||||
@authentication_classes((OAuth2Authentication, SessionAuthentication))
|
||||
@permission_classes((IsAuthenticated,))
|
||||
@throttle_classes([EnrollmentUserThrottle])
|
||||
def get_course_enrollment(request, course_id=None):
|
||||
if 'mode' in request.DATA:
|
||||
return Response(api.update_enrollment(request.user.username, course_id, request.DATA['mode']))
|
||||
elif 'deactivate' in request.DATA:
|
||||
return Response(api.deactivate_enrollment(request.user.username, course_id))
|
||||
elif course_id and request.method == 'POST':
|
||||
return Response(api.add_enrollment(request.user.username, course_id))
|
||||
else:
|
||||
return Response(api.get_enrollment(request.user.username, course_id))
|
||||
@@ -73,6 +73,9 @@ urlpatterns = ('', # nopep8
|
||||
# Feedback Form endpoint
|
||||
url(r'^submit_feedback$', 'util.views.submit_feedback'),
|
||||
|
||||
# Enrollment API RESTful endpoints
|
||||
url(r'^enrollment/v0/', include('enrollment.urls')),
|
||||
|
||||
)
|
||||
|
||||
if settings.FEATURES["ENABLE_MOBILE_REST_API"]:
|
||||
|
||||
Reference in New Issue
Block a user