Files
edx-platform/lms/djangoapps/support/views/enrollments.py
uzairr 07821927e2 Add credit modein support enrollment tool.
Currently, enrollment support tool is only allowing support members
to change course enrollment only one of the two modes i.e. audit and
verified.To move a learner other than these modes,they need to ping
devops.To broaden the scope of enrollment support tool,changes have
been done so that enrollment would be changed to other modes as well.

PROD-305
2019-07-19 18:14:35 +05:00

197 lines
8.3 KiB
Python

"""
Support tool for changing course enrollments.
"""
from __future__ import absolute_import
import six
from django.contrib.auth.models import User
from django.db import transaction
from django.db.models import Q
from django.http import HttpResponseBadRequest
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.views.generic import View
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from rest_framework.generics import GenericAPIView
from six import text_type
from course_modes.models import CourseMode
from edxmako.shortcuts import render_to_response
from lms.djangoapps.support.decorators import require_support_permission
from lms.djangoapps.support.serializers import ManualEnrollmentSerializer
from lms.djangoapps.verify_student.models import VerificationDeadline
from openedx.core.djangoapps.credit.email_utils import get_credit_provider_attribute_values
from openedx.core.djangoapps.enrollments.api import get_enrollments, update_enrollment
from openedx.core.djangoapps.enrollments.errors import CourseModeNotFoundError
from openedx.core.djangoapps.enrollments.serializers import ModeSerializer
from student.models import ENROLLED_TO_ENROLLED, CourseEnrollment, CourseEnrollmentAttribute, ManualEnrollmentAudit
from util.json_request import JsonResponse
class EnrollmentSupportView(View):
"""
View for viewing and changing learner enrollments, used by the
support team.
"""
@method_decorator(require_support_permission)
def get(self, request):
"""Render the enrollment support tool view."""
return render_to_response('support/enrollment.html', {
'username': request.GET.get('user', ''),
'enrollmentsUrl': reverse('support:enrollment_list'),
'enrollmentSupportUrl': reverse('support:enrollment')
})
class EnrollmentSupportListView(GenericAPIView):
"""
Allows viewing and changing learner enrollments by support
staff.
"""
# TODO: ARCH-91
# This view is excluded from Swagger doc generation because it
# does not specify a serializer class.
exclude_from_schema = True
@method_decorator(require_support_permission)
def get(self, request, username_or_email):
"""
Returns a list of enrollments for the given user, along with
information about previous manual enrollment changes.
"""
try:
user = User.objects.get(Q(username=username_or_email) | Q(email=username_or_email))
except User.DoesNotExist:
return JsonResponse([])
enrollments = get_enrollments(user.username, include_inactive=True)
for enrollment in enrollments:
# Folds the course_details field up into the main JSON object.
enrollment.update(**enrollment.pop('course_details'))
course_key = CourseKey.from_string(enrollment['course_id'])
# get the all courses modes and replace with existing modes.
enrollment['course_modes'] = self.get_course_modes(course_key)
# Add the price of the course's verified mode.
self.include_verified_mode_info(enrollment, course_key)
# Add manual enrollment history, if it exists
enrollment['manual_enrollment'] = self.manual_enrollment_data(enrollment, course_key)
return JsonResponse(enrollments)
@method_decorator(require_support_permission)
def post(self, request, username_or_email):
"""Allows support staff to alter a user's enrollment."""
try:
user = User.objects.get(Q(username=username_or_email) | Q(email=username_or_email))
course_id = request.data['course_id']
course_key = CourseKey.from_string(course_id)
old_mode = request.data['old_mode']
new_mode = request.data['new_mode']
reason = request.data['reason']
enrollment = CourseEnrollment.objects.get(user=user, course_id=course_key)
if enrollment.mode != old_mode:
return HttpResponseBadRequest(u'User {username} is not enrolled with mode {old_mode}.'.format(
username=user.username,
old_mode=old_mode
))
except KeyError as err:
return HttpResponseBadRequest(u'The field {} is required.'.format(text_type(err)))
except InvalidKeyError:
return HttpResponseBadRequest(u'Could not parse course key.')
except (CourseEnrollment.DoesNotExist, User.DoesNotExist):
return HttpResponseBadRequest(
u'Could not find enrollment for user {username} in course {course}.'.format(
username=username_or_email,
course=six.text_type(course_key)
)
)
try:
# Wrapped in a transaction so that we can be sure the
# ManualEnrollmentAudit record is always created correctly.
with transaction.atomic():
update_enrollment(user.username, course_id, mode=new_mode, include_expired=True)
manual_enrollment = ManualEnrollmentAudit.create_manual_enrollment_audit(
request.user,
enrollment.user.email,
ENROLLED_TO_ENROLLED,
reason=reason,
enrollment=enrollment
)
if new_mode == CourseMode.CREDIT_MODE:
provider_ids = get_credit_provider_attribute_values(course_key, 'id')
credit_provider_attr = {
'namespace': 'credit',
'name': 'provider_id',
'value': provider_ids[0],
}
CourseEnrollmentAttribute.add_enrollment_attr(
enrollment=enrollment, data_list=[credit_provider_attr]
)
return JsonResponse(ManualEnrollmentSerializer(instance=manual_enrollment).data)
except CourseModeNotFoundError as err:
return HttpResponseBadRequest(text_type(err))
@staticmethod
def include_verified_mode_info(enrollment_data, course_key):
"""
Add information about the verified mode for the given
`course_key`, if that course has a verified mode.
Args:
enrollment_data (dict): Dictionary representing a single enrollment.
course_key (CourseKey): The course which this enrollment belongs to.
Returns:
None
"""
course_modes = enrollment_data['course_modes']
for mode in course_modes:
if mode['slug'] == CourseMode.VERIFIED:
enrollment_data['verified_price'] = mode['min_price']
enrollment_data['verified_upgrade_deadline'] = mode['expiration_datetime']
enrollment_data['verification_deadline'] = VerificationDeadline.deadline_for_course(course_key)
@staticmethod
def manual_enrollment_data(enrollment_data, course_key):
"""
Returns serialized information about the manual enrollment
belonging to this enrollment, if it exists.
Args:
enrollment_data (dict): Representation of a single course enrollment.
course_key (CourseKey): The course for this enrollment.
Returns:
None: If no manual enrollment change has been made.
dict: Serialization of the latest manual enrollment change.
"""
user = User.objects.get(username=enrollment_data['user'])
enrollment = CourseEnrollment.get_enrollment(user, course_key)
manual_enrollment_audit = ManualEnrollmentAudit.get_manual_enrollment(enrollment)
if manual_enrollment_audit is None:
return {}
return ManualEnrollmentSerializer(instance=manual_enrollment_audit).data
@staticmethod
def get_course_modes(course_key):
"""
Returns a list of all modes including expired modes for a given course id
Arguments:
course_id (CourseKey): Search for course modes for this course.
Returns:
list of `Mode`
"""
course_modes = CourseMode.modes_for_course(
course_key,
include_expired=True,
exclude_credit=False
)
return [
ModeSerializer(mode).data
for mode in course_modes
]