Ejemplo n.º 1
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)
Ejemplo n.º 2
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)
Ejemplo n.º 3
0
def get_course_from_id(course_id):
    """
    Get Course object using the `course_id`.

    Arguments:
        course_id (str) :   ID of the course

    Returns:
        Course
    """
    course_key = CourseKey.from_string(course_id)
    return get_course_overviews([course_key])[0]
Ejemplo n.º 4
0
    def patch(self, request):
        """
        Patch method for the view.
        """
        if get_course_overviews is None:
            raise NotConnectedToOpenEdX(
                _('To use this endpoint, this package must be '
                  'installed in an Open edX environment.'))

        user = request.user
        enterprise_customer_id = request.query_params.get(
            'enterprise_id', None)
        course_id = request.query_params.get('course_id', None)
        marked_done = request.query_params.get('marked_done', None)
        if not enterprise_customer_id or not course_id or marked_done is None:
            return Response(
                {
                    'error':
                    'enterprise_id, course_id, and marked_done must be provided as query parameters'
                },
                status=HTTP_400_BAD_REQUEST)

        enterprise_customer_user = get_object_or_404(
            EnterpriseCustomerUser,
            user_id=user.id,
            enterprise_customer__uuid=enterprise_customer_id,
        )

        enterprise_enrollment = get_object_or_404(
            EnterpriseCourseEnrollment,
            enterprise_customer_user=enterprise_customer_user,
            course_id=course_id)

        enterprise_enrollment.marked_done = marked_done
        enterprise_enrollment.save()

        course_overviews = get_course_overviews([course_id])
        data = EnterpriseCourseEnrollmentSerializer(
            enterprise_enrollment,
            context={
                'request': request,
                'course_overviews': course_overviews
            },
        ).data

        return Response(data)
Ejemplo n.º 5
0
    def patch(self, request):
        """
        Patch method for the view.
        """
        if get_course_overviews is None:
            raise NotConnectedToOpenEdX(
                _('To use this endpoint, this package must be '
                  'installed in an Open edX environment.')
            )

        user = request.user
        enterprise_customer_id = request.query_params.get('enterprise_id', None)
        course_id = request.query_params.get('course_id', None)
        saved_for_later = request.query_params.get('saved_for_later', None)

        if not enterprise_customer_id or not course_id or saved_for_later is None:
            return Response(
                {'error': 'enterprise_id, course_id, and saved_for_later must be provided as query parameters'},
                status=HTTP_400_BAD_REQUEST
            )

        enterprise_customer_user = get_object_or_404(
            EnterpriseCustomerUser,
            user_id=user.id,
            enterprise_customer__uuid=enterprise_customer_id,
        )

        enterprise_enrollment = get_object_or_404(
            EnterpriseCourseEnrollment,
            enterprise_customer_user=enterprise_customer_user,
            course_id=course_id
        )

        # TODO: For now, this makes the change backward compatible, we will change this to true boolean support
        enterprise_enrollment.saved_for_later = saved_for_later.lower() == 'true'

        enterprise_enrollment.save()

        course_overviews = get_course_overviews([course_id])
        data = EnterpriseCourseEnrollmentSerializer(
            enterprise_enrollment,
            context={'request': request, 'course_overviews': course_overviews},
        ).data

        return Response(data)
Ejemplo n.º 6
0
    def get(self, request):
        """
        Get method for the view.
        """
        if get_course_overviews is None:
            raise NotConnectedToOpenEdX(
                _('To use this endpoint, this package must be '
                  'installed in an Open edX environment.'))

        user = request.user
        enterprise_customer_id = request.query_params.get(
            'enterprise_id', None)
        if not enterprise_customer_id:
            return Response(
                {
                    'error':
                    'enterprise_id must be provided as a query parameter'
                },
                status=HTTP_400_BAD_REQUEST)

        enterprise_customer_user = get_object_or_404(
            EnterpriseCustomerUser,
            user_id=user.id,
            enterprise_customer__uuid=enterprise_customer_id,
        )
        enterprise_enrollments = EnterpriseCourseEnrollment.objects.filter(
            enterprise_customer_user=enterprise_customer_user)

        course_overviews = get_course_overviews(
            enterprise_enrollments.values_list('course_id', flat=True))

        data = EnterpriseCourseEnrollmentSerializer(
            enterprise_enrollments,
            many=True,
            context={
                'request': request,
                'course_overviews': course_overviews
            },
        ).data

        return Response(data)
Ejemplo n.º 7
0
    def test_get_course_overviews(self):
        """
        get_course_overviews should return the expected CourseOverview data
        in serialized form (a list of dicts)
        """
        course_ids = []
        course_ids.append(str(CourseOverview.objects.first().id))
        course_ids.append(str(CourseOverview.objects.last().id))

        data = get_course_overviews(course_ids)
        assert len(data) == 2
        for overview in data:
            assert overview['id'] in course_ids

        fields = [
            'display_name_with_default',
            'has_started',
            'has_ended',
            'pacing',
        ]
        for field in fields:
            assert field in data[0]
Ejemplo n.º 8
0
    def get(self, request):
        """
        Returns a list of EnterpriseCourseEnrollment data related to the requesting user.

        Example response:

        [
            {
                "certificate_download_url": null,
                "course_run_id": "course-v1:edX+DemoX+Demo_Course",
                "course_run_status": "in_progress",
                "start_date": "2013-02-05T06:00:00Z",
                "end_date": null,
                "display_name": "edX Demonstration Course",
                "course_run_url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/course/",
                "due_dates": [],
                "pacing": "instructor",
                "org_name": "edX",
                "is_revoked": false,
                "is_enrollment_active": true
            }
        ]

        Query params:
          'enterprise_id' (UUID string, required): The enterprise customer UUID with which to filter
            EnterpriseCustomerRecords by.
          'is_active' (boolean string, optional): If provided, will filter the resulting list of enterprise
            enrollment records to only those for which the corresponding ``student.CourseEnrollment``
            record has an ``is_active`` equal to the provided boolean value ('true' or 'false').
        """
        if get_course_overviews is None:
            raise NotConnectedToOpenEdX(
                _('To use this endpoint, this package must be '
                  'installed in an Open edX environment.')
            )

        user = request.user
        enterprise_customer_id = request.query_params.get('enterprise_id', None)
        if not enterprise_customer_id:
            return Response(
                {'error': 'enterprise_id must be provided as a query parameter'},
                status=HTTP_400_BAD_REQUEST
            )

        enterprise_customer_user = get_object_or_404(
            EnterpriseCustomerUser,
            user_id=user.id,
            enterprise_customer__uuid=enterprise_customer_id,
        )
        enterprise_enrollments = EnterpriseCourseEnrollment.objects.filter(
            enterprise_customer_user=enterprise_customer_user
        )

        course_overviews = get_course_overviews(enterprise_enrollments.values_list('course_id', flat=True))

        data = EnterpriseCourseEnrollmentSerializer(
            enterprise_enrollments,
            many=True,
            context={'request': request, 'course_overviews': course_overviews},
        ).data

        if request.query_params.get('is_active'):
            is_active_filter_value = None
            if request.query_params['is_active'].lower() == 'true':
                is_active_filter_value = True
            if request.query_params['is_active'].lower() == 'false':
                is_active_filter_value = False
            if is_active_filter_value is not None:
                data = [
                    record for record in data
                    if record['is_enrollment_active'] == is_active_filter_value
                ]

        return Response(data)
Ejemplo n.º 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)