Commit 5a44f391 by Nimisha Asthagiri

MA-977 Use "supports" decorator on XBlocks for multi-device support

parent 56da2852
...@@ -575,11 +575,13 @@ class LoncapaProblem(object): ...@@ -575,11 +575,13 @@ class LoncapaProblem(object):
return {} return {}
@property @property
def has_responsive_ui(self): def has_multi_device_support(self):
""" """
Returns whether this capa problem has support for responsive UI. Returns whether this capa problem has multi-device support.
""" """
return all(responder.has_responsive_ui for responder in self.responders.values()) return all(
responder.multi_device_support for responder in self.responders.values()
)
# ======= Private Methods Below ======== # ======= Private Methods Below ========
......
...@@ -145,9 +145,9 @@ class LoncapaResponse(object): ...@@ -145,9 +145,9 @@ class LoncapaResponse(object):
required_attributes = [] required_attributes = []
# Overridable field that specifies whether this capa response type has support for # Overridable field that specifies whether this capa response type has support for
# responsive UI, for rendering on devices of different sizes and shapes. # for rendering on devices of different sizes and shapes.
# By default, we set this to False, allowing subclasses to override as appropriate. # By default, we set this to False, allowing subclasses to override as appropriate.
has_responsive_ui = False multi_device_support = False
def __init__(self, xml, inputfields, context, system, capa_module): def __init__(self, xml, inputfields, context, system, capa_module):
""" """
...@@ -808,7 +808,7 @@ class ChoiceResponse(LoncapaResponse): ...@@ -808,7 +808,7 @@ class ChoiceResponse(LoncapaResponse):
max_inputfields = 1 max_inputfields = 1
allowed_inputfields = ['checkboxgroup', 'radiogroup'] allowed_inputfields = ['checkboxgroup', 'radiogroup']
correct_choices = None correct_choices = None
has_responsive_ui = True multi_device_support = True
def setup_response(self): def setup_response(self):
self.assign_choice_names() self.assign_choice_names()
...@@ -993,7 +993,7 @@ class MultipleChoiceResponse(LoncapaResponse): ...@@ -993,7 +993,7 @@ class MultipleChoiceResponse(LoncapaResponse):
max_inputfields = 1 max_inputfields = 1
allowed_inputfields = ['choicegroup'] allowed_inputfields = ['choicegroup']
correct_choices = None correct_choices = None
has_responsive_ui = True multi_device_support = True
def setup_response(self): def setup_response(self):
# call secondary setup for MultipleChoice questions, to set name # call secondary setup for MultipleChoice questions, to set name
...@@ -1346,7 +1346,7 @@ class OptionResponse(LoncapaResponse): ...@@ -1346,7 +1346,7 @@ class OptionResponse(LoncapaResponse):
hint_tag = 'optionhint' hint_tag = 'optionhint'
allowed_inputfields = ['optioninput'] allowed_inputfields = ['optioninput']
answer_fields = None answer_fields = None
has_responsive_ui = True multi_device_support = True
def setup_response(self): def setup_response(self):
self.answer_fields = self.inputfields self.answer_fields = self.inputfields
...@@ -1421,7 +1421,7 @@ class NumericalResponse(LoncapaResponse): ...@@ -1421,7 +1421,7 @@ class NumericalResponse(LoncapaResponse):
allowed_inputfields = ['textline', 'formulaequationinput'] allowed_inputfields = ['textline', 'formulaequationinput']
required_attributes = ['answer'] required_attributes = ['answer']
max_inputfields = 1 max_inputfields = 1
has_responsive_ui = True multi_device_support = True
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.correct_answer = '' self.correct_answer = ''
...@@ -1645,7 +1645,7 @@ class StringResponse(LoncapaResponse): ...@@ -1645,7 +1645,7 @@ class StringResponse(LoncapaResponse):
required_attributes = ['answer'] required_attributes = ['answer']
max_inputfields = 1 max_inputfields = 1
correct_answer = [] correct_answer = []
has_responsive_ui = True multi_device_support = True
def setup_response_backward(self): def setup_response_backward(self):
self.correct_answer = [ self.correct_answer = [
......
...@@ -189,13 +189,6 @@ class CapaDescriptor(CapaFields, RawDescriptor): ...@@ -189,13 +189,6 @@ class CapaDescriptor(CapaFields, RawDescriptor):
registered_tags = responsetypes.registry.registered_tags() registered_tags = responsetypes.registry.registered_tags()
return set([node.tag for node in tree.iter() if node.tag in registered_tags]) return set([node.tag for node in tree.iter() if node.tag in registered_tags])
@property
def has_responsive_ui(self):
"""
Returns whether this module has support for responsive UI.
"""
return self.lcp.has_responsive_ui
def index_dictionary(self): def index_dictionary(self):
""" """
Return dictionary prepared with module content and type for indexing. Return dictionary prepared with module content and type for indexing.
...@@ -211,6 +204,17 @@ class CapaDescriptor(CapaFields, RawDescriptor): ...@@ -211,6 +204,17 @@ class CapaDescriptor(CapaFields, RawDescriptor):
result.update(index) result.update(index)
return result return result
def has_support(self, view, functionality):
"""
Override the XBlock.has_support method to return appropriate
value for the multi-device functionality.
Returns whether the given view has support for the given functionality.
"""
if functionality == "multi_device":
return self.lcp.has_multi_device_support
else:
return False
# Proxy to CapaModule for access to any of its attributes # Proxy to CapaModule for access to any of its attributes
answer_available = module_attr('answer_available') answer_available = module_attr('answer_available')
check_button_name = module_attr('check_button_name') check_button_name = module_attr('check_button_name')
......
...@@ -84,7 +84,9 @@ class HtmlModule(HtmlModuleMixin): ...@@ -84,7 +84,9 @@ class HtmlModule(HtmlModuleMixin):
""" """
Module for putting raw html in a course Module for putting raw html in a course
""" """
pass @XBlock.supports("multi_device")
def student_view(self, context):
return super(HtmlModule, self).student_view(context)
class HtmlDescriptor(HtmlFields, XmlDescriptor, EditingDescriptor): # pylint: disable=abstract-method class HtmlDescriptor(HtmlFields, XmlDescriptor, EditingDescriptor): # pylint: disable=abstract-method
...@@ -95,7 +97,6 @@ class HtmlDescriptor(HtmlFields, XmlDescriptor, EditingDescriptor): # pylint: d ...@@ -95,7 +97,6 @@ class HtmlDescriptor(HtmlFields, XmlDescriptor, EditingDescriptor): # pylint: d
module_class = HtmlModule module_class = HtmlModule
filename_extension = "xml" filename_extension = "xml"
template_dir_name = "html" template_dir_name = "html"
has_responsive_ui = True
show_in_read_only_mode = True show_in_read_only_mode = True
js = {'coffee': [resource_string(__name__, 'js/src/html/edit.coffee')]} js = {'coffee': [resource_string(__name__, 'js/src/html/edit.coffee')]}
......
...@@ -80,7 +80,7 @@ class CourseViewTestsMixin(object): ...@@ -80,7 +80,7 @@ class CourseViewTestsMixin(object):
factory = MultipleChoiceResponseXMLFactory() factory = MultipleChoiceResponseXMLFactory()
args = {'choices': [False, True, False]} args = {'choices': [False, True, False]}
problem_xml = factory.build_xml(**args) problem_xml = factory.build_xml(**args)
ItemFactory.create( self.problem = ItemFactory.create(
category="problem", category="problem",
parent_location=self.sequential.location, parent_location=self.sequential.location,
display_name="Problem 1", display_name="Problem 1",
...@@ -94,6 +94,12 @@ class CourseViewTestsMixin(object): ...@@ -94,6 +94,12 @@ class CourseViewTestsMixin(object):
display_name="Video 1", display_name="Video 1",
) )
self.html = ItemFactory.create(
category="html",
parent_location=self.sequential.location,
display_name="HTML 1",
)
self.empty_course = CourseFactory.create( self.empty_course = CourseFactory.create(
start=datetime(2014, 6, 16, 14, 30), start=datetime(2014, 6, 16, 14, 30),
end=datetime(2015, 1, 16), end=datetime(2015, 1, 16),
...@@ -457,7 +463,7 @@ class CourseBlocksOrNavigationTestMixin(CourseDetailTestMixin, CourseViewTestsMi ...@@ -457,7 +463,7 @@ class CourseBlocksOrNavigationTestMixin(CourseDetailTestMixin, CourseViewTestsMi
blocks = response.data[self.block_navigation_view_type] blocks = response.data[self.block_navigation_view_type]
# verify number of blocks # verify number of blocks
self.assertEquals(len(blocks), 4) self.assertEquals(len(blocks), 5)
# verify fields in blocks # verify fields in blocks
for field, block in product(self.block_fields, blocks.values()): for field, block in product(self.block_fields, blocks.values()):
...@@ -519,6 +525,23 @@ class CourseBlocksTestMixin(object): ...@@ -519,6 +525,23 @@ class CourseBlocksTestMixin(object):
self.assertIn('problem', root_block['block_count']) self.assertIn('problem', root_block['block_count'])
self.assertEquals(root_block['block_count']['problem'], 1) self.assertEquals(root_block['block_count']['problem'], 1)
def test_multi_device_support(self):
"""
Verifies the view's response when multi_device support is requested.
"""
response = self.http_get_for_course(
data={'fields': 'multi_device'}
)
self.assertEquals(response.status_code, 200)
for block, expected_multi_device_support in (
(self.problem, True),
(self.html, True),
(self.video, False)
):
block_response = response.data[self.block_navigation_view_type][unicode(block.location)]
self.assertEquals(block_response['multi_device'], expected_multi_device_support)
class CourseNavigationTestMixin(object): class CourseNavigationTestMixin(object):
""" """
...@@ -535,7 +558,7 @@ class CourseNavigationTestMixin(object): ...@@ -535,7 +558,7 @@ class CourseNavigationTestMixin(object):
) )
root_block = response.data[self.block_navigation_view_type][unicode(self.course.location)] root_block = response.data[self.block_navigation_view_type][unicode(self.course.location)]
self.assertIn('descendants', root_block) self.assertIn('descendants', root_block)
self.assertEquals(len(root_block['descendants']), 3) self.assertEquals(len(root_block['descendants']), 4)
def test_depth(self): def test_depth(self):
""" """
...@@ -545,7 +568,7 @@ class CourseNavigationTestMixin(object): ...@@ -545,7 +568,7 @@ class CourseNavigationTestMixin(object):
container_descendants = ( container_descendants = (
(self.course.location, 1), (self.course.location, 1),
(self.sequential.location, 2), (self.sequential.location, 3),
) )
for container_location, expected_num_descendants in container_descendants: for container_location, expected_num_descendants in container_descendants:
block = response.data[self.block_navigation_view_type][unicode(container_location)] block = response.data[self.block_navigation_view_type][unicode(container_location)]
......
...@@ -321,7 +321,7 @@ class CourseBlocksAndNavigation(ListAPIView): ...@@ -321,7 +321,7 @@ class CourseBlocksAndNavigation(ListAPIView):
GET api/course_structure/v0/courses/{course_id}/blocks+navigation/ GET api/course_structure/v0/courses/{course_id}/blocks+navigation/
&block_count=video &block_count=video
&block_json={"video":{"profiles":["mobile_low"]}} &block_json={"video":{"profiles":["mobile_low"]}}
&fields=graded,format,responsive_ui &fields=graded,format,multi_device
**Parameters**: **Parameters**:
...@@ -335,9 +335,9 @@ class CourseBlocksAndNavigation(ListAPIView): ...@@ -335,9 +335,9 @@ class CourseBlocksAndNavigation(ListAPIView):
Example: block_count="video,problem" Example: block_count="video,problem"
* fields: (list) Indicates which additional fields to return for each block. * fields: (list) Indicates which additional fields to return for each block.
Default is "children,graded,format,responsive_ui" Default is "children,graded,format,multi_device"
Example: fields=graded,format,responsive_ui Example: fields=graded,format,multi_device
* navigation_depth (integer) Indicates how far deep to traverse into the course hierarchy before bundling * navigation_depth (integer) Indicates how far deep to traverse into the course hierarchy before bundling
all the descendants. all the descendants.
...@@ -387,8 +387,9 @@ class CourseBlocksAndNavigation(ListAPIView): ...@@ -387,8 +387,9 @@ class CourseBlocksAndNavigation(ListAPIView):
Possible values can be "Homework", "Lab", "Midterm Exam", and "Final Exam". Possible values can be "Homework", "Lab", "Midterm Exam", and "Final Exam".
Returned only if "format" is included in the "fields" parameter. Returned only if "format" is included in the "fields" parameter.
* responsive_ui: (boolean) Whether or not the block's rendering obtained via block_url is responsive. * multi_device: (boolean) Whether or not the block's rendering obtained via block_url has support
Returned only if "responsive_ui" is included in the "fields" parameter. for multiple devices.
Returned only if "multi_device" is included in the "fields" parameter.
* navigation: A dictionary that maps block IDs to a collection of navigation information about each block. * navigation: A dictionary that maps block IDs to a collection of navigation information about each block.
Each block contains the following fields. Returned only if using the "navigation" endpoint. Each block contains the following fields. Returned only if using the "navigation" endpoint.
...@@ -405,7 +406,7 @@ class CourseBlocksAndNavigation(ListAPIView): ...@@ -405,7 +406,7 @@ class CourseBlocksAndNavigation(ListAPIView):
""" """
A class for encapsulating the request information, including what optional fields are requested. A class for encapsulating the request information, including what optional fields are requested.
""" """
DEFAULT_FIELDS = "children,graded,format,responsive_ui" DEFAULT_FIELDS = "children,graded,format,multi_device"
def __init__(self, request, course): def __init__(self, request, course):
self.request = request self.request = request
...@@ -417,10 +418,6 @@ class CourseBlocksAndNavigation(ListAPIView): ...@@ -417,10 +418,6 @@ class CourseBlocksAndNavigation(ListAPIView):
# fields # fields
self.fields = set(request.GET.get('fields', self.DEFAULT_FIELDS).split(",")) self.fields = set(request.GET.get('fields', self.DEFAULT_FIELDS).split(","))
# children
self.children = 'children' in self.fields
self.fields.discard('children')
# block_count # block_count
self.block_count = request.GET.get('block_count', "") self.block_count = request.GET.get('block_count', "")
self.block_count = ( self.block_count = (
...@@ -489,7 +486,7 @@ class CourseBlocksAndNavigation(ListAPIView): ...@@ -489,7 +486,7 @@ class CourseBlocksAndNavigation(ListAPIView):
self.descendants_of_parent = parent_block_info.descendants_of_self self.descendants_of_parent = parent_block_info.descendants_of_self
# add ourselves to the parent's children, if requested. # add ourselves to the parent's children, if requested.
if request_info.children: if 'children' in request_info.fields:
parent_block_info.value.setdefault("children", []).append(unicode(block.location)) parent_block_info.value.setdefault("children", []).append(unicode(block.location))
# the block's data to include in the response # the block's data to include in the response
...@@ -592,6 +589,13 @@ class CourseBlocksAndNavigation(ListAPIView): ...@@ -592,6 +589,13 @@ class CourseBlocksAndNavigation(ListAPIView):
# block JSON data # block JSON data
self.add_block_json(request_info, block_info) self.add_block_json(request_info, block_info)
# multi-device support
if 'multi_device' in request_info.fields:
block_info.value['multi_device'] = block_info.block.has_support(
getattr(block_info.block, 'student_view', None),
'multi_device'
)
# additional fields # additional fields
self.add_additional_fields(request_info, block_info) self.add_additional_fields(request_info, block_info)
...@@ -667,7 +671,6 @@ class CourseBlocksAndNavigation(ListAPIView): ...@@ -667,7 +671,6 @@ class CourseBlocksAndNavigation(ListAPIView):
FIELD_MAP = { FIELD_MAP = {
'graded': BlockApiField(block_field_name='graded', api_field_default=False), 'graded': BlockApiField(block_field_name='graded', api_field_default=False),
'format': BlockApiField(block_field_name='format', api_field_default=None), 'format': BlockApiField(block_field_name='format', api_field_default=None),
'responsive_ui': BlockApiField(block_field_name='has_responsive_ui', api_field_default=False),
} }
def add_additional_fields(self, request_info, block_info): def add_additional_fields(self, request_info, block_info):
......
...@@ -34,7 +34,7 @@ git+https://github.com/hmarr/django-debug-toolbar-mongo.git@b0686a76f1ce3532088c ...@@ -34,7 +34,7 @@ git+https://github.com/hmarr/django-debug-toolbar-mongo.git@b0686a76f1ce3532088c
git+https://github.com/edx/rfc6266.git@v0.0.5-edx#egg=rfc6266==0.0.5-edx git+https://github.com/edx/rfc6266.git@v0.0.5-edx#egg=rfc6266==0.0.5-edx
# Our libraries: # Our libraries:
-e git+https://github.com/edx/XBlock.git@017b46b80e712b1318379912257cf1cc1b68eb0e#egg=XBlock -e git+https://github.com/edx/XBlock.git@d1ff8cf31a9b94916ce06ba06d4176bd72e15768#egg=XBlock
-e git+https://github.com/edx/codejail.git@6b17c33a89bef0ac510926b1d7fea2748b73aadd#egg=codejail -e git+https://github.com/edx/codejail.git@6b17c33a89bef0ac510926b1d7fea2748b73aadd#egg=codejail
-e git+https://github.com/edx/js-test-tool.git@v0.1.6#egg=js_test_tool -e git+https://github.com/edx/js-test-tool.git@v0.1.6#egg=js_test_tool
-e git+https://github.com/edx/event-tracking.git@0.2.0#egg=event-tracking -e git+https://github.com/edx/event-tracking.git@0.2.0#egg=event-tracking
......
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