Commit 8b5ebb75 by Nimisha Asthagiri

fixup! course_api Navigation support.

parent 9726f070
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
""" """
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.forms import Form, CharField, ChoiceField, Field, MultipleHiddenInput from django.forms import Form, CharField, ChoiceField, Field, IntegerField, MultipleHiddenInput
from django.http import Http404 from django.http import Http404
from rest_framework.exceptions import PermissionDenied from rest_framework.exceptions import PermissionDenied
...@@ -11,8 +11,6 @@ from xmodule.modulestore.django import modulestore ...@@ -11,8 +11,6 @@ from xmodule.modulestore.django import modulestore
from opaque_keys import InvalidKeyError from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import UsageKey from opaque_keys.edx.keys import UsageKey
from transformers.student_view import StudentViewTransformer
from transformers.block_counts import BlockCountsTransformer
from .permissions import can_access_other_users_blocks, can_access_users_blocks from .permissions import can_access_other_users_blocks, can_access_users_blocks
...@@ -33,6 +31,7 @@ class BlockListGetForm(Form): ...@@ -33,6 +31,7 @@ class BlockListGetForm(Form):
student_view_data = ListField(required=False) student_view_data = ListField(required=False)
block_counts = ListField(required=False) block_counts = ListField(required=False)
depth = CharField(required=False) depth = CharField(required=False)
nav_depth = IntegerField(required=False, min_value=0)
return_type = ChoiceField( return_type = ChoiceField(
required=False, required=False,
choices=[(choice, choice) for choice in ['dict', 'list']], choices=[(choice, choice) for choice in ['dict', 'list']],
...@@ -107,8 +106,13 @@ class BlockListGetForm(Form): ...@@ -107,8 +106,13 @@ class BlockListGetForm(Form):
cleaned_data = super(BlockListGetForm, self).clean() cleaned_data = super(BlockListGetForm, self).clean()
# add additional requested_fields that are specified as separate parameters, if they were requested # add additional requested_fields that are specified as separate parameters, if they were requested
for additional_field in [StudentViewTransformer.STUDENT_VIEW_DATA, BlockCountsTransformer.BLOCK_COUNTS]: additional_requested_fields = [
if cleaned_data.get(additional_field): 'student_view_data',
'block_counts',
'nav_depth',
]
for additional_field in additional_requested_fields:
if not cleaned_data.get(additional_field) in (None, [], {}): # allow 0 as a requested value
cleaned_data['requested_fields'].add(additional_field) cleaned_data['requested_fields'].add(additional_field)
usage_key = cleaned_data.get('usage_key') usage_key = cleaned_data.get('usage_key')
......
...@@ -7,7 +7,6 @@ from rest_framework.reverse import reverse ...@@ -7,7 +7,6 @@ from rest_framework.reverse import reverse
from transformers import SUPPORTED_FIELDS from transformers import SUPPORTED_FIELDS
# TODO support depth parameter (MA-1366)
class BlockSerializer(serializers.Serializer): class BlockSerializer(serializers.Serializer):
""" """
Serializer for single course block Serializer for single course block
...@@ -48,7 +47,7 @@ class BlockSerializer(serializers.Serializer): ...@@ -48,7 +47,7 @@ class BlockSerializer(serializers.Serializer):
supported_field.default_value, supported_field.default_value,
) )
if field_value is not None: if field_value is not None:
data[supported_field.requested_field_name] = field_value data[supported_field.serializer_field_name] = field_value
if 'children' in self.context['requested_fields']: if 'children' in self.context['requested_fields']:
children = self.context['block_structure'].get_children(block_key) children = self.context['block_structure'].get_children(block_key)
......
...@@ -51,6 +51,7 @@ class TestBlockListGetForm(FormTestMixin, SharedModuleStoreTestCase): ...@@ -51,6 +51,7 @@ class TestBlockListGetForm(FormTestMixin, SharedModuleStoreTestCase):
self.cleaned_data = { self.cleaned_data = {
'block_counts': [], 'block_counts': [],
'depth': 0, 'depth': 0,
'nav_depth': None,
'return_type': 'dict', 'return_type': 'dict',
'requested_fields': {'display_name', 'type'}, 'requested_fields': {'display_name', 'type'},
'student_view_data': [], 'student_view_data': [],
...@@ -68,7 +69,7 @@ class TestBlockListGetForm(FormTestMixin, SharedModuleStoreTestCase): ...@@ -68,7 +69,7 @@ class TestBlockListGetForm(FormTestMixin, SharedModuleStoreTestCase):
def assert_equals_cleaned_data(self): def assert_equals_cleaned_data(self):
form = self.get_form(expected_valid=True) form = self.get_form(expected_valid=True)
self.assertEqual(form.cleaned_data, self.cleaned_data) self.assertDictEqual(form.cleaned_data, self.cleaned_data)
def test_basic(self): def test_basic(self):
self.assert_equals_cleaned_data() self.assert_equals_cleaned_data()
...@@ -141,6 +142,22 @@ class TestBlockListGetForm(FormTestMixin, SharedModuleStoreTestCase): ...@@ -141,6 +142,22 @@ class TestBlockListGetForm(FormTestMixin, SharedModuleStoreTestCase):
self.form_data['depth'] = 'not_an_integer' self.form_data['depth'] = 'not_an_integer'
self.assert_error('depth', "'not_an_integer' is not a valid depth value.") self.assert_error('depth', "'not_an_integer' is not a valid depth value.")
#-- nav depth
def test_nav_depth(self):
self.form_data['nav_depth'] = 3
self.cleaned_data['nav_depth'] = 3
self.cleaned_data['requested_fields'] |= {'nav_depth'}
self.assert_equals_cleaned_data()
def test_nav_depth_invalid(self):
self.form_data['nav_depth'] = 'not_an_integer'
self.assert_error('nav_depth', "Enter a whole number.")
def test_nav_depth_negative(self):
self.form_data['nav_depth'] = -1
self.assert_error('nav_depth', "Ensure this value is greater than or equal to 0.")
#-- return_type #-- return_type
def test_return_type(self): def test_return_type(self):
...@@ -158,8 +175,8 @@ class TestBlockListGetForm(FormTestMixin, SharedModuleStoreTestCase): ...@@ -158,8 +175,8 @@ class TestBlockListGetForm(FormTestMixin, SharedModuleStoreTestCase):
#-- requested fields #-- requested fields
def test_requested_fields(self): def test_requested_fields(self):
self.form_data.setlist('requested_fields', ['graded', 'some_other_field']) self.form_data.setlist('requested_fields', ['graded', 'nav_depth', 'some_other_field'])
self.cleaned_data['requested_fields'] |= {'graded', 'some_other_field'} self.cleaned_data['requested_fields'] |= {'graded', 'nav_depth', 'some_other_field'}
self.assert_equals_cleaned_data() self.assert_equals_cleaned_data()
@ddt.data('block_counts', 'student_view_data') @ddt.data('block_counts', 'student_view_data')
......
...@@ -43,7 +43,7 @@ class TestCourseBlocksView(SharedModuleStoreTestCase): ...@@ -43,7 +43,7 @@ class TestCourseBlocksView(SharedModuleStoreTestCase):
CourseEnrollmentFactory.create(user=self.user, course_id=self.course_key) CourseEnrollmentFactory.create(user=self.user, course_id=self.course_key)
def verify_response(self, expected_status_code=200, params=None, url=None): def verify_response(self, expected_status_code=200, params=None, url=None):
query_params = {'user': self.user.username} query_params = {'user': self.user.username, 'depth': 'all',}
if params: if params:
query_params.update(params) query_params.update(params)
response = self.client.get(url or self.url, query_params) response = self.client.get(url or self.url, query_params)
...@@ -127,6 +127,12 @@ class TestCourseBlocksView(SharedModuleStoreTestCase): ...@@ -127,6 +127,12 @@ class TestCourseBlocksView(SharedModuleStoreTestCase):
for block_data in response.data['blocks'].itervalues(): for block_data in response.data['blocks'].itervalues():
self.assert_in_iff('student_view_data', block_data, block_data['type'] == 'video') self.assert_in_iff('student_view_data', block_data, block_data['type'] == 'video')
def test_navigation_param(self):
response = self.verify_response(params={'nav_depth': 10})
self.verify_response_block_dict(response)
for block_data in response.data['blocks'].itervalues():
self.assertIn('descendants', block_data)
def test_requested_fields_param(self): def test_requested_fields_param(self):
response = self.verify_response( response = self.verify_response(
params={'requested_fields': ['graded', 'format', 'student_view_multi_device', 'children', 'not_a_field']} params={'requested_fields': ['graded', 'format', 'student_view_multi_device', 'children', 'not_a_field']}
......
...@@ -4,13 +4,21 @@ Course API Block Transformers ...@@ -4,13 +4,21 @@ Course API Block Transformers
from student_view import StudentViewTransformer from student_view import StudentViewTransformer
from block_counts import BlockCountsTransformer from block_counts import BlockCountsTransformer
from navigation import BlockNavigationTransformer
class SupportedFieldType(object): class SupportedFieldType(object):
def __init__(self, block_field_name, transformer=None, requested_field_name=None, default_value=None): def __init__(
self,
block_field_name,
transformer=None,
requested_field_name=None,
serializer_field_name=None,
default_value=None
):
self.transformer = transformer self.transformer = transformer
self.block_field_name = block_field_name self.block_field_name = block_field_name
self.requested_field_name = requested_field_name or block_field_name self.requested_field_name = requested_field_name or block_field_name
self.serializer_field_name = serializer_field_name or self.requested_field_name
self.default_value = default_value self.default_value = default_value
...@@ -25,4 +33,11 @@ SUPPORTED_FIELDS = ( ...@@ -25,4 +33,11 @@ SUPPORTED_FIELDS = (
# set the block_field_name to None so the entire data for the transformer is serialized # set the block_field_name to None so the entire data for the transformer is serialized
SupportedFieldType(None, BlockCountsTransformer, BlockCountsTransformer.BLOCK_COUNTS), SupportedFieldType(None, BlockCountsTransformer, BlockCountsTransformer.BLOCK_COUNTS),
SupportedFieldType(
BlockNavigationTransformer.BLOCK_NAVIGATION,
BlockNavigationTransformer,
requested_field_name='nav_depth',
serializer_field_name='descendants',
)
) )
from openedx.core.lib.block_cache.transformer import BlockStructureTransformer
class BlockDepthTransformer(BlockStructureTransformer):
"""
...
"""
VERSION = 1
BLOCK_DEPTH = 'block_depth'
def __init__(self, requested_depth=None):
self.requested_depth = requested_depth
@classmethod
def get_block_depth(cls, block_structure, block_key):
return block_structure.get_transformer_block_data(
block_key,
cls,
cls.BLOCK_DEPTH,
)
def transform(self, user_info, block_structure):
"""
Mutates block_structure based on the given user_info.
"""
for block_key in block_structure.topological_traversal():
parents = block_structure.get_parents(block_key)
if parents:
block_depth = min(
self.get_block_depth(block_structure, parent_key)
for parent_key in parents
) + 1
else:
block_depth = 0
block_structure.set_transformer_block_data(
block_key,
self,
self.BLOCK_DEPTH,
block_depth
)
if self.requested_depth is not None:
block_structure.remove_block_if(
lambda block_key: self.get_block_depth(block_structure, block_key) > self.requested_depth
)
from openedx.core.lib.block_cache.transformer import BlockStructureTransformer from openedx.core.lib.block_cache.transformer import BlockStructureTransformer
from course_api.blocks.transformers.block_counts import BlockCountsTransformer from course_api.blocks.transformers.block_counts import BlockCountsTransformer
from course_api.blocks.transformers.student_view import StudentViewTransformer from course_api.blocks.transformers.student_view import StudentViewTransformer
from course_api.blocks.transformers.block_depth import BlockDepthTransformer
from course_api.blocks.transformers.navigation import BlockNavigationTransformer
class BlocksAPITransformer(BlockStructureTransformer): class BlocksAPITransformer(BlockStructureTransformer):
...@@ -11,9 +13,11 @@ class BlocksAPITransformer(BlockStructureTransformer): ...@@ -11,9 +13,11 @@ class BlocksAPITransformer(BlockStructureTransformer):
STUDENT_VIEW_DATA = 'student_view_data' STUDENT_VIEW_DATA = 'student_view_data'
STUDENT_VIEW_MULTI_DEVICE = 'student_view_multi_device' STUDENT_VIEW_MULTI_DEVICE = 'student_view_multi_device'
def __init__(self, block_types_to_count, requested_student_view_data): def __init__(self, block_types_to_count, requested_student_view_data, depth=None, nav_depth=None):
self.block_types_to_count = block_types_to_count self.block_types_to_count = block_types_to_count
self.requested_student_view_data = requested_student_view_data self.requested_student_view_data = requested_student_view_data
self.depth = depth
self.nav_depth = nav_depth
@classmethod @classmethod
def collect(cls, block_structure): def collect(cls, block_structure):
...@@ -27,6 +31,8 @@ class BlocksAPITransformer(BlockStructureTransformer): ...@@ -27,6 +31,8 @@ class BlocksAPITransformer(BlockStructureTransformer):
# collect data from containing transformers # collect data from containing transformers
StudentViewTransformer.collect(block_structure) StudentViewTransformer.collect(block_structure)
BlockCountsTransformer.collect(block_structure) BlockCountsTransformer.collect(block_structure)
BlockDepthTransformer.collect(block_structure)
BlockNavigationTransformer.collect(block_structure)
# TODO support olx_data by calling export_to_xml(?) # TODO support olx_data by calling export_to_xml(?)
...@@ -36,3 +42,5 @@ class BlocksAPITransformer(BlockStructureTransformer): ...@@ -36,3 +42,5 @@ class BlocksAPITransformer(BlockStructureTransformer):
""" """
StudentViewTransformer(self.requested_student_view_data).transform(user_info, block_structure) StudentViewTransformer(self.requested_student_view_data).transform(user_info, block_structure)
BlockCountsTransformer(self.block_types_to_count).transform(user_info, block_structure) BlockCountsTransformer(self.block_types_to_count).transform(user_info, block_structure)
BlockDepthTransformer(self.depth).transform(user_info, block_structure)
BlockNavigationTransformer(self.nav_depth).transform(user_info, block_structure)
from itertools import chain
from openedx.core.lib.block_cache.transformer import BlockStructureTransformer
from .block_depth import BlockDepthTransformer
class DescendantList(object):
def __init__(self):
self.items = []
class BlockNavigationTransformer(BlockStructureTransformer):
"""
Note: This transformer assumes it is preceded by the BlockDepthTransformer 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 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, user_info, block_structure):
"""
Mutates block_structure based on the given user_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_data(
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(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_data(
block_key,
self,
self.BLOCK_NAVIGATION,
block_nav_list.items
)
block_structure.set_transformer_block_data(
block_key,
self,
self.BLOCK_NAVIGATION_FOR_CHILDREN,
children_descendants_list
)
...@@ -17,6 +17,9 @@ class StudentViewTransformer(BlockStructureTransformer): ...@@ -17,6 +17,9 @@ class StudentViewTransformer(BlockStructureTransformer):
""" """
Collect student_view_multi_device and student_view_data values for each block Collect student_view_multi_device and student_view_data values for each block
""" """
# collect basic xblock fields
block_structure.request_xblock_fields('category')
for block_key in block_structure.topological_traversal(): for block_key in block_structure.topological_traversal():
block = block_structure.get_xblock(block_key) block = block_structure.get_xblock(block_key)
......
from openedx.core.lib.block_cache.block_structure import BlockStructureFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import SampleCourseFactory
from ..block_counts import BlockCountsTransformer
class TestBlockCountsTransformer(ModuleStoreTestCase):
def setUp(self):
super(TestBlockCountsTransformer, self).setUp()
self.course_key = SampleCourseFactory.create().id
self.course_usage_key = self.store.make_course_usage_key(self.course_key)
self.block_structure = BlockStructureFactory.create_from_modulestore(self.course_usage_key, self.store)
def test_transform(self):
# collect phase
BlockCountsTransformer.collect(self.block_structure)
self.block_structure.collect_requested_xblock_fields()
# transform phase
BlockCountsTransformer(['problem', 'chapter']).transform(user_info=None, block_structure=self.block_structure)
# block_counts
chapter_x_key = self.course_key.make_usage_key('chapter', 'chapter_x')
block_counts_for_chapter_x = self.block_structure.get_transformer_block_data(
chapter_x_key, BlockCountsTransformer,
)
block_counts_for_course = self.block_structure.get_transformer_block_data(
self.course_usage_key, BlockCountsTransformer,
)
# verify count of chapters
self.assertEquals(block_counts_for_course['chapter'], 2)
# verify count of problems
self.assertEquals(block_counts_for_course['problem'], 6)
self.assertEquals(block_counts_for_chapter_x['problem'], 3)
# verify other block types are not counted
for block_type in ['course', 'html', 'video']:
self.assertNotIn(block_type, block_counts_for_course)
self.assertNotIn(block_type, block_counts_for_chapter_x)
"""
Tests for BlockDepthTransformer.
"""
import ddt
from unittest import TestCase
from course_api.blocks.transformers.block_depth import BlockDepthTransformer
from openedx.core.lib.block_cache.tests.test_utils import ChildrenMapTestMixin
from openedx.core.lib.block_cache.block_structure import BlockStructureCollectedData
@ddt.ddt
class BlockDepthTransformerTestCase(TestCase, ChildrenMapTestMixin):
"""
...
"""
@ddt.data(
(0, [], [], []),
(0, ChildrenMapTestMixin.SIMPLE_CHILDREN_MAP, [[], [], [], [], []], [1, 2, 3, 4]),
(1, ChildrenMapTestMixin.SIMPLE_CHILDREN_MAP, [[1, 2], [], [], [], []], [3, 4]),
(2, ChildrenMapTestMixin.SIMPLE_CHILDREN_MAP, ChildrenMapTestMixin.SIMPLE_CHILDREN_MAP, []),
(3, ChildrenMapTestMixin.SIMPLE_CHILDREN_MAP, ChildrenMapTestMixin.SIMPLE_CHILDREN_MAP, []),
(None, ChildrenMapTestMixin.SIMPLE_CHILDREN_MAP, ChildrenMapTestMixin.SIMPLE_CHILDREN_MAP, []),
(0, ChildrenMapTestMixin.DAG_CHILDREN_MAP, [[], [], [], [], [], [], []], [1, 2, 3, 4, 5, 6]),
(1, ChildrenMapTestMixin.DAG_CHILDREN_MAP, [[1, 2], [], [], [], [], [], []], [3, 4, 5, 6]),
(2, ChildrenMapTestMixin.DAG_CHILDREN_MAP, [[1, 2], [3], [3, 4], [], [], [], []], [5, 6]),
(3, ChildrenMapTestMixin.DAG_CHILDREN_MAP, ChildrenMapTestMixin.DAG_CHILDREN_MAP, []),
(4, ChildrenMapTestMixin.DAG_CHILDREN_MAP, ChildrenMapTestMixin.DAG_CHILDREN_MAP, []),
(None, ChildrenMapTestMixin.DAG_CHILDREN_MAP, ChildrenMapTestMixin.DAG_CHILDREN_MAP, []),
)
@ddt.unpack
def test_block_depth(self, block_depth, children_map, transformed_children_map, missing_blocks):
block_structure = self.create_block_structure(BlockStructureCollectedData, children_map)
BlockDepthTransformer(block_depth).transform(user_info=None, block_structure=block_structure)
block_structure.prune()
self.assert_block_structure(block_structure, transformed_children_map, missing_blocks)
"""
Tests for BlockNavigationTransformer.
"""
import ddt
from unittest import TestCase
from course_api.blocks.transformers.block_depth import BlockDepthTransformer
from 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 BlockStructureCollectedData, 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(BlockStructureCollectedData, children_map)
BlockDepthTransformer(depth).transform(user_info=None, block_structure=block_structure)
BlockNavigationTransformer(nav_depth).transform(user_info=None, block_structure=block_structure)
block_structure.prune()
for block_key, expected_nav in enumerate(expected_nav_map):
self.assertSetEqual(
set(expected_nav),
set(
block_structure.get_transformer_block_data(
block_key,
BlockNavigationTransformer,
BlockNavigationTransformer.BLOCK_NAVIGATION,
[]
)),
)
class BlockNavigationTransformerCourseTestCase(ModuleStoreTestCase):
"""
Uses SampleCourseFactory and Modulestore to test the Navigation transformer.
"""
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(user_info=None, block_structure=block_structure)
BlockNavigationTransformer(0).transform(user_info=None, block_structure=block_structure)
block_structure.prune()
self.assertTrue(block_structure.has_block(chapter_x_key))
course_descendants = block_structure.get_transformer_block_data(
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(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(block_key, course_descendants)
...@@ -10,10 +10,39 @@ class TestStudentViewTransformer(ModuleStoreTestCase): ...@@ -10,10 +10,39 @@ class TestStudentViewTransformer(ModuleStoreTestCase):
def setUp(self): def setUp(self):
super(TestStudentViewTransformer, self).setUp() super(TestStudentViewTransformer, self).setUp()
self.course_key = ToyCourseFactory.create().id self.course_key = ToyCourseFactory.create().id
self.course_root_loc = self.store.make_course_usage_key(self.course_key) self.course_usage_key = self.store.make_course_usage_key(self.course_key)
self.block_structure = BlockStructureFactory.create_from_modulestore( self.block_structure = BlockStructureFactory.create_from_modulestore(self.course_usage_key, self.store)
self.course_root_loc, self.store
)
def test_collect(self): def test_transform(self):
# collect phase
StudentViewTransformer.collect(self.block_structure) StudentViewTransformer.collect(self.block_structure)
self.block_structure.collect_requested_xblock_fields()
# transform phase
StudentViewTransformer('video').transform(user_info=None, block_structure=self.block_structure)
# verify video data
video_block_key = self.course_key.make_usage_key('video', 'sample_video')
self.assertIsNotNone(
self.block_structure.get_transformer_block_data(
video_block_key, StudentViewTransformer, StudentViewTransformer.STUDENT_VIEW_DATA,
)
)
self.assertFalse(
self.block_structure.get_transformer_block_data(
video_block_key, StudentViewTransformer, StudentViewTransformer.STUDENT_VIEW_MULTI_DEVICE,
)
)
# verify html data
html_block_key = self.course_key.make_usage_key('html', 'toyhtml')
self.assertIsNone(
self.block_structure.get_transformer_block_data(
html_block_key, StudentViewTransformer, StudentViewTransformer.STUDENT_VIEW_DATA,
)
)
self.assertTrue(
self.block_structure.get_transformer_block_data(
html_block_key, StudentViewTransformer, StudentViewTransformer.STUDENT_VIEW_MULTI_DEVICE,
)
)
...@@ -8,8 +8,6 @@ from openedx.core.lib.api.view_utils import view_auth_classes, DeveloperErrorVie ...@@ -8,8 +8,6 @@ from openedx.core.lib.api.view_utils import view_auth_classes, DeveloperErrorVie
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError
from transformers.blocks_api import BlocksAPITransformer from transformers.blocks_api import BlocksAPITransformer
from transformers.block_counts import BlockCountsTransformer
from transformers.student_view import StudentViewTransformer
from .forms import BlockListGetForm from .forms import BlockListGetForm
from .serializers import BlockSerializer, BlockDictSerializer from .serializers import BlockSerializer, BlockDictSerializer
...@@ -127,8 +125,10 @@ class CourseBlocks(DeveloperErrorViewMixin, ListAPIView): ...@@ -127,8 +125,10 @@ class CourseBlocks(DeveloperErrorViewMixin, ListAPIView):
# transform blocks # transform blocks
blocks_api_transformer = BlocksAPITransformer( blocks_api_transformer = BlocksAPITransformer(
params.cleaned_data.get(BlockCountsTransformer.BLOCK_COUNTS, []), params.cleaned_data.get('block_counts', []),
params.cleaned_data.get(StudentViewTransformer.STUDENT_VIEW_DATA, []), params.cleaned_data.get('student_view_data', []),
params.cleaned_data.get('depth', None),
params.cleaned_data.get('nav_depth', None),
) )
try: try:
blocks = get_course_blocks( blocks = get_course_blocks(
......
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