adding all the tests for data and api.
Updating tests for views Last chunk of tests and pep8 cleanup Code Review cleanup. Additional CR comments Changing serialization of suggested prices.
This commit is contained in:
@@ -3,7 +3,11 @@ Enrollment API for creating, updating, and deleting enrollments. Also provides a
|
||||
course level, such as available course modes.
|
||||
|
||||
"""
|
||||
from enrollment import data
|
||||
from django.utils import importlib
|
||||
import logging
|
||||
from django.conf import settings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CourseEnrollmentError(Exception):
|
||||
@@ -12,9 +16,26 @@ class CourseEnrollmentError(Exception):
|
||||
Describes any error that may occur when reading or updating enrollment information for a student or a course.
|
||||
|
||||
"""
|
||||
def __init__(self, msg, data=None):
|
||||
super(Exception, self).__init__(msg)
|
||||
# Corresponding information to help resolve the error.
|
||||
self.data = data
|
||||
|
||||
|
||||
class CourseModeNotFoundError(CourseEnrollmentError):
|
||||
pass
|
||||
|
||||
|
||||
class EnrollmentNotFoundError(CourseEnrollmentError):
|
||||
pass
|
||||
|
||||
|
||||
class EnrollmentApiLoadError(CourseEnrollmentError):
|
||||
pass
|
||||
|
||||
DEFAULT_DATA_API = 'enrollment.data'
|
||||
|
||||
|
||||
def get_enrollments(student_id):
|
||||
""" Retrieves all the courses a student is enrolled in.
|
||||
|
||||
@@ -31,36 +52,58 @@ def get_enrollments(student_id):
|
||||
>>> 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
|
||||
"created": "2014-10-20T20:18:00Z",
|
||||
"mode": "honor",
|
||||
"is_active": True,
|
||||
"student": "Bob",
|
||||
"course": {
|
||||
"course_id": "edX/DemoX/2014T2",
|
||||
"enrollment_end": 2014-12-20T20:18:00Z,
|
||||
"course_modes": [
|
||||
{
|
||||
"slug": "honor",
|
||||
"name": "Honor Code Certificate",
|
||||
"min_price": 0,
|
||||
"suggested_prices": "",
|
||||
"currency": "usd",
|
||||
"expiration_datetime": null,
|
||||
"description": null
|
||||
}
|
||||
],
|
||||
"enrollment_start": 2014-10-15T20:18:00Z,
|
||||
"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
|
||||
},
|
||||
"created": "2014-10-25T20:18:00Z",
|
||||
"mode": "verified",
|
||||
"is_active": True,
|
||||
"student": "Bob",
|
||||
"course": {
|
||||
"course_id": "edX/edX-Insider/2014T2",
|
||||
"enrollment_end": 2014-12-20T20:18:00Z,
|
||||
"course_modes": [
|
||||
{
|
||||
"slug": "honor",
|
||||
"name": "Honor Code Certificate",
|
||||
"min_price": 0,
|
||||
"suggested_prices": "",
|
||||
"currency": "usd",
|
||||
"expiration_datetime": null,
|
||||
"description": null
|
||||
}
|
||||
],
|
||||
"enrollment_start": 2014-10-15T20:18:00Z,
|
||||
"invite_only": True
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
"""
|
||||
return data.get_course_enrollments(student_id)
|
||||
enrollments = _data_api().get_course_enrollments(student_id)
|
||||
for enrollment in enrollments:
|
||||
enrollment['student'] = student_id
|
||||
return enrollments
|
||||
|
||||
|
||||
def get_enrollment(student_id, course_id):
|
||||
@@ -76,23 +119,35 @@ def get_enrollment(student_id, course_id):
|
||||
A serializable dictionary of the course enrollment.
|
||||
|
||||
Example:
|
||||
>>> add_enrollment("Bob", "edX/DemoX/2014T2")
|
||||
>>> get_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
|
||||
"created": "2014-10-20T20:18:00Z",
|
||||
"mode": "honor",
|
||||
"is_active": True,
|
||||
"student": "Bob",
|
||||
"course": {
|
||||
"course_id": "edX/DemoX/2014T2",
|
||||
"enrollment_end": 2014-12-20T20:18:00Z,
|
||||
"course_modes": [
|
||||
{
|
||||
"slug": "honor",
|
||||
"name": "Honor Code Certificate",
|
||||
"min_price": 0,
|
||||
"suggested_prices": "",
|
||||
"currency": "usd",
|
||||
"expiration_datetime": null,
|
||||
"description": null
|
||||
}
|
||||
],
|
||||
"enrollment_start": 2014-10-15T20:18:00Z,
|
||||
"invite_only": False
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
return data.get_course_enrollment(student_id, course_id)
|
||||
enrollment = _data_api().get_course_enrollment(student_id, course_id)
|
||||
enrollment['student'] = student_id
|
||||
return enrollment
|
||||
|
||||
|
||||
def add_enrollment(student_id, course_id, mode='honor', is_active=True):
|
||||
@@ -114,20 +169,33 @@ def add_enrollment(student_id, course_id, mode='honor', is_active=True):
|
||||
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
|
||||
"created": "2014-10-20T20:18:00Z",
|
||||
"mode": "honor",
|
||||
"is_active": True,
|
||||
"student": "Bob",
|
||||
"course": {
|
||||
"course_id": "edX/DemoX/2014T2",
|
||||
"enrollment_end": 2014-12-20T20:18:00Z,
|
||||
"course_modes": [
|
||||
{
|
||||
"slug": "honor",
|
||||
"name": "Honor Code Certificate",
|
||||
"min_price": 0,
|
||||
"suggested_prices": "",
|
||||
"currency": "usd",
|
||||
"expiration_datetime": null,
|
||||
"description": null
|
||||
}
|
||||
],
|
||||
"enrollment_start": 2014-10-15T20:18:00Z,
|
||||
"invite_only": False
|
||||
}
|
||||
}
|
||||
"""
|
||||
return data.update_course_enrollment(student_id, course_id, mode=mode, is_active=is_active)
|
||||
_validate_course_mode(course_id, mode)
|
||||
enrollment = _data_api().update_course_enrollment(student_id, course_id, mode=mode, is_active=is_active)
|
||||
enrollment['student'] = student_id
|
||||
return enrollment
|
||||
|
||||
|
||||
def deactivate_enrollment(student_id, course_id):
|
||||
@@ -146,20 +214,38 @@ def deactivate_enrollment(student_id, course_id):
|
||||
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
|
||||
"created": "2014-10-20T20:18:00Z",
|
||||
"mode": "honor",
|
||||
"is_active": False,
|
||||
"student": "Bob",
|
||||
"course": {
|
||||
"course_id": "edX/DemoX/2014T2",
|
||||
"enrollment_end": 2014-12-20T20:18:00Z,
|
||||
"course_modes": [
|
||||
{
|
||||
"slug": "honor",
|
||||
"name": "Honor Code Certificate",
|
||||
"min_price": 0,
|
||||
"suggested_prices": "",
|
||||
"currency": "usd",
|
||||
"expiration_datetime": null,
|
||||
"description": null
|
||||
}
|
||||
],
|
||||
"enrollment_start": 2014-10-15T20:18:00Z,
|
||||
"invite_only": False
|
||||
}
|
||||
}
|
||||
"""
|
||||
return data.update_course_enrollment(student_id, course_id, is_active=False)
|
||||
# Check to see if there is an enrollment. We do not want to create a deactivated enrollment.
|
||||
if not _data_api().get_course_enrollment(student_id, course_id):
|
||||
raise EnrollmentNotFoundError(
|
||||
u"No enrollment was found for student {student} in course {course}"
|
||||
.format(student=student_id, course=course_id)
|
||||
)
|
||||
enrollment = _data_api().update_course_enrollment(student_id, course_id, is_active=False)
|
||||
enrollment['student'] = student_id
|
||||
return enrollment
|
||||
|
||||
|
||||
def update_enrollment(student_id, course_id, mode):
|
||||
@@ -178,21 +264,34 @@ def update_enrollment(student_id, course_id, mode):
|
||||
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
|
||||
"created": "2014-10-20T20:18:00Z",
|
||||
"mode": "honor",
|
||||
"is_active": True,
|
||||
"student": "Bob",
|
||||
"course": {
|
||||
"course_id": "edX/DemoX/2014T2",
|
||||
"enrollment_end": 2014-12-20T20:18:00Z,
|
||||
"course_modes": [
|
||||
{
|
||||
"slug": "honor",
|
||||
"name": "Honor Code Certificate",
|
||||
"min_price": 0,
|
||||
"suggested_prices": "",
|
||||
"currency": "usd",
|
||||
"expiration_datetime": null,
|
||||
"description": null
|
||||
}
|
||||
],
|
||||
"enrollment_start": 2014-10-15T20:18:00Z,
|
||||
"invite_only": False
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
return data.update_course_enrollment(student_id, course_id, mode)
|
||||
_validate_course_mode(course_id, mode)
|
||||
enrollment = _data_api().update_course_enrollment(student_id, course_id, mode)
|
||||
enrollment['student'] = student_id
|
||||
return enrollment
|
||||
|
||||
|
||||
def get_course_enrollment_details(course_id):
|
||||
@@ -209,15 +308,67 @@ def get_course_enrollment_details(course_id):
|
||||
Example:
|
||||
>>> get_course_enrollment_details("edX/DemoX/2014T2")
|
||||
{
|
||||
course_id: "edX/DemoX/2014T2",
|
||||
course_modes: [
|
||||
"audit",
|
||||
"honor"
|
||||
"course_id": "edX/DemoX/2014T2",
|
||||
"enrollment_end": 2014-12-20T20:18:00Z,
|
||||
"course_modes": [
|
||||
{
|
||||
"slug": "honor",
|
||||
"name": "Honor Code Certificate",
|
||||
"min_price": 0,
|
||||
"suggested_prices": "",
|
||||
"currency": "usd",
|
||||
"expiration_datetime": null,
|
||||
"description": null
|
||||
}
|
||||
],
|
||||
enrollment_start: 2014-04-01,
|
||||
enrollment_end: 2014-06-01,
|
||||
invite_only: False
|
||||
"enrollment_start": 2014-10-15T20:18:00Z,
|
||||
"invite_only": False
|
||||
}
|
||||
|
||||
"""
|
||||
pass
|
||||
return _data_api().get_course_enrollment_info(course_id)
|
||||
|
||||
|
||||
def _validate_course_mode(course_id, mode):
|
||||
"""Checks to see if the specified course mode is valid for the course.
|
||||
|
||||
If the requested course mode is not available for the course, raise an error with corresponding
|
||||
course enrollment information.
|
||||
|
||||
'honor' is special cased. If there are no course modes configured, and the specified mode is
|
||||
'honor', return true, allowing the enrollment to be 'honor' even if the mode is not explicitly
|
||||
set for the course.
|
||||
|
||||
Args:
|
||||
course_id (str): The course to check against for available course modes.
|
||||
mode (str): The slug for the course mode specified in the enrollment.
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
Raises:
|
||||
CourseModeNotFound: raised if the course mode is not found.
|
||||
"""
|
||||
course_enrollment_info = _data_api().get_course_enrollment_info(course_id)
|
||||
course_modes = course_enrollment_info["course_modes"]
|
||||
if mode not in (mode['slug'] for mode in course_modes):
|
||||
msg = u"Specified course mode unavailable for course {course_id}".format(course_id=course_id)
|
||||
log.warn(msg)
|
||||
error = CourseModeNotFoundError(msg, course_enrollment_info)
|
||||
raise error
|
||||
|
||||
|
||||
def _data_api():
|
||||
"""Returns a Data API.
|
||||
This relies on Django settings to find the appropriate data API.
|
||||
|
||||
"""
|
||||
# We retrieve the settings in-line here (rather than using the
|
||||
# top-level constant), so that @override_settings will work
|
||||
# in the test suite.
|
||||
api_path = getattr(settings, "ENROLLMENT_DATA_API", DEFAULT_DATA_API)
|
||||
|
||||
try:
|
||||
return importlib.import_module(api_path)
|
||||
except (ImportError, ValueError):
|
||||
raise EnrollmentApiLoadError(api_path)
|
||||
|
||||
@@ -3,13 +3,29 @@ Data Aggregation Layer of the Enrollment API. Collects all enrollment specific d
|
||||
source to be used throughout the API.
|
||||
|
||||
"""
|
||||
import logging
|
||||
from django.contrib.auth.models import User
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from enrollment.serializers import CourseEnrollmentSerializer
|
||||
from student.models import CourseEnrollment
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
from enrollment.serializers import CourseEnrollmentSerializer, CourseField
|
||||
from student.models import CourseEnrollment, NonExistentCourseError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_course_enrollments(student_id):
|
||||
"""Retrieve a list representing all aggregated data for a student's course enrollments.
|
||||
|
||||
Construct a representation of all course enrollment data for a specific student..
|
||||
|
||||
Args:
|
||||
student_id (str): The name of the student to retrieve course enrollment information for.
|
||||
|
||||
Returns:
|
||||
A serializable list of dictionaries of all aggregated enrollment data for a student.
|
||||
|
||||
"""
|
||||
qset = CourseEnrollment.objects.filter(
|
||||
user__username=student_id, is_active=True
|
||||
).order_by('created')
|
||||
@@ -17,6 +33,18 @@ def get_course_enrollments(student_id):
|
||||
|
||||
|
||||
def get_course_enrollment(student_id, course_id):
|
||||
"""Retrieve an object representing all aggregated data for a student's course enrollment.
|
||||
|
||||
Get the course enrollment information for a specific student and course.
|
||||
|
||||
Args:
|
||||
student_id (str): The name of the student to retrieve course enrollment information for.
|
||||
course_id (str): The course to retrieve course enrollment information for.
|
||||
|
||||
Returns:
|
||||
A serializable dictionary representing the course enrollment.
|
||||
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
try:
|
||||
enrollment = CourseEnrollment.objects.get(
|
||||
@@ -28,6 +56,20 @@ def get_course_enrollment(student_id, course_id):
|
||||
|
||||
|
||||
def update_course_enrollment(student_id, course_id, mode=None, is_active=None):
|
||||
"""Modify a course enrollment for a student.
|
||||
|
||||
Allows updates to a specific course enrollment.
|
||||
|
||||
Args:
|
||||
student_id (str): The name of the student to retrieve course enrollment information for.
|
||||
course_id (str): The course to retrieve course enrollment information for.
|
||||
mode (str): (Optional) The mode for the new enrollment.
|
||||
is_active (boolean): (Optional) Determines if the enrollment is active.
|
||||
|
||||
Returns:
|
||||
A serializable dictionary representing the modified course enrollment.
|
||||
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
student = User.objects.get(username=student_id)
|
||||
if not CourseEnrollment.is_enrolled(student, course_key):
|
||||
@@ -41,8 +83,23 @@ def update_course_enrollment(student_id, course_id, mode=None, is_active=None):
|
||||
|
||||
|
||||
def get_course_enrollment_info(course_id):
|
||||
pass
|
||||
"""Returns all course enrollment information for the given course.
|
||||
|
||||
Based on the course id, return all related course information..
|
||||
|
||||
def get_course_enrollments_info(student_id):
|
||||
pass
|
||||
Args:
|
||||
course_id (str): The course to retrieve enrollment information for.
|
||||
|
||||
Returns:
|
||||
A serializable dictionary representing the course's enrollment information.
|
||||
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
course = modulestore().get_course(course_key)
|
||||
if course is None:
|
||||
log.warning(
|
||||
u"Requested enrollment information for unknown course {course}"
|
||||
.format(course=course_id)
|
||||
)
|
||||
raise NonExistentCourseError
|
||||
return CourseField().to_native(course)
|
||||
|
||||
@@ -3,12 +3,36 @@ Serializers for all Course Enrollment related return objects.
|
||||
|
||||
"""
|
||||
from rest_framework import serializers
|
||||
from rest_framework.fields import Field
|
||||
from student.models import CourseEnrollment
|
||||
from course_modes.models import CourseMode
|
||||
|
||||
|
||||
class StringListField(serializers.CharField):
|
||||
"""Custom Serializer for turning a comma delimited string into a list.
|
||||
|
||||
This field is designed to take a string such as "1,2,3" and turn it into an actual list
|
||||
[1,2,3]
|
||||
|
||||
"""
|
||||
def field_to_native(self, obj, field_name):
|
||||
"""
|
||||
Serialize the object's class name.
|
||||
"""
|
||||
if not obj.suggested_prices:
|
||||
return []
|
||||
|
||||
items = obj.suggested_prices.split(',')
|
||||
return [int(item) for item in items]
|
||||
|
||||
|
||||
class CourseField(serializers.RelatedField):
|
||||
"""Custom field to wrap a CourseDescriptor object. Read-only."""
|
||||
"""Read-Only representation of course enrollment information.
|
||||
|
||||
Aggregates course information from the CourseDescriptor as well as the Course Modes configured
|
||||
for enrolling in the course.
|
||||
|
||||
"""
|
||||
|
||||
def to_native(self, course):
|
||||
course_id = unicode(course.id)
|
||||
@@ -24,8 +48,10 @@ class CourseField(serializers.RelatedField):
|
||||
|
||||
|
||||
class CourseEnrollmentSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Serializes CourseEnrollment models
|
||||
"""Serializes CourseEnrollment models
|
||||
|
||||
Aggregates all data from the Course Enrollment table, and pulls in the serialization for
|
||||
the Course Descriptor and course modes, to give a complete representation of course enrollment.
|
||||
|
||||
"""
|
||||
course = CourseField()
|
||||
@@ -37,11 +63,17 @@ class CourseEnrollmentSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
||||
class ModeSerializer(serializers.Serializer):
|
||||
"""Serializes a course's 'Mode' tuples"""
|
||||
"""Serializes a course's 'Mode' tuples
|
||||
|
||||
Returns a serialized representation of the modes available for course enrollment. The course
|
||||
modes models are designed to return a tuple instead of the model object itself. This serializer
|
||||
does not handle the model object itself, but the tuple.
|
||||
|
||||
"""
|
||||
slug = serializers.CharField(max_length=100)
|
||||
name = serializers.CharField(max_length=255)
|
||||
min_price = serializers.IntegerField()
|
||||
suggested_prices = serializers.CharField(max_length=255)
|
||||
suggested_prices = StringListField(max_length=255)
|
||||
currency = serializers.CharField(max_length=8)
|
||||
expiration_datetime = serializers.DateTimeField()
|
||||
description = serializers.CharField()
|
||||
|
||||
96
common/djangoapps/enrollment/tests/fake_data_api.py
Normal file
96
common/djangoapps/enrollment/tests/fake_data_api.py
Normal file
@@ -0,0 +1,96 @@
|
||||
"""
|
||||
A Fake Data API for testing purposes.
|
||||
"""
|
||||
import copy
|
||||
import datetime
|
||||
|
||||
|
||||
_DEFAULT_FAKE_MODE = {
|
||||
"slug": "honor",
|
||||
"name": "Honor Code Certificate",
|
||||
"min_price": 0,
|
||||
"suggested_prices": "",
|
||||
"currency": "usd",
|
||||
"expiration_datetime": None,
|
||||
"description": None
|
||||
}
|
||||
|
||||
_ENROLLMENTS = []
|
||||
|
||||
_COURSES = []
|
||||
|
||||
|
||||
def get_course_enrollments(student_id):
|
||||
"""Stubbed out Enrollment data request."""
|
||||
return _ENROLLMENTS
|
||||
|
||||
|
||||
def get_course_enrollment(student_id, course_id):
|
||||
"""Stubbed out Enrollment data request."""
|
||||
return _get_fake_enrollment(student_id, course_id)
|
||||
|
||||
|
||||
def update_course_enrollment(student_id, course_id, mode=None, is_active=None):
|
||||
"""Stubbed out Enrollment data request."""
|
||||
enrollment = _get_fake_enrollment(student_id, course_id)
|
||||
if not enrollment:
|
||||
enrollment = add_enrollment(student_id, course_id)
|
||||
if mode is not None:
|
||||
enrollment['mode'] = mode
|
||||
if is_active is not None:
|
||||
enrollment['is_active'] = is_active
|
||||
return enrollment
|
||||
|
||||
|
||||
def get_course_enrollment_info(course_id):
|
||||
"""Stubbed out Enrollment data request."""
|
||||
return _get_fake_course_info(course_id)
|
||||
|
||||
|
||||
def _get_fake_enrollment(student_id, course_id):
|
||||
for enrollment in _ENROLLMENTS:
|
||||
if student_id == enrollment['student'] and course_id == enrollment['course']['course_id']:
|
||||
return enrollment
|
||||
|
||||
|
||||
def _get_fake_course_info(course_id):
|
||||
for course in _COURSES:
|
||||
if course_id == course['course_id']:
|
||||
return course
|
||||
|
||||
|
||||
def add_enrollment(student_id, course_id, is_active=True, mode='honor'):
|
||||
enrollment = {
|
||||
"created": datetime.datetime.now(),
|
||||
"mode": mode,
|
||||
"is_active": is_active,
|
||||
"course": _get_fake_course_info(course_id),
|
||||
"student": student_id
|
||||
}
|
||||
_ENROLLMENTS.append(enrollment)
|
||||
return enrollment
|
||||
|
||||
|
||||
def add_course(course_id, enrollment_start=None, enrollment_end=None, invite_only=False, course_modes=None):
|
||||
course_info = {
|
||||
"course_id": course_id,
|
||||
"enrollment_end": enrollment_end,
|
||||
"course_modes": [],
|
||||
"enrollment_start": enrollment_start,
|
||||
"invite_only": invite_only,
|
||||
}
|
||||
if not course_modes:
|
||||
course_info['course_modes'].append(_DEFAULT_FAKE_MODE)
|
||||
else:
|
||||
for mode in course_modes:
|
||||
new_mode = copy.deepcopy(_DEFAULT_FAKE_MODE)
|
||||
new_mode['slug'] = mode
|
||||
course_info['course_modes'].append(new_mode)
|
||||
_COURSES.append(course_info)
|
||||
|
||||
|
||||
def reset():
|
||||
global _COURSES
|
||||
_COURSES = []
|
||||
global _ENROLLMENTS
|
||||
_ENROLLMENTS = []
|
||||
151
common/djangoapps/enrollment/tests/test_api.py
Normal file
151
common/djangoapps/enrollment/tests/test_api.py
Normal file
@@ -0,0 +1,151 @@
|
||||
"""
|
||||
Tests for student enrollment.
|
||||
"""
|
||||
import ddt
|
||||
from nose.tools import raises
|
||||
import unittest
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.conf import settings
|
||||
from enrollment import api
|
||||
from enrollment.tests import fake_data_api
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@override_settings(ENROLLMENT_DATA_API="enrollment.tests.fake_data_api")
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
class EnrollmentTest(TestCase):
|
||||
"""
|
||||
Test student enrollment, especially with different course modes.
|
||||
"""
|
||||
USERNAME = "Bob"
|
||||
COURSE_ID = "some/great/course"
|
||||
|
||||
def setUp(self):
|
||||
fake_data_api.reset()
|
||||
|
||||
@ddt.data(
|
||||
# Default (no course modes in the database)
|
||||
# Expect automatically being enrolled as "honor".
|
||||
([], 'honor'),
|
||||
|
||||
# Audit / Verified / Honor
|
||||
# We should always go to the "choose your course" page.
|
||||
# We should also be enrolled as "honor" by default.
|
||||
(['honor', 'verified', 'audit'], 'honor'),
|
||||
|
||||
# Check for professional ed happy path.
|
||||
(['professional'], 'professional')
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_enroll(self, course_modes, mode):
|
||||
# Add a fake course enrollment information to the fake data API
|
||||
fake_data_api.add_course(self.COURSE_ID, course_modes=course_modes)
|
||||
# Enroll in the course and verify the URL we get sent to
|
||||
result = api.add_enrollment(self.USERNAME, self.COURSE_ID, mode=mode)
|
||||
self.assertIsNotNone(result)
|
||||
self.assertEquals(result['student'], self.USERNAME)
|
||||
self.assertEquals(result['course']['course_id'], self.COURSE_ID)
|
||||
self.assertEquals(result['mode'], mode)
|
||||
|
||||
get_result = api.get_enrollment(self.USERNAME, self.COURSE_ID)
|
||||
self.assertEquals(result, get_result)
|
||||
|
||||
@raises(api.CourseModeNotFoundError)
|
||||
def test_prof_ed_enroll(self):
|
||||
# Add a fake course enrollment information to the fake data API
|
||||
fake_data_api.add_course(self.COURSE_ID, course_modes=['professional'])
|
||||
# Enroll in the course and verify the URL we get sent to
|
||||
api.add_enrollment(self.USERNAME, self.COURSE_ID, mode='verified')
|
||||
|
||||
@ddt.data(
|
||||
# Default (no course modes in the database)
|
||||
# Expect that users are automatically enrolled as "honor".
|
||||
([], 'honor'),
|
||||
|
||||
# Audit / Verified / Honor
|
||||
# We should always go to the "choose your course" page.
|
||||
# We should also be enrolled as "honor" by default.
|
||||
(['honor', 'verified', 'audit'], 'honor'),
|
||||
|
||||
# Check for professional ed happy path.
|
||||
(['professional'], 'professional')
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_unenroll(self, course_modes, mode):
|
||||
# Add a fake course enrollment information to the fake data API
|
||||
fake_data_api.add_course(self.COURSE_ID, course_modes=course_modes)
|
||||
# Enroll in the course and verify the URL we get sent to
|
||||
result = api.add_enrollment(self.USERNAME, self.COURSE_ID, mode=mode)
|
||||
self.assertIsNotNone(result)
|
||||
self.assertEquals(result['student'], self.USERNAME)
|
||||
self.assertEquals(result['course']['course_id'], self.COURSE_ID)
|
||||
self.assertEquals(result['mode'], mode)
|
||||
self.assertTrue(result['is_active'])
|
||||
|
||||
result = api.deactivate_enrollment(self.USERNAME, self.COURSE_ID)
|
||||
self.assertIsNotNone(result)
|
||||
self.assertEquals(result['student'], self.USERNAME)
|
||||
self.assertEquals(result['course']['course_id'], self.COURSE_ID)
|
||||
self.assertEquals(result['mode'], mode)
|
||||
self.assertFalse(result['is_active'])
|
||||
|
||||
@raises(api.EnrollmentNotFoundError)
|
||||
def test_unenroll_not_enrolled_in_course(self):
|
||||
# Add a fake course enrollment information to the fake data API
|
||||
fake_data_api.add_course(self.COURSE_ID, course_modes=['honor'])
|
||||
api.deactivate_enrollment(self.USERNAME, self.COURSE_ID)
|
||||
|
||||
@ddt.data(
|
||||
# Simple test of honor and verified.
|
||||
([
|
||||
{'course_id': 'the/first/course', 'course_modes': [], 'mode': 'honor'},
|
||||
{'course_id': 'the/second/course', 'course_modes': ['honor', 'verified'], 'mode': 'verified'}
|
||||
]),
|
||||
|
||||
# No enrollments
|
||||
([]),
|
||||
|
||||
# One Enrollment
|
||||
([
|
||||
{'course_id': 'the/third/course', 'course_modes': ['honor', 'verified', 'audit'], 'mode': 'audit'}
|
||||
]),
|
||||
)
|
||||
def test_get_all_enrollments(self, enrollments):
|
||||
for enrollment in enrollments:
|
||||
fake_data_api.add_course(enrollment['course_id'], course_modes=enrollment['course_modes'])
|
||||
api.add_enrollment(self.USERNAME, enrollment['course_id'], enrollment['mode'])
|
||||
result = api.get_enrollments(self.USERNAME)
|
||||
self.assertEqual(len(enrollments), len(result))
|
||||
for result_enrollment in result:
|
||||
self.assertIn(
|
||||
result_enrollment['course']['course_id'],
|
||||
[enrollment['course_id'] for enrollment in enrollments]
|
||||
)
|
||||
|
||||
def test_update_enrollment(self):
|
||||
# Add a fake course enrollment information to the fake data API
|
||||
fake_data_api.add_course(self.COURSE_ID, course_modes=['honor', 'verified', 'audit'])
|
||||
# Enroll in the course and verify the URL we get sent to
|
||||
result = api.add_enrollment(self.USERNAME, self.COURSE_ID, mode='audit')
|
||||
get_result = api.get_enrollment(self.USERNAME, self.COURSE_ID)
|
||||
self.assertEquals(result, get_result)
|
||||
|
||||
result = api.update_enrollment(self.USERNAME, self.COURSE_ID, mode='honor')
|
||||
self.assertEquals('honor', result['mode'])
|
||||
|
||||
result = api.update_enrollment(self.USERNAME, self.COURSE_ID, mode='verified')
|
||||
self.assertEquals('verified', result['mode'])
|
||||
|
||||
def test_get_course_details(self):
|
||||
# Add a fake course enrollment information to the fake data API
|
||||
fake_data_api.add_course(self.COURSE_ID, course_modes=['honor', 'verified', 'audit'])
|
||||
result = api.get_course_enrollment_details(self.COURSE_ID)
|
||||
self.assertEquals(result['course_id'], self.COURSE_ID)
|
||||
self.assertEquals(3, len(result['course_modes']))
|
||||
|
||||
@override_settings(ENROLLMENT_DATA_API='foo.bar.biz.baz')
|
||||
@raises(api.EnrollmentApiLoadError)
|
||||
def test_data_api_config_error(self):
|
||||
# Enroll in the course and verify the URL we get sent to
|
||||
api.add_enrollment(self.USERNAME, self.COURSE_ID, mode='audit')
|
||||
172
common/djangoapps/enrollment/tests/test_data.py
Normal file
172
common/djangoapps/enrollment/tests/test_data.py
Normal file
@@ -0,0 +1,172 @@
|
||||
"""
|
||||
Test the Data Aggregation Layer for Course Enrollments.
|
||||
|
||||
"""
|
||||
import ddt
|
||||
from nose.tools import raises
|
||||
import unittest
|
||||
|
||||
from django.test.utils import override_settings
|
||||
from django.conf import settings
|
||||
from xmodule.modulestore.tests.django_utils import (
|
||||
ModuleStoreTestCase, mixed_store_config
|
||||
)
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from student.tests.factories import UserFactory, CourseModeFactory
|
||||
from student.models import CourseEnrollment, NonExistentCourseError
|
||||
from enrollment import data
|
||||
|
||||
# Since we don't need any XML course fixtures, use a modulestore configuration
|
||||
# that disables the XML modulestore.
|
||||
MODULESTORE_CONFIG = mixed_store_config(settings.COMMON_TEST_DATA_ROOT, {}, include_xml=False)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@override_settings(MODULESTORE=MODULESTORE_CONFIG)
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
class EnrollmentDataTest(ModuleStoreTestCase):
|
||||
"""
|
||||
Test course enrollment data aggregation.
|
||||
|
||||
"""
|
||||
USERNAME = "Bob"
|
||||
EMAIL = "bob@example.com"
|
||||
PASSWORD = "edx"
|
||||
|
||||
def setUp(self):
|
||||
""" Create a course and user, then log in. """
|
||||
super(EnrollmentDataTest, self).setUp()
|
||||
self.course = CourseFactory.create()
|
||||
self.user = UserFactory.create(username=self.USERNAME, email=self.EMAIL, password=self.PASSWORD)
|
||||
self.client.login(username=self.USERNAME, password=self.PASSWORD)
|
||||
|
||||
@ddt.data(
|
||||
# Default (no course modes in the database)
|
||||
# Expect that users are automatically enrolled as "honor".
|
||||
([], 'honor'),
|
||||
|
||||
# Audit / Verified / Honor
|
||||
# We should always go to the "choose your course" page.
|
||||
# We should also be enrolled as "honor" by default.
|
||||
(['honor', 'verified', 'audit'], 'honor'),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_enroll(self, course_modes, enrollment_mode):
|
||||
# Create the course modes (if any) required for this test case
|
||||
self._create_course_modes(course_modes)
|
||||
|
||||
enrollment = data.update_course_enrollment(
|
||||
self.user.username,
|
||||
unicode(self.course.id),
|
||||
mode=enrollment_mode,
|
||||
is_active=True
|
||||
)
|
||||
|
||||
self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course.id))
|
||||
course_mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
|
||||
self.assertTrue(is_active)
|
||||
self.assertEqual(course_mode, enrollment_mode)
|
||||
|
||||
# Confirm the returned enrollment and the data match up.
|
||||
self.assertEqual(course_mode, enrollment['mode'])
|
||||
self.assertEqual(is_active, enrollment['is_active'])
|
||||
|
||||
def test_unenroll(self):
|
||||
# Enroll the student in the course
|
||||
CourseEnrollment.enroll(self.user, self.course.id, mode="honor")
|
||||
|
||||
enrollment = data.update_course_enrollment(
|
||||
self.user.username,
|
||||
unicode(self.course.id),
|
||||
is_active=False
|
||||
)
|
||||
|
||||
# Determine that the returned enrollment is inactive.
|
||||
self.assertFalse(enrollment['is_active'])
|
||||
|
||||
# Expect that we're no longer enrolled
|
||||
self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course.id))
|
||||
|
||||
@ddt.data(
|
||||
# No course modes, no course enrollments.
|
||||
([]),
|
||||
|
||||
# Audit / Verified / Honor course modes, with three course enrollments.
|
||||
(['honor', 'verified', 'audit']),
|
||||
)
|
||||
def test_get_course_info(self, course_modes):
|
||||
self._create_course_modes(course_modes, course=self.course)
|
||||
result_course = data.get_course_enrollment_info(unicode(self.course.id))
|
||||
result_slugs = [mode['slug'] for mode in result_course['course_modes']]
|
||||
for course_mode in course_modes:
|
||||
self.assertIn(course_mode, result_slugs)
|
||||
|
||||
@ddt.data(
|
||||
# No course modes, no course enrollments.
|
||||
([], []),
|
||||
|
||||
# Audit / Verified / Honor course modes, with three course enrollments.
|
||||
(['honor', 'verified', 'audit'], ['1', '2', '3']),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_get_course_enrollments(self, course_modes, course_numbers):
|
||||
# Create all the courses
|
||||
created_courses = []
|
||||
for course_number in course_numbers:
|
||||
created_courses.append(CourseFactory.create(number=course_number))
|
||||
|
||||
created_enrollments = []
|
||||
for course in created_courses:
|
||||
self._create_course_modes(course_modes, course=course)
|
||||
# Create the original enrollment.
|
||||
created_enrollments.append(data.update_course_enrollment(
|
||||
self.user.username,
|
||||
unicode(course.id),
|
||||
))
|
||||
|
||||
# Compare the created enrollments with the results
|
||||
# from the get enrollments request.
|
||||
results = data.get_course_enrollments(self.user.username)
|
||||
self.assertEqual(results, created_enrollments)
|
||||
|
||||
@ddt.data(
|
||||
# Default (no course modes in the database)
|
||||
# Expect that users are automatically enrolled as "honor".
|
||||
([], 'honor'),
|
||||
|
||||
# Audit / Verified / Honor
|
||||
# We should always go to the "choose your course" page.
|
||||
# We should also be enrolled as "honor" by default.
|
||||
(['honor', 'verified', 'audit'], 'verified'),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_get_course_enrollment(self, course_modes, enrollment_mode):
|
||||
self._create_course_modes(course_modes)
|
||||
|
||||
# Try to get an enrollment before it exists.
|
||||
result = data.get_course_enrollment(self.user.username, unicode(self.course.id))
|
||||
self.assertIsNone(result)
|
||||
|
||||
# Create the original enrollment.
|
||||
enrollment = data.update_course_enrollment(
|
||||
self.user.username,
|
||||
unicode(self.course.id),
|
||||
mode=enrollment_mode,
|
||||
is_active=True
|
||||
)
|
||||
# Get the enrollment and compare it to the original.
|
||||
result = data.get_course_enrollment(self.user.username, unicode(self.course.id))
|
||||
self.assertEqual(enrollment, result)
|
||||
|
||||
@raises(NonExistentCourseError)
|
||||
def test_non_existent_course(self):
|
||||
data.get_course_enrollment_info("this/is/bananas")
|
||||
|
||||
def _create_course_modes(self, course_modes, course=None):
|
||||
course_id = course.id if course else self.course.id
|
||||
for mode_slug in course_modes:
|
||||
CourseModeFactory.create(
|
||||
course_id=course_id,
|
||||
mode_slug=mode_slug,
|
||||
mode_display_name=mode_slug,
|
||||
)
|
||||
152
common/djangoapps/enrollment/tests/test_views.py
Normal file
152
common/djangoapps/enrollment/tests/test_views.py
Normal file
@@ -0,0 +1,152 @@
|
||||
"""
|
||||
Tests for student enrollment.
|
||||
"""
|
||||
import ddt
|
||||
import json
|
||||
import unittest
|
||||
|
||||
from django.test.utils import override_settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
from rest_framework import status
|
||||
from django.conf import settings
|
||||
from xmodule.modulestore.tests.django_utils import (
|
||||
ModuleStoreTestCase, mixed_store_config
|
||||
)
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from student.tests.factories import UserFactory, CourseModeFactory
|
||||
from student.models import CourseEnrollment
|
||||
|
||||
# Since we don't need any XML course fixtures, use a modulestore configuration
|
||||
# that disables the XML modulestore.
|
||||
MODULESTORE_CONFIG = mixed_store_config(settings.COMMON_TEST_DATA_ROOT, {}, include_xml=False)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@override_settings(MODULESTORE=MODULESTORE_CONFIG)
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
class EnrollmentTest(ModuleStoreTestCase, APITestCase):
|
||||
"""
|
||||
Test student enrollment, especially with different course modes.
|
||||
"""
|
||||
USERNAME = "Bob"
|
||||
EMAIL = "bob@example.com"
|
||||
PASSWORD = "edx"
|
||||
|
||||
def setUp(self):
|
||||
""" Create a course and user, then log in. """
|
||||
super(EnrollmentTest, self).setUp()
|
||||
self.course = CourseFactory.create()
|
||||
self.user = UserFactory.create(username=self.USERNAME, email=self.EMAIL, password=self.PASSWORD)
|
||||
self.client.login(username=self.USERNAME, password=self.PASSWORD)
|
||||
|
||||
@ddt.data(
|
||||
# Default (no course modes in the database)
|
||||
# Expect that users are automatically enrolled as "honor".
|
||||
([], 'honor'),
|
||||
|
||||
# Audit / Verified / Honor
|
||||
# We should always go to the "choose your course" page.
|
||||
# We should also be enrolled as "honor" by default.
|
||||
(['honor', 'verified', 'audit'], 'honor'),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_enroll(self, course_modes, enrollment_mode):
|
||||
# Create the course modes (if any) required for this test case
|
||||
for mode_slug in course_modes:
|
||||
CourseModeFactory.create(
|
||||
course_id=self.course.id,
|
||||
mode_slug=mode_slug,
|
||||
mode_display_name=mode_slug,
|
||||
)
|
||||
|
||||
# Enroll in the course and verify the URL we get sent to
|
||||
self._create_enrollment()
|
||||
|
||||
self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course.id))
|
||||
course_mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
|
||||
self.assertTrue(is_active)
|
||||
self.assertEqual(course_mode, enrollment_mode)
|
||||
|
||||
def test_enroll_prof_ed(self):
|
||||
# Create the prod ed mode.
|
||||
CourseModeFactory.create(
|
||||
course_id=self.course.id,
|
||||
mode_slug='professional',
|
||||
mode_display_name='Professional Education',
|
||||
)
|
||||
|
||||
# Enroll in the course, this will fail if the mode is not explicitly professional.
|
||||
resp = self.client.post(reverse('courseenrollment', kwargs={'course_id': (unicode(self.course.id))}))
|
||||
self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# While the enrollment wrong is invalid, the response content should have
|
||||
# all the valid enrollment modes.
|
||||
data = json.loads(resp.content)
|
||||
self.assertEqual(unicode(self.course.id), data['course_id'])
|
||||
self.assertEqual(1, len(data['course_modes']))
|
||||
self.assertEqual('professional', data['course_modes'][0]['slug'])
|
||||
|
||||
def test_unenroll(self):
|
||||
# Create a course mode.
|
||||
CourseModeFactory.create(
|
||||
course_id=self.course.id,
|
||||
mode_slug='honor',
|
||||
mode_display_name='Honor',
|
||||
)
|
||||
|
||||
# Create an enrollment
|
||||
resp = self._create_enrollment()
|
||||
|
||||
# Deactivate the enrollment in the course and verify the URL we get sent to
|
||||
resp = self.client.post(reverse(
|
||||
'courseenrollment',
|
||||
kwargs={'course_id': (unicode(self.course.id))}
|
||||
), {'deactivate': True})
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
data = json.loads(resp.content)
|
||||
self.assertEqual(unicode(self.course.id), data['course']['course_id'])
|
||||
self.assertEqual('honor', data['mode'])
|
||||
self.assertFalse(data['is_active'])
|
||||
|
||||
def test_user_not_authenticated(self):
|
||||
# Log out, so we're no longer authenticated
|
||||
self.client.logout()
|
||||
|
||||
# Try to enroll, this should fail.
|
||||
resp = self.client.post(reverse('courseenrollment', kwargs={'course_id': (unicode(self.course.id))}))
|
||||
self.assertEqual(resp.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
def test_unenroll_not_enrolled_in_course(self):
|
||||
# Deactivate the enrollment in the course and verify the URL we get sent to
|
||||
resp = self.client.post(reverse(
|
||||
'courseenrollment',
|
||||
kwargs={'course_id': (unicode(self.course.id))}
|
||||
), {'deactivate': True})
|
||||
self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def test_invalid_enrollment_mode(self):
|
||||
# Request an enrollment with verified mode, which does not exist for this course.
|
||||
resp = self.client.post(reverse(
|
||||
'courseenrollment',
|
||||
kwargs={'course_id': (unicode(self.course.id))}),
|
||||
{'mode': 'verified'}
|
||||
)
|
||||
self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
data = json.loads(resp.content)
|
||||
self.assertEqual(unicode(self.course.id), data['course_id'])
|
||||
self.assertEqual('honor', data['course_modes'][0]['slug'])
|
||||
|
||||
def test_with_invalid_course_id(self):
|
||||
# Create an enrollment
|
||||
resp = self.client.post(reverse('courseenrollment', kwargs={'course_id': 'entirely/fake/course'}))
|
||||
self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def _create_enrollment(self):
|
||||
resp = self.client.post(reverse('courseenrollment', kwargs={'course_id': (unicode(self.course.id))}))
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
data = json.loads(resp.content)
|
||||
self.assertEqual(unicode(self.course.id), data['course']['course_id'])
|
||||
self.assertEqual('honor', data['mode'])
|
||||
self.assertTrue(data['is_active'])
|
||||
return resp
|
||||
@@ -3,12 +3,14 @@ The Enrollment API Views should be simple, lean HTTP endpoints for API access. T
|
||||
consist primarily of authentication, request validation, and serialization.
|
||||
|
||||
"""
|
||||
from rest_framework import status
|
||||
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
|
||||
from student.models import NonExistentCourseError
|
||||
|
||||
|
||||
class EnrollmentUserThrottle(UserRateThrottle):
|
||||
@@ -20,6 +22,17 @@ class EnrollmentUserThrottle(UserRateThrottle):
|
||||
@permission_classes((IsAuthenticated,))
|
||||
@throttle_classes([EnrollmentUserThrottle])
|
||||
def list_student_enrollments(request):
|
||||
"""List out all the enrollments for the current student
|
||||
|
||||
Returns a JSON response with all the course enrollments for the current student.
|
||||
|
||||
Args:
|
||||
request (Request): The GET request for course enrollment listings.
|
||||
|
||||
Returns:
|
||||
A JSON serialized representation of the student's course enrollments.
|
||||
|
||||
"""
|
||||
return Response(api.get_enrollments(request.user.username))
|
||||
|
||||
|
||||
@@ -28,11 +41,36 @@ def list_student_enrollments(request):
|
||||
@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))
|
||||
"""Create, read, or update enrollment information for a student.
|
||||
|
||||
HTTP Endpoint for all CRUD operations for a student 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. A POST request will create a
|
||||
new course enrollment for the current user. If 'mode' or 'deactivate' are found in the
|
||||
POST parameters, the mode can be modified, or the enrollment can be deactivated.
|
||||
course_id (str): URI element specifying the course location. Enrollment information will be
|
||||
returned, created, or updated for this particular course.
|
||||
|
||||
Return:
|
||||
A JSON serialized representation of the course enrollment. If this is a new or modified enrollment,
|
||||
the returned enrollment will reflect all changes.
|
||||
|
||||
"""
|
||||
try:
|
||||
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))
|
||||
except api.CourseModeNotFoundError as error:
|
||||
return Response(status=status.HTTP_400_BAD_REQUEST, data=error.data)
|
||||
except NonExistentCourseError:
|
||||
return Response(status=status.HTTP_400_BAD_REQUEST)
|
||||
except api.EnrollmentNotFoundError:
|
||||
return Response(status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
Reference in New Issue
Block a user