Commit ced7fde3 by Nimisha Asthagiri Committed by J. Cliff Dyer

Transformer: BlockNavigationTransformer

parent 9abebe05
"""
TODO
"""
from openedx.core.lib.block_cache.transformer import BlockStructureTransformer
from .block_depth import BlockDepthTransformer
class DescendantList(object):
"""
Contain
"""
def __init__(self):
self.items = []
class BlockNavigationTransformer(BlockStructureTransformer):
"""
Creates a table of contents for the course.
Prerequisites: BlockDepthTransformer must be run before this in the
transform phase.
"""
VERSION = 1
BLOCK_NAVIGATION = 'block_nav'
BLOCK_NAVIGATION_FOR_CHILDREN = 'children_block_nav'
def __init__(self, nav_depth):
self.nav_depth = nav_depth
@classmethod
def name(cls):
return "blocks_api:block_navigation"
@classmethod
def collect(cls, block_structure):
"""
Collects any information that's necessary to execute this transformer's
transform method.
"""
# collect basic xblock fields
block_structure.request_xblock_fields('hide_from_toc')
def transform(self, usage_info, block_structure): # pylint: disable=unused-argument
"""
Mutates block_structure based on the given usage_info.
"""
if self.nav_depth is None:
return
for block_key in block_structure.topological_traversal():
parents = block_structure.get_parents(block_key)
parents_descendants_list = set()
for parent_key in parents:
parent_nav = block_structure.get_transformer_block_field(
parent_key,
self,
self.BLOCK_NAVIGATION_FOR_CHILDREN,
)
if parent_nav is not None:
parents_descendants_list |= parent_nav
children_descendants_list = None
if (
not block_structure.get_xblock_field(block_key, 'hide_from_toc', False) and (
not parents or
any(parent_desc_list is not None for parent_desc_list in parents_descendants_list)
)
):
# add self to parent's descendants
for parent_desc_list in parents_descendants_list:
if parent_desc_list is not None:
parent_desc_list.items.append(unicode(block_key))
if BlockDepthTransformer.get_block_depth(block_structure, block_key) > self.nav_depth:
children_descendants_list = parents_descendants_list
else:
block_nav_list = DescendantList()
children_descendants_list = {block_nav_list}
block_structure.set_transformer_block_field(
block_key,
self,
self.BLOCK_NAVIGATION,
block_nav_list.items
)
block_structure.set_transformer_block_field(
block_key,
self,
self.BLOCK_NAVIGATION_FOR_CHILDREN,
children_descendants_list
)
# pylint: disable=protected-access
"""
Tests for BlockNavigationTransformer.
"""
import ddt
from unittest import TestCase
from lms.djangoapps.course_api.blocks.transformers.block_depth import BlockDepthTransformer
from lms.djangoapps.course_api.blocks.transformers.navigation import BlockNavigationTransformer
from openedx.core.lib.block_cache.tests.test_utils import ChildrenMapTestMixin
from openedx.core.lib.block_cache.block_structure import BlockStructureModulestoreData
from openedx.core.lib.block_cache.block_structure_factory import BlockStructureFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import SampleCourseFactory
from xmodule.modulestore import ModuleStoreEnum
@ddt.ddt
class BlockNavigationTransformerTestCase(TestCase, ChildrenMapTestMixin):
"""
Course-agnostic test class for testing the Navigation transformer.
"""
@ddt.data(
(0, 0, [], []),
(0, 0, ChildrenMapTestMixin.LINEAR_CHILDREN_MAP, [[], [], [], []]),
(None, 0, ChildrenMapTestMixin.LINEAR_CHILDREN_MAP, [[1, 2, 3], [], [], []]),
(None, 1, ChildrenMapTestMixin.LINEAR_CHILDREN_MAP, [[1], [2, 3], [], []]),
(None, 2, ChildrenMapTestMixin.LINEAR_CHILDREN_MAP, [[1], [2], [3], []]),
(None, 3, ChildrenMapTestMixin.LINEAR_CHILDREN_MAP, [[1], [2], [3], []]),
(None, 4, ChildrenMapTestMixin.LINEAR_CHILDREN_MAP, [[1], [2], [3], []]),
(1, 4, ChildrenMapTestMixin.LINEAR_CHILDREN_MAP, [[1], [], [], []]),
(2, 4, ChildrenMapTestMixin.LINEAR_CHILDREN_MAP, [[1], [2], [], []]),
(0, 0, ChildrenMapTestMixin.DAG_CHILDREN_MAP, [[], [], [], [], [], [], []]),
(0, 0, ChildrenMapTestMixin.DAG_CHILDREN_MAP, [[], [], [], [], [], [], []]),
(None, 0, ChildrenMapTestMixin.DAG_CHILDREN_MAP, [[1, 2, 3, 4, 5, 6], [], [], [], [], [], []]),
(None, 1, ChildrenMapTestMixin.DAG_CHILDREN_MAP, [[1, 2], [3, 5, 6], [3, 4, 5, 6], [], [], [], []]),
(None, 2, ChildrenMapTestMixin.DAG_CHILDREN_MAP, [[1, 2], [3], [3, 4], [5, 6], [], [], []]),
(None, 3, ChildrenMapTestMixin.DAG_CHILDREN_MAP, [[1, 2], [3], [3, 4], [5, 6], [], [], []]),
(None, 4, ChildrenMapTestMixin.DAG_CHILDREN_MAP, [[1, 2], [3], [3, 4], [5, 6], [], [], []]),
(1, 4, ChildrenMapTestMixin.DAG_CHILDREN_MAP, [[1, 2], [], [], [], [], [], []]),
(2, 4, ChildrenMapTestMixin.DAG_CHILDREN_MAP, [[1, 2], [3], [3, 4], [], [], [], []]),
)
@ddt.unpack
def test_navigation(self, depth, nav_depth, children_map, expected_nav_map):
block_structure = self.create_block_structure(BlockStructureModulestoreData, children_map)
BlockDepthTransformer(depth).transform(usage_info=None, block_structure=block_structure)
BlockNavigationTransformer(nav_depth).transform(usage_info=None, block_structure=block_structure)
block_structure._prune_unreachable()
for block_key, expected_nav in enumerate(expected_nav_map):
self.assertSetEqual(
set(unicode(block) for block in expected_nav),
set(
block_structure.get_transformer_block_field(
block_key,
BlockNavigationTransformer,
BlockNavigationTransformer.BLOCK_NAVIGATION,
[]
)
),
)
class BlockNavigationTransformerCourseTestCase(ModuleStoreTestCase):
"""
Uses SampleCourseFactory and Modulestore to test the Navigation transformer,
specifically to test enforcement of the hide_from_toc field
"""
def test_hide_from_toc(self):
course_key = SampleCourseFactory.create().id
course_usage_key = self.store.make_course_usage_key(course_key)
# hide chapter_x from TOC
chapter_x_key = course_key.make_usage_key('chapter', 'chapter_x')
chapter_x = self.store.get_item(chapter_x_key)
chapter_x.hide_from_toc = True
self.store.update_item(chapter_x, ModuleStoreEnum.UserID.test)
block_structure = BlockStructureFactory.create_from_modulestore(course_usage_key, self.store)
# collect phase
BlockDepthTransformer.collect(block_structure)
BlockNavigationTransformer.collect(block_structure)
block_structure._collect_requested_xblock_fields()
self.assertTrue(block_structure.has_block(chapter_x_key))
# transform phase
BlockDepthTransformer().transform(usage_info=None, block_structure=block_structure)
BlockNavigationTransformer(0).transform(usage_info=None, block_structure=block_structure)
block_structure._prune_unreachable()
self.assertTrue(block_structure.has_block(chapter_x_key))
course_descendants = block_structure.get_transformer_block_field(
course_usage_key,
BlockNavigationTransformer,
BlockNavigationTransformer.BLOCK_NAVIGATION,
)
# chapter_y and its descendants should be included
for block_key in [
course_key.make_usage_key('chapter', 'chapter_y'),
course_key.make_usage_key('sequential', 'sequential_y1'),
course_key.make_usage_key('vertical', 'vertical_y1a'),
course_key.make_usage_key('problem', 'problem_y1a_1'),
]:
self.assertIn(unicode(block_key), course_descendants)
# chapter_x and its descendants should not be included
for block_key in [
chapter_x_key,
course_key.make_usage_key('sequential', 'sequential_x1'),
course_key.make_usage_key('vertical', 'vertical_x1a'),
course_key.make_usage_key('problem', 'problem_x1a_1'),
]:
self.assertNotIn(unicode(block_key), course_descendants)
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