Commit d9e47a6b by Stephen Sanchez

Merge pull request #5695 from edx/sanchez/enrollment-tests-and-cleanup

enrollment tests and cleanup
parents aba600cf e7570c52
...@@ -3,13 +3,29 @@ Data Aggregation Layer of the Enrollment API. Collects all enrollment specific d ...@@ -3,13 +3,29 @@ Data Aggregation Layer of the Enrollment API. Collects all enrollment specific d
source to be used throughout the API. source to be used throughout the API.
""" """
import logging
from django.contrib.auth.models import User from django.contrib.auth.models import User
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
from enrollment.serializers import CourseEnrollmentSerializer from xmodule.modulestore.django import modulestore
from student.models import CourseEnrollment 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): 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( qset = CourseEnrollment.objects.filter(
user__username=student_id, is_active=True user__username=student_id, is_active=True
).order_by('created') ).order_by('created')
...@@ -17,6 +33,18 @@ def get_course_enrollments(student_id): ...@@ -17,6 +33,18 @@ def get_course_enrollments(student_id):
def get_course_enrollment(student_id, course_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) course_key = CourseKey.from_string(course_id)
try: try:
enrollment = CourseEnrollment.objects.get( enrollment = CourseEnrollment.objects.get(
...@@ -28,6 +56,20 @@ def get_course_enrollment(student_id, course_id): ...@@ -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): 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) course_key = CourseKey.from_string(course_id)
student = User.objects.get(username=student_id) student = User.objects.get(username=student_id)
if not CourseEnrollment.is_enrolled(student, course_key): 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): ...@@ -41,8 +83,23 @@ def update_course_enrollment(student_id, course_id, mode=None, is_active=None):
def get_course_enrollment_info(course_id): 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): Args:
pass 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. ...@@ -3,12 +3,36 @@ Serializers for all Course Enrollment related return objects.
""" """
from rest_framework import serializers from rest_framework import serializers
from rest_framework.fields import Field
from student.models import CourseEnrollment from student.models import CourseEnrollment
from course_modes.models import CourseMode 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): 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): def to_native(self, course):
course_id = unicode(course.id) course_id = unicode(course.id)
...@@ -24,8 +48,10 @@ class CourseField(serializers.RelatedField): ...@@ -24,8 +48,10 @@ class CourseField(serializers.RelatedField):
class CourseEnrollmentSerializer(serializers.ModelSerializer): 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() course = CourseField()
...@@ -37,11 +63,17 @@ class CourseEnrollmentSerializer(serializers.ModelSerializer): ...@@ -37,11 +63,17 @@ class CourseEnrollmentSerializer(serializers.ModelSerializer):
class ModeSerializer(serializers.Serializer): 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) slug = serializers.CharField(max_length=100)
name = serializers.CharField(max_length=255) name = serializers.CharField(max_length=255)
min_price = serializers.IntegerField() min_price = serializers.IntegerField()
suggested_prices = serializers.CharField(max_length=255) suggested_prices = StringListField(max_length=255)
currency = serializers.CharField(max_length=8) currency = serializers.CharField(max_length=8)
expiration_datetime = serializers.DateTimeField() expiration_datetime = serializers.DateTimeField()
description = serializers.CharField() description = serializers.CharField()
"""
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 = []
"""
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')
"""
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,
)
"""
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 ...@@ -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. consist primarily of authentication, request validation, and serialization.
""" """
from rest_framework import status
from rest_framework.authentication import OAuth2Authentication, SessionAuthentication from rest_framework.authentication import OAuth2Authentication, SessionAuthentication
from rest_framework.decorators import api_view, authentication_classes, permission_classes, throttle_classes from rest_framework.decorators import api_view, authentication_classes, permission_classes, throttle_classes
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.throttling import UserRateThrottle from rest_framework.throttling import UserRateThrottle
from enrollment import api from enrollment import api
from student.models import NonExistentCourseError
class EnrollmentUserThrottle(UserRateThrottle): class EnrollmentUserThrottle(UserRateThrottle):
...@@ -20,6 +22,17 @@ class EnrollmentUserThrottle(UserRateThrottle): ...@@ -20,6 +22,17 @@ class EnrollmentUserThrottle(UserRateThrottle):
@permission_classes((IsAuthenticated,)) @permission_classes((IsAuthenticated,))
@throttle_classes([EnrollmentUserThrottle]) @throttle_classes([EnrollmentUserThrottle])
def list_student_enrollments(request): 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)) return Response(api.get_enrollments(request.user.username))
...@@ -28,11 +41,36 @@ def list_student_enrollments(request): ...@@ -28,11 +41,36 @@ def list_student_enrollments(request):
@permission_classes((IsAuthenticated,)) @permission_classes((IsAuthenticated,))
@throttle_classes([EnrollmentUserThrottle]) @throttle_classes([EnrollmentUserThrottle])
def get_course_enrollment(request, course_id=None): def get_course_enrollment(request, course_id=None):
if 'mode' in request.DATA: """Create, read, or update enrollment information for a student.
return Response(api.update_enrollment(request.user.username, course_id, request.DATA['mode']))
elif 'deactivate' in request.DATA: HTTP Endpoint for all CRUD operations for a student course enrollment. Allows creation, reading, and
return Response(api.deactivate_enrollment(request.user.username, course_id)) updates of the current enrollment for a particular course.
elif course_id and request.method == 'POST':
return Response(api.add_enrollment(request.user.username, course_id)) Args:
else: request (Request): To get current course enrollment information, a GET request will return
return Response(api.get_enrollment(request.user.username, course_id)) 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)
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