Commit a1458b99 by Nimisha Asthagiri

Merge pull request #11684 from CredoReference/feature/edx-23-new

New optional parameters for course blocks API: lti_url, block_types_filter
parents 3a955973 7e4f3fd0
...@@ -20,6 +20,7 @@ def get_blocks( ...@@ -20,6 +20,7 @@ def get_blocks(
block_counts=None, block_counts=None,
student_view_data=None, student_view_data=None,
return_type='dict', return_type='dict',
block_types_filter=None,
): ):
""" """
Return a serialized representation of the course blocks. Return a serialized representation of the course blocks.
...@@ -44,6 +45,8 @@ def get_blocks( ...@@ -44,6 +45,8 @@ def get_blocks(
which blocks to return their student_view_data. which blocks to return their student_view_data.
return_type (string): Possible values are 'dict' or 'list'. Indicates return_type (string): Possible values are 'dict' or 'list'. Indicates
the format for returning the blocks. 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. # create ordered list of transformers, adding BlocksAPITransformer at end.
transformers = BlockStructureTransformers() transformers = BlockStructureTransformers()
...@@ -61,6 +64,16 @@ def get_blocks( ...@@ -61,6 +64,16 @@ def get_blocks(
# transform # transform
blocks = get_course_blocks(user, usage_key, transformers) 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 # serialize
serializer_context = { serializer_context = {
'request': request, 'request': request,
......
...@@ -31,6 +31,7 @@ class BlockListGetForm(Form): ...@@ -31,6 +31,7 @@ class BlockListGetForm(Form):
student_view_data = MultiValueField(required=False) student_view_data = MultiValueField(required=False)
usage_key = CharField(required=True) usage_key = CharField(required=True)
username = CharField(required=False) username = CharField(required=False)
block_types_filter = MultiValueField(required=False)
def clean_depth(self): def clean_depth(self):
""" """
...@@ -88,6 +89,7 @@ class BlockListGetForm(Form): ...@@ -88,6 +89,7 @@ class BlockListGetForm(Form):
'student_view_data', 'student_view_data',
'block_counts', 'block_counts',
'nav_depth', 'nav_depth',
'block_types_filter',
] ]
for additional_field in additional_requested_fields: for additional_field in additional_requested_fields:
field_value = cleaned_data.get(additional_field) field_value = cleaned_data.get(additional_field)
......
...@@ -44,6 +44,13 @@ class BlockSerializer(serializers.Serializer): # pylint: disable=abstract-metho ...@@ -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 # add additional requested fields that are supported by the various transformers
for supported_field in SUPPORTED_FIELDS: for supported_field in SUPPORTED_FIELDS:
if supported_field.requested_field_name in self.context['requested_fields']: if supported_field.requested_field_name in self.context['requested_fields']:
......
...@@ -77,3 +77,22 @@ class TestGetBlocks(EnableTransformerRegistryMixin, SharedModuleStoreTestCase): ...@@ -77,3 +77,22 @@ class TestGetBlocks(EnableTransformerRegistryMixin, SharedModuleStoreTestCase):
self.assertIn(unicode(block.location), blocks['blocks']) self.assertIn(unicode(block.location), blocks['blocks'])
else: else:
self.assertNotIn(unicode(block.location), blocks['blocks']) 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')
...@@ -60,6 +60,7 @@ class TestBlockListGetForm(EnableTransformerRegistryMixin, FormTestMixin, Shared ...@@ -60,6 +60,7 @@ class TestBlockListGetForm(EnableTransformerRegistryMixin, FormTestMixin, Shared
'usage_key': usage_key, 'usage_key': usage_key,
'username': self.student.username, 'username': self.student.username,
'user': self.student, 'user': self.student,
'block_types_filter': set(),
} }
def assert_raises_permission_denied(self): def assert_raises_permission_denied(self):
......
...@@ -70,6 +70,7 @@ class TestBlockSerializerBase(EnableTransformerRegistryMixin, SharedModuleStoreT ...@@ -70,6 +70,7 @@ class TestBlockSerializerBase(EnableTransformerRegistryMixin, SharedModuleStoreT
'block_counts', 'block_counts',
'student_view_data', 'student_view_data',
'student_view_multi_device', 'student_view_multi_device',
'lti_url',
]) ])
def assert_extended_block(self, serialized_block): def assert_extended_block(self, serialized_block):
...@@ -81,6 +82,7 @@ class TestBlockSerializerBase(EnableTransformerRegistryMixin, SharedModuleStoreT ...@@ -81,6 +82,7 @@ class TestBlockSerializerBase(EnableTransformerRegistryMixin, SharedModuleStoreT
'id', 'type', 'lms_web_url', 'student_view_url', 'id', 'type', 'lms_web_url', 'student_view_url',
'display_name', 'graded', 'display_name', 'graded',
'block_counts', 'student_view_multi_device', 'block_counts', 'student_view_multi_device',
'lti_url',
}, },
set(serialized_block.iterkeys()), set(serialized_block.iterkeys()),
) )
......
...@@ -30,9 +30,10 @@ class BlocksView(DeveloperErrorViewMixin, ListAPIView): ...@@ -30,9 +30,10 @@ class BlocksView(DeveloperErrorViewMixin, ListAPIView):
GET /api/courses/v1/blocks/<usage_id>/? GET /api/courses/v1/blocks/<usage_id>/?
username=anjali username=anjali
&depth=all &depth=all
&requested_fields=graded,format,student_view_multi_device &requested_fields=graded,format,student_view_multi_device,lti_url
&block_counts=video &block_counts=video
&student_view_data=video &student_view_data=video
&block_types_filter=problem,html
**Parameters**: **Parameters**:
...@@ -85,6 +86,12 @@ class BlocksView(DeveloperErrorViewMixin, ListAPIView): ...@@ -85,6 +86,12 @@ class BlocksView(DeveloperErrorViewMixin, ListAPIView):
Example: return_type=dict 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** **Response Values**
The following fields are returned with a successful response. The following fields are returned with a successful response.
...@@ -147,6 +154,7 @@ class BlocksView(DeveloperErrorViewMixin, ListAPIView): ...@@ -147,6 +154,7 @@ class BlocksView(DeveloperErrorViewMixin, ListAPIView):
if the student_view_url and the student_view_data fields are not if the student_view_url and the student_view_data fields are not
supported. supported.
* lti_url: The block URL for an LTI consumer.
""" """
def list(self, request, usage_key_string): # pylint: disable=arguments-differ def list(self, request, usage_key_string): # pylint: disable=arguments-differ
...@@ -177,7 +185,8 @@ class BlocksView(DeveloperErrorViewMixin, ListAPIView): ...@@ -177,7 +185,8 @@ class BlocksView(DeveloperErrorViewMixin, ListAPIView):
params.cleaned_data['requested_fields'], params.cleaned_data['requested_fields'],
params.cleaned_data.get('block_counts', []), params.cleaned_data.get('block_counts', []),
params.cleaned_data.get('student_view_data', []), 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: except ItemNotFoundError as exception:
...@@ -198,9 +207,10 @@ class BlocksInCourseView(BlocksView): ...@@ -198,9 +207,10 @@ class BlocksInCourseView(BlocksView):
GET /api/courses/v1/blocks/?course_id=<course_id> GET /api/courses/v1/blocks/?course_id=<course_id>
&username=anjali &username=anjali
&depth=all &depth=all
&requested_fields=graded,format,student_view_multi_device &requested_fields=graded,format,student_view_multi_device,lti_url
&block_counts=video &block_counts=video
&student_view_data=video &student_view_data=video
&block_types_filter=problem,html
**Parameters**: **Parameters**:
......
...@@ -66,11 +66,11 @@ class BlockStructure(object): ...@@ -66,11 +66,11 @@ class BlockStructure(object):
def __iter__(self): def __iter__(self):
""" """
The default iterator for a block structure is a topological The default iterator for a block structure is get_block_keys()
traversal since it's the more common case and we currently since we need to filter blocks as a list.
need to support DAGs. A topological traversal can be used to support DAGs.
""" """
return self.topological_traversal() return self.get_block_keys()
#--- Block structure relation methods ---# #--- Block structure relation methods ---#
......
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