Introduce EnrollmentTrackUserPartition.

TNL-6674
This commit is contained in:
cahrens
2017-03-13 20:42:14 -04:00
parent b6ba57ee2e
commit e5035746bf
53 changed files with 911 additions and 301 deletions

View File

@@ -9,10 +9,11 @@ from util.db import generate_int_id, MYSQL_MAX_INT
from django.utils.translation import ugettext as _
from contentstore.utils import reverse_usage_url
from xmodule.partitions.partitions import UserPartition
from xmodule.partitions.partitions_service import get_all_partitions_for_course, MINIMUM_STATIC_PARTITION_ID
from xmodule.split_test_module import get_split_user_partitions
from openedx.core.djangoapps.course_groups.partition_scheme import get_cohorted_user_partition
MINIMUM_GROUP_ID = 100
MINIMUM_GROUP_ID = MINIMUM_STATIC_PARTITION_ID
RANDOM_SCHEME = "random"
COHORT_SCHEME = "cohort"
@@ -84,7 +85,7 @@ class GroupConfiguration(object):
"""
Assign ids for the group_configuration's groups.
"""
used_ids = [g.id for p in self.course.user_partitions for g in p.groups]
used_ids = [g.id for p in get_all_partitions_for_course(self.course) for g in p.groups]
# Assign ids to every group in configuration.
for group in self.configuration.get('groups', []):
if group.get('id') is None:
@@ -96,7 +97,7 @@ class GroupConfiguration(object):
"""
Return a list of IDs that already in use.
"""
return set([p.id for p in course.user_partitions])
return set([p.id for p in get_all_partitions_for_course(course)])
def get_user_partition(self):
"""

View File

@@ -493,12 +493,12 @@ class GetUserPartitionInfoTest(ModuleStoreTestCase):
]
}
]
self.assertEqual(self._get_partition_info(), expected)
self.assertEqual(self._get_partition_info(schemes=["cohort", "random"]), expected)
# Update group access and expect that now one group is marked as selected.
self._set_group_access({0: [1]})
expected[0]["groups"][1]["selected"] = True
self.assertEqual(self._get_partition_info(), expected)
self.assertEqual(self._get_partition_info(schemes=["cohort", "random"]), expected)
def test_deleted_groups(self):
# Select a group that is not defined in the partition
@@ -546,7 +546,7 @@ class GetUserPartitionInfoTest(ModuleStoreTestCase):
])
# Expect that the inactive scheme is excluded from the results
partitions = self._get_partition_info()
partitions = self._get_partition_info(schemes=["cohort", "verification"])
self.assertEqual(len(partitions), 1)
self.assertEqual(partitions[0]["scheme"], "cohort")
@@ -572,7 +572,7 @@ class GetUserPartitionInfoTest(ModuleStoreTestCase):
])
# Expect that the partition with no groups is excluded from the results
partitions = self._get_partition_info()
partitions = self._get_partition_info(schemes=["cohort", "verification"])
self.assertEqual(len(partitions), 1)
self.assertEqual(partitions[0]["scheme"], "verification")

View File

@@ -14,6 +14,7 @@ from django_comment_common.utils import seed_permissions_roles
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
from openedx.core.djangoapps.site_configuration.models import SiteConfiguration
from xmodule.partitions.partitions_service import get_all_partitions_for_course
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
@@ -373,11 +374,11 @@ def get_user_partition_info(xblock, schemes=None, course=None):
schemes = set(schemes)
partitions = []
for p in sorted(course.user_partitions, key=lambda p: p.name):
for p in sorted(get_all_partitions_for_course(course, active_only=True), key=lambda p: p.name):
# Exclude disabled partitions, partitions with no groups defined
# Also filter by scheme name if there's a filter defined.
if p.active and p.groups and (schemes is None or p.scheme.name in schemes):
if p.groups and (schemes is None or p.scheme.name in schemes):
# First, add groups defined by the partition
groups = []
@@ -408,7 +409,7 @@ def get_user_partition_info(xblock, schemes=None, course=None):
# Put together the entire partition dictionary
partitions.append({
"id": p.id,
"name": p.name,
"name": unicode(p.name), # Convert into a string in case ugettext_lazy was used
"scheme": p.scheme.name,
"groups": groups,
})

View File

@@ -16,6 +16,7 @@ from xmodule.x_module import PREVIEW_VIEWS, STUDENT_VIEW, AUTHOR_VIEW
from xmodule.contentstore.django import contentstore
from xmodule.error_module import ErrorDescriptor
from xmodule.exceptions import NotFoundError, ProcessingError
from xmodule.partitions.partitions_service import PartitionService
from xmodule.studio_editable import has_author_view
from xmodule.services import SettingsService
from xmodule.modulestore.django import modulestore, ModuleI18nService
@@ -213,10 +214,24 @@ def _preview_module_system(request, descriptor, field_data):
"i18n": ModuleI18nService,
"settings": SettingsService(),
"user": DjangoXBlockUserService(request.user),
"partitions": StudioPartitionService(course_id=course_id)
},
)
class StudioPartitionService(PartitionService):
"""
A runtime mixin to allow the display and editing of component visibility based on user partitions.
"""
def get_user_group_id_for_partition(self, user, user_partition_id):
"""
Override this method to return None, as the split_test_module calls this
to determine which group a user should see, but is robust to getting a return
value of None meaning that all groups should be shown.
"""
return None
def _load_preview_module(request, descriptor):
"""
Return a preview XModule instantiated from the supplied descriptor. Will use mutable fields

View File

@@ -8,6 +8,7 @@ from pytz import UTC
from pyquery import PyQuery
from webob import Response
from django.conf import settings
from django.http import Http404
from django.test import TestCase
from django.test.client import RequestFactory
@@ -44,6 +45,7 @@ from xblock_django.user_service import DjangoXBlockUserService
from opaque_keys.edx.keys import UsageKey, CourseKey
from opaque_keys.edx.locations import Location
from xmodule.partitions.partitions import Group, UserPartition
from xmodule.partitions.partitions_service import ENROLLMENT_TRACK_PARTITION_ID, MINIMUM_STATIC_PARTITION_ID
class AsideTest(XBlockAside):
@@ -341,15 +343,17 @@ class GetItemTest(ItemTest):
)
def test_get_user_partitions_and_groups(self):
# Note about UserPartition and UserPartition Group IDs: these must not conflict with IDs used
# by dynamic user partitions.
self.course.user_partitions = [
UserPartition(
id=0,
id=MINIMUM_STATIC_PARTITION_ID,
name="Verification user partition",
scheme=UserPartition.get_scheme("verification"),
description="Verification user partition",
groups=[
Group(id=0, name="Group A"),
Group(id=1, name="Group B"),
Group(id=MINIMUM_STATIC_PARTITION_ID + 1, name="Group A"), # See note above.
Group(id=MINIMUM_STATIC_PARTITION_ID + 2, name="Group B"), # See note above.
],
),
]
@@ -365,18 +369,31 @@ class GetItemTest(ItemTest):
result = json.loads(resp.content)
self.assertEqual(result["user_partitions"], [
{
"id": 0,
"id": ENROLLMENT_TRACK_PARTITION_ID,
"name": "Enrollment Track Partition",
"scheme": "enrollment_track",
"groups": [
{
"id": settings.COURSE_ENROLLMENT_MODES["audit"],
"name": "Audit",
"selected": False,
"deleted": False,
}
]
},
{
"id": MINIMUM_STATIC_PARTITION_ID,
"name": "Verification user partition",
"scheme": "verification",
"groups": [
{
"id": 0,
"id": MINIMUM_STATIC_PARTITION_ID + 1,
"name": "Group A",
"selected": False,
"deleted": False,
},
{
"id": 1,
"id": MINIMUM_STATIC_PARTITION_ID + 2,
"name": "Group B",
"selected": False,
"deleted": False,
@@ -1704,15 +1721,30 @@ class TestEditSplitModule(ItemTest):
def setUp(self):
super(TestEditSplitModule, self).setUp()
self.user = UserFactory()
self.first_user_partition_group_1 = Group(unicode(MINIMUM_STATIC_PARTITION_ID + 1), 'alpha')
self.first_user_partition_group_2 = Group(unicode(MINIMUM_STATIC_PARTITION_ID + 2), 'beta')
self.first_user_partition = UserPartition(
MINIMUM_STATIC_PARTITION_ID, 'first_partition', 'First Partition',
[self.first_user_partition_group_1, self.first_user_partition_group_2]
)
# There is a test point below (test_create_groups) that purposefully wants the group IDs
# of the 2 partitions to overlap (which is not something that normally happens).
self.second_user_partition_group_1 = Group(unicode(MINIMUM_STATIC_PARTITION_ID + 1), 'Group 1')
self.second_user_partition_group_2 = Group(unicode(MINIMUM_STATIC_PARTITION_ID + 2), 'Group 2')
self.second_user_partition_group_3 = Group(unicode(MINIMUM_STATIC_PARTITION_ID + 3), 'Group 3')
self.second_user_partition = UserPartition(
MINIMUM_STATIC_PARTITION_ID + 10, 'second_partition', 'Second Partition',
[
self.second_user_partition_group_1,
self.second_user_partition_group_2,
self.second_user_partition_group_3
]
)
self.course.user_partitions = [
UserPartition(
0, 'first_partition', 'First Partition',
[Group("0", 'alpha'), Group("1", 'beta')]
),
UserPartition(
1, 'second_partition', 'Second Partition',
[Group("0", 'Group 0'), Group("1", 'Group 1'), Group("2", 'Group 2')]
)
self.first_user_partition,
self.second_user_partition
]
self.store.update_item(self.course, self.user.id)
root_usage_key = self._create_vertical()
@@ -1760,8 +1792,8 @@ class TestEditSplitModule(ItemTest):
self.assertEqual(-1, split_test.user_partition_id)
self.assertEqual(0, len(split_test.children))
# Set the user_partition_id to 0.
split_test = self._update_partition_id(0)
# Set the user_partition_id to match the first user_partition.
split_test = self._update_partition_id(self.first_user_partition.id)
# Verify that child verticals have been set to match the groups
self.assertEqual(2, len(split_test.children))
@@ -1769,13 +1801,13 @@ class TestEditSplitModule(ItemTest):
vertical_1 = self.get_item_from_modulestore(split_test.children[1], verify_is_draft=True)
self.assertEqual("vertical", vertical_0.category)
self.assertEqual("vertical", vertical_1.category)
self.assertEqual("Group ID 0", vertical_0.display_name)
self.assertEqual("Group ID 1", vertical_1.display_name)
self.assertEqual("Group ID " + unicode(MINIMUM_STATIC_PARTITION_ID + 1), vertical_0.display_name)
self.assertEqual("Group ID " + unicode(MINIMUM_STATIC_PARTITION_ID + 2), vertical_1.display_name)
# Verify that the group_id_to_child mapping is correct.
self.assertEqual(2, len(split_test.group_id_to_child))
self.assertEqual(vertical_0.location, split_test.group_id_to_child['0'])
self.assertEqual(vertical_1.location, split_test.group_id_to_child['1'])
self.assertEqual(vertical_0.location, split_test.group_id_to_child[str(self.first_user_partition_group_1.id)])
self.assertEqual(vertical_1.location, split_test.group_id_to_child[str(self.first_user_partition_group_2.id)])
def test_split_xblock_info_group_name(self):
"""
@@ -1785,8 +1817,8 @@ class TestEditSplitModule(ItemTest):
# Initially, no user_partition_id is set, and the split_test has no children.
self.assertEqual(split_test.user_partition_id, -1)
self.assertEqual(len(split_test.children), 0)
# Set the user_partition_id to 0.
split_test = self._update_partition_id(0)
# Set the user_partition_id to match the first user_partition.
split_test = self._update_partition_id(self.first_user_partition.id)
# Verify that child verticals have been set to match the groups
self.assertEqual(len(split_test.children), 2)
@@ -1808,13 +1840,13 @@ class TestEditSplitModule(ItemTest):
group configuration.
"""
# Set to first group configuration.
split_test = self._update_partition_id(0)
split_test = self._update_partition_id(self.first_user_partition.id)
self.assertEqual(2, len(split_test.children))
initial_vertical_0_location = split_test.children[0]
initial_vertical_1_location = split_test.children[1]
# Set to second group configuration
split_test = self._update_partition_id(1)
split_test = self._update_partition_id(self.second_user_partition.id)
# We don't remove existing children.
self.assertEqual(5, len(split_test.children))
self.assertEqual(initial_vertical_0_location, split_test.children[0])
@@ -1825,9 +1857,9 @@ class TestEditSplitModule(ItemTest):
# Verify that the group_id_to child mapping is correct.
self.assertEqual(3, len(split_test.group_id_to_child))
self.assertEqual(vertical_0.location, split_test.group_id_to_child['0'])
self.assertEqual(vertical_1.location, split_test.group_id_to_child['1'])
self.assertEqual(vertical_2.location, split_test.group_id_to_child['2'])
self.assertEqual(vertical_0.location, split_test.group_id_to_child[str(self.second_user_partition_group_1.id)])
self.assertEqual(vertical_1.location, split_test.group_id_to_child[str(self.second_user_partition_group_2.id)])
self.assertEqual(vertical_2.location, split_test.group_id_to_child[str(self.second_user_partition_group_3.id)])
self.assertNotEqual(initial_vertical_0_location, vertical_0.location)
self.assertNotEqual(initial_vertical_1_location, vertical_1.location)
@@ -1836,12 +1868,12 @@ class TestEditSplitModule(ItemTest):
Test that nothing happens when the user_partition_id is set to the same value twice.
"""
# Set to first group configuration.
split_test = self._update_partition_id(0)
split_test = self._update_partition_id(self.first_user_partition.id)
self.assertEqual(2, len(split_test.children))
initial_group_id_to_child = split_test.group_id_to_child
# Set again to first group configuration.
split_test = self._update_partition_id(0)
split_test = self._update_partition_id(self.first_user_partition.id)
self.assertEqual(2, len(split_test.children))
self.assertEqual(initial_group_id_to_child, split_test.group_id_to_child)
@@ -1852,7 +1884,7 @@ class TestEditSplitModule(ItemTest):
The user_partition_id will be updated, but children and group_id_to_child map will not change.
"""
# Set to first group configuration.
split_test = self._update_partition_id(0)
split_test = self._update_partition_id(self.first_user_partition.id)
self.assertEqual(2, len(split_test.children))
initial_group_id_to_child = split_test.group_id_to_child
@@ -1869,13 +1901,14 @@ class TestEditSplitModule(ItemTest):
TODO: move tests that can go over to common after the mixed modulestore work is done. # pylint: disable=fixme
"""
# Set to first group configuration.
split_test = self._update_partition_id(0)
split_test = self._update_partition_id(self.first_user_partition.id)
# Add a group to the first group configuration.
new_group_id = "1002"
split_test.user_partitions = [
UserPartition(
0, 'first_partition', 'First Partition',
[Group("0", 'alpha'), Group("1", 'beta'), Group("2", 'pie')]
self.first_user_partition.id, 'first_partition', 'First Partition',
[self.first_user_partition_group_1, self.first_user_partition_group_2, Group(new_group_id, 'pie')]
)
]
self.store.update_item(split_test, self.user.id)
@@ -1896,7 +1929,7 @@ class TestEditSplitModule(ItemTest):
split_test = self._assert_children(3)
self.assertNotEqual(group_id_to_child, split_test.group_id_to_child)
group_id_to_child = split_test.group_id_to_child
self.assertEqual(split_test.children[2], group_id_to_child["2"])
self.assertEqual(split_test.children[2], group_id_to_child[new_group_id])
# Call add_missing_groups again -- it should be a no-op.
split_test.add_missing_groups(self.request)

View File

@@ -93,6 +93,8 @@ FEATURES['LICENSING'] = True
FEATURES['ENABLE_MOBILE_REST_API'] = True # Enable video bumper in Studio
FEATURES['ENABLE_VIDEO_BUMPER'] = True # Enable video bumper in Studio settings
FEATURES['ENABLE_ENROLLMENT_TRACK_USER_PARTITION'] = True
# Enable partner support link in Studio footer
PARTNER_SUPPORT_EMAIL = 'partner-support@example.com'

View File

@@ -88,6 +88,8 @@ from lms.envs.common import (
# File upload defaults
FILE_UPLOAD_STORAGE_BUCKET_NAME,
FILE_UPLOAD_STORAGE_PREFIX,
COURSE_ENROLLMENT_MODES
)
from path import Path as path
from warnings import simplefilter
@@ -225,6 +227,9 @@ FEATURES = {
# Allow public account creation
'ALLOW_PUBLIC_ACCOUNT_CREATION': True,
# Whether or not the dynamic EnrollmentTrackUserPartition should be registered.
'ENABLE_ENROLLMENT_TRACK_USER_PARTITION': False,
}
ENABLE_JASMINE = False
@@ -883,6 +888,9 @@ INSTALLED_APPS = (
# for managing course modes
'course_modes',
# Verified Track Content Cohorting (Beta feature that will hopefully be removed)
'openedx.core.djangoapps.verified_track_content',
# Dark-launching languages
'openedx.core.djangoapps.dark_lang',

View File

@@ -320,6 +320,7 @@ FEATURES['ENABLE_COURSEWARE_INDEX'] = True
FEATURES['ENABLE_LIBRARY_INDEX'] = True
SEARCH_ENGINE = "search.tests.mock_search_engine.MockSearchEngine"
FEATURES['ENABLE_ENROLLMENT_TRACK_USER_PARTITION'] = True
# teams feature
FEATURES['ENABLE_TEAMS'] = True