Commit ae439310 by lenacom

New optional parameters for course blocks API: lti_url, block_types

parent 46533015
...@@ -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=None
): ):
""" """
Return a serialized representation of the course blocks. Return a serialized representation of the course blocks.
...@@ -61,6 +62,18 @@ def get_blocks( ...@@ -61,6 +62,18 @@ 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 and len(block_types) == 0:
block_types = None
if block_types:
block_keys_to_remove = []
for block_key in blocks:
block_type = blocks.get_xblock_field(block_key, 'category')
if not block_type in block_types:
block_keys_to_remove.append(block_key)
for block_key in block_keys_to_remove:
blocks.remove_block(block_key, 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 = 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',
] ]
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']:
...@@ -70,7 +77,6 @@ class BlockDictSerializer(serializers.Serializer): # pylint: disable=abstract-m ...@@ -70,7 +77,6 @@ class BlockDictSerializer(serializers.Serializer): # pylint: disable=abstract-m
Serializer that formats a BlockStructure object to a dictionary, rather Serializer that formats a BlockStructure object to a dictionary, rather
than a list, of blocks than a list, of blocks
""" """
root = serializers.CharField(source='root_block_usage_key')
blocks = serializers.SerializerMethodField() blocks = serializers.SerializerMethodField()
def get_blocks(self, structure): def get_blocks(self, structure):
......
...@@ -35,7 +35,6 @@ class TestGetBlocks(EnableTransformerRegistryMixin, SharedModuleStoreTestCase): ...@@ -35,7 +35,6 @@ class TestGetBlocks(EnableTransformerRegistryMixin, SharedModuleStoreTestCase):
def test_basic(self): def test_basic(self):
blocks = get_blocks(self.request, self.course.location, self.user) blocks = get_blocks(self.request, self.course.location, self.user)
self.assertEquals(blocks['root'], unicode(self.course.location))
# subtract for (1) the orphaned course About block and (2) the hidden Html block # subtract for (1) the orphaned course About block and (2) the hidden Html block
self.assertEquals(len(blocks['blocks']), len(self.store.get_items(self.course.id)) - 2) self.assertEquals(len(blocks['blocks']), len(self.store.get_items(self.course.id)) - 2)
...@@ -63,7 +62,6 @@ class TestGetBlocks(EnableTransformerRegistryMixin, SharedModuleStoreTestCase): ...@@ -63,7 +62,6 @@ class TestGetBlocks(EnableTransformerRegistryMixin, SharedModuleStoreTestCase):
sequential_block = self.store.get_item(self.course.id.make_usage_key('sequential', 'sequential_y1')) sequential_block = self.store.get_item(self.course.id.make_usage_key('sequential', 'sequential_y1'))
blocks = get_blocks(self.request, sequential_block.location, self.user) blocks = get_blocks(self.request, sequential_block.location, self.user)
self.assertEquals(blocks['root'], unicode(sequential_block.location))
self.assertEquals(len(blocks['blocks']), 5) self.assertEquals(len(blocks['blocks']), 5)
for block_type, block_name, is_inside_of_structure in ( for block_type, block_name, is_inside_of_structure in (
...@@ -77,3 +75,17 @@ class TestGetBlocks(EnableTransformerRegistryMixin, SharedModuleStoreTestCase): ...@@ -77,3 +75,17 @@ 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'))
block_types = ['problem']
blocks = get_blocks(self.request, sequential_block.location, self.user, block_types=block_types)
for block_type, block_name in (
('problem', 'problem_y1a_1'),
('problem', 'problem_y1a_2'),
('problem', 'problem_y1a_3'),
):
block = self.store.get_item(self.course.id.make_usage_key(block_type, block_name))
self.assertIn(unicode(block.location), blocks['blocks'])
...@@ -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': 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()),
) )
...@@ -136,9 +138,6 @@ class TestBlockDictSerializer(TestBlockSerializerBase): ...@@ -136,9 +138,6 @@ class TestBlockDictSerializer(TestBlockSerializerBase):
def test_basic(self): def test_basic(self):
serializer = self.create_serializer() serializer = self.create_serializer()
# verify root
self.assertEquals(serializer.data['root'], unicode(self.block_structure.root_block_usage_key))
# verify blocks # verify blocks
for block_key_string, serialized_block in serializer.data['blocks'].iteritems(): for block_key_string, serialized_block in serializer.data['blocks'].iteritems():
self.assertEquals(serialized_block['id'], block_key_string) self.assertEquals(serialized_block['id'], block_key_string)
......
...@@ -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=problem,html
**Parameters**: **Parameters**:
...@@ -85,6 +86,11 @@ class BlocksView(DeveloperErrorViewMixin, ListAPIView): ...@@ -85,6 +86,11 @@ class BlocksView(DeveloperErrorViewMixin, ListAPIView):
Example: return_type=dict Example: return_type=dict
* block_types: (list) Requested types of blocks. Possible values include sequential,
vertical, html, problem, video, and discussion.
Example: block_types=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 +153,7 @@ class BlocksView(DeveloperErrorViewMixin, ListAPIView): ...@@ -147,6 +153,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 +184,8 @@ class BlocksView(DeveloperErrorViewMixin, ListAPIView): ...@@ -177,7 +184,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', None),
) )
) )
except ItemNotFoundError as exception: except ItemNotFoundError as exception:
...@@ -198,9 +206,10 @@ class BlocksInCourseView(BlocksView): ...@@ -198,9 +206,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=problem,html
**Parameters**: **Parameters**:
......
...@@ -70,7 +70,7 @@ class BlockStructure(object): ...@@ -70,7 +70,7 @@ class BlockStructure(object):
traversal since it's the more common case and we currently traversal since it's the more common case and we currently
need to support DAGs. need 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