Convert AboutModule, CourseInfoModule, HtmlModule and StaticTabModule to XBlocks.
This commit is contained in:
@@ -22,7 +22,7 @@ from django.utils.translation import ugettext as _
|
||||
|
||||
from cms.djangoapps.contentstore.push_notification import enqueue_push_course_update
|
||||
from openedx.core.lib.xblock_utils import get_course_update_items
|
||||
from xmodule.html_module import CourseInfoModule
|
||||
from xmodule.html_module import CourseInfoBlock
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
|
||||
@@ -76,7 +76,7 @@ def update_course_updates(location, update, passed_id=None, user=None):
|
||||
"id": len(course_update_items) + 1,
|
||||
"date": update["date"],
|
||||
"content": update["content"],
|
||||
"status": CourseInfoModule.STATUS_VISIBLE
|
||||
"status": CourseInfoBlock.STATUS_VISIBLE
|
||||
}
|
||||
course_update_items.append(course_update_dict)
|
||||
enqueue_push_course_update(update, location.course_key)
|
||||
@@ -106,14 +106,14 @@ def _get_visible_update(course_update_items):
|
||||
"""
|
||||
if isinstance(course_update_items, dict):
|
||||
# single course update item
|
||||
if course_update_items.get("status") != CourseInfoModule.STATUS_DELETED:
|
||||
if course_update_items.get("status") != CourseInfoBlock.STATUS_DELETED:
|
||||
return _make_update_dict(course_update_items)
|
||||
else:
|
||||
# requested course update item has been deleted (soft delete)
|
||||
return {"error": _("Course update not found."), "status": 404}
|
||||
|
||||
return ([_make_update_dict(update) for update in course_update_items
|
||||
if update.get("status") != CourseInfoModule.STATUS_DELETED])
|
||||
if update.get("status") != CourseInfoBlock.STATUS_DELETED])
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@@ -138,7 +138,7 @@ def delete_course_update(location, update, passed_id, user):
|
||||
if 0 < passed_index <= len(course_update_items):
|
||||
course_update_item = course_update_items[passed_index - 1]
|
||||
# soft delete course update item
|
||||
course_update_item["status"] = CourseInfoModule.STATUS_DELETED
|
||||
course_update_item["status"] = CourseInfoBlock.STATUS_DELETED
|
||||
course_update_items[passed_index - 1] = course_update_item
|
||||
|
||||
# update db record
|
||||
|
||||
@@ -116,6 +116,6 @@ class TestDeleteOrphan(TestOrphanBase):
|
||||
# there should be one in published
|
||||
self.assertOrphanCount(course.id, 0)
|
||||
self.assertOrphanCount(published_branch, 1)
|
||||
self.assertIn(orphan, self.store.get_items(published_branch))
|
||||
self.assertIn(orphan.location, [x.location for x in self.store.get_items(published_branch)])
|
||||
|
||||
return course, orphan
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import absolute_import
|
||||
from xmodule import templates
|
||||
from xmodule.capa_module import ProblemBlock
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
from xmodule.html_module import HtmlDescriptor
|
||||
from xmodule.html_module import HtmlBlock
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.exceptions import DuplicateCourseError
|
||||
from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, ModuleStoreTestCase
|
||||
@@ -44,10 +44,10 @@ class TemplateTests(ModuleStoreTestCase):
|
||||
|
||||
def test_get_some_templates(self):
|
||||
self.assertEqual(len(SequenceDescriptor.templates()), 0)
|
||||
self.assertGreater(len(HtmlDescriptor.templates()), 0)
|
||||
self.assertGreater(len(HtmlBlock.templates()), 0)
|
||||
self.assertIsNone(SequenceDescriptor.get_template('doesntexist.yaml'))
|
||||
self.assertIsNone(HtmlDescriptor.get_template('doesntexist.yaml'))
|
||||
self.assertIsNotNone(HtmlDescriptor.get_template('announcement.yaml'))
|
||||
self.assertIsNone(HtmlBlock.get_template('doesntexist.yaml'))
|
||||
self.assertIsNotNone(HtmlBlock.get_template('announcement.yaml'))
|
||||
|
||||
def test_factories(self):
|
||||
test_course = CourseFactory.create(
|
||||
|
||||
@@ -680,7 +680,7 @@ class TestCourseReIndex(CourseTestCase):
|
||||
self.assertIn(self.SUCCESSFUL_RESPONSE, response.content)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@mock.patch('xmodule.html_module.HtmlDescriptor.index_dictionary')
|
||||
@mock.patch('xmodule.html_module.HtmlBlock.index_dictionary')
|
||||
def test_reindex_course_search_index_error(self, mock_index_dictionary):
|
||||
"""
|
||||
Test json response with mocked error data for html
|
||||
@@ -743,7 +743,7 @@ class TestCourseReIndex(CourseTestCase):
|
||||
with self.assertRaises(SearchIndexingError):
|
||||
reindex_course_and_check_access(self.course.id, self.user)
|
||||
|
||||
@mock.patch('xmodule.html_module.HtmlDescriptor.index_dictionary')
|
||||
@mock.patch('xmodule.html_module.HtmlBlock.index_dictionary')
|
||||
def test_reindex_html_error_json_responses(self, mock_index_dictionary):
|
||||
"""
|
||||
Test json response with mocked error data for html
|
||||
@@ -853,7 +853,7 @@ class TestCourseReIndex(CourseTestCase):
|
||||
with self.assertRaises(SearchIndexingError):
|
||||
CoursewareSearchIndexer.do_course_reindex(modulestore(), self.course.id)
|
||||
|
||||
@mock.patch('xmodule.html_module.HtmlDescriptor.index_dictionary')
|
||||
@mock.patch('xmodule.html_module.HtmlBlock.index_dictionary')
|
||||
def test_indexing_html_error_responses(self, mock_index_dictionary):
|
||||
"""
|
||||
Test do_course_reindex response with mocked error data for html
|
||||
|
||||
@@ -7,7 +7,6 @@ XMODULES = [
|
||||
"course = xmodule.course_module:CourseDescriptor",
|
||||
"customtag = xmodule.template_module:CustomTagDescriptor",
|
||||
"discuss = xmodule.backcompat_module:TranslateCustomTagDescriptor",
|
||||
"html = xmodule.html_module:HtmlDescriptor",
|
||||
"image = xmodule.backcompat_module:TranslateCustomTagDescriptor",
|
||||
"library_content = xmodule.library_content_module:LibraryContentDescriptor",
|
||||
"error = xmodule.error_module:ErrorDescriptor",
|
||||
@@ -20,10 +19,7 @@ XMODULES = [
|
||||
"slides = xmodule.backcompat_module:TranslateCustomTagDescriptor",
|
||||
"videodev = xmodule.backcompat_module:TranslateCustomTagDescriptor",
|
||||
"videosequence = xmodule.seq_module:SequenceDescriptor",
|
||||
"course_info = xmodule.html_module:CourseInfoDescriptor",
|
||||
"static_tab = xmodule.html_module:StaticTabDescriptor",
|
||||
"custom_tag_template = xmodule.raw_module:RawDescriptor",
|
||||
"about = xmodule.html_module:AboutDescriptor",
|
||||
"annotatable = xmodule.annotatable_module:AnnotatableDescriptor",
|
||||
"word_cloud = xmodule.word_cloud_module:WordCloudDescriptor",
|
||||
"hidden = xmodule.hidden_module:HiddenDescriptor",
|
||||
@@ -31,8 +27,12 @@ XMODULES = [
|
||||
"lti = xmodule.lti_module:LTIDescriptor",
|
||||
]
|
||||
XBLOCKS = [
|
||||
"about = xmodule.html_module:AboutBlock",
|
||||
"course_info = xmodule.html_module:CourseInfoBlock",
|
||||
"html = xmodule.html_module:HtmlBlock",
|
||||
"library = xmodule.library_root_xblock:LibraryRoot",
|
||||
"problem = xmodule.capa_module:ProblemBlock",
|
||||
"static_tab = xmodule.html_module:StaticTabBlock",
|
||||
"vertical = xmodule.vertical_block:VerticalBlock",
|
||||
"video = xmodule.video_module:VideoBlock",
|
||||
"videoalpha = xmodule.video_module:VideoBlock",
|
||||
|
||||
@@ -19,13 +19,21 @@ from web_fragments.fragment import Fragment
|
||||
from xblock.core import XBlock
|
||||
from xblock.fields import Boolean, List, Scope, String
|
||||
from xmodule.contentstore.content import StaticContent
|
||||
from xmodule.editing_module import EditingDescriptor
|
||||
from xmodule.editing_module import EditingMixin
|
||||
from xmodule.edxnotes_utils import edxnotes
|
||||
from xmodule.html_checker import check_html
|
||||
from xmodule.stringify import stringify_children
|
||||
from xmodule.util.misc import escape_html_characters
|
||||
from xmodule.x_module import DEPRECATION_VSCOMPAT_EVENT, XModule
|
||||
from xmodule.xml_module import XmlDescriptor, name_to_pathname
|
||||
from xmodule.util.xmodule_django import add_webpack_to_fragment
|
||||
from xmodule.x_module import (
|
||||
HTMLSnippet,
|
||||
ResourceTemplates,
|
||||
shim_xmodule_js,
|
||||
XModuleMixin,
|
||||
XModuleDescriptorToXBlockMixin,
|
||||
XModuleToXBlockMixin,
|
||||
)
|
||||
from xmodule.xml_module import XmlMixin, name_to_pathname
|
||||
|
||||
log = logging.getLogger("edx.courseware")
|
||||
|
||||
@@ -34,11 +42,14 @@ log = logging.getLogger("edx.courseware")
|
||||
_ = lambda text: text
|
||||
|
||||
|
||||
class HtmlBlock(object):
|
||||
@edxnotes
|
||||
@XBlock.needs("i18n")
|
||||
class HtmlBlock(
|
||||
XmlMixin, EditingMixin,
|
||||
XModuleDescriptorToXBlockMixin, XModuleToXBlockMixin, HTMLSnippet, ResourceTemplates, XModuleMixin,
|
||||
):
|
||||
"""
|
||||
This will eventually subclass XBlock and merge HtmlModule and HtmlDescriptor
|
||||
into one. For now, it's a place to put the pieces that are already sharable
|
||||
between the two (field information and XBlock handlers).
|
||||
The HTML XBlock.
|
||||
"""
|
||||
display_name = String(
|
||||
display_name=_("Display Name"),
|
||||
@@ -79,7 +90,10 @@ class HtmlBlock(object):
|
||||
"""
|
||||
Return a fragment that contains the html for the student view
|
||||
"""
|
||||
return Fragment(self.get_html())
|
||||
fragment = Fragment(self.get_html())
|
||||
add_webpack_to_fragment(fragment, 'HtmlBlockPreview')
|
||||
shim_xmodule_js(fragment, 'HTMLModule')
|
||||
return fragment
|
||||
|
||||
@XBlock.supports("multi_device")
|
||||
def public_view(self, context):
|
||||
@@ -101,54 +115,56 @@ class HtmlBlock(object):
|
||||
}
|
||||
|
||||
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.
|
||||
""" Returns html required for rendering the block. """
|
||||
# pylint: disable=no-member
|
||||
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
|
||||
|
||||
def studio_view(self, _context):
|
||||
"""
|
||||
Return the studio view.
|
||||
"""
|
||||
fragment = Fragment(
|
||||
self.system.render_template(self.mako_template, self.get_context())
|
||||
)
|
||||
add_webpack_to_fragment(fragment, 'HtmlBlockStudio')
|
||||
shim_xmodule_js(fragment, 'HTMLEditingDescriptor')
|
||||
return fragment
|
||||
|
||||
class HtmlModuleMixin(HtmlBlock, XModule):
|
||||
"""
|
||||
Attributes and methods used by HtmlModules internally.
|
||||
"""
|
||||
js = {
|
||||
preview_view_js = {
|
||||
'js': [
|
||||
resource_string(__name__, 'js/src/html/display.js'),
|
||||
resource_string(__name__, 'js/src/javascript_loader.js'),
|
||||
resource_string(__name__, 'js/src/collapsible.js'),
|
||||
resource_string(__name__, 'js/src/html/imageModal.js'),
|
||||
resource_string(__name__, 'js/common_static/js/vendor/draggabilly.js'),
|
||||
]
|
||||
],
|
||||
'xmodule_js': resource_string(__name__, 'js/src/xmodule.js'),
|
||||
}
|
||||
js_module_name = "HTMLModule"
|
||||
css = {'scss': [resource_string(__name__, 'css/html/display.scss')]}
|
||||
preview_view_css = {'scss': [resource_string(__name__, 'css/html/display.scss')]}
|
||||
|
||||
uses_xmodule_styles_setup = True
|
||||
requires_per_student_anonymous_id = True
|
||||
|
||||
@edxnotes
|
||||
class HtmlModule(HtmlModuleMixin):
|
||||
"""
|
||||
Module for putting raw html in a course
|
||||
"""
|
||||
|
||||
|
||||
class HtmlDescriptor(HtmlBlock, XmlDescriptor, EditingDescriptor): # pylint: disable=abstract-method
|
||||
"""
|
||||
Module for putting raw html in a course
|
||||
"""
|
||||
mako_template = "widgets/html-edit.html"
|
||||
module_class = HtmlModule
|
||||
resources_dir = None
|
||||
filename_extension = "xml"
|
||||
template_dir_name = "html"
|
||||
show_in_read_only_mode = True
|
||||
|
||||
js = {'js': [resource_string(__name__, 'js/src/html/edit.js')]}
|
||||
js_module_name = "HTMLEditingDescriptor"
|
||||
css = {'scss': [resource_string(__name__, 'css/editor/edit.scss'), resource_string(__name__, 'css/html/edit.scss')]}
|
||||
studio_view_js = {
|
||||
'js': [
|
||||
resource_string(__name__, 'js/src/html/edit.js')
|
||||
],
|
||||
'xmodule_js': resource_string(__name__, 'js/src/xmodule.js'),
|
||||
}
|
||||
studio_view_css = {
|
||||
'scss': [
|
||||
resource_string(__name__, 'css/editor/edit.scss'),
|
||||
resource_string(__name__, 'css/html/edit.scss')
|
||||
]
|
||||
}
|
||||
|
||||
# VS[compat] TODO (cpennington): Delete this method once all fall 2012 course
|
||||
# are being edited in the cms
|
||||
@@ -188,7 +204,7 @@ class HtmlDescriptor(HtmlBlock, XmlDescriptor, EditingDescriptor): # pylint: di
|
||||
an override to add in specific rendering context, in this case we need to
|
||||
add in a base path to our c4x content addressing scheme
|
||||
"""
|
||||
_context = EditingDescriptor.get_context(self)
|
||||
_context = EditingMixin.get_context(self)
|
||||
# Add some specific HTML rendering context when editing HTML modules where we pass
|
||||
# the root /c4x/ url for assets. This allows client-side substitutions to occur.
|
||||
_context.update({
|
||||
@@ -308,12 +324,12 @@ class HtmlDescriptor(HtmlBlock, XmlDescriptor, EditingDescriptor): # pylint: di
|
||||
"""
|
||||
`use_latex_compiler` should not be editable in the Studio settings editor.
|
||||
"""
|
||||
non_editable_fields = super(HtmlDescriptor, self).non_editable_metadata_fields
|
||||
non_editable_fields.append(HtmlDescriptor.use_latex_compiler)
|
||||
non_editable_fields = super(HtmlBlock, self).non_editable_metadata_fields
|
||||
non_editable_fields.append(HtmlBlock.use_latex_compiler)
|
||||
return non_editable_fields
|
||||
|
||||
def index_dictionary(self):
|
||||
xblock_body = super(HtmlDescriptor, self).index_dictionary()
|
||||
xblock_body = super(HtmlBlock, self).index_dictionary()
|
||||
# Removing script and style
|
||||
html_content = re.sub(
|
||||
re.compile(
|
||||
@@ -353,21 +369,12 @@ class AboutFields(object):
|
||||
|
||||
|
||||
@XBlock.tag("detached")
|
||||
class AboutModule(AboutFields, HtmlModuleMixin):
|
||||
class AboutBlock(AboutFields, HtmlBlock):
|
||||
"""
|
||||
Overriding defaults but otherwise treated as HtmlModule.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@XBlock.tag("detached")
|
||||
class AboutDescriptor(AboutFields, HtmlDescriptor):
|
||||
"""
|
||||
These pieces of course content are treated as HtmlModules but we need to overload where the templates are located
|
||||
These pieces of course content are treated as HtmlBlocks but we need to overload where the templates are located
|
||||
in order to be able to create new ones
|
||||
"""
|
||||
template_dir_name = "about"
|
||||
module_class = AboutModule
|
||||
|
||||
|
||||
class StaticTabFields(object):
|
||||
@@ -397,21 +404,12 @@ class StaticTabFields(object):
|
||||
|
||||
|
||||
@XBlock.tag("detached")
|
||||
class StaticTabModule(StaticTabFields, HtmlModuleMixin):
|
||||
class StaticTabBlock(StaticTabFields, HtmlBlock):
|
||||
"""
|
||||
Supports the field overrides
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@XBlock.tag("detached")
|
||||
class StaticTabDescriptor(StaticTabFields, HtmlDescriptor):
|
||||
"""
|
||||
These pieces of course content are treated as HtmlModules but we need to overload where the templates are located
|
||||
These pieces of course content are treated as HtmlBlocks but we need to overload where the templates are located
|
||||
in order to be able to create new ones
|
||||
"""
|
||||
template_dir_name = None
|
||||
module_class = StaticTabModule
|
||||
|
||||
|
||||
class CourseInfoFields(object):
|
||||
@@ -431,21 +429,17 @@ class CourseInfoFields(object):
|
||||
|
||||
|
||||
@XBlock.tag("detached")
|
||||
class CourseInfoModule(CourseInfoFields, HtmlModuleMixin):
|
||||
class CourseInfoBlock(CourseInfoFields, HtmlBlock):
|
||||
"""
|
||||
Just to support xblock field overrides
|
||||
These pieces of course content are treated as HtmlBlock but we need to overload where the templates are located
|
||||
in order to be able to create new ones
|
||||
"""
|
||||
# statuses
|
||||
STATUS_VISIBLE = 'visible'
|
||||
STATUS_DELETED = 'deleted'
|
||||
TEMPLATE_DIR = 'courseware'
|
||||
|
||||
@XBlock.supports("multi_device")
|
||||
def student_view(self, _context):
|
||||
"""
|
||||
Return a fragment that contains the html for the student view
|
||||
"""
|
||||
return Fragment(self.get_html())
|
||||
template_dir_name = None
|
||||
|
||||
def get_html(self):
|
||||
""" Returns html required for rendering XModule. """
|
||||
@@ -488,13 +482,3 @@ class CourseInfoModule(CourseInfoFields, HtmlModuleMixin):
|
||||
return datetime.strptime(date, '%B %d, %Y')
|
||||
except ValueError: # occurs for ill-formatted date values
|
||||
return datetime.today()
|
||||
|
||||
|
||||
@XBlock.tag("detached")
|
||||
class CourseInfoDescriptor(CourseInfoFields, HtmlDescriptor):
|
||||
"""
|
||||
These pieces of course content are treated as HtmlModules but we need to overload where the templates are located
|
||||
in order to be able to create new ones
|
||||
"""
|
||||
template_dir_name = None
|
||||
module_class = CourseInfoModule
|
||||
|
||||
@@ -133,7 +133,7 @@ class TestXMLModuleStore(TestCase):
|
||||
self.assertIsNotNone(parent, "get_parent failed to return a value")
|
||||
parent_loc = course_key.make_usage_key('vertical', 'vertical_test')
|
||||
self.assertEqual(parent.location, parent_loc)
|
||||
self.assertIn(shared_item, parent.get_children())
|
||||
self.assertIn(shared_item.location, [x.location for x in parent.get_children()])
|
||||
# ensure it's still a child of the other parent even tho it doesn't claim the other parent as its parent
|
||||
other_parent_loc = course_key.make_usage_key('vertical', 'zeta')
|
||||
other_parent = store.get_item(other_parent_loc)
|
||||
|
||||
@@ -22,6 +22,7 @@ from docopt import docopt
|
||||
from path import Path as path
|
||||
|
||||
from xmodule.capa_module import ProblemBlock
|
||||
from xmodule.html_module import AboutBlock, CourseInfoBlock, HtmlBlock, StaticTabBlock
|
||||
from xmodule.x_module import XModuleDescriptor, HTMLSnippet
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@@ -63,7 +64,11 @@ class VideoBlock(HTMLSnippet):
|
||||
# List of XBlocks which use this static content setup.
|
||||
# Should only be used for XModules being converted to XBlocks.
|
||||
XBLOCK_CLASSES = [
|
||||
AboutBlock,
|
||||
CourseInfoBlock,
|
||||
HtmlBlock,
|
||||
ProblemBlock,
|
||||
StaticTabBlock,
|
||||
VideoBlock,
|
||||
]
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from opaque_keys.edx.locator import CourseLocator
|
||||
from xblock.field_data import DictFieldData
|
||||
from xblock.fields import ScopeIds
|
||||
|
||||
from xmodule.html_module import CourseInfoModule, HtmlDescriptor, HtmlModule
|
||||
from xmodule.html_module import CourseInfoBlock, HtmlBlock
|
||||
|
||||
from ..x_module import PUBLIC_VIEW, STUDENT_VIEW
|
||||
from . import get_test_descriptor_system, get_test_system
|
||||
@@ -23,14 +23,14 @@ def instantiate_descriptor(**field_data):
|
||||
course_key = CourseLocator('org', 'course', 'run')
|
||||
usage_key = course_key.make_usage_key('html', 'SampleHtml')
|
||||
return system.construct_xblock_from_class(
|
||||
HtmlDescriptor,
|
||||
HtmlBlock,
|
||||
scope_ids=ScopeIds(None, None, usage_key, usage_key),
|
||||
field_data=DictFieldData(field_data),
|
||||
)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class HtmlModuleCourseApiTestCase(unittest.TestCase):
|
||||
class HtmlBlockCourseApiTestCase(unittest.TestCase):
|
||||
"""
|
||||
Test the HTML XModule's student_view_data method.
|
||||
"""
|
||||
@@ -45,10 +45,9 @@ class HtmlModuleCourseApiTestCase(unittest.TestCase):
|
||||
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())
|
||||
module = HtmlBlock(module_system, field_data, Mock())
|
||||
|
||||
with override_settings(**settings):
|
||||
self.assertEqual(module.student_view_data(), dict(
|
||||
@@ -76,10 +75,9 @@ class HtmlModuleCourseApiTestCase(unittest.TestCase):
|
||||
|
||||
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())
|
||||
module = HtmlBlock(module_system, field_data, Mock())
|
||||
self.assertEqual(module.student_view_data(), dict(enabled=True, html=html))
|
||||
|
||||
@ddt.data(
|
||||
@@ -91,22 +89,20 @@ class HtmlModuleCourseApiTestCase(unittest.TestCase):
|
||||
Ensure that student_view and public_view renders correctly.
|
||||
"""
|
||||
html = '<p>This is a test</p>'
|
||||
descriptor = Mock()
|
||||
field_data = DictFieldData({'data': html})
|
||||
module_system = get_test_system()
|
||||
module = HtmlModule(descriptor, module_system, field_data, Mock())
|
||||
module = HtmlBlock(module_system, field_data, Mock())
|
||||
rendered = module_system.render(module, view, {}).content
|
||||
self.assertIn(html, rendered)
|
||||
|
||||
|
||||
class HtmlModuleSubstitutionTestCase(unittest.TestCase):
|
||||
descriptor = Mock()
|
||||
class HtmlBlockSubstitutionTestCase(unittest.TestCase):
|
||||
|
||||
def test_substitution_works(self):
|
||||
sample_xml = '''%%USER_ID%%'''
|
||||
field_data = DictFieldData({'data': sample_xml})
|
||||
module_system = get_test_system()
|
||||
module = HtmlModule(self.descriptor, module_system, field_data, Mock())
|
||||
module = HtmlBlock(module_system, field_data, Mock())
|
||||
self.assertEqual(module.get_html(), str(module_system.anonymous_student_id))
|
||||
|
||||
def test_substitution_without_magic_string(self):
|
||||
@@ -117,7 +113,7 @@ class HtmlModuleSubstitutionTestCase(unittest.TestCase):
|
||||
'''
|
||||
field_data = DictFieldData({'data': sample_xml})
|
||||
module_system = get_test_system()
|
||||
module = HtmlModule(self.descriptor, module_system, field_data, Mock())
|
||||
module = HtmlBlock(module_system, field_data, Mock())
|
||||
self.assertEqual(module.get_html(), sample_xml)
|
||||
|
||||
def test_substitution_without_anonymous_student_id(self):
|
||||
@@ -125,13 +121,13 @@ class HtmlModuleSubstitutionTestCase(unittest.TestCase):
|
||||
field_data = DictFieldData({'data': sample_xml})
|
||||
module_system = get_test_system()
|
||||
module_system.anonymous_student_id = None
|
||||
module = HtmlModule(self.descriptor, module_system, field_data, Mock())
|
||||
module = HtmlBlock(module_system, field_data, Mock())
|
||||
self.assertEqual(module.get_html(), sample_xml)
|
||||
|
||||
|
||||
class HtmlDescriptorIndexingTestCase(unittest.TestCase):
|
||||
class HtmlBlockIndexingTestCase(unittest.TestCase):
|
||||
"""
|
||||
Make sure that HtmlDescriptor can format data for indexing as expected.
|
||||
Make sure that HtmlBlock can format data for indexing as expected.
|
||||
"""
|
||||
|
||||
def test_index_dictionary_simple_html_module(self):
|
||||
@@ -221,9 +217,9 @@ class HtmlDescriptorIndexingTestCase(unittest.TestCase):
|
||||
})
|
||||
|
||||
|
||||
class CourseInfoModuleTestCase(unittest.TestCase):
|
||||
class CourseInfoBlockTestCase(unittest.TestCase):
|
||||
"""
|
||||
Make sure that CourseInfoModule renders updates properly.
|
||||
Make sure that CourseInfoBlock renders updates properly.
|
||||
"""
|
||||
|
||||
def test_updates_render(self):
|
||||
@@ -235,7 +231,7 @@ class CourseInfoModuleTestCase(unittest.TestCase):
|
||||
"id": i,
|
||||
"date": data,
|
||||
"content": "This is a very important update!",
|
||||
"status": CourseInfoModule.STATUS_VISIBLE,
|
||||
"status": CourseInfoBlock.STATUS_VISIBLE,
|
||||
} for i, data in enumerate(
|
||||
[
|
||||
'January 1, 1970',
|
||||
@@ -245,8 +241,7 @@ class CourseInfoModuleTestCase(unittest.TestCase):
|
||||
]
|
||||
)
|
||||
]
|
||||
info_module = CourseInfoModule(
|
||||
Mock(),
|
||||
info_module = CourseInfoBlock(
|
||||
get_test_system(),
|
||||
DictFieldData({'items': sample_update_data, 'data': ""}),
|
||||
Mock()
|
||||
@@ -256,7 +251,7 @@ class CourseInfoModuleTestCase(unittest.TestCase):
|
||||
try:
|
||||
info_module.get_html()
|
||||
except ValueError:
|
||||
self.fail("CourseInfoModule could not parse an invalid date!")
|
||||
self.fail("CourseInfoBlock could not parse an invalid date!")
|
||||
|
||||
def test_updates_order(self):
|
||||
"""
|
||||
@@ -267,23 +262,22 @@ class CourseInfoModuleTestCase(unittest.TestCase):
|
||||
"id": 3,
|
||||
"date": "March 18, 1982",
|
||||
"content": "This is a very important update that was inserted last with an older date!",
|
||||
"status": CourseInfoModule.STATUS_VISIBLE,
|
||||
"status": CourseInfoBlock.STATUS_VISIBLE,
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"date": "January 1, 2012",
|
||||
"content": "This is a very important update that was inserted first!",
|
||||
"status": CourseInfoModule.STATUS_VISIBLE,
|
||||
"status": CourseInfoBlock.STATUS_VISIBLE,
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"date": "January 1, 2012",
|
||||
"content": "This is a very important update that was inserted second!",
|
||||
"status": CourseInfoModule.STATUS_VISIBLE,
|
||||
"status": CourseInfoBlock.STATUS_VISIBLE,
|
||||
}
|
||||
]
|
||||
info_module = CourseInfoModule(
|
||||
Mock(),
|
||||
info_module = CourseInfoBlock(
|
||||
Mock(),
|
||||
DictFieldData({'items': sample_update_data, 'data': ""}),
|
||||
Mock()
|
||||
@@ -296,19 +290,19 @@ class CourseInfoModuleTestCase(unittest.TestCase):
|
||||
"id": 2,
|
||||
"date": "January 1, 2012",
|
||||
"content": "This is a very important update that was inserted second!",
|
||||
"status": CourseInfoModule.STATUS_VISIBLE,
|
||||
"status": CourseInfoBlock.STATUS_VISIBLE,
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"date": "January 1, 2012",
|
||||
"content": "This is a very important update that was inserted first!",
|
||||
"status": CourseInfoModule.STATUS_VISIBLE,
|
||||
"status": CourseInfoBlock.STATUS_VISIBLE,
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"date": "March 18, 1982",
|
||||
"content": "This is a very important update that was inserted last with an older date!",
|
||||
"status": CourseInfoModule.STATUS_VISIBLE,
|
||||
"status": CourseInfoBlock.STATUS_VISIBLE,
|
||||
}
|
||||
],
|
||||
'hidden_updates': [],
|
||||
|
||||
@@ -320,7 +320,7 @@ class TestLibraryContentModuleWithSearchIndex(LibraryContentModuleTestMixin, Lib
|
||||
@patch(
|
||||
'xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.render', VanillaRuntime.render
|
||||
)
|
||||
@patch('xmodule.html_module.HtmlModule.author_view', dummy_render, create=True)
|
||||
@patch('xmodule.html_module.HtmlBlock.author_view', dummy_render, create=True)
|
||||
@patch('xmodule.x_module.DescriptorSystem.applicable_aside_types', lambda self, block: [])
|
||||
class TestLibraryContentRender(LibraryContentTest):
|
||||
"""
|
||||
|
||||
@@ -19,8 +19,8 @@ dummy_render = lambda block, _: Fragment(block.data) # pylint: disable=invalid-
|
||||
@patch(
|
||||
'xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.render', VanillaRuntime.render
|
||||
)
|
||||
@patch('xmodule.html_module.HtmlDescriptor.author_view', dummy_render, create=True)
|
||||
@patch('xmodule.html_module.HtmlDescriptor.has_author_view', True, create=True)
|
||||
@patch('xmodule.html_module.HtmlBlock.author_view', dummy_render, create=True)
|
||||
@patch('xmodule.html_module.HtmlBlock.has_author_view', True, create=True)
|
||||
@patch('xmodule.x_module.DescriptorSystem.applicable_aside_types', lambda self, block: [])
|
||||
class TestLibraryRoot(MixedSplitTestCase):
|
||||
"""
|
||||
|
||||
@@ -168,9 +168,9 @@ class SplitTestModuleLMSTest(SplitTestModuleTest):
|
||||
)
|
||||
|
||||
# Patch the definition_to_xml for the html children.
|
||||
@patch('xmodule.html_module.HtmlDescriptor.definition_to_xml')
|
||||
@patch('xmodule.html_module.HtmlBlock.definition_to_xml')
|
||||
def test_export_import_round_trip(self, def_to_xml):
|
||||
# The HtmlDescriptor definition_to_xml tries to write to the filesystem
|
||||
# The HtmlBlock definition_to_xml tries to write to the filesystem
|
||||
# before returning an xml object. Patch this to just return the xml.
|
||||
def_to_xml.return_value = lxml.etree.Element('html')
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ from xblock.fields import ScopeIds
|
||||
from xmodule.annotatable_module import AnnotatableDescriptor
|
||||
from xmodule.conditional_module import ConditionalDescriptor
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
from xmodule.html_module import HtmlDescriptor
|
||||
from xmodule.html_module import HtmlBlock
|
||||
from xmodule.poll_module import PollDescriptor
|
||||
from xmodule.randomize_module import RandomizeDescriptor
|
||||
from xmodule.seq_module import SequenceDescriptor
|
||||
@@ -56,7 +56,7 @@ from xmodule.x_module import (
|
||||
# TODO: Add more types of sample data
|
||||
LEAF_XMODULES = {
|
||||
AnnotatableDescriptor: [{}],
|
||||
HtmlDescriptor: [{}],
|
||||
HtmlBlock: [{}],
|
||||
PollDescriptor: [{'display_name': 'Poll Display Name'}],
|
||||
WordCloudDescriptor: [{}],
|
||||
}
|
||||
@@ -136,7 +136,7 @@ class ContainerModuleRuntimeFactory(ModuleSystemFactory):
|
||||
"""
|
||||
# pylint: disable=no-member
|
||||
if depth == 0:
|
||||
self.get_module.side_effect = lambda x: LeafModuleFactory(descriptor_cls=HtmlDescriptor)
|
||||
self.get_module.side_effect = lambda x: LeafModuleFactory(descriptor_cls=HtmlBlock)
|
||||
else:
|
||||
self.get_module.side_effect = lambda x: ContainerModuleFactory(
|
||||
descriptor_cls=VerticalBlock,
|
||||
@@ -164,7 +164,7 @@ class ContainerDescriptorRuntimeFactory(DescriptorSystemFactory):
|
||||
"""
|
||||
# pylint: disable=no-member
|
||||
if depth == 0:
|
||||
self.load_item.side_effect = lambda x: LeafModuleFactory(descriptor_cls=HtmlDescriptor)
|
||||
self.load_item.side_effect = lambda x: LeafModuleFactory(descriptor_cls=HtmlBlock)
|
||||
else:
|
||||
self.load_item.side_effect = lambda x: ContainerModuleFactory(
|
||||
descriptor_cls=VerticalBlock,
|
||||
|
||||
@@ -213,8 +213,8 @@ class ContainerPage(PageObject, HelpMixin):
|
||||
Returns:
|
||||
list: A list containing inner HTMl
|
||||
"""
|
||||
self.wait_for_element_visibility('.xmodule_HtmlModule', 'Xblock content is visible')
|
||||
html = self.q(css='.xmodule_HtmlModule').html
|
||||
self.wait_for_element_visibility('.xmodule_HtmlBlock', 'Xblock content is visible')
|
||||
html = self.q(css='.xmodule_HtmlBlock').html
|
||||
html = html[0].strip()
|
||||
return html
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ class PagesPage(CoursePage):
|
||||
'.wrapper-component-action-header .component-actions',
|
||||
"Tab's edit button is visible"
|
||||
)
|
||||
return self.q(css='div.xmodule_StaticTabModule').text
|
||||
return self.q(css='div.xmodule_StaticTabBlock').text
|
||||
|
||||
@property
|
||||
def built_in_page_titles(self):
|
||||
|
||||
@@ -58,7 +58,7 @@ class CopiedFromLmsBadContentTest(BadComponentTest):
|
||||
Return the "bad" HTML content that has been problematic for Studio.
|
||||
"""
|
||||
return """
|
||||
<div class="xblock xblock-student_view xmodule_display xmodule_HtmlModule xblock-initialized"
|
||||
<div class="xblock xblock-student_view xmodule_display xmodule_HtmlBlock xblock-initialized"
|
||||
data-runtime-class="LmsRuntime" data-init="XBlockToXModuleShim" data-block-type="html"
|
||||
data-runtime-version="1" data-type="HTMLModule" data-course-id="GeorgetownX/HUMW-421-01"
|
||||
data-request-token="thisIsNotARealRequestToken"
|
||||
@@ -81,7 +81,7 @@ class CopiedFromStudioBadContentTest(BadComponentTest):
|
||||
<ol class="components ui-sortable">
|
||||
<li class="component" data-locator="i4x://Wellesley_College/100/html/6390f1fd3fe640d49580b8415fe1330b"
|
||||
data-course-key="Wellesley_College/100/2014_Summer">
|
||||
<div class="xblock xblock-student_view xmodule_display xmodule_HtmlModule xblock-initialized"
|
||||
<div class="xblock xblock-student_view xmodule_display xmodule_HtmlBlock xblock-initialized"
|
||||
data-runtime-class="PreviewRuntime" data-init="XBlockToXModuleShim" data-runtime-version="1"
|
||||
data-request-token="thisIsNotARealRequestToken"
|
||||
data-usage-id="i4x://Wellesley_College/100/html/6390f1fd3fe640d49580b8415fe1330b"
|
||||
|
||||
@@ -49,9 +49,7 @@ class StudentViewTransformer(BlockStructureTransformer):
|
||||
# problem where your particular XModule explodes here (and don't
|
||||
# have the time to convert it to an XBlock), please try refactoring
|
||||
# so that you declare your student_view() method in a common
|
||||
# ancestor class of both your Descriptor and Module classes. As an
|
||||
# example, I changed the name of HtmlFields to HtmlBlock and moved
|
||||
# student_view() from HtmlModuleMixin to HtmlBlock.
|
||||
# ancestor class of both your Descriptor and Module classes.
|
||||
student_view = getattr(block.__class__, 'student_view', None)
|
||||
supports_multi_device = block.has_support(student_view, 'multi_device')
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ from student.models import CourseEnrollment, anonymous_id_for_user
|
||||
from verify_student.tests.factories import SoftwareSecurePhotoVerificationFactory
|
||||
from xblock_django.models import XBlockConfiguration
|
||||
from xmodule.capa_module import ProblemBlock
|
||||
from xmodule.html_module import AboutBlock, CourseInfoBlock, HtmlBlock, StaticTabBlock
|
||||
from xmodule.lti_module import LTIDescriptor
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.django import modulestore
|
||||
@@ -1524,7 +1525,7 @@ class TestHtmlModifiers(ModuleStoreTestCase):
|
||||
)
|
||||
result_fragment = module.render(STUDENT_VIEW)
|
||||
|
||||
self.assertEquals(len(PyQuery(result_fragment.content)('div.xblock.xblock-student_view.xmodule_HtmlModule')), 1)
|
||||
self.assertEquals(len(PyQuery(result_fragment.content)('div.xblock.xblock-student_view.xmodule_HtmlBlock')), 1)
|
||||
|
||||
def test_xmodule_display_wrapper_disabled(self):
|
||||
module = render.get_module(
|
||||
@@ -1536,7 +1537,7 @@ class TestHtmlModifiers(ModuleStoreTestCase):
|
||||
)
|
||||
result_fragment = module.render(STUDENT_VIEW)
|
||||
|
||||
self.assertNotIn('div class="xblock xblock-student_view xmodule_display xmodule_HtmlModule"',
|
||||
self.assertNotIn('div class="xblock xblock-student_view xmodule_display xmodule_HtmlBlock"',
|
||||
result_fragment.content)
|
||||
|
||||
def test_static_link_rewrite(self):
|
||||
@@ -1933,7 +1934,11 @@ class TestStaffDebugInfo(SharedModuleStoreTestCase):
|
||||
|
||||
PER_COURSE_ANONYMIZED_DESCRIPTORS = (LTIDescriptor, )
|
||||
PER_STUDENT_ANONYMIZED_XBLOCKS = [
|
||||
AboutBlock,
|
||||
CourseInfoBlock,
|
||||
HtmlBlock,
|
||||
ProblemBlock,
|
||||
StaticTabBlock,
|
||||
VideoBlock,
|
||||
]
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ def edxnotes(cls):
|
||||
# Import is placed here to avoid model import at project startup.
|
||||
from edxnotes.helpers import generate_uid, get_edxnotes_id_token, get_public_endpoint, get_token_url, is_feature_enabled
|
||||
is_studio = getattr(self.system, "is_author_mode", False)
|
||||
course = self.descriptor.runtime.modulestore.get_course(self.runtime.course_id)
|
||||
course = getattr(self, 'descriptor', self).runtime.modulestore.get_course(self.runtime.course_id)
|
||||
|
||||
# Must be disabled when:
|
||||
# - in Studio
|
||||
|
||||
@@ -58,7 +58,7 @@ from shoppingcart.models import Coupon, CourseRegCodeItem, PaidCourseRegistratio
|
||||
from student.models import CourseEnrollment
|
||||
from student.roles import CourseFinanceAdminRole, CourseInstructorRole, CourseSalesAdminRole, CourseStaffRole
|
||||
from util.json_request import JsonResponse
|
||||
from xmodule.html_module import HtmlDescriptor
|
||||
from xmodule.html_module import HtmlBlock
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.tabs import CourseTab
|
||||
|
||||
@@ -714,7 +714,7 @@ def _section_data_download(course, access):
|
||||
def null_applicable_aside_types(block): # pylint: disable=unused-argument
|
||||
"""
|
||||
get_aside method for monkey-patching into applicable_aside_types
|
||||
while rendering an HtmlDescriptor for email text editing. This returns
|
||||
while rendering an HtmlBlock for email text editing. This returns
|
||||
an empty list.
|
||||
"""
|
||||
return []
|
||||
@@ -726,8 +726,8 @@ def _section_send_email(course, access):
|
||||
|
||||
# Monkey-patch applicable_aside_types to return no asides for the duration of this render
|
||||
with patch.object(course.runtime, 'applicable_aside_types', null_applicable_aside_types):
|
||||
# This HtmlDescriptor is only being used to generate a nice text editor.
|
||||
html_module = HtmlDescriptor(
|
||||
# This HtmlBlock is only being used to generate a nice text editor.
|
||||
html_module = HtmlBlock(
|
||||
course.system,
|
||||
DictFieldData({'data': ''}),
|
||||
ScopeIds(None, None, None, course_key.make_usage_key('html', 'fake'))
|
||||
|
||||
@@ -11,7 +11,7 @@ from six.moves import range
|
||||
|
||||
from mobile_api.testutils import MobileAPITestCase, MobileAuthTestMixin, MobileCourseAccessTestMixin
|
||||
from mobile_api.utils import API_V1, API_V05
|
||||
from xmodule.html_module import CourseInfoModule
|
||||
from xmodule.html_module import CourseInfoBlock
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.xml_importer import import_course_from_xml
|
||||
@@ -60,7 +60,7 @@ class TestUpdates(MobileAPITestCase, MobileAuthTestMixin, MobileCourseAccessTest
|
||||
"id": num,
|
||||
"date": "Date" + str(num),
|
||||
"content": "<a href=\"/static/\">Update" + str(num) + "</a>",
|
||||
"status": CourseInfoModule.STATUS_VISIBLE
|
||||
"status": CourseInfoBlock.STATUS_VISIBLE
|
||||
}
|
||||
)
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user