def test_preserves_existing_user_partitions(self):
        # Add other, non-verified partition to the course
        self.course.user_partitions = [
            UserPartition(
                id=0,
                name='Cohort user partition',
                scheme=UserPartition.get_scheme('cohort'),
                description='Cohorted user partition',
                groups=[
                    Group(id=0, name="Group A"),
                    Group(id=1, name="Group B"),
                ],
            ),
            UserPartition(
                id=1,
                name='Random user partition',
                scheme=UserPartition.get_scheme('random'),
                description='Random user partition',
                groups=[
                    Group(id=0, name="Group A"),
                    Group(id=1, name="Group B"),
                ],
            ),
        ]
        self.course = self.store.update_item(self.course, ModuleStoreEnum.UserID.test)

        # Update the verification partitions.
        # The existing partitions should still be available
        self._update_partitions()
        partition_ids = [p.id for p in self.course.user_partitions]
        self.assertEqual(len(partition_ids), 3)
        self.assertIn(0, partition_ids)
        self.assertIn(1, partition_ids)
Example #2
0
    def test_exclude_partitions_with_no_groups(self):
        # The cohort partition has no groups defined
        self._set_partitions([
            UserPartition(
                id=0,
                name="Cohort user partition",
                scheme=UserPartition.get_scheme("cohort"),
                description="Cohorted user partition",
                groups=[],
            ),
            UserPartition(
                id=1,
                name="Verification user partition",
                scheme=UserPartition.get_scheme("verification"),
                description="Verification user partition",
                groups=[
                    Group(id=0, name="Group C"),
                ],
            ),
        ])

        # Expect that the partition with no groups is excluded from the results
        partitions = self._get_partition_info()
        self.assertEqual(len(partitions), 1)
        self.assertEqual(partitions[0]["scheme"], "verification")
Example #3
0
    def test_exclude_inactive_partitions(self):
        # Include an inactive verification scheme
        self._set_partitions([
            UserPartition(
                id=0,
                name="Cohort user partition",
                scheme=UserPartition.get_scheme("cohort"),
                description="Cohorted user partition",
                groups=[
                    Group(id=0, name="Group A"),
                    Group(id=1, name="Group B"),
                ],
            ),
            UserPartition(
                id=1,
                name="Verification user partition",
                scheme=UserPartition.get_scheme("verification"),
                description="Verification user partition",
                groups=[
                    Group(id=0, name="Group C"),
                ],
                active=False,
            ),
        ])

        # Expect that the inactive scheme is excluded from the results
        partitions = self._get_partition_info()
        self.assertEqual(len(partitions), 1)
        self.assertEqual(partitions[0]["scheme"], "cohort")
Example #4
0
    def setUp(self):
        """Create a dummy course. """
        super(GetUserPartitionInfoTest, self).setUp()
        self.course = CourseFactory()
        self.block = ItemFactory.create(category="problem", parent_location=self.course.location)  # pylint: disable=no-member

        # Set up some default partitions
        self._set_partitions([
            UserPartition(
                id=0,
                name="Cohort user partition",
                scheme=UserPartition.get_scheme("cohort"),
                description="Cohorted user partition",
                groups=[
                    Group(id=0, name="Group A"),
                    Group(id=1, name="Group B"),
                ],
            ),
            UserPartition(
                id=1,
                name="Random user partition",
                scheme=UserPartition.get_scheme("random"),
                description="Random user partition",
                groups=[
                    Group(id=0, name="Group C"),
                ],
            ),
        ])
    def get_or_create_content_group(store, course):
        """
        Returns the first user partition from the course which uses the
        CohortPartitionScheme, or generates one if no such partition is
        found.  The created partition is not saved to the course until
        the client explicitly creates a group within the partition and
        POSTs back.
        """
        content_group_configuration = get_cohorted_user_partition(course)
        if content_group_configuration is None:
            content_group_configuration = UserPartition(
                id=generate_int_id(MINIMUM_GROUP_ID, MYSQL_MAX_INT, GroupConfiguration.get_used_ids(course)),
                name=CONTENT_GROUP_CONFIGURATION_NAME,
                description=CONTENT_GROUP_CONFIGURATION_DESCRIPTION,
                groups=[],
                scheme_id=COHORT_SCHEME
            )
            return content_group_configuration.to_json()

        content_group_configuration = GroupConfiguration.update_content_group_usage_info(
            store,
            course,
            content_group_configuration
        )
        return content_group_configuration
    def test_from_json_broken(self):
        # Missing field
        jsonified = {
            "name": self.TEST_NAME,
            "description": self.TEST_DESCRIPTION,
            "groups": [group.to_json() for group in self.TEST_GROUPS],
            "version": UserPartition.VERSION,
            "scheme": self.TEST_SCHEME_NAME,
        }
        with self.assertRaisesRegexp(TypeError, "missing value key 'id'"):
            UserPartition.from_json(jsonified)

        # Missing scheme
        jsonified = {
            'id': self.TEST_ID,
            "name": self.TEST_NAME,
            "description": self.TEST_DESCRIPTION,
            "groups": [group.to_json() for group in self.TEST_GROUPS],
            "version": UserPartition.VERSION,
        }
        with self.assertRaisesRegexp(TypeError, "missing value key 'scheme'"):
            UserPartition.from_json(jsonified)

        # Invalid scheme
        jsonified = {
            'id': self.TEST_ID,
            "name": self.TEST_NAME,
            "description": self.TEST_DESCRIPTION,
            "groups": [group.to_json() for group in self.TEST_GROUPS],
            "version": UserPartition.VERSION,
            "scheme": "no_such_scheme",
        }
        with self.assertRaisesRegexp(UserPartitionError, "Unrecognized scheme"):
            UserPartition.from_json(jsonified)

        # Wrong version (it's over 9000!)
        # Wrong version (it's over 9000!)
        jsonified = {
            'id': self.TEST_ID,
            "name": self.TEST_NAME,
            "description": self.TEST_DESCRIPTION,
            "groups": [group.to_json() for group in self.TEST_GROUPS],
            "version": 9001,
            "scheme": self.TEST_SCHEME_NAME,
        }
        with self.assertRaisesRegexp(TypeError, "has unexpected version"):
            UserPartition.from_json(jsonified)

        # Has extra key - should not be a problem
        jsonified = {
            'id': self.TEST_ID,
            "name": self.TEST_NAME,
            "description": self.TEST_DESCRIPTION,
            "groups": [group.to_json() for group in self.TEST_GROUPS],
            "version": UserPartition.VERSION,
            "scheme": "mock",
            "programmer": "Cale",
        }
        user_partition = UserPartition.from_json(jsonified)
        self.assertNotIn("programmer", user_partition.to_json())
Example #7
0
class PartitionTestCase(TestCase):
    """Base class for test cases that require partitions"""
    shard = 2
    TEST_ID = 0
    TEST_NAME = "Mock Partition"
    TEST_DESCRIPTION = "for testing purposes"
    TEST_PARAMETERS = {"location": "block-v1:edX+DemoX+Demo+type@block@uuid"}
    TEST_GROUPS = [Group(0, 'Group 1'), Group(1, 'Group 2')]
    TEST_SCHEME_NAME = "mock"
    ENROLLMENT_TRACK_SCHEME_NAME = "enrollment_track"

    def setUp(self):
        super(PartitionTestCase, self).setUp()
        # Set up two user partition schemes: mock and random
        self.non_random_scheme = MockUserPartitionScheme(self.TEST_SCHEME_NAME)
        self.random_scheme = MockUserPartitionScheme("random")
        self.enrollment_track_scheme = MockEnrollmentTrackUserPartitionScheme(self.ENROLLMENT_TRACK_SCHEME_NAME)
        extensions = [
            Extension(
                self.non_random_scheme.name, USER_PARTITION_SCHEME_NAMESPACE, self.non_random_scheme, None
            ),
            Extension(
                self.random_scheme.name, USER_PARTITION_SCHEME_NAMESPACE, self.random_scheme, None
            ),
            Extension(
                self.enrollment_track_scheme.name, USER_PARTITION_SCHEME_NAMESPACE, self.enrollment_track_scheme, None
            ),
        ]
        UserPartition.scheme_extensions = ExtensionManager.make_test_instance(
            extensions, namespace=USER_PARTITION_SCHEME_NAMESPACE
        )

        # Be sure to clean up the global scheme_extensions after the test.
        self.addCleanup(self.cleanup_scheme_extensions)

        # Create a test partition
        self.user_partition = UserPartition(
            self.TEST_ID,
            self.TEST_NAME,
            self.TEST_DESCRIPTION,
            self.TEST_GROUPS,
            extensions[0].plugin,
            self.TEST_PARAMETERS,
        )

        # Make sure the names are set on the schemes (which happens normally in code, but may not happen in tests).
        self.user_partition.get_scheme(self.non_random_scheme.name)
        self.user_partition.get_scheme(self.random_scheme.name)

    def cleanup_scheme_extensions(self):
        """
        Unset the UserPartition.scheme_extensions cache.
        """
        UserPartition.scheme_extensions = None
Example #8
0
    def setUp(self):
        super(GroupVisibilityTest, self).setUp()

        chapter = ItemFactory.create(category='chapter', parent_location=self.course.location)
        sequential = ItemFactory.create(category='sequential', parent_location=chapter.location)
        vertical = ItemFactory.create(category='vertical', parent_location=sequential.location)
        html = ItemFactory.create(category='html', parent_location=vertical.location)
        problem = ItemFactory.create(
            category='problem', parent_location=vertical.location, data="<problem></problem>"
        )
        self.sequential = self.store.get_item(sequential.location)
        self.vertical = self.store.get_item(vertical.location)
        self.html = self.store.get_item(html.location)
        self.problem = self.store.get_item(problem.location)

        # Add partitions to the course
        self.course.user_partitions = [
            UserPartition(
                id=0,
                name="Partition 0",
                description="Partition 0",
                scheme=UserPartition.get_scheme("random"),
                groups=[
                    Group(id=0, name="Group A"),
                    Group(id=1, name="Group B"),
                ],
            ),
            UserPartition(
                id=1,
                name="Partition 1",
                description="Partition 1",
                scheme=UserPartition.get_scheme("random"),
                groups=[
                    Group(id=0, name="Group C"),
                    Group(id=1, name="Group D"),
                ],
            ),
            UserPartition(
                id=2,
                name="Partition 2",
                description="Partition 2",
                scheme=UserPartition.get_scheme("random"),
                groups=[
                    Group(id=0, name="Group E"),
                    Group(id=1, name="Group F"),
                    Group(id=2, name="Group G"),
                    Group(id=3, name="Group H"),
                ],
            ),
        ]
        self.course = self.store.update_item(self.course, ModuleStoreEnum.UserID.test)
    def test_to_json(self):
        groups = [Group(0, 'Group 1'), Group(1, 'Group 2')]
        upid = 0
        upname = "Test Partition"
        updesc = "for testing purposes"
        user_partition = UserPartition(upid, upname, updesc, groups)

        jsonified = user_partition.to_json()
        act_jsonified = {
            "id": upid,
            "name": upname,
            "description": updesc,
            "groups": [group.to_json() for group in groups],
            "version": user_partition.VERSION
        }
        self.assertEqual(jsonified, act_jsonified)
    def test_group_access_short_circuits(self):
        """
        Test that the group_access check short-circuits if there are no user_partitions defined
        except user_partitions in use by the split_test module.
        """
        # Initially, "red_cat" user can't view the vertical.
        self.set_group_access(self.chapter_location, {self.animal_partition.id: [self.dog_group.id]})
        self.check_access(self.red_cat, self.vertical_location, False)

        # Change the vertical's user_partitions value to the empty list. Now red_cat can view the vertical.
        self.set_user_partitions(self.vertical_location, [])
        self.check_access(self.red_cat, self.vertical_location, True)

        # Change the vertical's user_partitions value to include only "split_test" partitions.
        split_test_partition = UserPartition(
            199,
            'split_test partition',
            'nothing to look at here',
            [Group(2, 'random group')],
            scheme=UserPartition.get_scheme("random"),
        )
        self.set_user_partitions(self.vertical_location, [split_test_partition])
        self.check_access(self.red_cat, self.vertical_location, True)

        # Finally, add back in a cohort user_partition
        self.set_user_partitions(self.vertical_location, [split_test_partition, self.animal_partition])
        self.check_access(self.red_cat, self.vertical_location, False)
Example #11
0
    def setUp(self):
        super(PartitionTestCase, self).setUp()
        # Set up two user partition schemes: mock and random
        self.non_random_scheme = MockUserPartitionScheme(self.TEST_SCHEME_NAME)
        self.random_scheme = MockUserPartitionScheme("random")
        extensions = [
            Extension(
                self.non_random_scheme.name, USER_PARTITION_SCHEME_NAMESPACE, self.non_random_scheme, None
            ),
            Extension(
                self.random_scheme.name, USER_PARTITION_SCHEME_NAMESPACE, self.random_scheme, None
            ),
        ]
        UserPartition.scheme_extensions = ExtensionManager.make_test_instance(
            extensions, namespace=USER_PARTITION_SCHEME_NAMESPACE
        )

        # Create a test partition
        self.user_partition = UserPartition(
            self.TEST_ID,
            self.TEST_NAME,
            self.TEST_DESCRIPTION,
            self.TEST_GROUPS,
            extensions[0].plugin,
            self.TEST_PARAMETERS,
        )

        # Make sure the names are set on the schemes (which happens normally in code, but may not happen in tests).
        self.user_partition.get_scheme(self.non_random_scheme.name)
        self.user_partition.get_scheme(self.random_scheme.name)
    def add_seq_with_content_groups(self, groups=None):
        """
        Adds sequential and two content groups to first course in courses list.
        """
        config_course_cohorts(self.courses[0], is_cohorted=True)

        if groups is None:
            groups = self.groups

        self.user_partition = UserPartition(
            id=0,
            name='Partition 1',
            description='This is partition 1',
            groups=groups,
            scheme=CohortPartitionScheme
        )

        self.user_partition.scheme.name = "cohort"

        ItemFactory.create(
            parent_location=self.chapter.location,
            category='sequential',
            display_name="Lesson 1",
            publish_item=True,
            metadata={u"user_partitions": [self.user_partition.to_json()]}
        )

        self.first_cohort, self.second_cohort = [
            CohortFactory(course_id=self.courses[0].id) for _ in range(2)
        ]

        self.courses[0].user_partitions = [self.user_partition]
        self.courses[0].save()
        modulestore().update_item(self.courses[0], self.user.id)
Example #13
0
    def setUp(self):
        super(ReverificationPartitionTest, self).setUp()

        # creating course, checkpoint location and user partition mock object.
        self.course = CourseFactory.create()
        self.checkpoint_location = u'i4x://{org}/{course}/edx-reverification-block/first_uuid'.format(
            org=self.course.id.org, course=self.course.id.course
        )

        scheme = UserPartition.get_scheme("verification")
        self.user_partition = UserPartition(
            id=0,
            name=u"Verification Checkpoint",
            description=u"Verification Checkpoint",
            scheme=scheme,
            parameters={"location": self.checkpoint_location},
            groups=[
                Group(scheme.ALLOW, "Allow access to content"),
                Group(scheme.DENY, "Deny access to content"),
            ]
        )

        self.first_checkpoint = VerificationCheckpoint.objects.create(
            course_id=self.course.id,
            checkpoint_location=self.checkpoint_location
        )
def _create_enrollment_track_partition(course):
    """
    Create and return the dynamic enrollment track user partition.
    If it cannot be created, None is returned.
    """
    if not FEATURES.get('ENABLE_ENROLLMENT_TRACK_USER_PARTITION'):
        return None

    try:
        enrollment_track_scheme = UserPartition.get_scheme("enrollment_track")
    except UserPartitionError:
        log.warning("No 'enrollment_track' scheme registered, EnrollmentTrackUserPartition will not be created.")
        return None

    used_ids = set(p.id for p in course.user_partitions)
    if ENROLLMENT_TRACK_PARTITION_ID in used_ids:
        log.warning(
            "Can't add 'enrollment_track' partition, as ID {id} is assigned to {partition} in course {course}.".format(
                id=ENROLLMENT_TRACK_PARTITION_ID,
                partition=_get_partition_from_id(course.user_partitions, ENROLLMENT_TRACK_PARTITION_ID).name,
                course=unicode(course.id)
            )
        )
        return None

    partition = enrollment_track_scheme.create_user_partition(
        id=ENROLLMENT_TRACK_PARTITION_ID,
        name=_(u"Enrollment Track Groups"),
        description=_(u"Partition for segmenting users by enrollment track"),
        parameters={"course_id": unicode(course.id)}
    )
    return partition
 def get_user_partition(self):
     """
     Get user partition for saving in course.
     """
     try:
         return UserPartition.from_json(self.configuration)
     except ReadOnlyUserPartitionError:
         raise GroupConfigurationsValidationError(_("unable to load this type of group configuration"))
Example #16
0
    def test_version_upgrade_2_to_3(self):
        # Test that version 3 user partition raises error if 'scheme' field is
        # not provided (same behavior as version 2)
        jsonified = {
            'id': self.TEST_ID,
            "name": self.TEST_NAME,
            "description": self.TEST_DESCRIPTION,
            "parameters": self.TEST_PARAMETERS,
            "groups": [group.to_json() for group in self.TEST_GROUPS],
            "version": 2,
        }
        with self.assertRaisesRegexp(TypeError, "missing value key 'scheme'"):
            UserPartition.from_json(jsonified)

        # Test that version 3 partitions have a scheme specified
        # and a field 'parameters' (optional while setting user partition but
        # always present in response)
        jsonified = {
            "id": self.TEST_ID,
            "name": self.TEST_NAME,
            "description": self.TEST_DESCRIPTION,
            "groups": [group.to_json() for group in self.TEST_GROUPS],
            "version": 2,
            "scheme": self.TEST_SCHEME_NAME,
        }
        user_partition = UserPartition.from_json(jsonified)
        self.assertEqual(user_partition.scheme.name, self.TEST_SCHEME_NAME)
        self.assertEqual(user_partition.parameters, {})
        self.assertTrue(user_partition.active)

        # now test that parameters dict is present in response with same value
        # as provided
        jsonified = {
            "id": self.TEST_ID,
            "name": self.TEST_NAME,
            "description": self.TEST_DESCRIPTION,
            "groups": [group.to_json() for group in self.TEST_GROUPS],
            "parameters": self.TEST_PARAMETERS,
            "version": 3,
            "scheme": self.TEST_SCHEME_NAME,
        }
        user_partition = UserPartition.from_json(jsonified)
        self.assertEqual(user_partition.parameters, self.TEST_PARAMETERS)
        self.assertTrue(user_partition.active)
    def test_create_user_partition(self):
        user_partition = UserPartition.get_scheme('enrollment_track').create_user_partition(
            301, "partition", "test partition", parameters={"course_id": unicode(self.course.id)}
        )
        self.assertEqual(type(user_partition), EnrollmentTrackUserPartition)
        self.assertEqual(user_partition.name, "partition")

        groups = user_partition.groups
        self.assertEqual(1, len(groups))
        self.assertEqual("Audit", groups[0].name)
Example #18
0
 def test_version_upgrade(self):
     # Version 1 partitions did not have a scheme specified
     jsonified = {
         "id": self.TEST_ID,
         "name": self.TEST_NAME,
         "description": self.TEST_DESCRIPTION,
         "groups": [group.to_json() for group in self.TEST_GROUPS],
         "version": 1,
     }
     user_partition = UserPartition.from_json(jsonified)
     self.assertEqual(user_partition.scheme.name, "random")
    def test_can_handle_multiple_partitions(self):
        # Create the user partitions
        self.course.user_partitions = [
            UserPartition(
                id=0,
                name='Cohort user partition',
                scheme=UserPartition.get_scheme('cohort'),
                description='Cohorted user partition',
                groups=[
                    Group(id=0, name="Group A"),
                    Group(id=1, name="Group B"),
                ],
            ),
            UserPartition(
                id=1,
                name='Random user partition',
                scheme=UserPartition.get_scheme('random'),
                description='Random user partition',
                groups=[
                    Group(id=0, name="Group A"),
                    Group(id=1, name="Group B"),
                ],
            ),
        ]
        self.store.update_item(self.course, ModuleStoreEnum.UserID.test)

        # Assign group access rules for multiple partitions, one of which is a cohorted partition
        __, problem = self._create_problem_with_content_group(0, 1)
        problem.group_access = {
            0: [0],
            1: [1],
        }
        self.store.update_item(problem, ModuleStoreEnum.UserID.test)

        # This used to cause an exception since the code assumed that
        # only one partition would be available.
        actual = GroupConfiguration.get_content_groups_usage_info(self.store, self.course)
        self.assertEqual(actual.keys(), [0])

        actual = GroupConfiguration.get_content_groups_items_usage_info(self.store, self.course)
        self.assertEqual(actual.keys(), [0])
    def test_can_handle_duplicate_group_ids(self):
        # Create the user partitions
        self.course.user_partitions = [
            UserPartition(
                id=0,
                name='Cohort user partition 1',
                scheme=UserPartition.get_scheme('cohort'),
                description='Cohorted user partition',
                groups=[
                    Group(id=2, name="Group 1A"),
                    Group(id=3, name="Group 1B"),
                ],
            ),
            UserPartition(
                id=1,
                name='Cohort user partition 2',
                scheme=UserPartition.get_scheme('cohort'),
                description='Random user partition',
                groups=[
                    Group(id=2, name="Group 2A"),
                    Group(id=3, name="Group 2B"),
                ],
            ),
        ]
        self.store.update_item(self.course, ModuleStoreEnum.UserID.test)

        # Assign group access rules for multiple partitions, one of which is a cohorted partition
        self._create_problem_with_content_group(0, 2, name_suffix='0')
        self._create_problem_with_content_group(1, 3, name_suffix='1')

        # This used to cause an exception since the code assumed that
        # only one partition would be available.
        actual = GroupConfiguration.get_partitions_usage_info(self.store, self.course)
        self.assertEqual(actual.keys(), [0, 1])
        self.assertEqual(actual[0].keys(), [2])
        self.assertEqual(actual[1].keys(), [3])

        actual = GroupConfiguration.get_content_groups_items_usage_info(self.store, self.course)
        self.assertEqual(actual.keys(), [0, 1])
        self.assertEqual(actual[0].keys(), [2])
        self.assertEqual(actual[1].keys(), [3])
    def test_can_get_correct_usage_info_for_unit(self):
        """
        When group access is set on the unit level, the usage info should return a url to the unit, not
        the sequential parent of the unit.
        """
        self.course.user_partitions = [
            UserPartition(
                id=0,
                name='User Partition',
                scheme=UserPartition.get_scheme('cohort'),
                description='User Partition',
                groups=[
                    Group(id=0, name="Group")
                ],
            ),
        ]
        vertical, __ = self._create_problem_with_content_group(
            cid=0, group_id=0, name_suffix='0'
        )

        self.client.ajax_post(
            reverse_usage_url("xblock_handler", vertical.location),
            data={'metadata': {'group_access': {0: [0]}}}
        )

        actual = self._get_user_partition('cohort')
        # order of usage list is arbitrary, sort for reliable comparison
        actual['groups'][0]['usage'].sort(key=itemgetter('label'))
        expected = {
            'id': 0,
            'name': 'User Partition',
            'scheme': 'cohort',
            'description': 'User Partition',
            'version': UserPartition.VERSION,
            'groups': [
                {'id': 0, 'name': 'Group', 'version': 1, 'usage': [
                    {
                        'url': u"/container/{}".format(vertical.location),
                        'label': u"Test Subsection 0 / Test Unit 0"
                    },
                    {
                        'url': u"/container/{}".format(vertical.location),
                        'label': u"Test Unit 0 / Test Problem 0"
                    }
                ]},
            ],
            u'parameters': {},
            u'active': True,
        }

        self.maxDiff = None

        assert actual == expected
def create_enrollment_track_partition(course):
    """
    Create an EnrollmentTrackUserPartition instance for the given course.
    """
    enrollment_track_scheme = UserPartition.get_scheme("enrollment_track")
    partition = enrollment_track_scheme.create_user_partition(
        id=1,
        name="Test Enrollment Track Partition",
        description="Test partition for segmenting users by enrollment track",
        parameters={"course_id": unicode(course.id)}
    )
    return partition
    def test_from_json_broken(self):
        groups = [Group(0, 'Group 1'), Group(1, 'Group 2')]
        upid = 1
        upname = "Test Partition"
        updesc = "For Testing Purposes"

        # Missing field
        jsonified = {
            "name": upname,
            "description": updesc,
            "groups": [group.to_json() for group in groups],
            "version": UserPartition.VERSION
        }
        with self.assertRaisesRegexp(TypeError, "missing value key 'id'"):
            user_partition = UserPartition.from_json(jsonified)

        # Wrong version (it's over 9000!)
        jsonified = {
            'id': upid,
            "name": upname,
            "description": updesc,
            "groups": [group.to_json() for group in groups],
            "version": 9001
        }
        with self.assertRaisesRegexp(TypeError, "has unexpected version"):
            user_partition = UserPartition.from_json(jsonified)

        # Has extra key - should not be a problem
        jsonified = {
            'id': upid,
            "name": upname,
            "description": updesc,
            "groups": [group.to_json() for group in groups],
            "version": UserPartition.VERSION,
            "programmer": "Cale"
        }
        user_partition = UserPartition.from_json(jsonified)
        self.assertNotIn("programmer", user_partition.to_json())
Example #24
0
 def test_version_upgrade(self):
     # Test that version 1 partitions did not have a scheme specified
     # and have empty parameters
     jsonified = {
         "id": self.TEST_ID,
         "name": self.TEST_NAME,
         "description": self.TEST_DESCRIPTION,
         "groups": [group.to_json() for group in self.TEST_GROUPS],
         "version": 1,
     }
     user_partition = UserPartition.from_json(jsonified)
     self.assertEqual(user_partition.scheme.name, "random")  # pylint: disable=no-member
     self.assertEqual(user_partition.parameters, {})
     self.assertTrue(user_partition.active)
Example #25
0
class PartitionTestCase(TestCase):
    """Base class for test cases that require partitions"""
    TEST_ID = 0
    TEST_NAME = "Mock Partition"
    TEST_DESCRIPTION = "for testing purposes"
    TEST_GROUPS = [Group(0, 'Group 1'), Group(1, 'Group 2')]
    TEST_SCHEME_NAME = "mock"

    def setUp(self):
        super(PartitionTestCase, self).setUp()
        # Set up two user partition schemes: mock and random
        self.non_random_scheme = MockUserPartitionScheme(self.TEST_SCHEME_NAME)
        self.random_scheme = MockUserPartitionScheme("random")
        extensions = [
            Extension(
                self.non_random_scheme.name, USER_PARTITION_SCHEME_NAMESPACE, self.non_random_scheme, None
            ),
            Extension(
                self.random_scheme.name, USER_PARTITION_SCHEME_NAMESPACE, self.random_scheme, None
            ),
        ]
        UserPartition.scheme_extensions = ExtensionManager.make_test_instance(
            extensions, namespace=USER_PARTITION_SCHEME_NAMESPACE
        )

        # Create a test partition
        self.user_partition = UserPartition(
            self.TEST_ID,
            self.TEST_NAME,
            self.TEST_DESCRIPTION,
            self.TEST_GROUPS,
            extensions[0].plugin
        )

        # Make sure the names are set on the schemes (which happens normally in code, but may not happen in tests).
        self.user_partition.get_scheme(self.non_random_scheme.name)
        self.user_partition.get_scheme(self.random_scheme.name)
Example #26
0
def group_configurations_list_handler(request, course_key_string):
    """
    A RESTful handler for Group Configurations

    GET
        html: return Group Configurations list page (Backbone application)
    POST
        json: create new group configuration
    """
    course_key = CourseKey.from_string(course_key_string)
    course = _get_course_module(course_key, request.user)
    store = modulestore()

    if 'text/html' in request.META.get('HTTP_ACCEPT', 'text/html'):
        group_configuration_url = reverse_course_url('group_configurations_list_handler', course_key)
        split_test_enabled = SPLIT_TEST_COMPONENT_TYPE in course.advanced_modules

        return render_to_response('group_configurations.html', {
            'context_course': course,
            'group_configuration_url': group_configuration_url,
            'configurations': [u.to_json() for u in course.user_partitions] if split_test_enabled else None,
        })
    elif "application/json" in request.META.get('HTTP_ACCEPT') and request.method == 'POST':
        # create a new group configuration for the course
        try:
            configuration = GroupConfiguration.parse(request.body)
            GroupConfiguration.validate(configuration)
        except GroupConfigurationsValidationError as err:
            return JsonResponse({"error": err.message}, status=400)

        if not configuration.get("id"):
            configuration["id"] = random.randint(100, 10**12)

        # Assign ids to every group in configuration.
        for index, group in enumerate(configuration.get('groups', [])):
            group["id"] = index

        course.user_partitions.append(UserPartition.from_json(configuration))
        store.update_item(course, request.user.id)
        response = JsonResponse(configuration, status=201)

        response["Location"] = reverse_course_url(
            'group_configurations_detail_handler',
            course.id,
            kwargs={'group_configuration_id': configuration["id"]}
        )
        return response
    else:
        return HttpResponse(status=406)
Example #27
0
 def test_forward_compatibility(self):
     # If the user partition version is updated in a release,
     # then the release is rolled back, courses might contain
     # version numbers greater than the currently deployed
     # version number.
     newer_version_json = {
         "id": self.TEST_ID,
         "name": self.TEST_NAME,
         "description": self.TEST_DESCRIPTION,
         "groups": [group.to_json() for group in self.TEST_GROUPS],
         "version": UserPartition.VERSION + 1,
         "scheme": "mock",
         "additional_new_field": "foo",
     }
     partition = UserPartition.from_json(newer_version_json)
     self.assertEqual(partition.id, self.TEST_ID)
     self.assertEqual(partition.name, self.TEST_NAME)
Example #28
0
 def test_from_json(self):
     jsonified = {
         "id": self.TEST_ID,
         "name": self.TEST_NAME,
         "description": self.TEST_DESCRIPTION,
         "groups": [group.to_json() for group in self.TEST_GROUPS],
         "version": UserPartition.VERSION,
         "scheme": "mock",
     }
     user_partition = UserPartition.from_json(jsonified)
     self.assertEqual(user_partition.id, self.TEST_ID)
     self.assertEqual(user_partition.name, self.TEST_NAME)
     self.assertEqual(user_partition.description, self.TEST_DESCRIPTION)
     for act_group in user_partition.groups:
         self.assertIn(act_group.id, [0, 1])
         exp_group = self.TEST_GROUPS[act_group.id]
         self.assertEqual(exp_group.id, act_group.id)
         self.assertEqual(exp_group.name, act_group.name)
    def test_can_get_correct_usage_info_for_split_test(self):
        """
        When a split test is created and content group access is set for a problem within a group,
        the usage info should return a url to the split test, not to the group.
        """
        # Create user partition for groups in the split test,
        # and another partition to set group access for the problem within the split test.
        self._add_user_partitions(count=1)
        self.course.user_partitions += [
            UserPartition(
                id=1,
                name='Cohort User Partition',
                scheme=UserPartition.get_scheme('cohort'),
                description='Cohort User Partition',
                groups=[
                    Group(id=3, name="Problem Group")
                ],
            ),
        ]
        self.store.update_item(self.course, ModuleStoreEnum.UserID.test)

        __, split_test, problem = self._create_content_experiment(cid=0, name_suffix='0', group_id=3, cid_for_problem=1)

        expected = {
            'id': 1,
            'name': 'Cohort User Partition',
            'scheme': 'cohort',
            'description': 'Cohort User Partition',
            'version': UserPartition.VERSION,
            'groups': [
                {'id': 3, 'name': 'Problem Group', 'version': 1, 'usage': [
                    {
                        'url': '/container/{}'.format(split_test.location),
                        'label': 'Condition 1 vertical / Test Problem'
                    }
                ]},
            ],
            u'parameters': {},
            u'active': True,
        }
        actual = self._get_user_partition('cohort')

        self.assertEqual(actual, expected)
 def create_verification_user_partitions(self, checkpoint_names):
     """
     Create user partitions for verification checkpoints.
     """
     scheme = UserPartition.get_scheme("verification")
     self.course.user_partitions = [
         UserPartition(
             id=0,
             name=checkpoint_name,
             description="Verification checkpoint",
             scheme=scheme,
             groups=[
                 Group(scheme.ALLOW, "Completed verification at {}".format(checkpoint_name)),
                 Group(scheme.DENY, "Did not complete verification at {}".format(checkpoint_name)),
             ],
         )
         for checkpoint_name in checkpoint_names
     ]
     self.store.update_item(self.course, self.user.id)
Example #31
0
 def setUp(self):
     super(LmsXBlockMixinTestCase, self).setUp()
     self.user_partition = UserPartition(
         0, 'first_partition', 'First Partition',
         [Group(0, 'alpha'), Group(1, 'beta')])
     self.group1 = self.user_partition.groups[0]  # pylint: disable=no-member
     self.group2 = self.user_partition.groups[1]  # pylint: disable=no-member
     self.course = CourseFactory.create(
         user_partitions=[self.user_partition])
     self.section = ItemFactory.create(parent=self.course,
                                       category='chapter',
                                       display_name='Test Section')
     self.subsection = ItemFactory.create(parent=self.section,
                                          category='sequential',
                                          display_name='Test Subsection')
     self.vertical = ItemFactory.create(parent=self.subsection,
                                        category='vertical',
                                        display_name='Test Unit')
     self.video = ItemFactory.create(parent=self.subsection,
                                     category='video',
                                     display_name='Test Video')
Example #32
0
 def build_course(self):
     """
     Build up a course tree with a UserPartition.
     """
     # pylint: disable=attribute-defined-outside-init
     self.user_partition = UserPartition(
         0,
         'first_partition',
         'First Partition',
         [
             Group(0, 'alpha'),
             Group(1, 'beta')
         ]
     )
     self.group1 = self.user_partition.groups[0]    # pylint: disable=no-member
     self.group2 = self.user_partition.groups[1]    # pylint: disable=no-member
     self.course = CourseFactory.create(user_partitions=[self.user_partition])
     self.section = ItemFactory.create(parent=self.course, category='chapter', display_name='Test Section')
     self.subsection = ItemFactory.create(parent=self.section, category='sequential', display_name='Test Subsection')
     self.vertical = ItemFactory.create(parent=self.subsection, category='vertical', display_name='Test Unit')
     self.video = ItemFactory.create(parent=self.vertical, category='video', display_name='Test Video 1')
Example #33
0
    def test_from_json(self):
        jsonified = {
            "id": self.TEST_ID,
            "name": self.TEST_NAME,
            "description": self.TEST_DESCRIPTION,
            "parameters": self.TEST_PARAMETERS,
            "groups": [group.to_json() for group in self.TEST_GROUPS],
            "version": UserPartition.VERSION,
            "scheme": "mock",
        }
        user_partition = UserPartition.from_json(jsonified)
        self.assertEqual(user_partition.id, self.TEST_ID)
        self.assertEqual(user_partition.name, self.TEST_NAME)
        self.assertEqual(user_partition.description, self.TEST_DESCRIPTION)
        self.assertEqual(user_partition.parameters, self.TEST_PARAMETERS)

        for act_group in user_partition.groups:
            self.assertIn(act_group.id, [0, 1])
            exp_group = self.TEST_GROUPS[act_group.id]
            self.assertEqual(exp_group.id, act_group.id)
            self.assertEqual(exp_group.name, act_group.name)
    def test_from_json(self):
        jsonified = {
            "id": self.TEST_ID,
            "name": self.TEST_NAME,
            "description": self.TEST_DESCRIPTION,
            "parameters": self.TEST_PARAMETERS,
            "groups": [group.to_json() for group in self.TEST_GROUPS],
            "version": UserPartition.VERSION,
            "scheme": "mock",
        }
        user_partition = UserPartition.from_json(jsonified)
        assert user_partition.id == self.TEST_ID
        assert user_partition.name == self.TEST_NAME
        assert user_partition.description == self.TEST_DESCRIPTION
        assert user_partition.parameters == self.TEST_PARAMETERS

        for act_group in user_partition.groups:
            assert act_group.id in [0, 1]
            exp_group = self.TEST_GROUPS[act_group.id]
            assert exp_group.id == act_group.id
            assert exp_group.name == act_group.name
Example #35
0
    def setUpClass(cls):
        super().setUpClass()
        cls.partition = UserPartition(
            0,
            'first_partition',
            'First Partition',
            [
                Group(0, 'alpha'),
                Group(1, 'beta')
            ]
        )

        cls.course = CourseFactory.create(
            user_partitions=[cls.partition]
        )

        cls.chapter = ItemFactory.create(
            parent_location=cls.course.location,
            category="chapter",
            display_name="test chapter",
        )
Example #36
0
def create_content_gating_partition(course):
    """
    Create and return the Content Gating user partition.
    """

    enabled_for_course = ContentTypeGatingConfig.enabled_for_course(course_key=course.id)
    studio_override_for_course = ContentTypeGatingConfig.current(course_key=course.id).studio_override_enabled
    if not (enabled_for_course or studio_override_for_course):
        return None

    try:
        content_gate_scheme = UserPartition.get_scheme(CONTENT_TYPE_GATING_SCHEME)
    except UserPartitionError:
        LOG.warning(
            "No %r scheme registered, ContentTypeGatingPartitionScheme will not be created.",
            CONTENT_TYPE_GATING_SCHEME
        )
        return None

    used_ids = set(p.id for p in course.user_partitions)
    if CONTENT_GATING_PARTITION_ID in used_ids:
        # It's possible for course authors to add arbitrary partitions via XML import. If they do, and create a
        # partition with id 51, it will collide with the Content Gating Partition. We'll catch that here, and
        # then fix the course content as needed (or get the course team to).
        LOG.warning(
            "Can't add %r partition, as ID %r is assigned to %r in course %s.",
            CONTENT_TYPE_GATING_SCHEME,
            CONTENT_GATING_PARTITION_ID,
            _get_partition_from_id(course.user_partitions, CONTENT_GATING_PARTITION_ID).name,
            unicode(course.id),
        )
        return None

    partition = content_gate_scheme.create_user_partition(
        id=CONTENT_GATING_PARTITION_ID,
        name=_(u"Feature-based Enrollments"),
        description=_(u"Partition for segmenting users by access to gated content types"),
        parameters={"course_id": unicode(course.id)}
    )
    return partition
Example #37
0
    def test_cannot_delete_used_group_configuration(self):
        """
        Scenario: Ensure that the user cannot delete unused group configuration.
        Given I have a course with group configuration that is used in the Content Experiment
        When I go to the Group Configuration page
        Then I do not see delete button and I see a note about that
        When I edit the Group Configuration
        Then I do not see delete button and I see the note about that
        """
        # Create a new group configurations
        self.course_fixture._update_xblock(
            self.course_fixture._course_location, {
                "metadata": {
                    u"user_partitions": [
                        UserPartition(
                            0, "Name", "Description.",
                            [Group("0", "Group A"),
                             Group("1", "Group B")]).to_json()
                    ],
                },
            })
        vertical = self.course_fixture.get_nested_xblocks(
            category="vertical")[0]
        self.course_fixture.create_xblock(
            vertical.locator,
            XBlockFixtureDesc('split_test',
                              'Test Content Experiment',
                              metadata={'user_partition_id': 0}))
        # Go to the Group Configuration Page and click unit anchor
        self.page.visit()

        config = self.page.group_configurations[0]
        self.assertTrue(config.delete_button_is_disabled)
        self.assertIn('Cannot delete when in use by an experiment',
                      config.delete_note)

        config.edit()
        self.assertTrue(config.delete_button_is_disabled)
        self.assertIn('Cannot delete when in use by an experiment',
                      config.delete_note)
    def test_available_partitions(self):
        """
        Tests that the available partitions are populated correctly when editable_metadata_fields are called
        """
        self.assertEqual([], SplitTestDescriptor.user_partition_id.values)

        # user_partitions is empty, only the "Not Selected" item will appear.
        self.split_test_module.user_partition_id = SplitTestFields.no_partition_selected['value']
        self.split_test_module.editable_metadata_fields  # pylint: disable=pointless-statement
        partitions = SplitTestDescriptor.user_partition_id.values
        self.assertEqual(1, len(partitions))
        self.assertEqual(SplitTestFields.no_partition_selected['value'], partitions[0]['value'])

        # Populate user_partitions and call editable_metadata_fields again
        self.split_test_module.user_partitions = [
            UserPartition(0, 'first_partition', 'First Partition', [Group("0", 'alpha'), Group("1", 'beta')])
        ]
        self.split_test_module.editable_metadata_fields  # pylint: disable=pointless-statement
        partitions = SplitTestDescriptor.user_partition_id.values
        self.assertEqual(2, len(partitions))
        self.assertEqual(SplitTestFields.no_partition_selected['value'], partitions[0]['value'])
        self.assertEqual(0, partitions[1]['value'])
        self.assertEqual("first_partition", partitions[1]['display_name'])

        # Try again with a selected partition and verify that there is no option for "No Selection"
        self.split_test_module.user_partition_id = 0
        self.split_test_module.editable_metadata_fields  # pylint: disable=pointless-statement
        partitions = SplitTestDescriptor.user_partition_id.values
        self.assertEqual(1, len(partitions))
        self.assertEqual(0, partitions[0]['value'])
        self.assertEqual("first_partition", partitions[0]['display_name'])

        # Finally try again with an invalid selected partition and verify that "No Selection" is an option
        self.split_test_module.user_partition_id = 999
        self.split_test_module.editable_metadata_fields  # pylint: disable=pointless-statement
        partitions = SplitTestDescriptor.user_partition_id.values
        self.assertEqual(2, len(partitions))
        self.assertEqual(SplitTestFields.no_partition_selected['value'], partitions[0]['value'])
        self.assertEqual(0, partitions[1]['value'])
        self.assertEqual("first_partition", partitions[1]['display_name'])
    def test_render_author_view(self, group_configuration_url):
        """
        Test the rendering of the Studio author view.
        """
        def create_studio_context(root_xblock):
            """
            Context for rendering the studio "author_view".
            """
            return {
                'reorderable_items': set(),
                'root_xblock': root_xblock,
            }

        # The split_test module should render both its groups when it is the root
        context = create_studio_context(self.split_test_module)
        html = self.module_system.render(self.split_test_module, AUTHOR_VIEW,
                                         context).content
        self.assertIn('HTML FOR GROUP 0', html)
        self.assertIn('HTML FOR GROUP 1', html)

        # When rendering as a child, it shouldn't render either of its groups
        context = create_studio_context(self.course_sequence)
        html = self.module_system.render(self.split_test_module, AUTHOR_VIEW,
                                         context).content
        self.assertNotIn('HTML FOR GROUP 0', html)
        self.assertNotIn('HTML FOR GROUP 1', html)

        # The "Create Missing Groups" button should be rendered when groups are missing
        context = create_studio_context(self.split_test_module)
        self.split_test_module.user_partitions = [
            UserPartition(
                0, 'first_partition', 'First Partition',
                [Group("0", 'alpha'),
                 Group("1", 'beta'),
                 Group("2", 'gamma')])
        ]
        html = self.module_system.render(self.split_test_module, AUTHOR_VIEW,
                                         context).content
        self.assertIn('HTML FOR GROUP 0', html)
        self.assertIn('HTML FOR GROUP 1', html)
    def test_from_json(self):
        groups = [Group(0, 'Group 1'), Group(1, 'Group 2')]
        upid = 1
        upname = "Test Partition"
        updesc = "For Testing Purposes"

        jsonified = {
            "id": upid,
            "name": upname,
            "description": updesc,
            "groups": [group.to_json() for group in groups],
            "version": UserPartition.VERSION
        }
        user_partition = UserPartition.from_json(jsonified)
        self.assertEqual(user_partition.id, upid)
        self.assertEqual(user_partition.name, upname)
        self.assertEqual(user_partition.description, updesc)
        for act_group in user_partition.groups:
            self.assertIn(act_group.id, [0, 1])
            exp_group = groups[act_group.id]
            self.assertEqual(exp_group.id, act_group.id)
            self.assertEqual(exp_group.name, act_group.name)
Example #41
0
 def setUp(self):
     super(GetCourseTopicsTest, self).setUp()
     self.maxDiff = None  # pylint: disable=invalid-name
     self.partition = UserPartition(
         0,
         "partition",
         "Test Partition",
         [Group(0, "Cohort A"), Group(1, "Cohort B")],
         scheme_id="cohort"
     )
     self.course = CourseFactory.create(
         org="x",
         course="y",
         run="z",
         start=datetime.now(UTC),
         discussion_topics={"Test Topic": {"id": "non-courseware-topic-id"}},
         user_partitions=[self.partition],
         cohort_config={"cohorted": True},
         days_early_for_beta=3
     )
     self.user = UserFactory.create()
     CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id)
Example #42
0
    def test_user_in_deleted_group(self):
        # get a group assigned to the user - should be group 0 or 1
        old_group = self.partitions_service.get_user_group_for_partition(self.partition_id)
        self.assertIn(old_group, [0, 1])

        # Change the group definitions! No more group 0 or 1
        groups = [Group(3, 'Group 3'), Group(4, 'Group 4')]
        user_partition = UserPartition(self.partition_id, 'Test Partition', 'for testing purposes', groups)
        self.partitions_service = StaticPartitionService(
            [user_partition],
            user_tags_service=self.user_tags_service,
            course_id=Mock(),
            track_function=Mock()
        )

        # Now, get a new group using the same call - should be 3 or 4
        new_group = self.partitions_service.get_user_group_for_partition(self.partition_id)
        self.assertIn(new_group, [3, 4])

        # We should get the same group over multiple calls
        new_group_2 = self.partitions_service.get_user_group_for_partition(self.partition_id)
        self.assertEqual(new_group, new_group_2)
Example #43
0
    def test_partition_changes_nondestructive(self):
        """
        If the name of a user partition is changed, or a group is added to the
        partition, links from cohorts do not break.

        If the name of a group is changed, links from cohorts do not break.
        """
        self.setup_student_in_group_0()

        # to simulate a non-destructive configuration change on the course, create
        # a new partition with the same id and scheme but with groups renamed and
        # a group added
        new_groups = [Group(10, 'New Group 10'), Group(20, 'New Group 20'), Group(30, 'New Group 30')]
        new_user_partition = UserPartition(
            0,  # same id
            'Different Partition',
            'dummy',
            new_groups,
            scheme=CohortPartitionScheme,
        )
        # the link should still work
        self.assert_student_in_group(new_groups[0], new_user_partition)
    def setup_groups_partitions(self, num_user_partitions=1, num_groups=4, active=True):
        """
        Sets up groups and user partitions for testing.
        """
        # Set up groups
        self.groups = []
        for group_num in range(1, num_groups + 1):
            self.groups.append(Group(group_num, 'Group ' + unicode(group_num)))

        # Set up user partitions
        self.user_partitions = []
        for user_partition_num in range(1, num_user_partitions + 1):
            user_partition = UserPartition(
                id=user_partition_num,
                name='Partition ' + unicode(user_partition_num),
                description='This is partition ' + unicode(user_partition_num),
                groups=self.groups,
                scheme=CohortPartitionScheme,
                active=active,
            )
            user_partition.scheme.name = "cohort"
            self.user_partitions.append(user_partition)
    def test_view_index_ok(self):
        """
        Basic check that the groups configuration page responds correctly.
        """

        self.course.user_partitions = [
            UserPartition(0, 'First name', 'First description', [
                Group(0, 'Group A'),
                Group(1, 'Group B'),
                Group(2, 'Group C')
            ]),
        ]
        self.save_course()

        if SPLIT_TEST_COMPONENT_TYPE not in self.course.advanced_modules:
            self.course.advanced_modules.append(SPLIT_TEST_COMPONENT_TYPE)
            self.store.update_item(self.course, self.user.id)

        response = self.client.get(self._url())
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'First name')
        self.assertContains(response, 'Group C')
Example #46
0
def create_content_gating_partition(course):
    """
    Create and return the Content Gating user partition.
    """

    if not (CONTENT_TYPE_GATING_FLAG.is_enabled()
            or CONTENT_TYPE_GATING_STUDIO_UI_FLAG.is_enabled()):
        return None

    try:
        content_gate_scheme = UserPartition.get_scheme("content_type_gate")
    except UserPartitionError:
        LOG.warning(
            "No 'content_type_gate' scheme registered, ContentTypeGatingPartitionScheme will not be created."
        )
        return None

    used_ids = set(p.id for p in course.user_partitions)
    if CONTENT_GATING_PARTITION_ID in used_ids:
        # It's possible for course authors to add arbitrary partitions via XML import. If they do, and create a
        # partition with id 51, it will collide with the Content Gating Partition. We'll catch that here, and
        # then fix the course content as needed (or get the course team to).
        LOG.warning(
            "Can't add 'content_type_gate' partition, as ID {id} is assigned to {partition} in course {course}."
            .format(id=CONTENT_GATING_PARTITION_ID,
                    partition=_get_partition_from_id(
                        course.user_partitions,
                        CONTENT_GATING_PARTITION_ID).name,
                    course=unicode(course.id)))
        return None

    partition = content_gate_scheme.create_user_partition(
        id=CONTENT_GATING_PARTITION_ID,
        name=_(u"Feature-based Enrollments"),
        description=_(
            u"Partition for segmenting users by access to gated content types"
        ),
        parameters={"course_id": unicode(course.id)})
    return partition
Example #47
0
def create_enrollment_track_partition_with_course_id(course_id):
    """
    Create and return the dynamic enrollment track user partition based only on course_id.
    If it cannot be created, None is returned.
    """
    if not FEATURES.get('ENABLE_ENROLLMENT_TRACK_USER_PARTITION'):
        return None

    try:
        enrollment_track_scheme = UserPartition.get_scheme("enrollment_track")
    except UserPartitionError:
        log.warning(
            "No 'enrollment_track' scheme registered, EnrollmentTrackUserPartition will not be created."
        )
        return None

    partition = enrollment_track_scheme.create_user_partition(
        id=ENROLLMENT_TRACK_PARTITION_ID,
        name=_("Enrollment Track Groups"),
        description=_("Partition for segmenting users by enrollment track"),
        parameters={"course_id": str(course_id)})
    return partition
Example #48
0
    def setUp(self):
        """
        Set up a simple course with a grading policy, a UserPartition, and 2 sections, both graded as "homework".
        One section is pre-populated with a problem (with 2 inputs), visible to all students.
        The second section is empty. Test cases should add conditional content to it.
        """
        super(TestConditionalContent, self).setUp()

        self.user_partition_group_0 = 0
        self.user_partition_group_1 = 1
        self.partition = UserPartition(
            0, 'first_partition', 'First Partition', [
                Group(self.user_partition_group_0, 'alpha'),
                Group(self.user_partition_group_1, 'beta')
            ])

        self.course = CourseFactory.create(display_name=self.COURSE_NAME,
                                           number=self.COURSE_SLUG,
                                           user_partitions=[self.partition])

        grading_policy = {
            "GRADER": [{
                "type": "Homework",
                "min_count": 2,
                "drop_count": 0,
                "short_label": "HW",
                "passing_grade": 0,
                "weight": 1.0
            }]
        }
        self.add_grading_policy(grading_policy)

        self.homework_all = self.add_graded_section_to_course('homework1',
                                                              weight=0.8)
        self.p1_all_html_id = self.add_dropdown_to_section(
            self.homework_all.location, 'H1P1', 2).location.html_id()

        self.homework_conditional = self.add_graded_section_to_course(
            'homework2', weight=0.2)
Example #49
0
def _other_partitions(verified_partitions, exclude_partitions, course_key):
    """
    Retrieve all partitions NOT associated with the current set of ICRV blocks.

    Any partition associated with a deleted ICRV block will be marked as inactive
    so its access rules will no longer be enforced.

    Arguments:
        all_partitions (list of UserPartition): All verified partitions defined in the course.
        exclude_partitions (list of UserPartition): Partitions to exclude (e.g. the ICRV partitions already added)
        course_key (CourseKey): Identifier for the course (used for logging).

    Returns: list of `UserPartition`s

    """
    results = []
    partition_by_id = {p.id: p for p in verified_partitions}
    other_partition_ids = set(p.id for p in verified_partitions) - set(
        p.id for p in exclude_partitions)

    for pid in other_partition_ids:
        partition = partition_by_id[pid]
        results.append(
            UserPartition(
                id=partition.id,
                name=partition.name,
                description=partition.description,
                scheme=partition.scheme,
                parameters=partition.parameters,
                groups=partition.groups,
                active=False,
            ))
        log.info(
            ("Disabled partition %s in course %s because the "
             "associated in-course-reverification checkpoint does not exist."),
            partition.id, course_key)

    return results
Example #50
0
 def build_course(self):
     """
     Build up a course tree with a UserPartition.
     """
     # pylint: disable=attribute-defined-outside-init
     self.user_partition = UserPartition(
         0, 'first_partition', 'First Partition',
         [Group(0, 'alpha'), Group(1, 'beta')])
     self.group1 = self.user_partition.groups[0]
     self.group2 = self.user_partition.groups[1]
     self.course = CourseFactory.create(
         user_partitions=[self.user_partition])
     section = ItemFactory.create(parent=self.course,
                                  category='chapter',
                                  display_name='Test Section')
     subsection = ItemFactory.create(parent=section,
                                     category='sequential',
                                     display_name='Test Subsection')
     vertical = ItemFactory.create(parent=subsection,
                                   category='vertical',
                                   display_name='Test Unit')
     video = ItemFactory.create(parent=vertical,
                                category='video',
                                display_name='Test Video 1')
     split_test = ItemFactory.create(parent=vertical,
                                     category='split_test',
                                     display_name='Test Content Experiment')
     child_vertical = ItemFactory.create(parent=split_test,
                                         category='vertical')
     child_html_module = ItemFactory.create(parent=child_vertical,
                                            category='html')
     self.section_location = section.location
     self.subsection_location = subsection.location
     self.vertical_location = vertical.location
     self.video_location = video.location
     self.split_test_location = split_test.location
     self.child_vertical_location = child_vertical.location
     self.child_html_module_location = child_html_module.location
    def setUp(self):
        self.partition = UserPartition(
            0, 'first_partition', 'First Partition',
            [Group(0, 'alpha'), Group(1, 'beta')])

        self.course = CourseFactory.create(number=self.COURSE_NUMBER,
                                           user_partitions=[self.partition])

        self.chapter = ItemFactory.create(
            parent_location=self.course.location,
            category="chapter",
            display_name="test chapter",
        )
        self.sequential = ItemFactory.create(
            parent_location=self.chapter.location,
            category="sequential",
            display_name="Split Test Tests",
        )

        self.student = UserFactory.create()
        CourseEnrollmentFactory.create(user=self.student,
                                       course_id=self.course.id)
        self.client.login(username=self.student.username, password='******')
Example #52
0
    def setUp(self):
        # Set up two user partition schemes: mock and random
        extensions = [
            Extension(
                self.TEST_SCHEME_NAME, USER_PARTITION_SCHEME_NAMESPACE,
                MockUserPartitionScheme(self.TEST_SCHEME_NAME), None
            ),
            Extension(
                "random", USER_PARTITION_SCHEME_NAMESPACE, MockUserPartitionScheme("random"), None
            ),
        ]
        UserPartition.scheme_extensions = ExtensionManager.make_test_instance(
            extensions, namespace=USER_PARTITION_SCHEME_NAMESPACE
        )

        # Create a test partition
        self.user_partition = UserPartition(
            self.TEST_ID,
            self.TEST_NAME,
            self.TEST_DESCRIPTION,
            self.TEST_GROUPS,
            extensions[0].plugin
        )
Example #53
0
    def test_filter_inactive_user_partitions(self):
        """
        Tests supplying the `active_only` parameter.
        """
        self.user_partition = UserPartition(
            self.TEST_ID,
            self.TEST_NAME,
            self.TEST_DESCRIPTION,
            self.TEST_GROUPS,
            self.non_random_scheme,
            self.TEST_PARAMETERS,
            active=False
        )
        self.course.user_partitions = [self.user_partition]

        all_partitions = get_all_partitions_for_course(self.course, active_only=True)
        self.assertEqual(1, len(all_partitions))
        self.assertEqual(self.ENROLLMENT_TRACK_SCHEME_NAME, all_partitions[0].scheme.name)

        all_partitions = get_all_partitions_for_course(self.course, active_only=False)
        self.assertEqual(2, len(all_partitions))
        self.assertEqual(self.TEST_SCHEME_NAME, all_partitions[0].scheme.name)
        self.assertEqual(self.ENROLLMENT_TRACK_SCHEME_NAME, all_partitions[1].scheme.name)
Example #54
0
    def test_unicode_user_partitions(self):
        """
        Test that user partition groups can contain unicode characters.
        """
        user_groups = [u'ÞrÖfessÖr X', u'MàgnëtÖ']
        user_partition = UserPartition(
            0,
            'x_man',
            'X Man',
            [
                Group(0, user_groups[0]),
                Group(1, user_groups[1])
            ]
        )

        # Create course with group configurations
        self.initialize_course(
            course_factory_kwargs={
                'user_partitions': [user_partition]
            }
        )

        _groups = [group.name for group in self.course.user_partitions[0].groups]
        self.assertEqual(_groups, user_groups)
Example #55
0
    def create_poorly_configured_split_instance(self):
        """
        Creates a split test instance with a missing group and an inactive group.

        Returns the container page.
        """
        unit = self.go_to_unit_page(make_draft=True)
        add_advanced_component(unit, 0, 'split_test')
        container = self.go_to_container_page()
        container.edit()
        component_editor = ComponentEditorView(self.browser, container.locator)
        component_editor.set_select_value_and_save('Group Configuration',
                                                   'Configuration alpha,beta')
        self.course_fix.add_advanced_settings({
            u"user_partitions": {
                "value": [
                    UserPartition(0, 'Configuration alpha,beta', 'first',
                                  [Group("0", 'alpha'),
                                   Group("2", 'gamma')]).to_json()
                ]
            }
        })
        self.course_fix._add_advanced_settings()
        return self.go_to_container_page()
Example #56
0
    def test_view_index_ok(self):
        """
        Basic check that the groups configuration page responds correctly.
        """

        # This creates a random UserPartition.
        self.course.user_partitions = [
            UserPartition(0, 'First name', 'First description', [
                Group(0, 'Group A'),
                Group(1, 'Group B'),
                Group(2, 'Group C')
            ]),
        ]
        self.save_course()

        if 'split_test' not in self.course.advanced_modules:
            self.course.advanced_modules.append('split_test')
            self.store.update_item(self.course, self.user.id)

        response = self.client.get(self._url())
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'First name', count=1)
        self.assertContains(response, 'Group C')
        self.assertContains(response, CONTENT_GROUP_CONFIGURATION_NAME)
Example #57
0
    def test_missing_partition(self):
        """
        If the user partition is deleted (or its id is changed), there's no
        referential integrity enforced, so any references from cohorts to that
        partition's groups will be lost.  A warning should be logged when links
        are found from cohorts to partitions that do not exist.
        """
        self.setup_student_in_group_0()

        # to simulate another destructive change on the course, create a new
        # partition with a different id, but using the same groups.
        new_user_partition = UserPartition(
            1,  # different id
            'Moved Partition',
            'dummy',
            [Group(10, 'Group 10'), Group(20, 'Group 20')],  # same ids
            scheme=CohortPartitionScheme,
        )
        # the partition will not be found even though the group ids match, so the
        # scheme returns None (and logs a warning).
        with patch('openedx.core.djangoapps.course_groups.partition_scheme.log') as mock_log:
            self.assert_student_in_group(None, new_user_partition)
            self.assertTrue(mock_log.warn.called)
            self.assertRegexpMatches(mock_log.warn.call_args[0][0], 'partition mismatch')
Example #58
0
 def test_get_scheme(self):
     self.assertEqual(UserPartition.get_scheme('cohort'),
                      CohortPartitionScheme)
     with self.assertRaisesRegexp(UserPartitionError,
                                  'Unrecognized scheme'):
         UserPartition.get_scheme('other')
Example #59
0
 def from_json(self, values):
     return [UserPartition.from_json(v) for v in values]
Example #60
0
    def setUp(self):
        super(GroupAccessTestCase, self).setUp()  # lint-amnesty, pylint: disable=super-with-arguments

        UserPartition.scheme_extensions = ExtensionManager.make_test_instance(
            [
                Extension("memory", USER_PARTITION_SCHEME_NAMESPACE,
                          MemoryUserPartitionScheme(), None),
                Extension("random", USER_PARTITION_SCHEME_NAMESPACE,
                          MemoryUserPartitionScheme(), None)
            ],
            namespace=USER_PARTITION_SCHEME_NAMESPACE)

        self.cat_group = Group(10, 'cats')
        self.dog_group = Group(20, 'dogs')
        self.worm_group = Group(30, 'worms')
        self.animal_partition = UserPartition(
            0,
            'Pet Partition',
            'which animal are you?',
            [self.cat_group, self.dog_group, self.worm_group],
            scheme=UserPartition.get_scheme("memory"),
        )

        self.red_group = Group(1000, 'red')
        self.blue_group = Group(2000, 'blue')
        self.gray_group = Group(3000, 'gray')
        self.color_partition = UserPartition(
            100,
            'Color Partition',
            'what color are you?',
            [self.red_group, self.blue_group, self.gray_group],
            scheme=UserPartition.get_scheme("memory"),
        )

        self.course = CourseFactory.create(
            user_partitions=[self.animal_partition, self.color_partition], )
        with self.store.bulk_operations(self.course.id, emit_signals=False):
            chapter = ItemFactory.create(category='chapter',
                                         parent=self.course)
            section = ItemFactory.create(category='sequential', parent=chapter)
            vertical = ItemFactory.create(category='vertical', parent=section)
            component = ItemFactory.create(category='problem', parent=vertical)

            self.chapter_location = chapter.location
            self.section_location = section.location
            self.vertical_location = vertical.location
            self.component_location = component.location

        self.red_cat = UserFactory()  # student in red and cat groups
        self.set_user_group(self.red_cat, self.animal_partition,
                            self.cat_group)
        self.set_user_group(self.red_cat, self.color_partition, self.red_group)

        self.blue_dog = UserFactory()  # student in blue and dog groups
        self.set_user_group(self.blue_dog, self.animal_partition,
                            self.dog_group)
        self.set_user_group(self.blue_dog, self.color_partition,
                            self.blue_group)

        self.white_mouse = UserFactory()  # student in no group

        self.gray_worm = UserFactory()  # student in deleted group
        self.set_user_group(self.gray_worm, self.animal_partition,
                            self.worm_group)
        self.set_user_group(self.gray_worm, self.color_partition,
                            self.gray_group)
        # delete the gray/worm groups from the partitions now so we can test scenarios
        # for user whose group is missing.
        self.animal_partition.groups.pop()
        self.color_partition.groups.pop()

        # add a staff user, whose access will be unconditional in spite of group access.
        self.staff = StaffFactory.create(course_key=self.course.id)