diff --git a/lms/djangoapps/course_api/blocks/api.py b/lms/djangoapps/course_api/blocks/api.py index 12636fef52..ab8fd53bbc 100644 --- a/lms/djangoapps/course_api/blocks/api.py +++ b/lms/djangoapps/course_api/blocks/api.py @@ -20,6 +20,7 @@ def get_blocks( block_counts=None, student_view_data=None, return_type='dict', + block_types_filter=None, ): """ Return a serialized representation of the course blocks. @@ -44,6 +45,8 @@ def get_blocks( which blocks to return their student_view_data. return_type (string): Possible values are 'dict' or 'list'. Indicates the format for returning the blocks. + block_types_filter (list): Optional list of block type names used to filter + the final result of returned blocks. """ # create ordered list of transformers, adding BlocksAPITransformer at end. transformers = BlockStructureTransformers() @@ -61,6 +64,16 @@ def get_blocks( # transform blocks = get_course_blocks(user, usage_key, transformers) + # filter blocks by types + if block_types_filter: + block_keys_to_remove = [] + for block_key in blocks: + block_type = blocks.get_xblock_field(block_key, 'category') + if block_type not in block_types_filter: + block_keys_to_remove.append(block_key) + for block_key in block_keys_to_remove: + blocks.remove_block(block_key, keep_descendants=True) + # serialize serializer_context = { 'request': request, diff --git a/lms/djangoapps/course_api/blocks/forms.py b/lms/djangoapps/course_api/blocks/forms.py index d77a148f81..76363e6679 100644 --- a/lms/djangoapps/course_api/blocks/forms.py +++ b/lms/djangoapps/course_api/blocks/forms.py @@ -31,6 +31,7 @@ class BlockListGetForm(Form): student_view_data = MultiValueField(required=False) usage_key = CharField(required=True) username = CharField(required=False) + block_types_filter = MultiValueField(required=False) def clean_depth(self): """ @@ -88,6 +89,7 @@ class BlockListGetForm(Form): 'student_view_data', 'block_counts', 'nav_depth', + 'block_types_filter', ] for additional_field in additional_requested_fields: field_value = cleaned_data.get(additional_field) diff --git a/lms/djangoapps/course_api/blocks/serializers.py b/lms/djangoapps/course_api/blocks/serializers.py index f0682f3041..3f69703c7d 100644 --- a/lms/djangoapps/course_api/blocks/serializers.py +++ b/lms/djangoapps/course_api/blocks/serializers.py @@ -44,6 +44,13 @@ class BlockSerializer(serializers.Serializer): # pylint: disable=abstract-metho ), } + if 'lti_url' in self.context['requested_fields']: + data['lti_url'] = reverse( + 'lti_provider_launch', + kwargs={'course_id': unicode(block_key.course_key), 'usage_id': unicode(block_key)}, + request=self.context['request'], + ) + # add additional requested fields that are supported by the various transformers for supported_field in SUPPORTED_FIELDS: if supported_field.requested_field_name in self.context['requested_fields']: diff --git a/lms/djangoapps/course_api/blocks/tests/test_api.py b/lms/djangoapps/course_api/blocks/tests/test_api.py index b4ab1112a5..8cbea0bd16 100644 --- a/lms/djangoapps/course_api/blocks/tests/test_api.py +++ b/lms/djangoapps/course_api/blocks/tests/test_api.py @@ -77,3 +77,22 @@ class TestGetBlocks(EnableTransformerRegistryMixin, SharedModuleStoreTestCase): self.assertIn(unicode(block.location), blocks['blocks']) else: self.assertNotIn(unicode(block.location), blocks['blocks']) + + def test_filtering_by_block_types(self): + sequential_block = self.store.get_item(self.course.id.make_usage_key('sequential', 'sequential_y1')) + + # not filtered blocks + blocks = get_blocks(self.request, sequential_block.location, self.user, requested_fields=['type']) + self.assertEquals(len(blocks['blocks']), 5) + found_not_problem = False + for block in blocks['blocks'].itervalues(): + if block['type'] != 'problem': + found_not_problem = True + self.assertTrue(found_not_problem) + + # filtered blocks + blocks = get_blocks(self.request, sequential_block.location, self.user, + block_types_filter=['problem'], requested_fields=['type']) + self.assertEquals(len(blocks['blocks']), 3) + for block in blocks['blocks'].itervalues(): + self.assertEqual(block['type'], 'problem') diff --git a/lms/djangoapps/course_api/blocks/tests/test_forms.py b/lms/djangoapps/course_api/blocks/tests/test_forms.py index 78f94e4b71..01c2fc0dcd 100644 --- a/lms/djangoapps/course_api/blocks/tests/test_forms.py +++ b/lms/djangoapps/course_api/blocks/tests/test_forms.py @@ -60,6 +60,7 @@ class TestBlockListGetForm(EnableTransformerRegistryMixin, FormTestMixin, Shared 'usage_key': usage_key, 'username': self.student.username, 'user': self.student, + 'block_types_filter': set(), } def assert_raises_permission_denied(self): diff --git a/lms/djangoapps/course_api/blocks/tests/test_serializers.py b/lms/djangoapps/course_api/blocks/tests/test_serializers.py index f3a8691c28..179970b30b 100644 --- a/lms/djangoapps/course_api/blocks/tests/test_serializers.py +++ b/lms/djangoapps/course_api/blocks/tests/test_serializers.py @@ -70,6 +70,7 @@ class TestBlockSerializerBase(EnableTransformerRegistryMixin, SharedModuleStoreT 'block_counts', 'student_view_data', 'student_view_multi_device', + 'lti_url', ]) def assert_extended_block(self, serialized_block): @@ -81,6 +82,7 @@ class TestBlockSerializerBase(EnableTransformerRegistryMixin, SharedModuleStoreT 'id', 'type', 'lms_web_url', 'student_view_url', 'display_name', 'graded', 'block_counts', 'student_view_multi_device', + 'lti_url', }, set(serialized_block.iterkeys()), ) diff --git a/lms/djangoapps/course_api/blocks/views.py b/lms/djangoapps/course_api/blocks/views.py index 8cbc4dcee4..f6e73ed928 100644 --- a/lms/djangoapps/course_api/blocks/views.py +++ b/lms/djangoapps/course_api/blocks/views.py @@ -30,9 +30,10 @@ class BlocksView(DeveloperErrorViewMixin, ListAPIView): GET /api/courses/v1/blocks//? username=anjali &depth=all - &requested_fields=graded,format,student_view_multi_device + &requested_fields=graded,format,student_view_multi_device,lti_url &block_counts=video &student_view_data=video + &block_types_filter=problem,html **Parameters**: @@ -85,6 +86,12 @@ class BlocksView(DeveloperErrorViewMixin, ListAPIView): Example: return_type=dict + * block_types_filter: (list) Requested types of blocks used to filter the final result + of returned blocks. Possible values include sequential, vertical, html, problem, + video, and discussion. + + Example: block_types_filter=vertical,html + **Response Values** The following fields are returned with a successful response. @@ -147,6 +154,7 @@ class BlocksView(DeveloperErrorViewMixin, ListAPIView): if the student_view_url and the student_view_data fields are not supported. + * lti_url: The block URL for an LTI consumer. """ def list(self, request, usage_key_string): # pylint: disable=arguments-differ @@ -177,7 +185,8 @@ class BlocksView(DeveloperErrorViewMixin, ListAPIView): params.cleaned_data['requested_fields'], params.cleaned_data.get('block_counts', []), params.cleaned_data.get('student_view_data', []), - params.cleaned_data['return_type'] + params.cleaned_data['return_type'], + params.cleaned_data.get('block_types_filter', None), ) ) except ItemNotFoundError as exception: @@ -198,9 +207,10 @@ class BlocksInCourseView(BlocksView): GET /api/courses/v1/blocks/?course_id= &username=anjali &depth=all - &requested_fields=graded,format,student_view_multi_device + &requested_fields=graded,format,student_view_multi_device,lti_url &block_counts=video &student_view_data=video + &block_types_filter=problem,html **Parameters**: diff --git a/openedx/core/lib/block_structure/block_structure.py b/openedx/core/lib/block_structure/block_structure.py index a526612d78..c0a40b46da 100644 --- a/openedx/core/lib/block_structure/block_structure.py +++ b/openedx/core/lib/block_structure/block_structure.py @@ -66,11 +66,11 @@ class BlockStructure(object): def __iter__(self): """ - The default iterator for a block structure is a topological - traversal since it's the more common case and we currently - need to support DAGs. + The default iterator for a block structure is get_block_keys() + since we need to filter blocks as a list. + A topological traversal can be used to support DAGs. """ - return self.topological_traversal() + return self.get_block_keys() #--- Block structure relation methods ---#