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
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))
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]
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
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
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)
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)
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)
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)