Add student_view_data to HTML XBlock

to allow the HTML to be downloadable via the Course Blocks API.

Feature flag ENABLE_HTML_XBLOCK_STUDENT_VIEW_DATA must be set to enable this feature.
This commit is contained in:
bradmerlin
2017-08-28 17:23:17 +02:00
committed by Jillian Vogel
parent 783dba1c75
commit ab011314ef
5 changed files with 79 additions and 4 deletions

View File

@@ -6,6 +6,7 @@ import sys
import textwrap
from datetime import datetime
from django.conf import settings
from fs.errors import ResourceNotFoundError
from lxml import etree
from path import Path as path
@@ -69,6 +70,8 @@ class HtmlBlock(object):
scope=Scope.settings
)
ENABLE_HTML_XBLOCK_STUDENT_VIEW_DATA = 'ENABLE_HTML_XBLOCK_STUDENT_VIEW_DATA'
@XBlock.supports("multi_device")
def student_view(self, _context):
"""
@@ -76,13 +79,25 @@ class HtmlBlock(object):
"""
return Fragment(self.get_html())
def student_view_data(self, context=None): # pylint: disable=unused-argument
"""
Return a JSON representation of the student_view of this XBlock.
"""
if getattr(settings, 'FEATURES', {}).get(self.ENABLE_HTML_XBLOCK_STUDENT_VIEW_DATA, False):
return {'enabled': True, 'html': self.get_html()}
else:
return {
'enabled': False,
'message': 'To enable, set FEATURES["{}"]'.format(self.ENABLE_HTML_XBLOCK_STUDENT_VIEW_DATA)
}
def get_html(self):
""" Returns html required for rendering XModule. """
# When we switch this to an XBlock, we can merge this with student_view,
# but for now the XModule mixin requires that this method be defined.
# pylint: disable=no-member
if self.system.anonymous_student_id:
if self.data is not None and getattr(self.system, 'anonymous_student_id', None) is not None:
return self.data.replace("%%USER_ID%%", self.system.anonymous_student_id)
return self.data

View File

@@ -1,6 +1,9 @@
import unittest
from mock import Mock
import ddt
from django.test.utils import override_settings
from opaque_keys.edx.locator import CourseLocator
from xblock.field_data import DictFieldData
from xblock.fields import ScopeIds
@@ -24,6 +27,60 @@ def instantiate_descriptor(**field_data):
)
@ddt.ddt
class HtmlModuleCourseApiTestCase(unittest.TestCase):
"""
Test the HTML XModule's student_view_data method.
"""
@ddt.data(
dict(),
dict(FEATURES={}),
dict(FEATURES=dict(ENABLE_HTML_XBLOCK_STUDENT_VIEW_DATA=False))
)
def test_disabled(self, settings):
"""
Ensure that student_view_data does not return html if the ENABLE_HTML_XBLOCK_STUDENT_VIEW_DATA feature flag
is not set.
"""
descriptor = Mock()
field_data = DictFieldData({'data': '<h1>Some HTML</h1>'})
module_system = get_test_system()
module = HtmlModule(descriptor, module_system, field_data, Mock())
with override_settings(**settings):
self.assertEqual(module.student_view_data(), dict(
enabled=False,
message='To enable, set FEATURES["ENABLE_HTML_XBLOCK_STUDENT_VIEW_DATA"]',
))
@ddt.data(
'<h1>Some content</h1>', # Valid HTML
'',
None,
'<h1>Some content</h', # Invalid HTML
'<script>alert()</script>', # Does not escape tags
'<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">', # Images allowed
'short string ' * 100, # May contain long strings
)
@override_settings(FEATURES=dict(ENABLE_HTML_XBLOCK_STUDENT_VIEW_DATA=True))
def test_common_values(self, html):
"""
Ensure that student_view_data will return HTML data when enabled,
can handle likely input,
and doesn't modify the HTML in any way.
This means that it does NOT protect against XSS, escape HTML tags, etc.
Note that the %%USER_ID%% substitution is tested below.
"""
descriptor = Mock()
field_data = DictFieldData({'data': html})
module_system = get_test_system()
module = HtmlModule(descriptor, module_system, field_data, Mock())
self.assertEqual(module.student_view_data(), dict(enabled=True, html=html))
class HtmlModuleSubstitutionTestCase(unittest.TestCase):
descriptor = Mock()

View File

@@ -22,7 +22,7 @@ class TestBlocksView(SharedModuleStoreTestCase):
Test class for BlocksView
"""
requested_fields = ['graded', 'format', 'student_view_multi_device', 'children', 'not_a_field', 'due']
BLOCK_TYPES_WITH_STUDENT_VIEW_DATA = ['video', 'discussion']
BLOCK_TYPES_WITH_STUDENT_VIEW_DATA = ['video', 'discussion', 'html']
@classmethod
def setUpClass(cls):

View File

@@ -44,7 +44,7 @@ class TestStudentViewTransformer(ModuleStoreTestCase):
# verify html data
html_block_key = self.course_key.make_usage_key('html', 'toyhtml')
self.assertIsNone(
self.assertIsNotNone(
self.block_structure.get_transformer_block_field(
html_block_key, StudentViewTransformer, StudentViewTransformer.STUDENT_VIEW_DATA,
)

View File

@@ -413,6 +413,9 @@ FEATURES = {
# Set to enable Enterprise integration
'ENABLE_ENTERPRISE_INTEGRATION': False,
# Whether HTML XBlocks/XModules return HTML content with the Course Blocks API student_view_data
'ENABLE_HTML_XBLOCK_STUDENT_VIEW_DATA': False,
}
# Settings for the course reviews tool template and identification key, set either to None to disable course reviews