diff --git a/common/lib/xmodule/setup.py b/common/lib/xmodule/setup.py index 095fa5a3cca0..22271d305b53 100644 --- a/common/lib/xmodule/setup.py +++ b/common/lib/xmodule/setup.py @@ -4,10 +4,8 @@ XMODULES = [ "book = xmodule.backcompat_module:TranslateCustomTagDescriptor", - "customtag = xmodule.template_module:CustomTagDescriptor", "discuss = xmodule.backcompat_module:TranslateCustomTagDescriptor", "image = xmodule.backcompat_module:TranslateCustomTagDescriptor", - "poll_question = xmodule.poll_module:PollDescriptor", "section = xmodule.backcompat_module:SemanticSectionDescriptor", "slides = xmodule.backcompat_module:TranslateCustomTagDescriptor", "videodev = xmodule.backcompat_module:TranslateCustomTagDescriptor", @@ -21,6 +19,7 @@ "conditional = xmodule.conditional_module:ConditionalBlock", "course = xmodule.course_module:CourseBlock", "course_info = xmodule.html_module:CourseInfoBlock", + "customtag = xmodule.template_module:CustomTagBlock", "error = xmodule.error_module:ErrorBlock", "hidden = xmodule.hidden_module:HiddenDescriptor", "html = xmodule.html_module:HtmlBlock", @@ -29,6 +28,7 @@ "library_sourced = xmodule.library_sourced_block:LibrarySourcedBlock", "lti = xmodule.lti_module:LTIBlock", "nonstaff_error = xmodule.error_module:NonStaffErrorBlock", + "poll_question = xmodule.poll_module:PollBlock", "problem = xmodule.capa_module:ProblemBlock", "problemset = xmodule.seq_module:SequenceBlock", "randomize = xmodule.randomize_module:RandomizeBlock", diff --git a/common/lib/xmodule/xmodule/poll_module.py b/common/lib/xmodule/xmodule/poll_module.py index 519ee5280e94..6c739f4a91f6 100644 --- a/common/lib/xmodule/xmodule/poll_module.py +++ b/common/lib/xmodule/xmodule/poll_module.py @@ -14,19 +14,38 @@ from copy import deepcopy from pkg_resources import resource_string +from web_fragments.fragment import Fragment + from lxml import etree from openedx.core.djangolib.markup import Text, HTML from xblock.fields import Boolean, Dict, List, Scope, String # lint-amnesty, pylint: disable=wrong-import-order -from xmodule.mako_module import MakoModuleDescriptor +from xmodule.mako_module import MakoTemplateBlockBase from xmodule.stringify import stringify_children -from xmodule.x_module import XModule -from xmodule.xml_module import XmlDescriptor +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 log = logging.getLogger(__name__) _ = lambda text: text -class PollFields(object): # lint-amnesty, pylint: disable=missing-class-docstring +class PollBlock( + MakoTemplateBlockBase, + XmlMixin, + XModuleDescriptorToXBlockMixin, + XModuleToXBlockMixin, + HTMLSnippet, + ResourceTemplates, + XModuleMixin, +): # pylint: disable=abstract-method + """Poll Module""" # Name of poll to use in links to this poll display_name = String( help=_("The display name for this component."), @@ -61,18 +80,33 @@ class PollFields(object): # lint-amnesty, pylint: disable=missing-class-docstri default='' ) + resources_dir = None + uses_xmodule_styles_setup = True -class PollModule(PollFields, XModule): - """Poll Module""" - js = { + preview_view_js = { 'js': [ resource_string(__name__, 'js/src/javascript_loader.js'), resource_string(__name__, 'js/src/poll/poll.js'), resource_string(__name__, 'js/src/poll/poll_main.js') - ] + ], + 'xmodule_js': resource_string(__name__, 'js/src/xmodule.js'), + } + preview_view_css = { + 'scss': [ + resource_string(__name__, 'css/poll/display.scss') + ], + } + + # There is no studio_view() for this XBlock but this is needed to make the + # the static_content command happy. + studio_view_js = { + 'js': [], + 'xmodule_js': resource_string(__name__, 'js/src/xmodule.js') + } + + studio_view_css = { + 'scss': [] } - css = {'scss': [resource_string(__name__, 'css/poll/display.scss')]} - js_module_name = "Poll" def handle_ajax(self, dispatch, data): # lint-amnesty, pylint: disable=unused-argument """Ajax handler. @@ -103,7 +137,7 @@ def handle_ajax(self, dispatch, data): # lint-amnesty, pylint: disable=unused-a 'total': sum(self.poll_answers.values()) }) elif dispatch == 'reset_poll' and self.voted and \ - self.descriptor.xml_attributes.get('reset', 'True').lower() != 'false': + self.xml_attributes.get('reset', 'True').lower() != 'false': self.voted = False # FIXME: fix this, when xblock will support mutable types. @@ -117,16 +151,21 @@ def handle_ajax(self, dispatch, data): # lint-amnesty, pylint: disable=unused-a else: # return error message return json.dumps({'error': 'Unknown Command!'}) - def get_html(self): - """Renders parameters to template.""" + def student_view(self, _context): + """ + Renders the student view. + """ + fragment = Fragment() params = { 'element_id': self.location.html_id(), 'element_class': self.location.block_type, - 'ajax_url': self.system.ajax_url, + 'ajax_url': self.ajax_url, 'configuration_json': self.dump_poll(), } - self.content = self.system.render_template('poll.html', params) # lint-amnesty, pylint: disable=attribute-defined-outside-init - return self.content + fragment.add_content(self.system.render_template('poll.html', params)) + add_webpack_to_fragment(fragment, 'PollBlockPreview') + shim_xmodule_js(fragment, 'Poll') + return fragment def dump_poll(self): """Dump poll information. @@ -160,17 +199,12 @@ def dump_poll(self): 'poll_answer': self.poll_answer, 'poll_answers': self.poll_answers if self.voted else {}, 'total': sum(self.poll_answers.values()) if self.voted else 0, - 'reset': str(self.descriptor.xml_attributes.get('reset', 'true')).lower() + 'reset': str(self.xml_attributes.get('reset', 'true')).lower() }) - -class PollDescriptor(PollFields, MakoModuleDescriptor, XmlDescriptor): # lint-amnesty, pylint: disable=missing-class-docstring _tag_name = 'poll_question' _child_tag_name = 'answer' - module_class = PollModule - resources_dir = None - @classmethod def definition_from_xml(cls, xml_object, system): """Pull out the data into dictionary. diff --git a/common/lib/xmodule/xmodule/static_content.py b/common/lib/xmodule/xmodule/static_content.py index eac98a2f75ce..58e03bd05ac5 100755 --- a/common/lib/xmodule/xmodule/static_content.py +++ b/common/lib/xmodule/xmodule/static_content.py @@ -26,8 +26,10 @@ from xmodule.html_module import AboutBlock, CourseInfoBlock, HtmlBlock, StaticTabBlock from xmodule.library_content_module import LibraryContentBlock from xmodule.lti_module import LTIBlock +from xmodule.poll_module import PollBlock from xmodule.seq_module import SequenceBlock from xmodule.split_test_module import SplitTestBlock +from xmodule.template_module import CustomTagBlock from xmodule.word_cloud_module import WordCloudBlock from xmodule.x_module import XModuleDescriptor, HTMLSnippet @@ -74,9 +76,11 @@ class VideoBlock(HTMLSnippet): # lint-amnesty, pylint: disable=abstract-method AnnotatableBlock, ConditionalBlock, CourseInfoBlock, + CustomTagBlock, HtmlBlock, LibraryContentBlock, LTIBlock, + PollBlock, ProblemBlock, SequenceBlock, SplitTestBlock, diff --git a/common/lib/xmodule/xmodule/template_module.py b/common/lib/xmodule/xmodule/template_module.py index d53856a2a913..a94cf457fa29 100644 --- a/common/lib/xmodule/xmodule/template_module.py +++ b/common/lib/xmodule/xmodule/template_module.py @@ -5,11 +5,32 @@ from string import Template from lxml import etree -from xmodule.raw_module import RawDescriptor -from xmodule.x_module import XModule # lint-amnesty, pylint: disable=unused-import - - -class CustomTagModule(XModule): +from pkg_resources import resource_string +from web_fragments.fragment import Fragment +from xmodule.editing_module import EditingMixin +from xmodule.raw_module import RawMixin +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 + + +class CustomTagBlock( + RawMixin, + XmlMixin, + EditingMixin, + XModuleDescriptorToXBlockMixin, + XModuleToXBlockMixin, + HTMLSnippet, + ResourceTemplates, + XModuleMixin, +): # pylint: disable=abstract-method """ This module supports tags of the form @@ -31,17 +52,35 @@ class CustomTagModule(XModule): Renders to:: More information given in the text """ - - def get_html(self): - return self.descriptor.rendered_html - - -class CustomTagDescriptor(RawDescriptor): - """ Descriptor for custom tags. Loads the template when created.""" - module_class = CustomTagModule resources_dir = None template_dir_name = 'customtag' + preview_view_js = { + 'js': [], + 'xmodule_js': resource_string(__name__, 'js/src/xmodule.js'), + } + preview_view_css = { + 'scss': [], + } + studio_view_js = { + 'js': [resource_string(__name__, 'js/src/raw/edit/xml.js')], + 'xmodule_js': resource_string(__name__, 'js/src/xmodule.js'), + } + studio_view_css = { + 'scss': [resource_string(__name__, 'css/codemirror/codemirror.scss')], + } + + 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, 'CustomTagBlockStudio') + shim_xmodule_js(fragment, 'XMLEditingDescriptor') + return fragment + def render_template(self, system, xml_data): '''Render the template, given the definition xml_data''' xmltree = etree.fromstring(xml_data) @@ -71,6 +110,14 @@ def render_template(self, system, xml_data): def rendered_html(self): return self.render_template(self.system, self.data) + def student_view(self, _context): + """ + Renders the student view. + """ + fragment = Fragment() + fragment.add_content(self.rendered_html) + return fragment + def export_to_file(self): """ Custom tags are special: since they're already pointers, we don't want diff --git a/common/lib/xmodule/xmodule/tests/__init__.py b/common/lib/xmodule/xmodule/tests/__init__.py index d8c76e34bb81..f8b531aa52e8 100644 --- a/common/lib/xmodule/xmodule/tests/__init__.py +++ b/common/lib/xmodule/xmodule/tests/__init__.py @@ -191,29 +191,6 @@ def test_load_class(self): assert str(vc) == vc_str -class LogicTest(unittest.TestCase): - """Base class for testing xmodule logic.""" - descriptor_class = None - raw_field_data = {} - - def setUp(self): - super(LogicTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments - self.system = get_test_system() - self.descriptor = Mock(name="descriptor", url_name='', category='test') - - self.xmodule_class = self.descriptor_class.module_class - usage_key = self.system.course_id.make_usage_key(self.descriptor.category, 'test_loc') - # ScopeIds has 4 fields: user_id, block_type, def_id, usage_id - scope_ids = ScopeIds(1, self.descriptor.category, usage_key, usage_key) - self.xmodule = self.xmodule_class( - self.descriptor, self.system, DictFieldData(self.raw_field_data), scope_ids - ) - - def ajax_request(self, dispatch, data): - """Call Xmodule.handle_ajax.""" - return json.loads(self.xmodule.handle_ajax(dispatch, data)) - - def map_references(value, field, actual_course_key): """ Map the references in value to actual_course_key and return value diff --git a/common/lib/xmodule/xmodule/tests/test_poll.py b/common/lib/xmodule/xmodule/tests/test_poll.py index f2e46aa184f7..631e8335e41d 100644 --- a/common/lib/xmodule/xmodule/tests/test_poll.py +++ b/common/lib/xmodule/xmodule/tests/test_poll.py @@ -1,24 +1,42 @@ # -*- coding: utf-8 -*- """Test for Poll Xmodule functional logic.""" +import json +import unittest from mock import Mock -from xmodule.poll_module import PollDescriptor +from xblock.field_data import DictFieldData +from xblock.fields import ScopeIds +from xmodule.poll_module import PollBlock -from . import LogicTest +from . import get_test_system from .test_import import DummySystem -class PollModuleTest(LogicTest): +class PollBlockTest(unittest.TestCase): """Logic tests for Poll Xmodule.""" - descriptor_class = PollDescriptor + raw_field_data = { 'poll_answers': {'Yes': 1, 'Dont_know': 0, 'No': 0}, 'voted': False, 'poll_answer': '' } + def setUp(self): + super().setUp() + self.system = get_test_system() + usage_key = self.system.course_id.make_usage_key(PollBlock.category, 'test_loc') + # ScopeIds has 4 fields: user_id, block_type, def_id, usage_id + scope_ids = ScopeIds(1, PollBlock.category, usage_key, usage_key) + self.xmodule = PollBlock( + self.system, DictFieldData(self.raw_field_data), scope_ids + ) + + def ajax_request(self, dispatch, data): + """Call Xmodule.handle_ajax.""" + return json.loads(self.xmodule.handle_ajax(dispatch, data)) + def test_bad_ajax_request(self): # Make sure that answer for incorrect request is error json. response = self.ajax_request('bad_answer', {}) @@ -52,7 +70,7 @@ def test_poll_export_with_unescaped_characters_xml(self): ''' - output = PollDescriptor.from_xml(sample_poll_xml, module_system, id_generator) + output = PollBlock.from_xml(sample_poll_xml, module_system, id_generator) # Update the answer with invalid character. invalid_characters_poll_answer = output.answers[0] # Invalid less-than character. diff --git a/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py b/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py index 685fe0becc03..c742448ffa1a 100644 --- a/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py +++ b/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py @@ -34,7 +34,7 @@ from xmodule.conditional_module import ConditionalBlock from xmodule.course_module import CourseBlock from xmodule.html_module import HtmlBlock -from xmodule.poll_module import PollDescriptor +from xmodule.poll_module import PollBlock from xmodule.randomize_module import RandomizeBlock from xmodule.seq_module import SequenceBlock from xmodule.tests import get_test_descriptor_system, get_test_system @@ -57,7 +57,7 @@ LEAF_XMODULES = { AnnotatableBlock: [{}], HtmlBlock: [{}], - PollDescriptor: [{'display_name': 'Poll Display Name'}], + PollBlock: [{'display_name': 'Poll Display Name'}], WordCloudBlock: [{}], } @@ -76,7 +76,7 @@ # These modules are not editable in studio yet NOT_STUDIO_EDITABLE = ( - PollDescriptor, + PollBlock, )