Commit 0434c493 by Albert St. Aubin

Added Entitlement enroll and unenroll logic to the Enrollment API

parent 96f35451
...@@ -18,6 +18,7 @@ from rest_framework.views import APIView ...@@ -18,6 +18,7 @@ from rest_framework.views import APIView
from course_modes.models import CourseMode from course_modes.models import CourseMode
from enrollment import api from enrollment import api
from enrollment.errors import CourseEnrollmentError, CourseEnrollmentExistsError, CourseModeNotFoundError from enrollment.errors import CourseEnrollmentError, CourseEnrollmentExistsError, CourseModeNotFoundError
from entitlements.models import CourseEntitlement
from openedx.core.djangoapps.cors_csrf.authentication import SessionAuthenticationCrossDomainCsrf from openedx.core.djangoapps.cors_csrf.authentication import SessionAuthenticationCrossDomainCsrf
from openedx.core.djangoapps.cors_csrf.decorators import ensure_csrf_cookie_cross_domain from openedx.core.djangoapps.cors_csrf.decorators import ensure_csrf_cookie_cross_domain
from openedx.core.djangoapps.embargo import api as embargo_api from openedx.core.djangoapps.embargo import api as embargo_api
...@@ -36,6 +37,7 @@ from openedx.features.enterprise_support.api import ( ...@@ -36,6 +37,7 @@ from openedx.features.enterprise_support.api import (
enterprise_enabled enterprise_enabled
) )
from student.auth import user_has_role from student.auth import user_has_role
from student.models import CourseEnrollment
from student.models import User from student.models import User
from student.roles import CourseStaffRole, GlobalStaff from student.roles import CourseStaffRole, GlobalStaff
from util.disable_rate_limit import can_disable_rate_limit from util.disable_rate_limit import can_disable_rate_limit
...@@ -528,25 +530,11 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): ...@@ -528,25 +530,11 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn):
# Get the User, Course ID, and Mode from the request. # Get the User, Course ID, and Mode from the request.
username = request.data.get('user', request.user.username) username = request.data.get('user', request.user.username)
# Note that course_id is actually the Course Run Key
course_id = request.data.get('course_details', {}).get('course_id') course_id = request.data.get('course_details', {}).get('course_id')
course_uuid = request.data.get('course_details', {}).get('course_uuid')
if not course_id:
return Response(
status=status.HTTP_400_BAD_REQUEST,
data={"message": u"Course ID must be specified to create a new enrollment."}
)
try:
course_id = CourseKey.from_string(course_id)
except InvalidKeyError:
return Response(
status=status.HTTP_400_BAD_REQUEST,
data={
"message": u"No course '{course_id}' found for enrollment".format(course_id=course_id)
}
)
mode = request.data.get('mode') mode = request.data.get('mode')
is_active = request.data.get('is_active')
has_api_key_permissions = self.has_api_key_permissions(request) has_api_key_permissions = self.has_api_key_permissions(request)
...@@ -579,13 +567,46 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): ...@@ -579,13 +567,46 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn):
} }
) )
course_entitlement = None
if course_uuid:
course_entitlement = CourseEntitlement.get_active_user_course_entitlements(user, course_uuid)
if course_entitlement and course_entitlement.enrollment_course_run is not None and is_active:
return Response(
status=status.HTTP_400_BAD_REQUEST,
data={
"message": u"Entitlement for {course_uuid} already has an enrollment applied".format(
course_uuid=course_uuid
)
}
)
if not course_id:
if course_entitlement and course_entitlement.enrollment_course_run is not None:
course_id = course_entitlement.enrollment_course_run.course_id
else:
return Response(
status=status.HTTP_400_BAD_REQUEST,
data={"message": u"Course ID must be specified to create a new enrollment."}
)
else:
try:
course_id = CourseKey.from_string(course_id)
except InvalidKeyError:
return Response(
status=status.HTTP_400_BAD_REQUEST,
data={
"message": u"No course '{course_id}' found for enrollment".format(course_id=course_id)
}
)
embargo_response = embargo_api.get_embargo_response(request, course_id, user) embargo_response = embargo_api.get_embargo_response(request, course_id, user)
if embargo_response: if embargo_response:
return embargo_response return embargo_response
try: try:
is_active = request.data.get('is_active')
# Check if the requested activation status is None or a Boolean # Check if the requested activation status is None or a Boolean
if is_active is not None and not isinstance(is_active, bool): if is_active is not None and not isinstance(is_active, bool):
return Response( return Response(
...@@ -612,60 +633,87 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): ...@@ -612,60 +633,87 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn):
} }
consent_client.provide_consent(**kwargs) consent_client.provide_consent(**kwargs)
enrollment_attributes = request.data.get('enrollment_attributes') # Add Enrollment for the Entitlement user with the correct Mode
enrollment = api.get_enrollment(username, unicode(course_id)) # This should only occur if the User has a Course Entitlement in place.
mode_changed = enrollment and mode is not None and enrollment['mode'] != mode # As a reault the api_key_permissions do not apply the User may enroll themselves based on the entitlement.
active_changed = enrollment and is_active is not None and enrollment['is_active'] != is_active if course_entitlement and is_active:
missing_attrs = [] mode = course_entitlement.mode
if enrollment_attributes: response = api.add_enrollment(
actual_attrs = [
u"{namespace}:{name}".format(**attr)
for attr in enrollment_attributes
]
missing_attrs = set(REQUIRED_ATTRIBUTES.get(mode, [])) - set(actual_attrs)
if has_api_key_permissions and (mode_changed or active_changed):
if mode_changed and active_changed and not is_active:
# if the requester wanted to deactivate but specified the wrong mode, fail
# the request (on the assumption that the requester had outdated information
# about the currently active enrollment).
msg = u"Enrollment mode mismatch: active mode={}, requested mode={}. Won't deactivate.".format(
enrollment["mode"], mode
)
log.warning(msg)
return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg})
if len(missing_attrs) > 0:
msg = u"Missing enrollment attributes: requested mode={} required attributes={}".format(
mode, REQUIRED_ATTRIBUTES.get(mode)
)
log.warning(msg)
return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg})
response = api.update_enrollment(
username, username,
unicode(course_id), unicode(course_id),
mode=mode, mode=mode,
is_active=is_active, is_active=True,
enrollment_attributes=enrollment_attributes,
# If we are updating enrollment by authorized api caller, we should allow expired modes
include_expired=has_api_key_permissions
) )
else: CourseEntitlement.set_enrollment(
# Will reactivate inactive enrollments. entitlement=course_entitlement,
response = api.add_enrollment( enrollment=CourseEnrollment.get_enrollment(user, course_id)
)
log.info('Enrolling [%s] entitlement for run [%s] of Course [%s].', username, course_id, course_uuid)
elif course_entitlement and not is_active:
# Unenroll the course as part of the entitlement
response = api.update_enrollment(
username, username,
unicode(course_id), unicode(course_id),
mode=mode, mode=mode,
is_active=is_active, is_active=is_active,
enrollment_attributes=enrollment_attributes
) )
CourseEntitlement.set_enrollment(course_entitlement, None)
log.info('Unenrolling [%s] entitlement for run [%s] of Course [%s].', username, course_id, course_uuid)
else:
enrollment_attributes = request.data.get('enrollment_attributes')
enrollment = api.get_enrollment(username, unicode(course_id))
mode_changed = enrollment and mode is not None and enrollment['mode'] != mode
active_changed = enrollment and is_active is not None and enrollment['is_active'] != is_active
missing_attrs = []
if enrollment_attributes:
actual_attrs = [
u"{namespace}:{name}".format(**attr)
for attr in enrollment_attributes
]
missing_attrs = set(REQUIRED_ATTRIBUTES.get(mode, [])) - set(actual_attrs)
if has_api_key_permissions and (mode_changed or active_changed):
if mode_changed and active_changed and not is_active:
# if the requester wanted to deactivate but specified the wrong mode, fail
# the request (on the assumption that the requester had outdated information
# about the currently active enrollment).
msg = u"Enrollment mode mismatch: active mode={}, requested mode={}. Won't deactivate.".format(
enrollment["mode"], mode
)
log.warning(msg)
return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg})
if len(missing_attrs) > 0:
msg = u"Missing enrollment attributes: requested mode={} required attributes={}".format(
mode, REQUIRED_ATTRIBUTES.get(mode)
)
log.warning(msg)
return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg})
response = api.update_enrollment(
username,
unicode(course_id),
mode=mode,
is_active=is_active,
enrollment_attributes=enrollment_attributes,
# If we are updating enrollment by authorized api caller, we should allow expired modes
include_expired=has_api_key_permissions
)
else:
# Will reactivate inactive enrollments.
response = api.add_enrollment(
username,
unicode(course_id),
mode=mode,
is_active=is_active,
enrollment_attributes=enrollment_attributes
)
email_opt_in = request.data.get('email_opt_in', None) email_opt_in = request.data.get('email_opt_in', None)
if email_opt_in is not None: if email_opt_in is not None:
org = course_id.org org = course_id.org
update_email_opt_in(request.user, org, email_opt_in) update_email_opt_in(request.user, org, email_opt_in)
log.info('The user [%s] has already been enrolled in course run [%s].', username, course_id) log.info('The user [%s] has already been enrolled in course run [%s].', username, course_id)
return Response(response) return Response(response)
except CourseModeNotFoundError as error: except CourseModeNotFoundError as error:
return Response( return Response(
......
...@@ -24,3 +24,26 @@ class CourseEntitlement(TimeStampedModel): ...@@ -24,3 +24,26 @@ class CourseEntitlement(TimeStampedModel):
help_text='The current Course enrollment for this entitlement. If NULL the Learner has not enrolled.' help_text='The current Course enrollment for this entitlement. If NULL the Learner has not enrolled.'
) )
order_number = models.CharField(max_length=128, null=True) order_number = models.CharField(max_length=128, null=True)
@classmethod
def get_active_user_course_entitlements(cls, user, course_uuid):
"""
Returns all the available sessions for a given course.
"""
try:
entitlement = cls.objects.get(
user=user,
course_uuid=course_uuid,
expired_at=None,
)
return entitlement
except cls.DoesNotExist:
return None
@classmethod
def set_enrollment(cls, entitlement, enrollment):
"""
Fulfills an entitlement by specifying a session.
"""
cls.objects.filter(id=entitlement.id).update(enrollment_course_run=enrollment)
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