Пример #1
0
    def is_user_enrolled(cls, user, course_id, course_mode):
        """
        Query the enrollment API and determine if a learner is enrolled in a given course run track.

        Args:
            user: The user whose enrollment needs to be checked
            course_mode: The mode with which the enrollment should be checked
            course_id: course id of the course where enrollment should be checked.

        Returns:
            Boolean: Whether or not enrollment exists

        """
        enrollment_client = EnrollmentApiClient()
        try:
            enrollments = enrollment_client.get_course_enrollment(
                user.username, course_id)
            if enrollments and course_mode == enrollments.get('mode'):
                return True
        except HttpClientError as exc:
            logging.error(
                'Error while checking enrollment status of user %(user)s: %(message)s',
                dict(user=user.username, message=str(exc)))
        except KeyError as exc:
            logging.warning(
                'Error while parsing enrollment data of user %(user)s: %(message)s',
                dict(user=user.username, message=str(exc)))
        return False
Пример #2
0
 def clean_course(self):
     """
     Verify course ID and retrieve course details.
     """
     course_id = self.cleaned_data[self.Fields.COURSE].strip()
     if not course_id:
         return None
     try:
         client = EnrollmentApiClient()
         return client.get_course_details(course_id)
     except (HttpClientError, HttpServerError):
         raise ValidationError(ValidationMessages.INVALID_COURSE_ID.format(course_id=course_id))
Пример #3
0
    def _get_all_enrollments(self, enterprise_customer_user):
        """
        Return a list of course ids representing a given EnterpriseCustomerUser's course enrollments,
        including both enterprise and non-enterprise course enrollments

        Args:
            enterprise_customer_user: The instance of EnterpriseCustomerUser
                being rendered with this admin form.
        """
        enrollment_client = EnrollmentApiClient()
        enrollments = enrollment_client.get_enrolled_courses(enterprise_customer_user.username)
        return [enrollment['course_details']['course_id'] for enrollment in enrollments]
Пример #4
0
    def enroll_user(cls, enterprise_customer, user, course_mode, *course_ids):
        """
        Enroll a single user in any number of courses using a particular course mode.

        Args:
            enterprise_customer: The EnterpriseCustomer which is sponsoring the enrollment
            user: The user who needs to be enrolled in the course
            course_mode: The mode with which the enrollment should be created
            *course_ids: An iterable containing any number of course IDs to eventually enroll the user in.

        Returns:
            Boolean: Whether or not enrollment succeeded for all courses specified
        """
        enterprise_customer_user, __ = EnterpriseCustomerUser.objects.get_or_create(
            enterprise_customer=enterprise_customer, user_id=user.id)
        enrollment_client = EnrollmentApiClient()
        succeeded = True
        for course_id in course_ids:
            try:
                enrollment_client.enroll_user_in_course(
                    user.username, course_id, course_mode)
            except HttpClientError as exc:
                # Check if user is already enrolled then we should ignore exception
                if cls.is_user_enrolled(user, course_id, course_mode):
                    succeeded = True
                else:
                    succeeded = False
                    default_message = 'No error message provided'
                    try:
                        error_message = json.loads(exc.content.decode()).get(
                            'message', default_message)
                    except ValueError:
                        error_message = default_message
                    logging.error(
                        'Error while enrolling user %(user)s: %(message)s',
                        dict(user=user.username, message=error_message))
            if succeeded:
                __, created = EnterpriseCourseEnrollment.objects.get_or_create(
                    enterprise_customer_user=enterprise_customer_user,
                    course_id=course_id,
                    defaults={
                        'source':
                        EnterpriseEnrollmentSource.get_source(
                            EnterpriseEnrollmentSource.MANUAL)
                    })
                if created:
                    track_enrollment('admin-enrollment', user.id, course_id)
        return succeeded
Пример #5
0
    def clean_course(self):
        """
        Verify course ID has an associated course in LMS.
        """
        course_id = self.cleaned_data[self.Fields.COURSE].strip()
        client = EnrollmentApiClient()
        # Checks whether a course exist in lms with the given course id.
        if not client.get_course_details(course_id):
            raise ValidationError(
                ValidationMessages.INVALID_COURSE_ID.format(
                    course_id=course_id))

        # Checks whether a course exists in customer catalog.
        if not self.is_course_in_catalog(course_id):
            raise ValidationError(
                ValidationMessages.COURSE_NOT_EXIST_IN_CATALOG)
        return course_id
Пример #6
0
    def license_revoke(self, request, *args, **kwargs):
        """
        Changes the mode for a user's licensed enterprise course enrollments to the "audit" course mode,
        or unenroll the user if no audit mode exists for a given course.

        Will return a response with status 200 if no errors were encountered while modifying the course enrollment,
        or a 422 if any errors were encountered.  The content of the response is of the form:

        {
            'course-v1:puppies': {'success': true, 'message': 'unenrolled'},
            'course-v1:birds': {'success': true, 'message': 'moved to audit'},
            'course-v1:kittens': {'success': true, 'message': 'course already completed'},
            'course-v1:snakes': {'success': false, 'message': 'unenroll_user_from_course returned false'},
            'course-v1:lizards': {'success': false, 'message': 'Some other exception'},
        }

        The first four messages are the values of constants that a client may expect to receive and parse accordingly.
        """
        if not all([get_course_overviews, get_certificate_for_user, CourseMode]):
            raise NotConnectedToOpenEdX(
                _('To use this endpoint, this package must be '
                  'installed in an Open edX environment.')
            )

        request_data = request.data.copy()
        invalid_response = self._validate_license_revoke_data(request_data)
        if invalid_response:
            return invalid_response

        user_id = request_data.get('user_id')
        enterprise_id = request_data.get('enterprise_id')

        enterprise_customer_user = get_object_or_404(
            models.EnterpriseCustomerUser,
            user_id=user_id,
            enterprise_customer=enterprise_id,
        )
        enrollments_by_course_id = self._enrollments_by_course_for_licensed_user(enterprise_customer_user)

        enrollment_api_client = EnrollmentApiClient()

        revocation_results = {}
        any_failures = False
        for course_overview in get_course_overviews(list(enrollments_by_course_id.keys())):
            course_id = str(course_overview.get('id'))
            enterprise_enrollment = enrollments_by_course_id.get(course_id)
            try:
                revocation_status = self._revoke_enrollment(
                    enrollment_api_client, enterprise_enrollment, course_overview
                )
                revocation_results[course_id] = {'success': True, 'message': revocation_status}
            except EnrollmentModificationException as exc:
                revocation_results[course_id] = {'success': False, 'message': str(exc)}
                any_failures = True

        status_code = status.HTTP_200_OK if not any_failures else status.HTTP_422_UNPROCESSABLE_ENTITY
        return Response(revocation_results, status=status_code)
Пример #7
0
    def bulk_licensed_enrollments_expiration(self, request):
        """
        Changes the mode for licensed enterprise course enrollments to the "audit" course mode,
        or unenroll the user if no audit mode exists for each expired license uuid
        """
        expired_license_uuids = get_request_value(
            request, self.REQ_EXP_LICENSE_UUIDS_PARAM, '')

        if not expired_license_uuids:
            return Response('Parameter {} must be provided'.format(
                self.REQ_EXP_LICENSE_UUIDS_PARAM),
                            status=status.HTTP_400_BAD_REQUEST)

        licensed_enrollments = models.LicensedEnterpriseCourseEnrollment.objects.filter(
            license_uuid__in=expired_license_uuids).select_related(
                'enterprise_course_enrollment')

        enrollment_api_client = EnrollmentApiClient()
        course_overviews = get_course_overviews(
            list(
                licensed_enrollments.values_list(
                    'enterprise_course_enrollment__course_id', flat=True)))
        indexed_overviews = {
            overview.get('id'): overview
            for overview in course_overviews
        }

        any_failures = False
        for licensed_enrollment in licensed_enrollments:
            enterprise_course_enrollment = licensed_enrollment.enterprise_course_enrollment
            course_id = enterprise_course_enrollment.course_id
            course_overview = indexed_overviews.get(course_id)
            try:
                termination_status = self._terminate_enrollment(
                    enrollment_api_client, enterprise_course_enrollment,
                    course_overview)
                LOGGER.info((
                    "EnterpriseCourseEnrollment record with enterprise license %s "
                    "unenrolled to status %s."
                ), enterprise_course_enrollment.licensed_with.license_uuid,
                            termination_status)
                if termination_status != self.EnrollmentTerminationStatus.COURSE_COMPLETED:
                    enterprise_course_enrollment.saved_for_later = True
                    enterprise_course_enrollment.save()
            except EnrollmentModificationException as exc:
                LOGGER.error((
                    "Failed to unenroll EnterpriseCourseEnrollment record for enterprise license %s. "
                    "error message %s."
                ), enterprise_course_enrollment.licensed_with.license_uuid,
                             str(exc))
                any_failures = True

        status_code = status.HTTP_200_OK if not any_failures else status.HTTP_422_UNPROCESSABLE_ENTITY
        return Response(status=status_code)
Пример #8
0
    def get_enrolled_course_string(self, enterprise_customer_user):
        """
        Get an HTML string representing the courses the user is enrolled in.
        """
        enrollment_client = EnrollmentApiClient()
        enrolled_courses = enrollment_client.get_enrolled_courses(
            self.username(enterprise_customer_user))
        course_details = []
        courses_client = CourseApiClient()
        for course in enrolled_courses:
            course_id = course['course_details']['course_id']
            name = courses_client.get_course_details(course_id)['name']
            course_details.append({
                'course_id': course_id,
                'course_name': name
            })

        template = '<a href="{url}">{course_name}</a>'
        joiner = '<br/>'
        return joiner.join(
            template.format(
                url=reverse('about_course', args=[course['course_id']]),
                course_name=course['course_name'],
            ) for course in course_details)
Пример #9
0
    def license_revoke(self, request, *args, **kwargs):
        """
        Changes the mode for a user's licensed enterprise course enrollments to the "audit" course mode.
        """
        if get_course_overviews is None or get_certificate_for_user is None:
            raise NotConnectedToOpenEdX(
                _('To use this endpoint, this package must be '
                  'installed in an Open edX environment.'))

        request_data = request.data.copy()
        self._validate_license_revoke_data(request_data)

        user_id = request_data.get('user_id')
        enterprise_id = request_data.get('enterprise_id')
        audit_mode = CourseModes.AUDIT

        enterprise_customer_user = get_object_or_404(
            models.EnterpriseCustomerUser,
            user_id=user_id,
            enterprise_customer=enterprise_id,
        )
        licensed_enrollments = self.queryset.filter(
            enterprise_course_enrollment__enterprise_customer_user=
            enterprise_customer_user)

        enrollments_by_course_id = {
            enrollment.enterprise_course_enrollment.course_id:
            enrollment.enterprise_course_enrollment
            for enrollment in licensed_enrollments
        }
        course_overviews = get_course_overviews(
            list(enrollments_by_course_id.keys()))

        enrollment_api_client = EnrollmentApiClient()
        for course_overview in course_overviews:
            course_run_id = course_overview.get('id')
            enterprise_enrollment = enrollments_by_course_id.get(course_run_id)
            certificate_info = get_certificate_for_user(
                enterprise_customer_user.username, course_run_id) or {}
            course_run_status = get_course_run_status(
                course_overview,
                certificate_info,
                enterprise_enrollment,
            )

            if course_run_status == CourseRunProgressStatuses.COMPLETED:
                # skip updating the enrollment mode for this course as it is already completed, either
                # meaning the user has earned a certificate or the course has ended.
                continue

            try:
                enrollment_api_client.update_course_enrollment_mode_for_user(
                    username=enterprise_customer_user.username,
                    course_id=course_run_id,
                    mode=audit_mode,
                )
                LOGGER.info(
                    'Updated LMS enrollment for User {user} and Enterprise {enterprise} in Course {course_id} '
                    'to Course Mode {mode}.'.format(
                        user=enterprise_customer_user.username,
                        enterprise=enterprise_id,
                        course_id=course_run_id,
                        mode=audit_mode,
                    ))
            except Exception as exc:  # pylint: disable=broad-except
                msg = (
                    'Unable to update LMS enrollment for User {user} and Enterprise {enterprise} in Course {course_id} '
                    'to Course Mode {mode}'.format(
                        user=enterprise_customer_user.username,
                        enterprise=enterprise_id,
                        course_id=course_run_id,
                        mode=audit_mode,
                    ))
                LOGGER.error('{msg}: {exc}'.format(msg=msg, exc=exc))
                return Response(msg,
                                status=status.HTTP_500_INTERNAL_SERVER_ERROR)

            # mark the licensed enterprise course enrollment as "revoked"
            licensed_enrollment = enterprise_enrollment.license
            licensed_enrollment.is_revoked = True
            licensed_enrollment.save()

            # mark the enterprise course enrollment as "saved for later"
            enterprise_enrollment.saved_for_later = True
            enterprise_enrollment.save()

        return Response(status=status.HTTP_204_NO_CONTENT)