def test_user_retirement(self): """ Test that the external_user_key is uccessfully retired for a user's program enrollments and history. """ new_status = 'withdrawn' self.enrollment.status = new_status self.enrollment.save() # Ensure that all the records had values for external_user_key self.assertEquals(self.enrollment.external_user_key, 'abc') self.assertTrue(self.enrollment.historical_records.all()) for record in self.enrollment.historical_records.all(): self.assertEquals(record.external_user_key, 'abc') ProgramEnrollment.retire_user(self.user.id) self.enrollment.refresh_from_db() # Ensure those values are retired self.assertEquals(self.enrollment.external_user_key, None) self.assertTrue(self.enrollment.historical_records.all()) for record in self.enrollment.historical_records.all(): self.assertEquals(record.external_user_key, None)
def test_user_retirement(self): """ Test that the external_user_key is successfully retired for a user's program enrollments and history. """ new_status = 'canceled' self.enrollment.status = new_status self.enrollment.save() # Ensure that all the records had values for external_user_key self.assertEqual(self.enrollment.external_user_key, 'abc') self.assertTrue(self.enrollment.historical_records.all()) for record in self.enrollment.historical_records.all(): self.assertEqual(record.external_user_key, 'abc') ProgramEnrollment.retire_user(self.user.id) self.enrollment.refresh_from_db() # Ensure those values are retired self.assertEqual(self.enrollment.external_user_key, None) self.assertTrue(self.enrollment.historical_records.all()) for record in self.enrollment.historical_records.all(): self.assertEqual(record.external_user_key, None)
def validate(self, attrs): """ This modifies self.instance in the case of updates """ if not self.instance: enrollment = ProgramEnrollment(**attrs) enrollment.full_clean() else: for key, value in attrs.items(): setattr(self.instance, key, value) self.instance.full_clean() return attrs
def patch(self, request, **kwargs): """ Modify the program enrollments for a list of learners """ if len(request.data) > MAX_ENROLLMENT_RECORDS: return Response( status=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE, content_type='application/json', ) program_uuid = kwargs['program_uuid'] student_data = self._request_data_by_student_key(request, program_uuid) if None in student_data: return Response('invalid enrollment record', status.HTTP_422_UNPROCESSABLE_ENTITY) response_data = {} response_data.update( self._remove_duplicate_entries(request, student_data)) existing_enrollments = { enrollment.external_user_key: enrollment for enrollment in ProgramEnrollment.bulk_read_by_student_key( program_uuid, student_data) } enrollments_to_create = {} for external_user_key in student_data.keys(): if external_user_key not in existing_enrollments: student_data.pop(external_user_key) response_data[ external_user_key] = CourseEnrollmentResponseStatuses.NOT_IN_PROGRAM for external_user_key, enrollment in existing_enrollments.items(): student = { key: value for key, value in student_data[external_user_key].items() if key == 'status' } enrollment_serializer = ProgramEnrollmentSerializer(enrollment, data=student, partial=True) if enrollment_serializer.is_valid(): enrollments_to_create[( external_user_key, enrollment.curriculum_uuid)] = enrollment_serializer enrollment_serializer.save() response_data[external_user_key] = student['status'] else: serializer_is_invalid = enrollment_serializer.errors['status'][ 0].code == 'invalid_choice' if 'status' in enrollment_serializer.errors and serializer_is_invalid: response_data[ external_user_key] = CourseEnrollmentResponseStatuses.INVALID_STATUS return self._get_created_or_updated_response(request, enrollments_to_create, response_data, status.HTTP_200_OK)
def get_existing_program_enrollments(self, program_uuid, student_data): """ Returns the existing program enrollments for the given students and program """ student_keys = [data['student_key'] for data in student_data] return { e.external_user_key: e for e in ProgramEnrollment.bulk_read_by_student_key( program_uuid, student_keys) }
def _remove_existing_entries(self, program_uuid, student_data): """ Helper method to remove entries that have existing ProgramEnrollment records. """ result = {} existing_enrollments = ProgramEnrollment.bulk_read_by_student_key(program_uuid, student_data) for enrollment in existing_enrollments: result[enrollment.external_user_key] = CourseEnrollmentResponseStatuses.CONFLICT student_data.pop(enrollment.external_user_key) return result
def test_bulk_read_by_student_key(self): curriculum_a = uuid4() curriculum_b = uuid4() enrollments = [] student_data = {} for i in range(5): # This will give us 4 program enrollments for self.program_uuid # and 1 enrollment for self.other_program_uuid user_curriculum = curriculum_b if i % 2 else curriculum_a user_status = 'pending' if i % 2 else 'enrolled' user_program = self.other_program_uuid if i == 4 else self.program_uuid user_key = 'student-{}'.format(i) enrollments.append( ProgramEnrollment.objects.create( user=None, external_user_key=user_key, program_uuid=user_program, curriculum_uuid=user_curriculum, status=user_status, )) student_data[user_key] = {'curriculum_uuid': user_curriculum} enrollment_records = ProgramEnrollment.bulk_read_by_student_key( self.program_uuid, student_data) expected = { 'student-0': { 'curriculum_uuid': curriculum_a, 'status': 'enrolled', 'program_uuid': self.program_uuid }, 'student-1': { 'curriculum_uuid': curriculum_b, 'status': 'pending', 'program_uuid': self.program_uuid }, 'student-2': { 'curriculum_uuid': curriculum_a, 'status': 'enrolled', 'program_uuid': self.program_uuid }, 'student-3': { 'curriculum_uuid': curriculum_b, 'status': 'pending', 'program_uuid': self.program_uuid }, } assert expected == { enrollment.external_user_key: { 'curriculum_uuid': enrollment.curriculum_uuid, 'status': enrollment.status, 'program_uuid': enrollment.program_uuid, } for enrollment in enrollment_records }
def _remove_existing_entries(self, program_uuid, student_data): """ Helper method to remove entries that have existing ProgramEnrollment records. """ result = {} existing_enrollments = ProgramEnrollment.bulk_read_by_student_key( program_uuid, student_data) for enrollment in existing_enrollments: result[ enrollment. external_user_key] = CourseEnrollmentResponseStatuses.CONFLICT student_data.pop(enrollment.external_user_key) return result
def get_program_enrollments(self, program_uuid, external_student_keys): """ Does a bulk read of ProgramEnrollments for a given program and list of external student keys and returns a dict keyed by external student key """ program_enrollments = ProgramEnrollment.bulk_read_by_student_key( program_uuid, external_student_keys).prefetch_related( 'program_course_enrollments').select_related('user') return { program_enrollment.external_user_key: program_enrollment for program_enrollment in program_enrollments }
def patch(self, request, **kwargs): """ Modify the program enrollments for a list of learners """ if len(request.data) > MAX_ENROLLMENT_RECORDS: return Response( status=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE, content_type='application/json', ) program_uuid = kwargs['program_uuid'] student_data = self._request_data_by_student_key(request, program_uuid) response_data = {} response_data.update(self._remove_duplicate_entries(request, student_data)) existing_enrollments = { enrollment.external_user_key: enrollment for enrollment in ProgramEnrollment.bulk_read_by_student_key(program_uuid, student_data) } enrollments_to_create = {} for external_user_key in student_data.keys(): if external_user_key not in existing_enrollments: student_data.pop(external_user_key) response_data[external_user_key] = CourseEnrollmentResponseStatuses.NOT_IN_PROGRAM for external_user_key, enrollment in existing_enrollments.items(): student = {key: value for key, value in student_data[external_user_key].items() if key == 'status'} enrollment_serializer = ProgramEnrollmentSerializer(enrollment, data=student, partial=True) if enrollment_serializer.is_valid(): enrollments_to_create[(external_user_key, enrollment.curriculum_uuid)] = enrollment_serializer enrollment_serializer.save() response_data[external_user_key] = student['status'] else: serializer_is_invalid = enrollment_serializer.errors['status'][0].code == 'invalid_choice' if 'status' in enrollment_serializer.errors and serializer_is_invalid: response_data[external_user_key] = CourseEnrollmentResponseStatuses.INVALID_STATUS return self._get_created_or_updated_response(request, enrollments_to_create, response_data, status.HTTP_200_OK)
def test_bulk_read_by_student_key(self): curriculum_a = uuid4() curriculum_b = uuid4() enrollments = [] student_data = {} for i in xrange(5): # This will give us 4 program enrollments for self.program_uuid # and 1 enrollment for self.other_program_uuid user_curriculum = curriculum_b if i % 2 else curriculum_a user_status = 'pending' if i % 2 else 'enrolled' user_program = self.other_program_uuid if i == 4 else self.program_uuid user_key = 'student-{}'.format(i) enrollments.append( ProgramEnrollment.objects.create( user=None, external_user_key=user_key, program_uuid=user_program, curriculum_uuid=user_curriculum, status=user_status, ) ) student_data[user_key] = {'curriculum_uuid': user_curriculum} enrollment_records = ProgramEnrollment.bulk_read_by_student_key(self.program_uuid, student_data) expected = { 'student-0': {'curriculum_uuid': curriculum_a, 'status': 'enrolled', 'program_uuid': self.program_uuid}, 'student-1': {'curriculum_uuid': curriculum_b, 'status': 'pending', 'program_uuid': self.program_uuid}, 'student-2': {'curriculum_uuid': curriculum_a, 'status': 'enrolled', 'program_uuid': self.program_uuid}, 'student-3': {'curriculum_uuid': curriculum_b, 'status': 'pending', 'program_uuid': self.program_uuid}, } assert expected == { enrollment.external_user_key: { 'curriculum_uuid': enrollment.curriculum_uuid, 'status': enrollment.status, 'program_uuid': enrollment.program_uuid, } for enrollment in enrollment_records }
def validate(self, attrs): enrollment = ProgramEnrollment(**attrs) enrollment.full_clean() return attrs
def post(self, request, *args, **kwargs): """ This is the POST for ProgramEnrollments """ if len(request.data) > 25: return Response( status=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE, content_type='application/json', ) program_uuid = kwargs['program_uuid'] student_data = OrderedDict(( row.get('external_user_key'), { 'program_uuid': program_uuid, 'curriculum_uuid': row.get('curriculum_uuid'), 'status': row.get('status'), 'external_user_key': row.get('external_user_key'), }) for row in request.data ) key_counter = Counter([enrollment.get('external_user_key') for enrollment in request.data]) response_data = {} for student_key, count in key_counter.items(): if count > 1: response_data[student_key] = CourseEnrollmentResponseStatuses.DUPLICATED student_data.pop(student_key) existing_enrollments = ProgramEnrollment.bulk_read_by_student_key(program_uuid, student_data) for enrollment in existing_enrollments: response_data[enrollment.external_user_key] = CourseEnrollmentResponseStatuses.CONFLICT student_data.pop(enrollment.external_user_key) enrollments_to_create = {} for student_key, data in student_data.items(): curriculum_uuid = data['curriculum_uuid'] existing_user = get_user_by_program_id(student_key, program_uuid) if existing_user: data['user'] = existing_user.id serializer = ProgramEnrollmentSerializer(data=data) if serializer.is_valid(): enrollments_to_create[(student_key, curriculum_uuid)] = serializer response_data[student_key] = data.get('status') else: if 'status' in serializer.errors and serializer.errors['status'][0].code == 'invalid_choice': response_data[student_key] = CourseEnrollmentResponseStatuses.INVALID_STATUS else: return Response( 'invalid enrollment record', status.HTTP_422_UNPROCESSABLE_ENTITY ) for enrollment_serializer in enrollments_to_create.values(): # create the model enrollment_serializer.save() # TODO: make this a bulk save if not enrollments_to_create: return Response( status=status.HTTP_422_UNPROCESSABLE_ENTITY, data=response_data, content_type='application/json', ) if len(request.data) != len(enrollments_to_create): return Response( status=status.HTTP_207_MULTI_STATUS, data=response_data, content_type='application/json', ) return Response( status=status.HTTP_201_CREATED, data=response_data, content_type='application/json', )
def _listen_for_lms_retire(sender, **kwargs): # pylint: disable=unused-argument """ Listener for the USER_RETIRE_LMS_MISC signal, does user retirement """ user = kwargs.get('user') ProgramEnrollment.retire_user(user.id)