enrollments.py 7.43 KB
Newer Older
1 2 3 4 5 6
"""
Support tool for changing course enrollments.
"""
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.db import transaction
7
from django.db.models import Q
8 9 10 11 12 13 14 15 16 17 18
from django.http import HttpResponseBadRequest
from django.utils.decorators import method_decorator
from django.views.generic import View
from rest_framework.generics import GenericAPIView
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey

from course_modes.models import CourseMode
from edxmako.shortcuts import render_to_response
from enrollment.api import get_enrollments, update_enrollment
from enrollment.errors import CourseModeNotFoundError
Tasawer committed
19
from enrollment.serializers import ModeSerializer
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
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 student.models import CourseEnrollment, ManualEnrollmentAudit, ENROLLED_TO_ENROLLED
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.
    """

    @method_decorator(require_support_permission)
50
    def get(self, request, username_or_email):
51 52 53 54
        """
        Returns a list of enrollments for the given user, along with
        information about previous manual enrollment changes.
        """
55 56 57 58 59 60
        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)
61 62 63 64
        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'])
Tasawer committed
65 66
            # get the all courses modes and replace with existing modes.
            enrollment['course_modes'] = self.get_course_modes(course_key)
67 68 69 70 71 72 73
            # 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)
74
    def post(self, request, username_or_email):
75 76
        """Allows support staff to alter a user's enrollment."""
        try:
77
            user = User.objects.get(Q(username=username_or_email) | Q(email=username_or_email))
78 79 80 81 82
            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']
83
            enrollment = CourseEnrollment.objects.get(user=user, course_id=course_key)
84 85
            if enrollment.mode != old_mode:
                return HttpResponseBadRequest(u'User {username} is not enrolled with mode {old_mode}.'.format(
86
                    username=user.username,
87 88
                    old_mode=old_mode
                ))
Tasawer committed
89 90
            if new_mode == CourseMode.CREDIT_MODE:
                return HttpResponseBadRequest(u'Enrollment cannot be changed to credit mode.')
91 92 93 94
        except KeyError as err:
            return HttpResponseBadRequest(u'The field {} is required.'.format(err.message))
        except InvalidKeyError:
            return HttpResponseBadRequest(u'Could not parse course key.')
95
        except (CourseEnrollment.DoesNotExist, User.DoesNotExist):
96 97
            return HttpResponseBadRequest(
                u'Could not find enrollment for user {username} in course {course}.'.format(
98
                    username=username_or_email,
99 100 101 102 103 104 105
                    course=unicode(course_key)
                )
            )
        try:
            # Wrapped in a transaction so that we can be sure the
            # ManualEnrollmentAudit record is always created correctly.
            with transaction.atomic():
Tasawer committed
106
                update_enrollment(user.username, course_id, mode=new_mode, include_expired=True)
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
                manual_enrollment = ManualEnrollmentAudit.create_manual_enrollment_audit(
                    request.user,
                    enrollment.user.email,
                    ENROLLED_TO_ENROLLED,
                    reason=reason,
                    enrollment=enrollment
                )
                return JsonResponse(ManualEnrollmentSerializer(instance=manual_enrollment).data)
        except CourseModeNotFoundError as err:
            return HttpResponseBadRequest(err.message)

    @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
Tasawer committed
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178

    @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
        )
        return [
            ModeSerializer(mode).data
            for mode in course_modes
        ]