Allow enrollments in expired modes to be deactivated
Facilitates revocation of enrollments in expired modes. XCOM-490.
This commit is contained in:
@@ -137,9 +137,11 @@ def add_enrollment(user_id, course_id, mode='honor', is_active=True):
|
||||
|
||||
Enrolls a user in a course. If the mode is not specified, this will default to 'honor'.
|
||||
|
||||
Args:
|
||||
Arguments:
|
||||
user_id (str): The user to enroll.
|
||||
course_id (str): The course to enroll the user in.
|
||||
|
||||
Keyword Arguments:
|
||||
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
|
||||
@@ -177,7 +179,7 @@ def add_enrollment(user_id, course_id, mode='honor', is_active=True):
|
||||
}
|
||||
}
|
||||
"""
|
||||
_validate_course_mode(course_id, mode)
|
||||
_validate_course_mode(course_id, mode, is_active=is_active)
|
||||
return _data_api().create_course_enrollment(user_id, course_id, mode, is_active)
|
||||
|
||||
|
||||
@@ -186,11 +188,14 @@ def update_enrollment(user_id, course_id, mode=None, is_active=None, enrollment_
|
||||
|
||||
Update a course enrollment for the given user and course.
|
||||
|
||||
Args:
|
||||
Arguments:
|
||||
user_id (str): The user associated with the updated enrollment.
|
||||
course_id (str): The course associated with the updated enrollment.
|
||||
|
||||
Keyword Arguments:
|
||||
mode (str): The new course mode for this enrollment.
|
||||
is_active (bool): Sets whether the enrollment is active or not.
|
||||
enrollment_attributes (list): Attributes to be set the enrollment.
|
||||
|
||||
Returns:
|
||||
A serializable dictionary representing the updated enrollment.
|
||||
@@ -226,7 +231,7 @@ def update_enrollment(user_id, course_id, mode=None, is_active=None, enrollment_
|
||||
|
||||
"""
|
||||
if mode is not None:
|
||||
_validate_course_mode(course_id, mode)
|
||||
_validate_course_mode(course_id, mode, is_active=is_active)
|
||||
enrollment = _data_api().update_course_enrollment(user_id, course_id, mode=mode, is_active=is_active)
|
||||
if enrollment is None:
|
||||
msg = u"Course Enrollment not found for user {user} in course {course}".format(user=user_id, course=course_id)
|
||||
@@ -353,7 +358,7 @@ def get_enrollment_attributes(user_id, course_id):
|
||||
return _data_api().get_enrollment_attributes(user_id, course_id)
|
||||
|
||||
|
||||
def _validate_course_mode(course_id, mode):
|
||||
def _validate_course_mode(course_id, mode, is_active=None):
|
||||
"""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
|
||||
@@ -363,17 +368,24 @@ def _validate_course_mode(course_id, mode):
|
||||
'honor', return true, allowing the enrollment to be 'honor' even if the mode is not explicitly
|
||||
set for the course.
|
||||
|
||||
Args:
|
||||
Arguments:
|
||||
course_id (str): The course to check against for available course modes.
|
||||
mode (str): The slug for the course mode specified in the enrollment.
|
||||
|
||||
Keyword Arguments:
|
||||
is_active (bool): Whether the enrollment is to be activated or deactivated.
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
Raises:
|
||||
CourseModeNotFound: raised if the course mode is not found.
|
||||
"""
|
||||
course_enrollment_info = _data_api().get_course_enrollment_info(course_id)
|
||||
# If the client has requested an enrollment deactivation, we want to include expired modes
|
||||
# in the set of available modes. This allows us to unenroll users from expired modes.
|
||||
include_expired = not is_active if is_active is not None else False
|
||||
|
||||
course_enrollment_info = _data_api().get_course_enrollment_info(course_id, include_expired=include_expired)
|
||||
course_modes = course_enrollment_info["course_modes"]
|
||||
available_modes = [m['slug'] for m in course_modes]
|
||||
if mode not in available_modes:
|
||||
|
||||
@@ -40,7 +40,11 @@ class CourseField(serializers.RelatedField):
|
||||
|
||||
def to_native(self, course, **kwargs):
|
||||
course_modes = ModeSerializer(
|
||||
CourseMode.modes_for_course(course.id, kwargs.get('include_expired', False), only_selectable=False)
|
||||
CourseMode.modes_for_course(
|
||||
course.id,
|
||||
include_expired=kwargs.get('include_expired', False),
|
||||
only_selectable=False
|
||||
)
|
||||
).data # pylint: disable=no-member
|
||||
|
||||
return {
|
||||
|
||||
@@ -18,6 +18,7 @@ from django.conf import settings
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, check_mongo_calls_range
|
||||
from django.test.utils import override_settings
|
||||
import pytz
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from embargo.models import CountryAccessRule, Country, RestrictedCourse
|
||||
@@ -716,6 +717,26 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase):
|
||||
expected_status=expected_status,
|
||||
)
|
||||
|
||||
def test_deactivate_enrollment_expired_mode(self):
|
||||
"""Verify that an enrollment in an expired mode can be deactivated."""
|
||||
for mode in (CourseMode.HONOR, CourseMode.VERIFIED):
|
||||
CourseModeFactory.create(
|
||||
course_id=self.course.id,
|
||||
mode_slug=mode,
|
||||
mode_display_name=mode,
|
||||
)
|
||||
|
||||
# Create verified enrollment.
|
||||
self.assert_enrollment_status(as_server=True, mode=CourseMode.VERIFIED)
|
||||
|
||||
# Change verified mode expiration.
|
||||
mode = CourseMode.objects.get(course_id=self.course.id, mode_slug=CourseMode.VERIFIED)
|
||||
mode.expiration_datetime = datetime.datetime(year=1970, month=1, day=1, tzinfo=pytz.utc)
|
||||
mode.save()
|
||||
|
||||
# Deactivate enrollment.
|
||||
self.assert_enrollment_activation(False, CourseMode.VERIFIED)
|
||||
|
||||
def test_change_mode_from_user(self):
|
||||
"""Users should not be able to alter the enrollment mode on an enrollment. """
|
||||
# Create an honor and verified mode for a course. This allows an update.
|
||||
|
||||
@@ -598,7 +598,7 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn):
|
||||
data={
|
||||
"message": (
|
||||
u"The course mode '{mode}' is not available for course '{course_id}'."
|
||||
).format(mode="honor", course_id=course_id),
|
||||
).format(mode=mode, course_id=course_id),
|
||||
"course_details": error.data
|
||||
})
|
||||
except CourseNotFoundError:
|
||||
|
||||
Reference in New Issue
Block a user