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(
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,
......
......@@ -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)
......
......@@ -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']:
......
......@@ -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')
......@@ -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):
......
......@@ -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()),
)
......
......@@ -30,9 +30,10 @@ class BlocksView(DeveloperErrorViewMixin, ListAPIView):
GET /api/courses/v1/blocks/<usage_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**:
......@@ -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=<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**:
......
......@@ -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 ---#
......
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