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:
committed by
Jillian Vogel
parent
783dba1c75
commit
ab011314ef
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user