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'
Пример #2
0
    def test_cohort_change_filter_prevent_move(self):
        """
        Test prevent the user's cohort change through a pipeline step.

        Expected result:
            - CohortChangeRequested is triggered and executes TestStopCohortChangeStep.
            - The user can't change cohorts.
        """
        CohortMembership.assign(cohort=self.first_cohort, user=self.user)

        with self.assertRaises(CohortChangeNotAllowed):
            CohortMembership.assign(cohort=self.second_cohort, user=self.user)
    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'
Пример #4
0
    def test_cohort_change_without_filter_configuration(self):
        """
        Test usual cohort change process, without filter's intervention.

        Expected result:
            - CohortChangeRequested does not have any effect on the cohort change process.
            - The cohort assignment process ends successfully.
        """
        CohortMembership.assign(cohort=self.first_cohort, user=self.user)

        cohort_membership, _ = CohortMembership.assign(
            cohort=self.second_cohort, user=self.user)

        self.assertEqual({}, cohort_membership.user.profile.get_meta())
    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'])
Пример #6
0
    def test_cohort_change_filter_executed(self):
        """
        Test whether the student cohort change filter is triggered before the user's
        changes cohort.

        Expected result:
            - CohortChangeRequested is triggered and executes TestCohortChangeStep.
            - The user's profile meta contains cohort_info.
        """
        CohortMembership.assign(cohort=self.first_cohort, user=self.user)

        cohort_membership, _ = CohortMembership.assign(
            cohort=self.second_cohort, user=self.user)

        self.assertEqual(
            {
                "cohort_info":
                "Changed from Cohort FirstCohort to Cohort SecondCohort"
            },
            cohort_membership.user.profile.get_meta(),
        )
    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'])
Пример #8
0
    def test_send_cohort_membership_changed_event(self):
        """
        Test whether the COHORT_MEMBERSHIP_CHANGED event is sent when a cohort
        membership update ends.

        Expected result:
            - COHORT_MEMBERSHIP_CHANGED is sent and received by the mocked receiver.
            - The arguments that the receiver gets are the arguments sent by the event
            except the metadata generated on the fly.
        """
        event_receiver = Mock(side_effect=self._event_receiver_side_effect)
        COHORT_MEMBERSHIP_CHANGED.connect(event_receiver)

        cohort_membership, _ = CohortMembership.assign(
            cohort=self.cohort,
            user=self.user,
        )

        self.assertTrue(self.receiver_called)
        self.assertDictContainsSubset(
            {
                "signal": COHORT_MEMBERSHIP_CHANGED,
                "sender": None,
                "cohort": CohortData(
                    user=UserData(
                        pii=UserPersonalData(
                            username=cohort_membership.user.username,
                            email=cohort_membership.user.email,
                            name=cohort_membership.user.profile.name,
                        ),
                        id=cohort_membership.user.id,
                        is_active=cohort_membership.user.is_active,
                    ),
                    course=CourseData(
                        course_key=cohort_membership.course_id,
                    ),
                    name=cohort_membership.course_user_group.name,
                ),
            },
            event_receiver.call_args.kwargs
        )