Aside rendered
" + + content = String(default="default_content", scope=Scope.content) + data_field = String(default="default_data", scope=Scope.settings) + + @XBlockAside.aside_for('student_view') + def student_view_aside(self, block, context): # pylint: disable=unused-argument + """Add to the student view""" + return Fragment(self.FRAG_CONTENT) + + +class TestAsidesXmlStore(TestCase): + """ + Test Asides sourced from xml store + """ + @patch('xmodule.x_module.descriptor_global_applicable_aside_types', lambda block: ['test_aside']) + @XBlockAside.register_temp_plugin(AsideTestType, 'test_aside') + def test_xml_aside(self): + """ + Check that the xml modulestore read in all the asides with their values + """ + with XmlModulestoreBuilder().build(course_ids=['edX/aside_test/2012_Fall']) as store: + def check_block(block): + """ + Check whether block has the expected aside w/ its fields and then recurse to the block's children + """ + asides = block.runtime.get_asides(block) + self.assertEqual(len(asides), 1, "Found {} asides but expected only test_aside".format(asides)) + self.assertIsInstance(asides[0], AsideTestType) + category = block.scope_ids.block_type + self.assertEqual(asides[0].data_field, "{} aside data".format(category)) + self.assertEqual(asides[0].content, "{} Aside".format(category.capitalize())) + + for child in block.get_children(): + check_block(child) + + check_block(store.get_course(store.make_course_key('edX', "aside_test", "2012_Fall"))) diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_libraries.py b/common/lib/xmodule/xmodule/modulestore/tests/test_libraries.py index 1447b02384..0e61e325be 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_libraries.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/test_libraries.py @@ -203,6 +203,6 @@ class TestLibraries(MixedSplitTestCase): message = u"Hello world" hello_render = lambda _, context: Fragment(message) with patch('xmodule.html_module.HtmlDescriptor.author_view', hello_render, create=True): - with patch('xmodule.x_module.descriptor_global_get_asides', lambda block: []): + with patch('xmodule.x_module.descriptor_global_applicable_aside_types', lambda block: []): result = library.render(AUTHOR_VIEW, context) self.assertIn(message, result.content) diff --git a/common/lib/xmodule/xmodule/modulestore/xml.py b/common/lib/xmodule/xmodule/modulestore/xml.py index 0007d57574..a836597c00 100644 --- a/common/lib/xmodule/xmodule/modulestore/xml.py +++ b/common/lib/xmodule/xmodule/modulestore/xml.py @@ -22,7 +22,6 @@ from xmodule.x_module import XMLParsingSystem, policy_key, OpaqueKeyReader, Asid from xmodule.modulestore.xml_exporter import DEFAULT_CONTENT_FIELDS from xmodule.modulestore import ModuleStoreEnum, ModuleStoreReadBase from xmodule.tabs import CourseTabList -from opaque_keys.edx.keys import UsageKey from opaque_keys.edx.locations import SlashSeparatedCourseKey, Location from opaque_keys.edx.locator import CourseLocator @@ -33,7 +32,7 @@ from xblock.runtime import DictKeyValueStore from .exceptions import ItemNotFoundError from .inheritance import compute_inherited_metadata, inheriting_field_data -from xblock.fields import ScopeIds, Reference, ReferenceList, ReferenceValueDict +from xblock.fields import ScopeIds edx_xml_parser = etree.XMLParser(dtd_validation=False, load_dtd=False, remove_comments=True, remove_blank_text=True) @@ -171,9 +170,9 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem): make_name_unique(xml_data) - descriptor = create_block_from_xml( - etree.tostring(xml_data, encoding='unicode'), - self, + descriptor = self.xblock_from_node( + xml_data, + None, # parent_id id_manager, ) except Exception as err: # pylint: disable=broad-except @@ -279,71 +278,6 @@ class CourseLocationManager(OpaqueKeyReader, AsideKeyGenerator): return usage_id -def _make_usage_key(course_key, value): - """ - Makes value into a UsageKey inside the specified course. - If value is already a UsageKey, returns that. - """ - if isinstance(value, UsageKey): - return value - return course_key.make_usage_key_from_deprecated_string(value) - - -def _convert_reference_fields_to_keys(xblock): # pylint: disable=invalid-name - """ - Find all fields of type reference and convert the payload into UsageKeys - """ - course_key = xblock.scope_ids.usage_id.course_key - - for field in xblock.fields.itervalues(): - if field.is_set_on(xblock): - field_value = getattr(xblock, field.name) - if isinstance(field, Reference): - setattr(xblock, field.name, _make_usage_key(course_key, field_value)) - elif isinstance(field, ReferenceList): - setattr(xblock, field.name, [_make_usage_key(course_key, ele) for ele in field_value]) - elif isinstance(field, ReferenceValueDict): - for key, subvalue in field_value.iteritems(): - assert isinstance(subvalue, basestring) - field_value[key] = _make_usage_key(course_key, subvalue) - setattr(xblock, field.name, field_value) - - -def create_block_from_xml(xml_data, system, id_generator): - """ - Create an XBlock instance from XML data. - - Args: - xml_data (string): A string containing valid xml. - system (XMLParsingSystem): The :class:`.XMLParsingSystem` used to connect the block - to the outside world. - id_generator (IdGenerator): An :class:`~xblock.runtime.IdGenerator` that - will be used to construct the usage_id and definition_id for the block. - - Returns: - XBlock: The fully instantiated :class:`~xblock.core.XBlock`. - - """ - node = etree.fromstring(xml_data) - raw_class = system.load_block_type(node.tag) - xblock_class = system.mixologist.mix(raw_class) - - # leave next line commented out - useful for low-level debugging - # log.debug('[create_block_from_xml] tag=%s, class=%s' % (node.tag, xblock_class)) - - block_type = node.tag - url_name = node.get('url_name') - def_id = id_generator.create_definition(block_type, url_name) - usage_id = id_generator.create_usage(def_id) - - scope_ids = ScopeIds(None, block_type, def_id, usage_id) - xblock = xblock_class.parse_xml(node, system, scope_ids, id_generator) - - _convert_reference_fields_to_keys(xblock) - - return xblock - - class ParentTracker(object): """A simple class to factor out the logic for tracking location parent pointers.""" def __init__(self): diff --git a/common/lib/xmodule/xmodule/modulestore/xml_importer.py b/common/lib/xmodule/xmodule/modulestore/xml_importer.py index 39a61bd342..4b946e4bb5 100644 --- a/common/lib/xmodule/xmodule/modulestore/xml_importer.py +++ b/common/lib/xmodule/xmodule/modulestore/xml_importer.py @@ -480,6 +480,8 @@ def _import_module_and_update_references( fields = {} for field_name, field in module.fields.iteritems(): if field.is_set_on(module): + if field.scope == Scope.parent: + continue if isinstance(field, Reference): fields[field_name] = _convert_reference_fields_to_new_namespace(field.read_from(module)) elif isinstance(field, ReferenceList): diff --git a/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py b/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py index 647a92f27c..8bc094f66d 100644 --- a/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py +++ b/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py @@ -262,7 +262,7 @@ class XBlockWrapperTestMixin(object): This is a mixin for building tests of the implementation of the XBlock api by wrapping XModule native functions. - You can creat an actual test case by inheriting from this class and UnitTest, + You can create an actual test case by inheriting from this class and UnitTest, and implement skip_if_invalid and check_property. """ @@ -289,6 +289,8 @@ class XBlockWrapperTestMixin(object): mocked_course = Mock() modulestore = Mock() modulestore.get_course.return_value = mocked_course + # pylint: disable=no-member + descriptor.runtime.id_reader.get_definition_id = Mock(return_value='a') descriptor.runtime.modulestore = modulestore self.check_property(descriptor) @@ -299,6 +301,8 @@ class XBlockWrapperTestMixin(object): descriptor_cls, fields = cls_and_fields self.skip_if_invalid(descriptor_cls) descriptor = ContainerModuleFactory(descriptor_cls=descriptor_cls, depth=2, **fields) + # pylint: disable=no-member + descriptor.runtime.id_reader.get_definition_id = Mock(return_value='a') self.check_property(descriptor) # Test that when an xmodule is generated from descriptor_cls @@ -347,7 +351,9 @@ class TestStudioView(XBlockWrapperTestMixin, TestCase): """ Assert that studio_view and get_html render the same. """ - self.assertEqual(descriptor.get_html(), descriptor.render(STUDIO_VIEW).content) + html = descriptor.get_html() + rendered_content = descriptor.render(STUDIO_VIEW).content + self.assertEqual(html, rendered_content) class TestXModuleHandler(TestCase): diff --git a/common/lib/xmodule/xmodule/tests/xml/__init__.py b/common/lib/xmodule/xmodule/tests/xml/__init__.py index 534d3d3d4c..c6ec34785c 100644 --- a/common/lib/xmodule/xmodule/tests/xml/__init__.py +++ b/common/lib/xmodule/xmodule/tests/xml/__init__.py @@ -2,12 +2,13 @@ Xml parsing tests for XModules """ import pprint +from lxml import etree from mock import Mock from unittest import TestCase from xmodule.x_module import XMLParsingSystem, policy_key from xmodule.mako_module import MakoDescriptorSystem -from xmodule.modulestore.xml import create_block_from_xml, CourseLocationManager +from xmodule.modulestore.xml import CourseLocationManager from opaque_keys.edx.locations import SlashSeparatedCourseKey, Location from xblock.runtime import KvsFieldData, DictKeyValueStore @@ -40,9 +41,9 @@ class InMemorySystem(XMLParsingSystem, MakoDescriptorSystem): # pylint: disable def process_xml(self, xml): # pylint: disable=method-hidden """Parse `xml` as an XBlock, and add it to `self._descriptors`""" - descriptor = create_block_from_xml( - xml, - self, + descriptor = self.xblock_from_node( + etree.fromstring(xml), + None, CourseLocationManager(self.course_id), ) self._descriptors[descriptor.location.to_deprecated_string()] = descriptor diff --git a/common/lib/xmodule/xmodule/video_module/video_module.py b/common/lib/xmodule/xmodule/video_module/video_module.py index a9b093590d..b7f7e10fe5 100644 --- a/common/lib/xmodule/xmodule/video_module/video_module.py +++ b/common/lib/xmodule/xmodule/video_module/video_module.py @@ -18,7 +18,6 @@ Examples of html5 videos for manual testing: import copy import json import logging -import os.path from collections import OrderedDict from operator import itemgetter @@ -30,14 +29,13 @@ from django.conf import settings from xblock.fields import ScopeIds from xblock.runtime import KvsFieldData -from xmodule.exceptions import NotFoundError from xmodule.modulestore.inheritance import InheritanceKeyValueStore, own_metadata from xmodule.x_module import XModule, module_attr from xmodule.editing_module import TabsEditingDescriptor from xmodule.raw_module import EmptyDataRawDescriptor from xmodule.xml_module import is_pointer_tag, name_to_pathname, deserialize_field -from .transcripts_utils import Transcript, VideoTranscriptsMixin +from .transcripts_utils import VideoTranscriptsMixin from .video_utils import create_youtube_string, get_video_from_cdn from .video_xfields import VideoFields from .video_handlers import VideoStudentViewHandlers, VideoStudioViewHandlers @@ -300,7 +298,7 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler super(VideoDescriptor, self).__init__(*args, **kwargs) # For backwards compatibility -- if we've got XML data, parse it out and set the metadata fields if self.data: - field_data = self._parse_video_xml(self.data) + field_data = self._parse_video_xml(etree.fromstring(self.data)) self._field_data.set_many(self, field_data) del self.data @@ -406,8 +404,9 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler usage_id = id_generator.create_usage(definition_id) if is_pointer_tag(xml_object): filepath = cls._format_filepath(xml_object.tag, name_to_pathname(url_name)) - xml_data = etree.tostring(cls.load_file(filepath, system.resources_fs, usage_id)) - field_data = cls._parse_video_xml(xml_data) + xml_object = cls.load_file(filepath, system.resources_fs, usage_id) + system.parse_asides(xml_object, definition_id, usage_id, id_generator) + field_data = cls._parse_video_xml(xml_object) kvs = InheritanceKeyValueStore(initial_values=field_data) field_data = KvsFieldData(kvs) video = system.construct_xblock_from_class( @@ -543,12 +542,11 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler return ret @classmethod - def _parse_video_xml(cls, xml_data): + def _parse_video_xml(cls, xml): """ Parse video fields out of xml_data. The fields are set if they are present in the XML. """ - xml = etree.fromstring(xml_data) field_data = {} # Convert between key types for certain attributes -- diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index 5b96721153..e660211fd5 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -15,8 +15,9 @@ from pkg_resources import ( from webob import Response from webob.multidict import MultiDict -from xblock.core import XBlock -from xblock.fields import Scope, Integer, Float, List, XBlockMixin, String, Dict +from xblock.core import XBlock, XBlockAside +from xblock.fields import Scope, Integer, Float, List, XBlockMixin, String, Dict, ScopeIds, Reference, \ + ReferenceList, ReferenceValueDict from xblock.fragment import Fragment from xblock.runtime import Runtime, IdReader, IdGenerator from xmodule.fields import RelativeTime @@ -852,6 +853,7 @@ class XModuleDescriptor(XModuleMixin, HTMLSnippet, ResourceTemplates, XBlock): """ Interpret the parsed XML in `node`, creating an XModuleDescriptor. """ + # It'd be great to not reserialize and deserialize the xml xml = etree.tostring(node) block = cls.from_xml(xml, runtime, id_generator) return block @@ -1131,14 +1133,13 @@ def descriptor_global_local_resource_url(block, uri): # pylint: disable=invalid raise NotImplementedError("Applications must monkey-patch this function before using local_resource_url for studio_view") -# This function exists to give applications (LMS/CMS) a place to monkey-patch until -# we can refactor modulestore to split out the FieldData half of its interface from -# the Runtime part of its interface. This function matches the Runtime.get_asides interface -def descriptor_global_get_asides(block): # pylint: disable=unused-argument +# pylint: disable=invalid-name +def descriptor_global_applicable_aside_types(block): # pylint: disable=unused-argument """ - See :meth:`xblock.runtime.Runtime.get_asides`. + See :meth:`xblock.runtime.Runtime.applicable_aside_types`. """ - raise NotImplementedError("Applications must monkey-patch this function before using get_asides from a DescriptorSystem.") + raise NotImplementedError("Applications must monkey-patch this function before using applicable_aside_types" + " from a DescriptorSystem.") class MetricsMixin(object): @@ -1315,18 +1316,18 @@ class DescriptorSystem(MetricsMixin, ConfigurableFragmentWrapper, Runtime): # p # global function that the application can override. return descriptor_global_local_resource_url(block, uri) - def get_asides(self, block): + def applicable_aside_types(self, block): """ - See :meth:`xblock.runtime.Runtime:get_asides` for documentation. + See :meth:`xblock.runtime.Runtime:applicable_aside_types` for documentation. """ if getattr(block, 'xmodule_runtime', None) is not None: - return block.xmodule_runtime.get_asides(block) + return block.xmodule_runtime.applicable_aside_types(block) else: # Currently, Modulestore is responsible for instantiating DescriptorSystems # This means that LMS/CMS don't have a way to define a subclass of DescriptorSystem # that implements the correct get_asides. So, for now, instead, we will reference a # global function that the application can override. - return descriptor_global_get_asides(block) + return descriptor_global_applicable_aside_types(block) def resource_url(self, resource): """ @@ -1357,6 +1358,103 @@ class XMLParsingSystem(DescriptorSystem): super(XMLParsingSystem, self).__init__(**kwargs) self.process_xml = process_xml + def _usage_id_from_node(self, node, parent_id, id_generator=None): + """Create a new usage id from an XML dom node. + + Args: + node (lxml.etree.Element): The DOM node to interpret. + parent_id: The usage ID of the parent block + id_generator (IdGenerator): The :class:`.IdGenerator` to use + for creating ids + Returns: + UsageKey: the usage key for the new xblock + """ + return self.xblock_from_node(node, parent_id, id_generator).scope_ids.usage_id + + def xblock_from_node(self, node, parent_id, id_generator=None): + """ + Create an XBlock instance from XML data. + + Args: + xml_data (string): A string containing valid xml. + system (XMLParsingSystem): The :class:`.XMLParsingSystem` used to connect the block + to the outside world. + id_generator (IdGenerator): An :class:`~xblock.runtime.IdGenerator` that + will be used to construct the usage_id and definition_id for the block. + + Returns: + XBlock: The fully instantiated :class:`~xblock.core.XBlock`. + + """ + id_generator = id_generator or self.id_generator + # leave next line commented out - useful for low-level debugging + # log.debug('[_usage_id_from_node] tag=%s, class=%s' % (node.tag, xblock_class)) + + block_type = node.tag + # remove xblock-family from elements + node.attrib.pop('xblock-family', None) + + url_name = node.get('url_name') # difference from XBlock.runtime + def_id = id_generator.create_definition(block_type, url_name) + usage_id = id_generator.create_usage(def_id) + + keys = ScopeIds(None, block_type, def_id, usage_id) + block_class = self.mixologist.mix(self.load_block_type(block_type)) + + self.parse_asides(node, def_id, usage_id, id_generator) + + block = block_class.parse_xml(node, self, keys, id_generator) + self._convert_reference_fields_to_keys(block) # difference from XBlock.runtime + block.parent = parent_id + block.save() + + return block + + def parse_asides(self, node, def_id, usage_id, id_generator): + """pull the asides out of the xml payload and instantiate them""" + aside_children = [] + for child in node.iterchildren(): + # get xblock-family from node + xblock_family = child.attrib.pop('xblock-family', None) + if xblock_family: + xblock_family = self._family_id_to_superclass(xblock_family) + if issubclass(xblock_family, XBlockAside): + aside_children.append(child) + # now process them & remove them from the xml payload + for child in aside_children: + self._aside_from_xml(child, def_id, usage_id, id_generator) + node.remove(child) + + def _make_usage_key(self, course_key, value): + """ + Makes value into a UsageKey inside the specified course. + If value is already a UsageKey, returns that. + """ + if isinstance(value, UsageKey): + return value + return course_key.make_usage_key_from_deprecated_string(value) + + def _convert_reference_fields_to_keys(self, xblock): # pylint: disable=invalid-name + """ + Find all fields of type reference and convert the payload into UsageKeys + """ + course_key = xblock.scope_ids.usage_id.course_key + + for field in xblock.fields.itervalues(): + if field.is_set_on(xblock): + field_value = getattr(xblock, field.name) + if field_value is None: + continue + elif isinstance(field, Reference): + setattr(xblock, field.name, self._make_usage_key(course_key, field_value)) + elif isinstance(field, ReferenceList): + setattr(xblock, field.name, [self._make_usage_key(course_key, ele) for ele in field_value]) + elif isinstance(field, ReferenceValueDict): + for key, subvalue in field_value.iteritems(): + assert isinstance(subvalue, basestring) + field_value[key] = self._make_usage_key(course_key, subvalue) + setattr(xblock, field.name, field_value) + class ModuleSystem(MetricsMixin, ConfigurableFragmentWrapper, Runtime): # pylint: disable=abstract-method """ diff --git a/common/lib/xmodule/xmodule/xml_module.py b/common/lib/xmodule/xmodule/xml_module.py index dc9a3931da..1f7973c188 100644 --- a/common/lib/xmodule/xmodule/xml_module.py +++ b/common/lib/xmodule/xmodule/xml_module.py @@ -181,10 +181,18 @@ class XmlDescriptor(XModuleDescriptor): raise Exception, msg, sys.exc_info()[2] @classmethod - def load_definition(cls, xml_object, system, def_id): - '''Load a descriptor definition from the specified xml_object. + def load_definition(cls, xml_object, system, def_id, id_generator): + ''' + Load a descriptor definition from the specified xml_object. Subclasses should not need to override this except in special - cases (e.g. html module)''' + cases (e.g. html module) + + Args: + xml_object: an lxml.etree._Element containing the definition to load + system: the modulestore system (aka, runtime) which accesses data and provides access to services + def_id: the definition id for the block--used to compute the usage id and asides ids + id_generator: used to generate the usage_id + ''' # VS[compat] -- the filename attr should go away once everything is # converted. (note: make sure html files still work once this goes away) @@ -208,6 +216,8 @@ class XmlDescriptor(XModuleDescriptor): break definition_xml = cls.load_file(filepath, system.resources_fs, def_id) + usage_id = id_generator.create_usage(def_id) + system.parse_asides(definition_xml, def_id, usage_id, id_generator) # Add the attributes from the pointer node definition_xml.attrib.update(xml_object.attrib) @@ -281,11 +291,12 @@ class XmlDescriptor(XModuleDescriptor): # read the actual definition file--named using url_name.replace(':','/') filepath = cls._format_filepath(xml_object.tag, name_to_pathname(url_name)) definition_xml = cls.load_file(filepath, system.resources_fs, def_id) + system.parse_asides(definition_xml, def_id, usage_id, id_generator) else: definition_xml = xml_object filepath = None - definition, children = cls.load_definition(definition_xml, system, def_id) # note this removes metadata + definition, children = cls.load_definition(definition_xml, system, def_id, id_generator) # note this removes metadata # VS[compat] -- make Ike's github preview links work in both old and # new file layouts diff --git a/common/test/data/aside/README.md b/common/test/data/aside/README.md new file mode 100644 index 0000000000..59ab392ed3 --- /dev/null +++ b/common/test/data/aside/README.md @@ -0,0 +1 @@ +This is a very very simple course, useful for initial debugging of processing code. diff --git a/common/test/data/aside/course.xml b/common/test/data/aside/course.xml new file mode 100644 index 0000000000..f04994e343 --- /dev/null +++ b/common/test/data/aside/course.xml @@ -0,0 +1 @@ +V;0EmY9^JwRNKKMYD|j#@cG`L|F@ISaef(ouJcdU_X@h-&bx0$R+?0vfj$*F zS4o46b9!ZcSm^=J0D*?J{!jBI!(}XSzY59;(3jq{mDGW+QE(r}weAayCo1#2PQ=Nj zwIvwOSDrKcdSmTLKL$o71z4l0Lj#T}oN1r^eyyeqRI%_RLg1`n%Wdu4Dlf6%u`@s# zOP^`r=$MCp0;6UB 8@z&yS?Mv0B?%dJh5iY+z^No;BLc33UsmV~v%;_I7BO=yIn^1kdG9OmLip z_1{%0pEzt~s^Qbp%bHFx-#INBLwHY=Uk0d+-Q#egaHcGdz_Kh#WxtR&QX`8=kQrAo z=F-qnO<;Y}KZZSM+KGq29VAJXQ(}`@miw}9*q;Rmf8N 0BNO_i){$mz%i#5cg*M?Nl_G!-vX`)z>Nn?b9GNZ4G71 z3s~y-*22S=rzy|DA^t*JUqf)R2s_ZzBgAPH%T1KXe~RpyM!%ds93D<{ZI1PD=dm!J z{aWg9s8`1f!eZR8tHt&;p4(*^lHly3xAIr#js9daH%GI*|JndE#rMvfQd81;1(d&V zn@_Wc)X-S0@~VGa+P+8nL2mi@gz0;i*M)xWD MeWs^ZH|b4O$cR?{?bR%@vi3 zgHA8}LE~*SwidiWRE7NFXM-nAhoG#ItlhzrwB3isW-b?*Vc%ptUF8%awtG6G-n8eS z(+84`pD|RqHZGz^w8b0~#jw=N-&-9E=Bv4L;aP{t;T}Oo&cz+3yAP1W{sCwYP%G|m ztfDn%8anuFjv3+-#41Ph(VR$DfWVyX{RV9nMRw0cmJ5wXCuu~NwLuVG VPfMU5ju`mkS5 zM|C<;sbAwzf<2@$tkgQ7eQv~WaJnjb#_7a!j>hTn!r&g2a6ErLN1k GFm~mv;l^Pgb6~Re-sU?BIZ-*J)f|D$^kawWZwv7b06s`AS)e-6C%2=2$n7ojq~W z%0nPLbz<1wL83X)Kj{a{cQ(;9>$BoBP0Y$OrvkPbwnO9BtQXqH_QKU<{a~S0EsT0O zp=Wg%y&Zu|oJ1?1V{1LI&4V8#eTObThMdu8->?LD=t#p?%;cQdRaGRsGwJ%iV<{w8 z-p<5T?Af3uSb6w0l?qNd(RCTL2}zu}>4DuGx6}8X&Nrxa*nG{4FJ9=;M8js)bCaH( z4qyKI9NI *=8y~lfFIK(;m9)2fyN{5(NXS_TK+t~ zpIghyckWx<@NBo#kulCCwqD^Hzu8)1rrIA%T1liFEmCt@Gm$De`Q+9j!{qecDu%kM z=*7|vlWiF !v#hqdGmPI$eI9FKm39$Fs1H{!%@xkDi%eeG+=nfv*`N0e)Fv&Al1^+)c6w zM$PE*!1&XhIhpY<6*mps-+VI=(YLaWe{rMp#pr0>6P6n^A6-a_L@Jdq2y|^@26I2_ zYE!@V7Ko9YHkoZ (HzVgULX=WxWu3ks6ThKY96qcHBYx)N=uplikaPX;xG#B`A7x$#Ub{~g~{@a zP@%AI?hKxLDI2dcB)bAz>k7OJ JBZ&T@Rrh7V%F9g^F? zcX7hW4vsddp*vs3J@!do&VPE$%fo1k1})?iefs>c=3QD%+s6yBZyniD1rKxc(5%s> zPW2r9_bKe0 QC4?fmBTmMdZf`X@8`~@2qX(2(@0o?F5O2 z?vFqd%tKsw2mOQ;yha0wmKr|*Oiw@Kj?pI4hP7Z7W)%Y@fBMRLb;JNn1LbfVMp!1g5u&N1iQSU-)$V-?mTr$y=8_aR*~d|ktEb}J11 vH3A~NMTOrvo}VIsGx* K5KRqG9rbF#bW6=#}wn#&hb7^0-BH|D(O#%g9%jkZmmCq)zBv#Q)e2gk}ErNS2o z(84iOsTM+3qD+s3VxioU!(+ok{F6gI_jJVSdEHhuF9!lO2p`q*uXqx_JfIsLx7_An zIE@`buNXn<)& O6_0i#k0P%t9l3Cl;)}ul3bZQ&INstnDbX4Sl z S;fLU6!771U1HU8C_|4mR)5*Ar&}W5x(|B(YYg(*(de`kilqd z&?(x;nIg@tGt)V1Tr9skd;esCMKcrnRe859>szeCF7YX&@cEvDSFRJy`^3zl=JRe* zO{Mv>p@IGz&w>^Ma2i6u*pw7FFD(W8N@uJtz48-6DZ48mZ2U8m=_E!jdh_S={UrA{ zZ}Z6-8Esa*0N?71=ghFW%cgnH_k6H?n09Hd_Hv*@**nk+uP5^40Ce&Kn?)F>x4!QU zZPUs$b%PzWwR)ssv_Dd(#=KaU+!1}pgclgh^h}pJ<~;LrX09x$+8W9GT-H07gZG%=@zd_ OY%-*v!8lFqC;yS zW8mQr*L%@VJYD1xO-Vhzbx{yg!~C&eln~Q)jZiBRY7HGD9*>6MD)a1So|%GI^+rPW zZYgk!PCouAy*dNG`-Q3Wd?RBl6j-g2*}iOfKlSCua<+f2-}B-{mtII}e1DomZ;u`j z%^7xzK`wL{VO*>d<$6X??!HsDDqd|U*j+kPfDY7Oo$L`)6->)KPFPn>O$feODZ_D? z(3NBPC{)?G9r~zA{;iNw7r4* ;u`f@XeS+ J)7?na2yZY>KyInk)-vj}*wsB0bzkb_o3uCLLwTm*RrpS&GwqcjDy!uRkt- z-gT|9f7L^q@^Y;I5Cl)vCpZ7TG@97w`K_%@!2V)>hdn$u#`6mw3wI%p{YhSv5w$!2 zJp;2=iI$=G&YFznP=sc#&pCfU%h9YnBK|A!NW;!XOX%+_V8z6>G}h5O3z zcYa2hEhP^)BtSIz7B$T~Voh`jL_QfqtwvI_C6A+y0uGk@P422OP2JjpZWs~5WgMxV zN?B?_ACmD(C6wGh>6wS)>sr3ZmW8~drN%-_ zeN5x$~g?u%vzz8lfyksx9v(e940vsvuNpHocXPiC;^#0+sD=DNn z0SZS64brE4o7&%{ZVlO|Vb0r((~zn4N}ING7JeE~8v&m1c&FXXI8HvU`x?B(J7%3c zCYfG)QHu?a?;a|}^Bo?Yw3MM3shMCZ%Yw%|m|LA$_hNXo+_HvQ8B$-mBHuclvv_>u z5oq^pqkh)!YGKG1dV@&{U-B*SE!rsUsVLx}<5OFU?Ke||(9%2?{lz6Os(TWo+2p2D zt)scBlhp2+B TI?ZBY9@Z^7CM1%22 ErNckx z1B{q$-FX)*5)pMYPuF;s_N1@NHPyO_eR8jnL`rEDTH;rLxx?@+O#v r3Yr()}B+qVTS4BvORfa|Ip&0 z3WglJN_Q_{p9+HZ_URtgE|ut!+&1^N#LM(`EXe|`>S4t-(lw(euqeVilzl5WK*)16 z?L_%Y;a-9}k=9_U2ZVIoB?>aUGb{Q}#p#9X8qUxfVG*fNs#uy;9g~<{U2bNBs9bGT zr8tqX(SfAtE%PrT>xM$K=ii!i#Y^?_urtdB?C9{h^p^CZd^E=(haZdM3%~Ri#|N2( zpC>*bim#2A=;D2vFNz2kY~uy@XHaa9V}PT<_?U!1jOpHk2QpNA^jJLD)5#q&O}Sbh zr7yAgMG5_?xU`t>=NTwhDO0^->)<~>m@2Cq`M@*L?cHM6JOR}e%Ix8m;2rgn!DJd( zf{{ }U4S=gHPY#uG>We*jK8yCR?zV}2 zE2^0+J9odARnIFKbnc+AQ`fq*GaRXpCBzt*Wye6k_P$;$t-Rw+4yD7sORh`(WU=q$ zC0)J6$8w2>L@MhAqK%rbb7)uCRe~q(^okMEWZ2ZE-3|=CtFkIOyRE{MCw`}rp;{y= zJf9#jHB5W>K9wOjhg~}?S!V1k!lr|QF_!BMa1=*AJjja5FLcT>cZP_@Dlbd}^pw#% z GPPI`@_P4(?J#gpse+^{Crl%us(!Cdsp2MGQ-2%z zId}So%a?W5BYg4B8=61Vzfg@v*BKFTy`%0I2kg`e*d}~>@c~~WF%@8wMWh~;d| -&93>Sf%}we CPi*-634NWx@jRT&WHJ`pSUON0$iF}e(Vspfw>dbRdzaCun3%9 zsVO*TCjkAsb}gJtW;}E|UZ+dzKjD3!mZ5*%FSouBkZ_K@^M>sLD}VXcGOgI@tpl;I zy2fP8Ff@K^zPhSlgID$Wr5)r&ZUVOIcrT-2Gu-gGRnz#CSB$gF!c;jgwjP-ccYo 6Zp0BWXGv zbX91Rf&4pK4s5&jv#|}P#~dr0yF_ERHa(sxkZsiQrR>gQ`b`n`^c@^O5@w4jI6NdL zNQ}UGB>~zW$O-&5(j0GA@O`5HL43c$_{gfFNTQRJpV%eexyl!!-e@yR-$6>p9-(IE z<_&&Up0k6?N)mQyEWKrFz}?mIpo{}L1Yb7KXgHSFeWlhHXp!dVyDpH6OOrd!6$3Aw z%x0%P&bBoemA k1%h>%_(~;qZJqzuopbr z=*RMkDa3229C~1yQrW~8yJL1F`F!u|Ny+hpb(4xA?isVGz)$X%CtHWy6+;uQZ^}PC zxEgCLYn7;6@ia}ba5XbOf4f$;zE^bl6Gt27FZeGU)bCmd5Li^`H^Bt_T^_)x@8o9d z<^Z$e`cKzy`koU>%m6fX0fqpe03Vo77@+MALAdz +S%bRN`I3{oTvf-1uNUXjQ!2!bIOYg3IK%!L nS? zk>dPQ^qbW(`I9{REke~5=%IWATziWw4UlnnvvEcMkkrLB1%<3luyThYi$Q?DD; jHILh*S!Fz-!cI~G2{$TpFd?lQQ?2c z1O)zVEEpNPzmF9V7C^Etf0qG8fymYJUotTGpK}2T{6hv55&Ne;Fp_@yFMWc7C`#?` zHUvSakpEo *69^TOSxQmjHepXJ;e<`zzV}Do$``BpZ9Z%2ec$e5|CXpr{oxsZgk(Fj5%=d56Q{ qAP`&tDr^Y>T0)Sg=l}W2FLKb;4f$Vqy@ Lab 2A: Superposition Experiment -<<<<<<< Updated upstream Isn't the toy course great?
Let's add some markup that uses non-ascii characters. @@ -8,6 +7,3 @@ For example, we should be able to write words like encyclopædia, or foreig Looking beyond latin-1, we should handle math symbols: πr² ≤ ∞. And it shouldn't matter if we use entities or numeric codes — Ω ≠ π ≡ Ω ≠ π.
-======= -Isn't the toy course great? — ≤
->>>>>>> Stashed changes diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index 492558d274..6499886398 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -324,9 +324,9 @@ def _section_data_download(course, access): return section_data -def null_get_asides(block): # pylint: disable=unused-argument +def null_applicable_aside_types(block): # pylint: disable=unused-argument """ - get_aside method for monkey-patching into descriptor_global_get_asides + get_aside method for monkey-patching into descriptor_global_applicable_aside_types while rendering an HtmlDescriptor for email text editing. This returns an empty list. """ @@ -337,8 +337,8 @@ def _section_send_email(course, access): """ Provide data for the corresponding bulk email section """ course_key = course.id - # Monkey-patch descriptor_global_get_asides to return no asides for the duration of this render - with patch('xmodule.x_module.descriptor_global_get_asides', null_get_asides): + # Monkey-patch descriptor_global_applicable_aside_types to return no asides for the duration of this render + with patch('xmodule.x_module.descriptor_global_applicable_aside_types', null_applicable_aside_types): # This HtmlDescriptor is only being used to generate a nice text editor. html_module = HtmlDescriptor( course.system, diff --git a/lms/djangoapps/lms_xblock/runtime.py b/lms/djangoapps/lms_xblock/runtime.py index 1cad66d8d5..c319370e99 100644 --- a/lms/djangoapps/lms_xblock/runtime.py +++ b/lms/djangoapps/lms_xblock/runtime.py @@ -226,7 +226,7 @@ class LmsModuleSystem(LmsHandlerUrls, ModuleSystem): # pylint: disable=abstract extra_data, ) - def get_asides(self, block): + def applicable_aside_types(self, block): """ Return all of the asides which might be decorating this `block`. @@ -242,8 +242,4 @@ class LmsModuleSystem(LmsHandlerUrls, ModuleSystem): # pylint: disable=abstract if block.scope_ids.block_type in config.disabled_blocks.split(): return [] - return [ - self.get_aside_of_type(block, aside_type) - for aside_type, __ - in XBlockAside.load_classes() - ] + return super(LmsModuleSystem, self).applicable_aside_types()