Commit 2021ba63 by Nimisha Asthagiri

fixup! user_partitions unit tests, DAG errors, etc.

parent 2d64c54c
......@@ -3,6 +3,9 @@
"""
from openedx.core.lib.block_cache.transformer import BlockStructureTransformer
from courseware.access_utils import check_start_date
from xmodule.course_metadata_utils import DEFAULT_START_DATE
from .utils import get_field_on_block
class StartDateTransformer(BlockStructureTransformer):
......@@ -36,13 +39,21 @@ class StartDateTransformer(BlockStructureTransformer):
) if parents else None
# set the merged value for this block
block_start = block_structure.get_xblock(block_key).start
block_start = get_field_on_block(block_structure.get_xblock(block_key), 'start')
if min_all_parents_start_date is None:
# no parents so just use value on block or default
merged_start_value = block_start or DEFAULT_START_DATE
elif not block_start:
# no value on this block so take value from parents
merged_start_value = min_all_parents_start_date
else:
# max of merged-start-from-all-parents and this block
merged_start_value = max(min_all_parents_start_date, block_start)
block_structure.set_transformer_block_data(
block_key,
cls,
cls.MERGED_START_DATE,
# max of merged-start-from-all-parents and this block
max(min_all_parents_start_date or block_start, block_start)
merged_start_value
)
def transform(self, user_info, block_structure):
......
......@@ -47,10 +47,11 @@ class ContentLibraryTransformerTestCase(CourseStructureTestCase):
"""
Get a course hierarchy to test with.
"""
return {
return [{
'org': 'ContentLibraryTransformer',
'course': 'CL101F',
'run': 'test_run',
'#type': 'course',
'#ref': 'course',
'#children': [
{
......@@ -103,7 +104,7 @@ class ContentLibraryTransformerTestCase(CourseStructureTestCase):
],
}
]
}
}]
def test_course_structure_with_user_course_library(self):
"""
......@@ -128,7 +129,7 @@ class ContentLibraryTransformerTestCase(CourseStructureTestCase):
self.assertEqual(
set(trans_block_structure.get_block_keys()),
self.get_block_key_set('course', 'chapter1', 'lesson1', 'vertical1', 'library_content1')
self.get_block_key_set(self.blocks, 'course', 'chapter1', 'lesson1', 'vertical1', 'library_content1')
)
# Check course structure again, with mocked selected modules for a user.
......@@ -145,6 +146,7 @@ class ContentLibraryTransformerTestCase(CourseStructureTestCase):
self.assertEqual(
set(trans_block_structure.get_block_keys()),
self.get_block_key_set(
self.blocks,
'course',
'chapter1',
'lesson1',
......@@ -156,4 +158,4 @@ class ContentLibraryTransformerTestCase(CourseStructureTestCase):
)
def test_staff_user(self):
self.assert_staff_access_to_all_blocks(self.staff, self.course, self.blocks, self.transformer)
self.assert_staff_access_to_all_blocks(self.course, self.blocks, self.transformer)
......@@ -51,11 +51,12 @@ class SplitTestTransformerTestCase(CourseStructureTestCase):
Returns: dict[course_structure]
"""
return {
return [{
'org': 'SplitTestTransformer',
'course': 'ST101F',
'run': 'test_run',
'user_partitions': [self.split_test_user_partition],
'#type': 'course',
'#ref': 'course',
'#children': [
{
......@@ -113,15 +114,9 @@ class SplitTestTransformerTestCase(CourseStructureTestCase):
],
}
]
}
}]
def test_user(self):
trans_block_structure = get_course_blocks(
self.user,
self.course.location,
transformers={self.transformer},
)
# user was randomly assigned to one of the groups
user_groups = get_user_partition_groups(
self.course.id, [self.split_test_user_partition], self.user
......@@ -129,19 +124,18 @@ class SplitTestTransformerTestCase(CourseStructureTestCase):
self.assertEquals(len(user_groups), 1)
group = user_groups[self.split_test_user_partition_id]
# determine expected blocks
expected_blocks = ['course', 'chapter1', 'lesson1', 'vertical1']
if group.id == 3:
expected_blocks += ['vertical2', 'html1']
else:
expected_blocks += ['vertical3', 'html2']
self.assertEqual(set(trans_block_structure.get_block_keys()), set(self.get_block_key_set(*expected_blocks)))
# calling again should result in the same block set
reloaded_structure = get_course_blocks(
self.user,
self.course.location,
transformers={self.transformer}
)
self.assertEqual(set(reloaded_structure.get_block_keys()), set(self.get_block_key_set(*expected_blocks)))
expected_blocks += (['vertical2', 'html1'] if group.id == 3 else ['vertical3', 'html2'])
# calling twice should result in the same block set
for _ in range(2):
trans_block_structure = get_course_blocks(
self.user,
self.course.location,
transformers={self.transformer},
)
self.assertEqual(
set(trans_block_structure.get_block_keys()),
set(self.get_block_key_set(self.blocks, *expected_blocks))
)
......@@ -7,10 +7,8 @@ from datetime import timedelta
from django.utils.timezone import now
from mock import patch
from xmodule.course_metadata_utils import DEFAULT_START_DATE
from ..start_date import StartDateTransformer
from .test_helpers import BlockParentsMapTestCase
from ..start_date import StartDateTransformer, DEFAULT_START_DATE
from .test_helpers import BlockParentsMapTestCase, update_block
@ddt.ddt
......@@ -43,10 +41,19 @@ class StartDateTransformerTestCase(BlockParentsMapTestCase):
@patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
@ddt.data(
({}, {}, {}),
({0: StartDateType.default}, {}, {}),
({0: StartDateType.future}, {}, {}),
({0: StartDateType.released}, {0, 1, 2, 3, 4, 5, 6}, {}),
# has_access checks on block directly and doesn't follow negative access set on parent/ancestor (i.e., 0)
({1: StartDateType.released}, {}, {1, 3, 4, 6}),
({2: StartDateType.released}, {}, {2, 5, 6}),
({1: StartDateType.released, 2: StartDateType.released}, {}, {1, 2, 3, 4, 5, 6}),
({0: StartDateType.released, 4: StartDateType.future}, {0, 1, 2, 3, 5}, {}),
# DAG conflicts: has_access relies on field inheritance so it takes only the value from the first parent-chain
({0: StartDateType.released, 4: StartDateType.future}, {0, 1, 2, 3, 5, 6}, {6}),
({0: StartDateType.released, 2: StartDateType.released, 4: StartDateType.future}, {0, 1, 2, 3, 5, 6}, {6}),
({0: StartDateType.released, 2: StartDateType.future, 4: StartDateType.released}, {0, 1, 3, 4, 6}, {}),
)
@ddt.unpack
def test_block_start_date(
......@@ -55,7 +62,7 @@ class StartDateTransformerTestCase(BlockParentsMapTestCase):
for i, start_date_type in start_date_type_values.iteritems():
block = self.get_block(i)
block.start = self.StartDateType.start(start_date_type)
self.update_block(block)
update_block(block)
self.check_transformer_results(
expected_student_visible_blocks, blocks_with_differing_student_access, [StartDateTransformer()]
......
......@@ -18,6 +18,8 @@ class UserPartitionTransformerTestCase(CourseStructureTestCase):
"""
UserPartitionTransformer Test
"""
TEST_PARTITION_ID = 0
def setUp(self):
"""
Setup course structure and create user for user partition transformer test.
......@@ -25,10 +27,9 @@ class UserPartitionTransformerTestCase(CourseStructureTestCase):
super(UserPartitionTransformerTestCase, self).setUp()
# Set up user partitions and groups.
self.groups = [Group(1, 'Group 1'), Group(2, 'Group 2')]
self.content_groups = [1, 2]
self.groups = [Group(1, 'Group 1'), Group(2, 'Group 2'), Group(3, 'Group 3'), Group(4, 'Group 4')]
self.user_partition = UserPartition(
id=0,
id=self.TEST_PARTITION_ID,
name='Partition 1',
description='This is partition 1',
groups=self.groups,
......@@ -46,8 +47,18 @@ class UserPartitionTransformerTestCase(CourseStructureTestCase):
# Set up cohorts.
config_course_cohorts(self.course, is_cohorted=True)
self.cohorts = [CohortFactory(course_id=self.course.id) for __ in enumerate(self.groups)]
self.add_user_to_cohort_group(self.cohorts[0], self.groups[0])
self.cohorts = []
for group in self.groups:
cohort = CohortFactory(course_id=self.course.id)
self.cohorts.append(cohort)
link_cohort_to_partition_group(
cohort,
self.user_partition.id,
group.id,
)
add_user_to_cohort(self.cohorts[0], self.user.username)
self.transformer = UserPartitionTransformer()
def get_course_hierarchy(self):
......@@ -56,62 +67,104 @@ class UserPartitionTransformerTestCase(CourseStructureTestCase):
Assumes self.user_partition has already been initialized.
"""
return {
'org': 'UserPartitionTransformer',
'course': 'UP101F',
'run': 'test_run',
'user_partitions': [self.user_partition],
'#ref': 'course',
'#children': [
{
'#type': 'chapter',
'#ref': 'chapter1',
'#children': [
{
'metadata': {
'group_access': {0: [0, 1, 2]},
},
'#type': 'sequential',
'#ref': 'lesson1',
'#children': [
{
'#type': 'vertical',
'#ref': 'vertical1',
'#children': [
{
'metadata': {'group_access': {0: [0]}},
'#type': 'html',
'#ref': 'html1',
},
{
'metadata': {'group_access': {0: [1]}},
'#type': 'html',
'#ref': 'html2',
}
],
}
],
}
],
}
]
}
def add_user_to_cohort_group(self, cohort, group):
"""
Add user to cohort, link cohort to content group, and update blocks.
"""
add_user_to_cohort(cohort, self.user.username)
link_cohort_to_partition_group(
cohort,
self.user_partition.id,
group.id,
)
# course
# / \
# / \
# A[1, 2, 3] B
# / | \ |
# / | \ |
# / | \ |
# C[1, 2] D[2, 3] E /
# / | \ | / \ /
# / | \ | / \ /
# / | \ | / \ /
# F G[1] H[2] I J K[4] /
# / \ / / \ /
# / \ / / \ /
# / \ / / \/
# L[1, 2] M[1, 2, 3] N O
#
return [
{
'org': 'UserPartitionTransformer',
'course': 'UP101F',
'run': 'test_run',
'user_partitions': [self.user_partition],
'#type': 'course',
'#ref': 'course',
'#children': [
{
'#type': 'vertical',
'#ref': 'A',
'metadata': {'group_access': {self.TEST_PARTITION_ID: [0, 1, 2, 3]}},
},
{'#type': 'vertical', '#ref': 'B'},
],
},
{
'#type': 'vertical',
'#ref': 'C',
'#parents': ['A'],
'metadata': {'group_access': {self.TEST_PARTITION_ID: [1, 2]}},
'#children': [
{'#type': 'vertical', '#ref': 'F'},
{
'#type': 'vertical',
'#ref': 'G',
'metadata': {'group_access': {self.TEST_PARTITION_ID: [1]}},
},
{
'#type': 'vertical',
'#ref': 'H',
'metadata': {'group_access': {self.TEST_PARTITION_ID: [2]}},
},
],
},
{
'#type': 'vertical',
'#ref': 'D',
'#parents': ['A'],
'metadata': {'group_access': {self.TEST_PARTITION_ID: [2, 3]}},
'#children': [{'#type': 'vertical', '#ref': 'I'}],
},
{
'#type': 'vertical',
'#ref': 'E',
'#parents': ['A'],
'#children': [{'#type': 'vertical', '#ref': 'J'}],
},
{
'#type': 'vertical',
'#ref': 'K',
'#parents': ['E'],
'metadata': {'group_access': {self.TEST_PARTITION_ID: [4]}},
'#children': [{'#type': 'vertical', '#ref': 'N'}],
},
{
'#type': 'vertical',
'#ref': 'L',
'#parents': ['G'],
'metadata': {'group_access': {self.TEST_PARTITION_ID: [1, 2]}},
},
{
'#type': 'vertical',
'#ref': 'M',
'#parents': ['G', 'H'],
'metadata': {'group_access': {self.TEST_PARTITION_ID: [1, 2, 3]}},
},
{
'#type': 'vertical',
'#ref': 'O',
'#parents': ['K', 'B'],
},
]
def test_user_assigned(self):
"""
Test when user is assigned to group in user partition.
"""
# TODO ddt with testing user in different groups
trans_block_structure = get_course_blocks(
self.user,
self.course.location,
......@@ -119,8 +172,8 @@ class UserPartitionTransformerTestCase(CourseStructureTestCase):
)
self.assertSetEqual(
set(trans_block_structure.get_block_keys()),
self.get_block_key_set('course', 'chapter1', 'lesson1', 'vertical1', 'html2')
self.get_block_key_set(self.blocks, 'course', 'A', 'B', 'C', 'E', 'F', 'G', 'J', 'L', 'M', 'O')
)
def test_staff_user(self):
self.assert_staff_access_to_all_blocks(self.staff, self.course, self.blocks, self.transformer)
self.assert_staff_access_to_all_blocks(self.course, self.blocks, self.transformer)
......@@ -4,7 +4,7 @@ Tests for VisibilityTransformer.
import ddt
from course_blocks.transformers.visibility import VisibilityTransformer
from .test_helpers import BlockParentsMapTestCase
from .test_helpers import BlockParentsMapTestCase, update_block
@ddt.ddt
......@@ -33,7 +33,7 @@ class VisibilityTransformerTestCase(BlockParentsMapTestCase):
for i, _ in enumerate(self.parents_map):
block = self.get_block(i)
block.visible_to_staff_only = (i in staff_only_blocks)
self.update_block(block)
update_block(block)
self.check_transformer_results(
expected_student_visible_blocks, blocks_with_differing_student_access, [VisibilityTransformer()]
......
......@@ -4,7 +4,7 @@ User Partitions Transformer
from openedx.core.lib.block_cache.transformer import BlockStructureTransformer
from .split_test import SplitTestTransformer
from .utils import get_field_on_block
class MergedGroupAccess(object):
"""
......@@ -36,19 +36,26 @@ class MergedGroupAccess(object):
# Note that a user must have access to all partitions in group_access
# or _access in order to access a block.
block_group_access = getattr(xblock, 'group_access', {})
self._access = {} # { partition.id: set(IDs of groups that can access partition }
# Get the group_access value that is directly set on the xblock.
# Do not get the inherited value since field inheritance doesn't
# take a union of them for DAGs.
block_group_access = get_field_on_block(xblock, 'group_access', default_value={})
for partition in user_partitions:
# Within this loop, None <=> Universe set <=> "No access restriction"
block_group_ids = set(block_group_access.get(partition.id, [])) or None
parents_group_ids = [
merged_parent_access._access[partition.id]
for merged_parent_access in merged_parent_access_list
if partition.id in merged_parent_access._access
]
parents_group_ids = []
for merged_parent_access in merged_parent_access_list:
if partition.id in merged_parent_access._access:
parents_group_ids.append(merged_parent_access._access[partition.id])
else:
parents_group_ids = []
break
merged_parent_group_ids = (
set().union(*parents_group_ids)
if parents_group_ids != []
......@@ -193,6 +200,7 @@ class UserPartitionTransformer(BlockStructureTransformer):
user_groups = get_user_partition_groups(
user_info.course_key, user_partitions, user_info.user
)
# TODO test this when deserializing across processes
block_structure.remove_block_if(
lambda block_key: not block_structure.get_transformer_block_data(
block_key, self, 'merged_group_access'
......
"""
Common Helper utilities for transformers
"""
def get_field_on_block(block, field_name, default_value=None):
# Get the field value that is directly set on the xblock.
# Do not get the inherited value since field inheritance
# returns value from only a single parent chain
# (e.g., doesn't take a union in DAGs).
if block.fields[field_name].is_set_on(block):
return getattr(block, field_name)
else:
return default_value
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment