def test_migrated(self): source = self._create_user(enrolled=self.course) target = self._create_user() self._create_user_progress(source) with patch('openedx.core.djangoapps.user_api.completion.tasks.update_user_gradebook') as update_user_gradebook: outcome = _migrate_progress(self.course_id, source.email, target.email) course_key = str(self.course.id) update_user_gradebook.assert_has_calls([ call(course_key, source.id), call(course_key, target.id) ]) self.assertEqual(outcome, OUTCOME_MIGRATED) # Check that all user's progress transferred to another user assert CourseEnrollment.objects.filter(user=target, course=self.course.id).exists() assert BlockCompletion.user_learning_context_completion_queryset(user=target, context_key=self.course.id).exists() assert StudentItem.objects.filter( course_id=self.course.id, student_id=anonymous_id_for_user(target, self.course.id) ).exists() assert StudentModule.objects.filter(student=target, course_id=self.course.id).exists()
def _migrate_progress(course, source, target): """ Task that migrates progress from one user to another """ log.info('Started progress migration from "%s" to "%s" for "%s" course', source, target, course) try: course_key = CourseKey.from_string(course) except InvalidKeyError: log.warning('Migration failed. Invalid course key: %s', course) return OUTCOME_COURSE_KEY_INVALID try: get_course(course_key) except ValueError: log.warning('Migration failed. Course not found:: %s', course_key) return OUTCOME_COURSE_NOT_FOUND try: source = get_user_model().objects.get(email=source) except ObjectDoesNotExist: log.warning('Migration failed. Source user with such email not found: %s', source) return OUTCOME_SOURCE_NOT_FOUND try: enrollment = CourseEnrollment.objects.select_for_update().get(user=source, course=course_key) except ObjectDoesNotExist: log.warning( 'Migration failed. Source user with email "%s" not enrolled in "%s" course', source.email, course_key ) return OUTCOME_SOURCE_NOT_ENROLLED try: target = get_user_model().objects.get(email=target) except ObjectDoesNotExist: log.warning('Migration failed. Target user with such email not found: %s', target) return OUTCOME_TARGET_NOT_FOUND try: assert not BlockCompletion.user_learning_context_completion_queryset( user=target, context_key=course_key ).exists() anonymous_ids = AnonymousUserId.objects.filter(user=target, course_id=course_key).values('anonymous_user_id') assert not StudentItem.objects.filter(course_id=course_key, student_id__in=anonymous_ids).exists() except AssertionError: log.warning( 'Migration failed. Target user with email "%s" already enrolled in "%s" course and progress is present.', target.email, course_key ) return OUTCOME_TARGET_ALREADY_ENROLLED # Fetch completions for source user completions = BlockCompletion.user_learning_context_completion_queryset( user=source, context_key=course_key ).select_for_update() # Fetch edx-submissions data for source user anonymous_ids = AnonymousUserId.objects.filter(user=source, course_id=course_key).values('anonymous_user_id') submissions = StudentItem.objects.select_for_update().filter(course_id=course_key, student_id__in=anonymous_ids) # Fetch StudentModule table data for source user student_states = StudentModule.objects.select_for_update().filter(student=source, course_id=course_key) # Actually migrate completions and progress try: # Modify enrollment try: target_enrollment = CourseEnrollment.objects.select_for_update().get(user=target, course=course_key) except ObjectDoesNotExist: enrollment.user = target else: enrollment.is_active = False target_enrollment.is_active = True target_enrollment.save() finally: enrollment.save() # Migrate completions for user for completion in completions: completion.user = target completion.save() # Migrate edx-submissions for submission in submissions: submission.student_id = anonymous_id_for_user(target, course_key) submission.save() # Migrate StudentModule for state in student_states: state.student = target state.save() log.info('Removing stale aggregators for source user.') Aggregator.objects.filter(user=source, course_key=course_key).delete() except Exception: log.exception("Unexpected error while migrating user progress.") return OUTCOME_FAILED_MIGRATION log.info('Updating gradebook for %s user.', source.email) update_user_gradebook(course, source.id) log.info('Updating gradebook for %s user.', target.email) update_user_gradebook(course, target.id) log.info( 'User progress in "%s" course successfully migrated from "%s" to "%s"', course_key, source.email, target.email ) return OUTCOME_MIGRATED