def handle(self, *args, **options): """ Execute the command. Since this is designed to fix any issues cause by running pre-CohortMembership code with the database already migrated to post-CohortMembership state, we will use the pre-CohortMembership table CourseUserGroup as the canonical source of truth. This way, changes made in the window are persisted. """ commit = 'commit' in options memberships_to_delete = 0 memberships_to_add = 0 # Begin by removing any data in CohortMemberships that does not match CourseUserGroups data for membership in CohortMembership.objects.all(): try: CourseUserGroup.objects.get( group_type=CourseUserGroup.COHORT, users__id=membership.user.id, course_id=membership.course_id, id=membership.course_user_group.id ) except CourseUserGroup.DoesNotExist: memberships_to_delete += 1 if commit: membership.delete() # Now we can add any CourseUserGroup data that is missing a backing CohortMembership for course_group in CourseUserGroup.objects.filter(group_type=CourseUserGroup.COHORT): for user in course_group.users.all(): try: CohortMembership.objects.get( user=user, course_id=course_group.course_id, course_user_group_id=course_group.id ) except CohortMembership.DoesNotExist: memberships_to_add += 1 if commit: membership = CohortMembership( course_user_group=course_group, user=user, course_id=course_group.course_id ) try: membership.save() except IntegrityError: # If the user is in multiple cohorts, we arbitrarily choose between them # In this case, allow the pre-existing entry to be "correct" course_group.users.remove(user) user.course_groups.remove(course_group) print '{} CohortMemberships did not match the CourseUserGroup table and will be deleted'.format( memberships_to_delete ) print '{} CourseUserGroup users do not have a CohortMembership; one will be added if it is valid'.format( memberships_to_add ) if commit: print 'Changes have been made and saved.' else: print 'Dry run, changes have not been saved. Run again with "commit" argument to save changes'
def test_post_cohortmembership_fix(self): """ Test that changes made *after* migration, but *before* turning on new code are handled properly """ # First, we're going to simulate some problem states that can arise during this window config_course_cohorts(self.course1, is_cohorted=True, auto_cohorts=["Course1AutoGroup1", "Course1AutoGroup2"]) # Get the cohorts from the courses, which will cause auto cohorts to be created cohort_handler(self.request, unicode(self.course1.id)) course_1_auto_cohort_1 = get_cohort_by_name(self.course1.id, "Course1AutoGroup1") course_1_auto_cohort_2 = get_cohort_by_name(self.course1.id, "Course1AutoGroup2") # When migrations were first run, the users were assigned to CohortMemberships correctly membership1 = CohortMembership( course_id=course_1_auto_cohort_1.course_id, user=self.user1, course_user_group=course_1_auto_cohort_1 ) membership1.save() membership2 = CohortMembership( course_id=course_1_auto_cohort_1.course_id, user=self.user2, course_user_group=course_1_auto_cohort_1 ) membership2.save() # But before CohortMembership code was turned on, some changes were made: course_1_auto_cohort_2.users.add(self.user1) # user1 is now in 2 cohorts in the same course! course_1_auto_cohort_2.users.add(self.user2) course_1_auto_cohort_1.users.remove(self.user2) # and user2 was moved, but no one told CohortMembership! # run the post-CohortMembership command, dry-run call_command('post_cohort_membership_fix') # Verify nothing was changed in dry-run mode. self.assertEqual(self.user1.course_groups.count(), 2) # CourseUserGroup has 2 entries for user1 self.assertEqual(CohortMembership.objects.get(user=self.user2).course_user_group.name, 'Course1AutoGroup1') user2_cohorts = list(self.user2.course_groups.values_list('name', flat=True)) self.assertEqual(user2_cohorts, ['Course1AutoGroup2']) # CourseUserGroup and CohortMembership disagree # run the post-CohortMembership command, and commit it call_command('post_cohort_membership_fix', commit='commit') # verify that both databases agree about the (corrected) state of the memberships self.assertEqual(self.user1.course_groups.count(), 1) self.assertEqual(CohortMembership.objects.filter(user=self.user1).count(), 1) self.assertEqual(self.user2.course_groups.count(), 1) self.assertEqual(CohortMembership.objects.filter(user=self.user2).count(), 1) self.assertEqual(CohortMembership.objects.get(user=self.user2).course_user_group.name, 'Course1AutoGroup2') user2_cohorts = list(self.user2.course_groups.values_list('name', flat=True)) self.assertEqual(user2_cohorts, ['Course1AutoGroup2'])
def handle(self, *args, **options): """ Execute the command. Since this is designed to fix any issues cause by running pre-CohortMembership code with the database already migrated to post-CohortMembership state, we will use the pre-CohortMembership table CourseUserGroup as the canonical source of truth. This way, changes made in the window are persisted. """ commit = options['commit'] memberships_to_delete = 0 memberships_to_add = 0 # Begin by removing any data in CohortMemberships that does not match CourseUserGroups data for membership in CohortMembership.objects.all(): try: CourseUserGroup.objects.get(group_type=CourseUserGroup.COHORT, users__id=membership.user.id, course_id=membership.course_id, id=membership.course_user_group.id) except CourseUserGroup.DoesNotExist: memberships_to_delete += 1 if commit: membership.delete() # Now we can add any CourseUserGroup data that is missing a backing CohortMembership for course_group in CourseUserGroup.objects.filter( group_type=CourseUserGroup.COHORT): for user in course_group.users.all(): try: CohortMembership.objects.get( user=user, course_id=course_group.course_id, course_user_group_id=course_group.id) except CohortMembership.DoesNotExist: memberships_to_add += 1 if commit: membership = CohortMembership( course_user_group=course_group, user=user, course_id=course_group.course_id) try: membership.save() except IntegrityError: # If the user is in multiple cohorts, we arbitrarily choose between them # In this case, allow the pre-existing entry to be "correct" course_group.users.remove(user) user.course_groups.remove(course_group) print '{} CohortMemberships did not match the CourseUserGroup table and will be deleted'.format( memberships_to_delete) print '{} CourseUserGroup users do not have a CohortMembership; one will be added if it is valid'.format( memberships_to_add) if commit: print 'Changes have been made and saved.' else: print 'Dry run, changes have not been saved. Run again with "commit" argument to save changes'