def add_or_update_enrollment_attr(user_id, course_id, attributes): """Set enrollment attributes for the enrollment of given user in the course provided. Args: course_id (str): The Course to set enrollment attributes for. user_id (str): The User to set enrollment attributes for. attributes (list): Attributes to be set. Example: >>>add_or_update_enrollment_attr( "Bob", "course-v1-edX-DemoX-1T2015", [ { "namespace": "credit", "name": "provider_id", "value": "hogwarts", }, ] ) """ course_key = CourseKey.from_string(course_id) user = _get_user(user_id) enrollment = CourseEnrollment.get_enrollment(user, course_key) if not _invalid_attribute(attributes) and enrollment is not None: CourseEnrollmentAttribute.add_enrollment_attr(enrollment, attributes)
def add_or_update_enrollment_attr(user_id, course_id, attributes): """Set enrollment attributes for the enrollment of given user in the course provided. Args: course_id (str): The Course to set enrollment attributes for. user_id (str): The User to set enrollment attributes for. attributes (list): Attributes to be set. Example: >>>add_or_update_enrollment_attr( "Bob", "course-v1-edX-DemoX-1T2015", [ { "namespace": "credit", "name": "provider_id", "value": "hogwarts", }, ] ) """ course_key = CourseKey.from_string(course_id) user = _get_user(user_id) enrollment = CourseEnrollment.get_enrollment(user, course_key) if not _invalid_attribute(attributes) and enrollment is not None: CourseEnrollmentAttribute.add_enrollment_attr(enrollment, attributes)
def update_enrollment_mode(self, course_key, user, mode, course_enrollment): """ update the enrollment mode based on the learner existing state. """ # if student already had a enrollment and its mode is same as the provided one if course_enrollment.mode == mode: logger.info( "Student [%s] is already enrolled in Course [%s] in mode [%s].", user.username, course_key, course_enrollment.mode) # set the enrollment to active if its not already active. if not course_enrollment.is_active: course_enrollment.update_enrollment(is_active=True) else: # if student enrollment exists update it to new mode. with transaction.atomic(): course_enrollment.update_enrollment(mode=mode, is_active=True, skip_refund=True) course_enrollment.save() if mode == 'credit': enrollment_attrs = [{ 'namespace': 'credit', 'name': 'provider_id', 'value': course_key.org }] CourseEnrollmentAttribute.add_enrollment_attr( enrollment=course_enrollment, data_list=enrollment_attrs)
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))
def update_enrollments(self, identifier, enrollment_args, options, error_users, success_users, enrollment_attrs=None): """ Update enrollments for a specific user identifier (email or username). """ users = options[identifier].split(",") credit_provider_attr = {} if options['to_mode'] == 'credit': provider_ids = get_credit_provider_attribute_values( enrollment_args.get('course_id'), 'id') credit_provider_attr = { 'namespace': 'credit', 'name': 'provider_id', 'value': provider_ids[0], } for identified_user in users: logger.info(identified_user) try: user_args = {identifier: identified_user} enrollment_args['user'] = User.objects.get(**user_args) enrollments = CourseEnrollment.objects.filter( **enrollment_args) enrollment_attrs = [] with transaction.atomic(): for enrollment in enrollments: enrollment.update_enrollment(mode=options['to_mode']) enrollment.save() if options['to_mode'] == 'credit': enrollment_attrs.append(credit_provider_attr) CourseEnrollmentAttribute.add_enrollment_attr( enrollment=enrollment, data_list=enrollment_attrs) if options['noop']: raise RollbackException('Forced rollback.') except RollbackException: success_users.append(identified_user) continue except Exception as exception: # pylint: disable=broad-except error_users.append((identified_user, exception)) continue success_users.append(identified_user) logger.info('Updated user [%s] to mode [%s]', identified_user, options['to_mode'])
def update_enrollments(self, identifier, enrollment_args, options, error_users, success_users, enrollment_attrs=None): """ Update enrollments for a specific user identifier (email or username). """ users = options[identifier].split(",") credit_provider_attr = {} if options['to_mode'] == 'credit': provider_ids = get_credit_provider_attribute_values( enrollment_args.get('course_id'), 'id' ) credit_provider_attr = { 'namespace': 'credit', 'name': 'provider_id', 'value': provider_ids[0], } for identified_user in users: logger.info(identified_user) try: user_args = { identifier: identified_user } enrollment_args['user'] = User.objects.get(**user_args) enrollments = CourseEnrollment.objects.filter(**enrollment_args) enrollment_attrs = [] with transaction.atomic(): for enrollment in enrollments: enrollment.update_enrollment(mode=options['to_mode']) enrollment.save() if options['to_mode'] == 'credit': enrollment_attrs.append(credit_provider_attr) CourseEnrollmentAttribute.add_enrollment_attr( enrollment=enrollment, data_list=enrollment_attrs ) if options['noop']: raise RollbackException('Forced rollback.') except RollbackException: success_users.append(identified_user) continue except Exception as exception: # pylint: disable=broad-except error_users.append((identified_user, exception)) continue success_users.append(identified_user) logger.info('Updated user [%s] to mode [%s]', identified_user, options['to_mode'])
def test_multiple_refunds_dashbaord_page_error(self): """ Order with mutiple refunds will not throw 500 error when dashboard page will access.""" now = datetime.now(pytz.UTC).replace(microsecond=0) order_date = now + timedelta(days=1) expected_content = '{{"date_placed": "{date}"}}'.format(date=order_date.strftime(ECOMMERCE_DATE_FORMAT)) httpretty.register_uri( httpretty.GET, '{url}/orders/{order}/'.format(url=TEST_API_URL, order=self.ORDER_NUMBER), status=200, body=expected_content, adding_headers={'Content-Type': JSON} ) # creating multiple attributes for same order. for attribute_count in range(2): # pylint: disable=unused-variable self.enrollment.attributes.add(CourseEnrollmentAttribute( enrollment=self.enrollment, namespace='order', name='order_number', value=self.ORDER_NUMBER )) self.client.login(username=self.user.username, password=self.USER_PASSWORD) resp = self.client.post(reverse('student.views.dashboard', args=[])) self.assertEqual(resp.status_code, 200)
def test_refund_cutoff_date(self, order_date_delta, course_start_delta, expected_date_delta, days): """ Assert that the later date is used with the configurable refund period in calculating the returned cutoff date. """ now = datetime.now(pytz.UTC).replace(microsecond=0) order_date = now + order_date_delta course_start = now + course_start_delta expected_date = now + expected_date_delta refund_period = timedelta(days=days) expected_content = '{{"date_placed": "{date}"}}'.format(date=order_date.strftime(ECOMMERCE_DATE_FORMAT)) httpretty.register_uri( httpretty.GET, '{url}/orders/{order}/'.format(url=TEST_API_URL, order=self.ORDER_NUMBER), status=200, body=expected_content, adding_headers={'Content-Type': JSON} ) self.enrollment.course_overview.start = course_start self.enrollment.attributes.add(CourseEnrollmentAttribute( enrollment=self.enrollment, namespace='order', name='order_number', value=self.ORDER_NUMBER )) with patch('student.models.EnrollmentRefundConfiguration.current') as config: instance = config.return_value instance.refund_window = refund_period self.assertEqual( self.enrollment.refund_cutoff_date(), expected_date + refund_period )
def get_enrollment_attributes(user_id, course_id): """Retrieve enrollment attributes for given user for provided course. Args: user_id: The User to get enrollment attributes for course_id (str): The Course to get enrollment attributes for. Example: >>>get_enrollment_attributes("Bob", "course-v1-edX-DemoX-1T2015") [ { "namespace": "credit", "name": "provider_id", "value": "hogwarts", }, ] Returns: list """ course_key = CourseKey.from_string(course_id) user = _get_user(user_id) enrollment = CourseEnrollment.get_enrollment(user, course_key) return CourseEnrollmentAttribute.get_enrollment_attributes(enrollment)
def get_enrollment_attributes(user_id, course_id): """Retrieve enrollment attributes for given user for provided course. Args: user_id: The User to get enrollment attributes for course_id (str): The Course to get enrollment attributes for. Example: >>>get_enrollment_attributes("Bob", "course-v1-edX-DemoX-1T2015") [ { "namespace": "credit", "name": "provider_id", "value": "hogwarts", }, ] Returns: list """ course_key = CourseKey.from_string(course_id) user = _get_user(user_id) enrollment = CourseEnrollment.get_enrollment(user, course_key) return CourseEnrollmentAttribute.get_enrollment_attributes(enrollment)
def handle(self, *args, **options): """ Main handler for the command.""" file_path = options['csv_file_path'] if not path.isfile(file_path): raise CommandError("File not found.") with open(file_path) as csv_file: course_key = None user = None file_reader = csv.DictReader(csv_file) headers = file_reader.fieldnames if not ('course_id' in headers and 'mode' in headers and 'user' in headers): raise CommandError('Invalid input CSV file.') for row in file_reader: try: course_key = CourseKey.from_string(row['course_id']) except InvalidKeyError: logger.warning('Invalid or non-existent course id [{}]'.format(row['course_id'])) try: user = User.objects.get(username=row['user']) except: logger.warning('Invalid or non-existent user [{}]'.format(row['user'])) if course_key and user: try: course_enrollment = CourseEnrollment.get_enrollment(user, course_key) # If student is not enrolled in course enroll the student in free mode if not course_enrollment: # try to create a enroll user in default course enrollment mode in case of # professional it will break because of no default course mode. try: course_enrollment = CourseEnrollment.get_or_create_enrollment(user=user, course_key=course_key) except Exception: # pylint: disable=broad-except # In case if no free mode is available. course_enrollment = None if course_enrollment: # if student already had a enrollment and its mode is same as the provided one if course_enrollment.mode == row['mode']: logger.info("Student [%s] is already enrolled in Course [%s] in mode [%s].", user.username, course_key, course_enrollment.mode) # set the enrollment to active if its not already active. if not course_enrollment.is_active: course_enrollment.update_enrollment(is_active=True) else: # if student enrollment exists update it to new mode. with transaction.atomic(): course_enrollment.update_enrollment( mode=row['mode'], is_active=True, skip_refund=True ) course_enrollment.save() if row['mode'] == 'credit': enrollment_attrs = [{ 'namespace': 'credit', 'name': 'provider_id', 'value': course_key.org, }] CourseEnrollmentAttribute.add_enrollment_attr(enrollment=course_enrollment, data_list=enrollment_attrs) else: # if student enrollment do not exists directly enroll in new mode. CourseEnrollment.enroll(user=user, course_key=course_key, mode=row['mode']) except Exception as e: logger.info("Unable to update student [%s] course [%s] enrollment to mode [%s] " "because of Exception [%s]", row['user'], row['course_id'], row['mode'], repr(e))
def handle(self, *args, **options): """ Main handler for the command.""" file_path = options['csv_file_path'] if not path.isfile(file_path): raise CommandError("File not found.") with open(file_path) as csv_file: course_key = None user = None file_reader = csv.DictReader(csv_file) headers = file_reader.fieldnames if not ('course_id' in headers and 'mode' in headers and 'user' in headers): raise CommandError('Invalid input CSV file.') for row in file_reader: try: course_key = CourseKey.from_string(row['course_id']) except InvalidKeyError: logger.warning('Invalid or non-existent course id [{}]'.format(row['course_id'])) try: user = User.objects.get(username=row['user']) except: logger.warning('Invalid or non-existent user [{}]'.format(row['user'])) if course_key and user: try: course_enrollment = CourseEnrollment.get_enrollment(user, course_key) # If student is not enrolled in course enroll the student in free mode if not course_enrollment: # try to create a enroll user in default course enrollment mode in case of # professional it will break because of no default course mode. try: course_enrollment = CourseEnrollment.get_or_create_enrollment(user=user, course_key=course_key) except Exception: # pylint: disable=broad-except # In case if no free mode is available. course_enrollment = None if course_enrollment: # if student already had a enrollment and its mode is same as the provided one if course_enrollment.mode == row['mode']: logger.info("Student [%s] is already enrolled in Course [%s] in mode [%s].", user.username, course_key, course_enrollment.mode) # set the enrollment to active if its not already active. if not course_enrollment.is_active: course_enrollment.update_enrollment(is_active=True) else: # if student enrollment exists update it to new mode. with transaction.atomic(): course_enrollment.update_enrollment( mode=row['mode'], is_active=True, skip_refund=True ) course_enrollment.save() if row['mode'] == 'credit': enrollment_attrs = [{ 'namespace': 'credit', 'name': 'provider_id', 'value': course_key.org, }] CourseEnrollmentAttribute.add_enrollment_attr(enrollment=course_enrollment, data_list=enrollment_attrs) else: # if student enrollment do not exists directly enroll in new mode. CourseEnrollment.enroll(user=user, course_key=course_key, mode=row['mode']) except Exception as e: logger.info("Unable to update student [%s] course [%s] enrollment to mode [%s] " "because of Exception [%s]", row['user'], row['course_id'], row['mode'], repr(e))