Merge pull request #11684 from CredoReference/feature/edx-23-new
New optional parameters for course blocks API: lti_url, block_types_filter
This commit is contained in:
@@ -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 ---#
|
||||
|
||||
|
||||
Reference in New Issue
Block a user