refactor: rename ModuleStore runtimes now that XModules are gone (#35523)

* Consolidates and renames the runtime used as a base for all the others:
  * Before: `xmodule.x_module:DescriptorSystem` and
            `xmodule.mako_block:MakoDescriptorSystem`.
  * After:  `xmodule.x_module:ModuleStoreRuntime`.

* Co-locates and renames the runtimes for importing course OLX:
  * Before: `xmodule.x_module:XMLParsingSystem` and
            `xmodule.modulestore.xml:ImportSystem`.
  * After:  `xmodule.modulestore.xml:XMLParsingModuleStoreRuntime` and
            `xmodule.modulestore.xml:XMLImportingModuleStoreRuntime`.
  * Note: I would have liked to consolidate these, but it would have
          involved nontrivial test refactoring.

* Renames the stub Old Mongo runtime:
  * Before: `xmodule.modulestore.mongo.base:CachingDescriptorSystem`.
  * After: `xmodule.modulestore.mongo.base:OldModuleStoreRuntime`.

* Renames the Split Mongo runtime, the which is what runs courses in LMS and CMS:
  * Before: `xmodule.modulestore.split_mongo.caching_descriptor_system:CachingDescriptorSystem`.
  * After: `xmodule.modulestore.split_mongo.runtime:SplitModuleStoreRuntime`.

* Renames some of the dummy runtimes used only in unit tests.
This commit is contained in:
Kyle McCormick
2025-10-29 15:46:07 -04:00
committed by GitHub
parent 31b1e6ecc4
commit 834cb9482d
42 changed files with 327 additions and 310 deletions

View File

@@ -529,7 +529,7 @@ def _import_xml_node_to_parent(
node_copied_version = node.attrib.get('copied_from_version', None) node_copied_version = node.attrib.get('copied_from_version', None)
# Modulestore's IdGenerator here is SplitMongoIdManager which is assigned # Modulestore's IdGenerator here is SplitMongoIdManager which is assigned
# by CachingDescriptorSystem Runtime and since we need our custom ImportIdGenerator # by SplitModuleStoreRuntime and since we need our custom ImportIdGenerator
# here we are temporaraliy swtiching it. # here we are temporaraliy swtiching it.
original_id_generator = runtime.id_generator original_id_generator = runtime.id_generator
@@ -566,7 +566,8 @@ def _import_xml_node_to_parent(
else: else:
# We have to handle the children ourselves, because there are lots of complex interactions between # We have to handle the children ourselves, because there are lots of complex interactions between
# * the vanilla XBlock parse_xml() method, and its lack of API for "create and save a new XBlock" # * the vanilla XBlock parse_xml() method, and its lack of API for "create and save a new XBlock"
# * the XmlMixin version of parse_xml() which only works with ImportSystem, not modulestore or the v2 runtime # * the XmlMixin version of parse_xml() which only works with XMLImportingModuleStoreRuntime,
# not modulestore or the v2 runtime
# * the modulestore APIs for creating and saving a new XBlock, which work but don't support XML parsing. # * the modulestore APIs for creating and saving a new XBlock, which work but don't support XML parsing.
# We can safely assume that if the XBLock class supports children, every child node will be the XML # We can safely assume that if the XBLock class supports children, every child node will be the XML
# serialization of a child block, in order. For blocks that don't support children, their XML content/nodes # serialization of a child block, in order. For blocks that don't support children, their XML content/nodes

View File

@@ -1855,7 +1855,7 @@ class TestDuplicateItemWithAsides(ItemTest, DuplicateHelper):
@XBlockAside.register_temp_plugin(AsideTest, "test_aside") @XBlockAside.register_temp_plugin(AsideTest, "test_aside")
@patch( @patch(
"xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.applicable_aside_types", "xmodule.modulestore.split_mongo.runtime.SplitModuleStoreRuntime.applicable_aside_types",
lambda self, block: ["test_aside"], lambda self, block: ["test_aside"],
) )
def test_duplicate_equality_with_asides(self): def test_duplicate_equality_with_asides(self):
@@ -2700,8 +2700,8 @@ class TestEditSplitModule(ItemTest):
group_id_to_child = split_test.group_id_to_child.copy() group_id_to_child = split_test.group_id_to_child.copy()
self.assertEqual(2, len(group_id_to_child)) self.assertEqual(2, len(group_id_to_child))
# CachingDescriptorSystem is used in tests. # SplitModuleStoreRuntime is used in tests.
# CachingDescriptorSystem doesn't have user service, that's needed for # SplitModuleStoreRuntime doesn't have user service, that's needed for
# SplitTestBlock. So, in this line of code we add this service manually. # SplitTestBlock. So, in this line of code we add this service manually.
split_test.runtime._services["user"] = DjangoXBlockUserService( # pylint: disable=protected-access split_test.runtime._services["user"] = DjangoXBlockUserService( # pylint: disable=protected-access
self.user self.user
@@ -4449,7 +4449,7 @@ class TestXBlockPublishingInfo(ItemTest):
@patch( @patch(
"xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.applicable_aside_types", "xmodule.modulestore.split_mongo.runtime.SplitModuleStoreRuntime.applicable_aside_types",
lambda self, block: ["test_aside"], lambda self, block: ["test_aside"],
) )
class TestUpdateFromSource(ModuleStoreTestCase): class TestUpdateFromSource(ModuleStoreTestCase):

View File

@@ -16,7 +16,7 @@ class ReplaceURLService(Service):
A service for replacing static/course/jump-to-id URLs with absolute URLs in XBlocks. A service for replacing static/course/jump-to-id URLs with absolute URLs in XBlocks.
Args: Args:
block: (optional) An XBlock instance. Used when retrieving the service from the DescriptorSystem. block: (optional) An XBlock instance. Used when retrieving the service from the ModuleStoreRuntime.
static_asset_path: (optional) Path for static assets, which overrides data_directory and course_id, if nonempty static_asset_path: (optional) Path for static assets, which overrides data_directory and course_id, if nonempty
static_paths_out: (optional) Array to collect tuples for each static URI found: static_paths_out: (optional) Array to collect tuples for each static URI found:
* the original unmodified static URI * the original unmodified static URI
@@ -39,7 +39,7 @@ class ReplaceURLService(Service):
self.jump_to_id_base_url = jump_to_id_base_url self.jump_to_id_base_url = jump_to_id_base_url
self.lookup_asset_url = lookup_asset_url self.lookup_asset_url = lookup_asset_url
# This is needed because the `Service` class initialization expects the XBlock passed as an `xblock` keyword # This is needed because the `Service` class initialization expects the XBlock passed as an `xblock` keyword
# argument, but the `service` method from the `DescriptorSystem` passes a `block`. # argument, but the `service` method from the `ModuleStoreRuntime` passes a `block`.
self._xblock = self.xblock() or block self._xblock = self.xblock() or block
def replace_urls(self, text, static_replace_only=False): def replace_urls(self, text, static_replace_only=False):

View File

@@ -108,7 +108,7 @@ class TestCourseListing(ModuleStoreTestCase, MilestonesTestCaseMixin):
self._create_course_with_access_groups(course_key) self._create_course_with_access_groups(course_key)
with mock.patch( with mock.patch(
'xmodule.modulestore.split_mongo.caching_descriptor_system.SplitMongoKVS', mock.Mock(side_effect=Exception) 'xmodule.modulestore.split_mongo.runtime.SplitMongoKVS', mock.Mock(side_effect=Exception)
): ):
assert isinstance(modulestore().get_course(course_key), ErrorBlock) assert isinstance(modulestore().get_course(course_key), ErrorBlock)

View File

@@ -242,8 +242,7 @@ To support the Libraries Relaunch in Sumac:
Video blocks. Video blocks.
* We will define method(s) for syncing update on the XBlock runtime so that * We will define method(s) for syncing update on the XBlock runtime so that
they are available in the SplitModuleStore's XBlock Runtime they are available in the SplitModuleStoreRuntime.
(CachingDescriptorSystem).
* Either in the initial implementation or in a later implementation, it may * Either in the initial implementation or in a later implementation, it may
make sense to declare abstract versions of the syncing method(s) higher up make sense to declare abstract versions of the syncing method(s) higher up
@@ -355,10 +354,10 @@ inheritance hierarchy of CachingDescriptorSystem and SplitModuleStoreRuntime.)
########################################################################### ###########################################################################
# xmodule/modulestore/split_mongo/caching_descriptor_system.py # xmodule/modulestore/split_mongo/runtime.py
########################################################################### ###########################################################################
class CachingDescriptorSystem(...): class SplitModuleStoreRuntime(...):
def validate_upstream_key(self, usage_key: UsageKey | str) -> UsageKey: def validate_upstream_key(self, usage_key: UsageKey | str) -> UsageKey:
""" """

View File

@@ -20,7 +20,7 @@ from xmodule.modulestore.django import modulestore
def get_block_side_effect(block_locator, user_known): def get_block_side_effect(block_locator, user_known):
""" """
Side effect for `CachingDescriptorSystem.get_block` Side effect for `SplitModuleStoreRuntime.get_block`
""" """
store = modulestore() store = modulestore()
course = store.get_course(block_locator.course_key) course = store.get_course(block_locator.course_key)
@@ -126,8 +126,8 @@ class TestGetCourseBlocks(UserPartitionTestMixin, CourseStructureTestCase):
Access checks are done through the transformers and through Runtime get_block_for_descriptor. Due Access checks are done through the transformers and through Runtime get_block_for_descriptor. Due
to the runtime limitations during the tests, the Runtime access checks are not performed as to the runtime limitations during the tests, the Runtime access checks are not performed as
get_block_for_descriptor is never called and Block is returned by CachingDescriptorSystem.get_block. get_block_for_descriptor is never called and Block is returned by SplitModuleStoreRuntime.get_block.
In this test, we mock the CachingDescriptorSystem.get_block and check block access for known and unknown users. In this test, we mock the SplitModuleStoreRuntime.get_block and check block access for known and unknown users.
For known users, it performs the Runtime access checks through get_block_for_descriptor. For unknown, it For known users, it performs the Runtime access checks through get_block_for_descriptor. For unknown, it
skips the access checks. skips the access checks.
""" """
@@ -137,7 +137,7 @@ class TestGetCourseBlocks(UserPartitionTestMixin, CourseStructureTestCase):
add_user_to_cohort(cohort, self.user.username) add_user_to_cohort(cohort, self.user.username)
side_effect = get_block_side_effect_for_known_user if user_known else get_block_side_effect_for_unknown_user side_effect = get_block_side_effect_for_known_user if user_known else get_block_side_effect_for_unknown_user
with patch('xmodule.modulestore.split_mongo.split.CachingDescriptorSystem.get_block', side_effect=side_effect): with patch('xmodule.modulestore.split_mongo.split.SplitModuleStoreRuntime.get_block', side_effect=side_effect):
block_structure = get_course_blocks( block_structure = get_course_blocks(
self.user, self.user,
self.course.location, self.course.location,

View File

@@ -122,7 +122,7 @@ class LmsModuleRenderError(Exception):
def make_track_function(request): def make_track_function(request):
''' '''
Make a tracking function that logs what happened. Make a tracking function that logs what happened.
For use in DescriptorSystem. For use in ModuleStoreRuntime.
''' '''
from common.djangoapps.track import views as track_views from common.djangoapps.track import views as track_views

View File

@@ -66,7 +66,7 @@ class BaseTestXmodule(ModuleStoreTestCase):
def new_module_runtime(self, runtime=None, **kwargs): def new_module_runtime(self, runtime=None, **kwargs):
""" """
Generate a new DescriptorSystem that is minimally set up for testing Generate a new ModuleStoreRuntime that is minimally set up for testing
""" """
if runtime: if runtime:
return prepare_block_runtime(runtime, course_id=self.course.id, **kwargs) return prepare_block_runtime(runtime, course_id=self.course.id, **kwargs)

View File

@@ -57,7 +57,7 @@ from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory, Toy
from xmodule.modulestore.tests.test_asides import AsideTestType # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.test_asides import AsideTestType # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.services import RebindUserServiceError from xmodule.services import RebindUserServiceError
from xmodule.video_block import VideoBlock # lint-amnesty, pylint: disable=wrong-import-order from xmodule.video_block import VideoBlock # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.x_module import STUDENT_VIEW, DescriptorSystem # lint-amnesty, pylint: disable=wrong-import-order from xmodule.x_module import STUDENT_VIEW, ModuleStoreRuntime # lint-amnesty, pylint: disable=wrong-import-order
from common.djangoapps.course_modes.models import CourseMode # lint-amnesty, pylint: disable=reimported from common.djangoapps.course_modes.models import CourseMode # lint-amnesty, pylint: disable=reimported
from common.djangoapps.student.tests.factories import ( from common.djangoapps.student.tests.factories import (
BetaTesterFactory, BetaTesterFactory,
@@ -461,8 +461,11 @@ class BlockRenderTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
@override_settings(FIELD_OVERRIDE_PROVIDERS=( @override_settings(FIELD_OVERRIDE_PROVIDERS=(
'lms.djangoapps.courseware.student_field_overrides.IndividualStudentOverrideProvider', 'lms.djangoapps.courseware.student_field_overrides.IndividualStudentOverrideProvider',
)) ))
@patch('xmodule.modulestore.xml.ImportSystem.applicable_aside_types', lambda self, block: ['test_aside']) @patch(
@patch('xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.applicable_aside_types', 'xmodule.modulestore.xml.XMLImportingModuleStoreRuntime.applicable_aside_types',
lambda self, block: ['test_aside']
)
@patch('xmodule.modulestore.split_mongo.runtime.SplitModuleStoreRuntime.applicable_aside_types',
lambda self, block: ['test_aside']) lambda self, block: ['test_aside'])
@XBlockAside.register_temp_plugin(AsideTestType, 'test_aside') @XBlockAside.register_temp_plugin(AsideTestType, 'test_aside')
@ddt.data('regular', 'test_aside') @ddt.data('regular', 'test_aside')
@@ -1920,7 +1923,7 @@ class TestAnonymousStudentId(SharedModuleStoreTestCase, LoginEnrollmentTestCase)
location=location, location=location,
static_asset_path=None, static_asset_path=None,
_runtime=Mock( _runtime=Mock(
spec=DescriptorSystem, spec=ModuleStoreRuntime,
resources_fs=None, resources_fs=None,
mixologist=Mock(_mixins=(), name='mixologist'), mixologist=Mock(_mixins=(), name='mixologist'),
_services={}, _services={},
@@ -1933,7 +1936,7 @@ class TestAnonymousStudentId(SharedModuleStoreTestCase, LoginEnrollmentTestCase)
fields={}, fields={},
days_early_for_beta=None, days_early_for_beta=None,
) )
block.runtime = DescriptorSystem(None, None, None) block.runtime = ModuleStoreRuntime(None, None, None)
# Use the xblock_class's bind_for_student method # Use the xblock_class's bind_for_student method
block.bind_for_student = partial(xblock_class.bind_for_student, block) block.bind_for_student = partial(xblock_class.bind_for_student, block)
@@ -2006,9 +2009,9 @@ class TestModuleTrackingContext(SharedModuleStoreTestCase):
assert problem_display_name == block_info['display_name'] assert problem_display_name == block_info['display_name']
@XBlockAside.register_temp_plugin(AsideTestType, 'test_aside') @XBlockAside.register_temp_plugin(AsideTestType, 'test_aside')
@patch('xmodule.modulestore.mongo.base.CachingDescriptorSystem.applicable_aside_types', @patch('xmodule.modulestore.mongo.base.OldModuleStoreRuntime.applicable_aside_types',
lambda self, block: ['test_aside']) lambda self, block: ['test_aside'])
@patch('xmodule.x_module.DescriptorSystem.applicable_aside_types', @patch('xmodule.x_module.ModuleStoreRuntime.applicable_aside_types',
lambda self, block: ['test_aside']) lambda self, block: ['test_aside'])
def test_context_contains_aside_info(self, mock_tracker): def test_context_contains_aside_info(self, mock_tracker):
""" """

View File

@@ -47,7 +47,7 @@ from xmodule.modulestore.inheritance import own_metadata
from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
from xmodule.tests.helpers import override_descriptor_system # pylint: disable=unused-import from xmodule.tests.helpers import override_descriptor_system # pylint: disable=unused-import
from xmodule.tests.test_import import DummySystem from xmodule.tests.test_import import DummyModuleStoreRuntime
from xmodule.tests.test_video import VideoBlockTestBase from xmodule.tests.test_video import VideoBlockTestBase
from xmodule.video_block import VideoBlock, bumper_utils, video_utils from xmodule.video_block import VideoBlock, bumper_utils, video_utils
from xmodule.video_block.transcripts_utils import Transcript, save_to_store, subs_filename from xmodule.video_block.transcripts_utils import Transcript, save_to_store, subs_filename
@@ -1989,7 +1989,7 @@ class VideoBlockTest(TestCase, VideoBlockTestBase):
Test that import val data internal works as expected. Test that import val data internal works as expected.
""" """
create_profile('mobile') create_profile('mobile')
module_system = DummySystem(load_error_blocks=True) module_system = DummyModuleStoreRuntime(load_error_blocks=True)
edx_video_id = 'test_edx_video_id' edx_video_id = 'test_edx_video_id'
sub_id = '0CzPOIIdUsA' sub_id = '0CzPOIIdUsA'
@@ -2095,7 +2095,7 @@ class VideoBlockTest(TestCase, VideoBlockTestBase):
""" """
xml_data = """<video><video_asset></video_asset></video>""" xml_data = """<video><video_asset></video_asset></video>"""
xml_object = etree.fromstring(xml_data) xml_object = etree.fromstring(xml_data)
module_system = DummySystem(load_error_blocks=True) module_system = DummyModuleStoreRuntime(load_error_blocks=True)
# Verify edx_video_id is empty before. # Verify edx_video_id is empty before.
assert self.block.edx_video_id == '' assert self.block.edx_video_id == ''
@@ -2131,7 +2131,7 @@ class VideoBlockTest(TestCase, VideoBlockTestBase):
val_transcript_provider=val_transcript_provider val_transcript_provider=val_transcript_provider
) )
xml_object = etree.fromstring(xml_data) xml_object = etree.fromstring(xml_data)
module_system = DummySystem(load_error_blocks=True) module_system = DummyModuleStoreRuntime(load_error_blocks=True)
# Create static directory in import file system and place transcript files inside it. # Create static directory in import file system and place transcript files inside it.
module_system.resources_fs.makedirs(EXPORT_IMPORT_STATIC_DIR, recreate=True) module_system.resources_fs.makedirs(EXPORT_IMPORT_STATIC_DIR, recreate=True)
@@ -2237,7 +2237,7 @@ class VideoBlockTest(TestCase, VideoBlockTestBase):
edx_video_id = 'test_edx_video_id' edx_video_id = 'test_edx_video_id'
language_code = 'en' language_code = 'en'
module_system = DummySystem(load_error_blocks=True) module_system = DummyModuleStoreRuntime(load_error_blocks=True)
# Create static directory in import file system and place transcript files inside it. # Create static directory in import file system and place transcript files inside it.
module_system.resources_fs.makedirs(EXPORT_IMPORT_STATIC_DIR, recreate=True) module_system.resources_fs.makedirs(EXPORT_IMPORT_STATIC_DIR, recreate=True)
@@ -2306,7 +2306,7 @@ class VideoBlockTest(TestCase, VideoBlockTestBase):
def test_import_val_data_invalid(self): def test_import_val_data_invalid(self):
create_profile('mobile') create_profile('mobile')
module_system = DummySystem(load_error_blocks=True) module_system = DummyModuleStoreRuntime(load_error_blocks=True)
# Negative file_size is invalid # Negative file_size is invalid
xml_data = """ xml_data = """

View File

@@ -11,7 +11,7 @@ from django.test import TestCase
from opaque_keys.edx.locations import BlockUsageLocator, CourseLocator from opaque_keys.edx.locations import BlockUsageLocator, CourseLocator
from xblock.fields import ScopeIds from xblock.fields import ScopeIds
from xmodule.x_module import DescriptorSystem from xmodule.x_module import ModuleStoreRuntime
from lms.djangoapps.lms_xblock.runtime import handler_url from lms.djangoapps.lms_xblock.runtime import handler_url
@@ -51,7 +51,7 @@ class TestHandlerUrl(TestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.block = BlockMock(name='block') self.block = BlockMock(name='block')
self.runtime = DescriptorSystem( self.runtime = ModuleStoreRuntime(
load_item=Mock(name='get_test_descriptor_system.load_item'), load_item=Mock(name='get_test_descriptor_system.load_item'),
resources_fs=Mock(name='get_test_descriptor_system.resources_fs'), resources_fs=Mock(name='get_test_descriptor_system.resources_fs'),
error_tracker=Mock(name='get_test_descriptor_system.error_tracker') error_tracker=Mock(name='get_test_descriptor_system.error_tracker')

View File

@@ -157,7 +157,7 @@ class RuntimeShim:
""" """
# We can't parse XML in a vacuum - we need to know the parent block and/or the # We can't parse XML in a vacuum - we need to know the parent block and/or the
# OLX file that holds this XML in order to generate useful definition keys etc. # OLX file that holds this XML in order to generate useful definition keys etc.
# The older ImportSystem runtime could do this because it stored the course_id # The older XMLImportingModuleStoreRuntime runtime could do this because it stored the course_id
# as part of the runtime. # as part of the runtime.
raise NotImplementedError("This newer runtime does not support process_xml()") raise NotImplementedError("This newer runtime does not support process_xml()")
@@ -244,7 +244,7 @@ class RuntimeShim:
def get_field_provenance(self, xblock, field): def get_field_provenance(self, xblock, field):
""" """
A Studio-specific method that was implemented on DescriptorSystem. A Studio-specific method that was implemented on ModuleStoreRuntime.
Used by the problem block. Used by the problem block.
For the given xblock, return a dict for the field's current state: For the given xblock, return a dict for the field's current state:

View File

@@ -177,7 +177,10 @@ class TestXBlockAside(SharedModuleStoreTestCase):
"""test if xblock is not aside""" """test if xblock is not aside"""
assert is_xblock_aside(self.block.scope_ids.usage_id) is False assert is_xblock_aside(self.block.scope_ids.usage_id) is False
@patch('xmodule.modulestore.xml.ImportSystem.applicable_aside_types', lambda self, block: ['test_aside']) @patch(
'xmodule.modulestore.xml.XMLImportingModuleStoreRuntime.applicable_aside_types',
lambda self, block: ['test_aside'],
)
@XBlockAside.register_temp_plugin(AsideTestType, 'test_aside') @XBlockAside.register_temp_plugin(AsideTestType, 'test_aside')
def test_get_aside(self): def test_get_aside(self):
"""test get aside success""" """test get aside success"""

View File

@@ -93,7 +93,7 @@ class LoncapaSystem(object):
i18n: an object implementing the `gettext.Translations` interface so i18n: an object implementing the `gettext.Translations` interface so
that we can use `.ugettext` to localize strings. that we can use `.ugettext` to localize strings.
See :class:`DescriptorSystem` for documentation of other attributes. See :class:`ModuleStoreRuntime` for documentation of other attributes.
""" """

View File

@@ -79,7 +79,7 @@ class ErrorBlock(
Build a new ErrorBlock using ``system``. Build a new ErrorBlock using ``system``.
Arguments: Arguments:
system (:class:`DescriptorSystem`): The :class:`DescriptorSystem` used system (:class:`ModuleStoreRuntime`): The :class:`ModuleStoreRuntime` used
to construct the XBlock that had an error. to construct the XBlock that had an error.
contents (unicode): An encoding of the content of the xblock that had an error. contents (unicode): An encoding of the content of the xblock that had an error.
error_msg (unicode): A message describing the error. error_msg (unicode): A message describing the error.

View File

@@ -5,30 +5,7 @@ Code to handle mako templating for XModules and XBlocks.
from web_fragments.fragment import Fragment from web_fragments.fragment import Fragment
from .x_module import DescriptorSystem, shim_xmodule_js from .x_module import shim_xmodule_js
class MakoDescriptorSystem(DescriptorSystem): # lint-amnesty, pylint: disable=abstract-method
"""
Descriptor system that renders mako templates.
"""
def __init__(self, render_template, **kwargs):
super().__init__(**kwargs)
self.render_template = render_template
# Add the MakoService to the runtime services.
# If it already exists, do not attempt to reinitialize it; otherwise, this could override the `namespace_prefix`
# of the `MakoService`, breaking template rendering in Studio.
#
# This is not needed by most XBlocks, because the MakoService is added to their runtimes.
# However, there are a few cases where the MakoService is not added to the XBlock's
# runtime. Specifically:
# * in the Instructor Dashboard bulk emails tab, when rendering the HtmlBlock for its WYSIWYG editor.
# * during testing, when fetching factory-created blocks.
if 'mako' not in self._services:
from common.djangoapps.edxmako.services import MakoService
self._services['mako'] = MakoService()
class MakoTemplateBlockBase: class MakoTemplateBlockBase:

View File

@@ -377,7 +377,7 @@ class EditInfo:
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.from_storable(kwargs) self.from_storable(kwargs)
# For details, see caching_descriptor_system.py get_subtree_edited_by/on. # For details, see runtime.py get_subtree_edited_by/on.
self._subtree_edited_on = kwargs.get('_subtree_edited_on', None) self._subtree_edited_on = kwargs.get('_subtree_edited_on', None)
self._subtree_edited_by = kwargs.get('_subtree_edited_by', None) self._subtree_edited_by = kwargs.get('_subtree_edited_by', None)

View File

@@ -36,7 +36,6 @@ from xmodule.course_block import CourseSummary
from xmodule.error_block import ErrorBlock from xmodule.error_block import ErrorBlock
from xmodule.errortracker import exc_info_to_str, null_error_tracker from xmodule.errortracker import exc_info_to_str, null_error_tracker
from xmodule.exceptions import HeartbeatFailure from xmodule.exceptions import HeartbeatFailure
from xmodule.mako_block import MakoDescriptorSystem
from xmodule.modulestore import BulkOperationsMixin, ModuleStoreEnum, ModuleStoreWriteBase from xmodule.modulestore import BulkOperationsMixin, ModuleStoreEnum, ModuleStoreWriteBase
from xmodule.modulestore.draft_and_published import DIRECT_ONLY_CATEGORIES, ModuleStoreDraftAndPublished from xmodule.modulestore.draft_and_published import DIRECT_ONLY_CATEGORIES, ModuleStoreDraftAndPublished
from xmodule.modulestore.edit_info import EditInfoRuntimeMixin from xmodule.modulestore.edit_info import EditInfoRuntimeMixin
@@ -46,6 +45,7 @@ from xmodule.modulestore.xml import CourseLocationManager
from xmodule.mongo_utils import connect_to_mongodb, create_collection_index from xmodule.mongo_utils import connect_to_mongodb, create_collection_index
from xmodule.partitions.partitions_service import PartitionService from xmodule.partitions.partitions_service import PartitionService
from xmodule.services import SettingsService from xmodule.services import SettingsService
from xmodule.x_module import ModuleStoreRuntime
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -146,19 +146,19 @@ class MongoKeyValueStore(InheritanceKeyValueStore):
) )
class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin): # lint-amnesty, pylint: disable=abstract-method class OldModuleStoreRuntime(ModuleStoreRuntime, EditInfoRuntimeMixin): # pylint: disable=abstract-method
""" """
A system that has a cache of block json that it will use to load blocks A system that has a cache of block json that it will use to load blocks
from, with a backup of calling to the underlying modulestore for more data from, with a backup of calling to the underlying modulestore for more data
""" """
# This CachingDescriptorSystem runtime sets block._field_data on each block via construct_xblock_from_class(), # This OldModuleStoreRuntime sets block._field_data on each block via construct_xblock_from_class(),
# rather than the newer approach of providing a "field-data" service via runtime.service(). As a result, during # rather than the newer approach of providing a "field-data" service via runtime.service(). As a result, during
# bind_for_student() we can't just set ._bound_field_data; we must overwrite block._field_data. # bind_for_student() we can't just set ._bound_field_data; we must overwrite block._field_data.
uses_deprecated_field_data = True uses_deprecated_field_data = True
def __repr__(self): def __repr__(self):
return "CachingDescriptorSystem{!r}".format(( return "{}{!r}".format(self.__class__.__name__, (
self.modulestore, self.modulestore,
str(self.course_id), str(self.course_id),
[str(key) for key in self.module_data.keys()], [str(key) for key in self.module_data.keys()],
@@ -177,12 +177,12 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin): # li
default_class: The default_class to use when loading an default_class: The default_class to use when loading an
XModuleDescriptor from the module_data XModuleDescriptor from the module_data
resources_fs: a filesystem, as per MakoDescriptorSystem resources_fs: a filesystem, as per ModuleStoreRuntime
error_tracker: a function that logs errors for later display to users error_tracker: a function that logs errors for later display to users
render_template: a function for rendering templates, as per render_template: a function for rendering templates, as per
MakoDescriptorSystem ModuleStoreRuntime
""" """
id_manager = CourseLocationManager(course_key) id_manager = CourseLocationManager(course_key)
kwargs.setdefault('id_reader', id_manager) kwargs.setdefault('id_reader', id_manager)
@@ -660,7 +660,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
data_dir (optional): The directory name to use as the root data directory for this XModule data_dir (optional): The directory name to use as the root data directory for this XModule
data_cache (dict): A dictionary mapping from UsageKeys to xblock field data data_cache (dict): A dictionary mapping from UsageKeys to xblock field data
(this is the xblock data loaded from the database) (this is the xblock data loaded from the database)
using_descriptor_system (CachingDescriptorSystem): The existing CachingDescriptorSystem using_descriptor_system (OldModuleStoreRuntime): The existing runtime
to add data to, and to load the XBlocks from. to add data to, and to load the XBlocks from.
for_parent (:class:`XBlock`): The parent of the XBlock being loaded. for_parent (:class:`XBlock`): The parent of the XBlock being loaded.
""" """
@@ -687,7 +687,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
services["partitions"] = PartitionService(course_key) services["partitions"] = PartitionService(course_key)
system = CachingDescriptorSystem( system = OldModuleStoreRuntime(
modulestore=self, modulestore=self,
course_key=course_key, course_key=course_key,
module_data=data_cache, module_data=data_cache,
@@ -922,7 +922,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
descendents of the queried blocks for more efficient results later descendents of the queried blocks for more efficient results later
in the request. The depth is counted in the number of in the request. The depth is counted in the number of
calls to get_children() to cache. None indicates to cache all descendents. calls to get_children() to cache. None indicates to cache all descendents.
using_descriptor_system (CachingDescriptorSystem): The existing CachingDescriptorSystem using_descriptor_system (SplitModuleStoreRuntime): The existing SplitModuleStoreRuntime
to add data to, and to load the XBlocks from. to add data to, and to load the XBlocks from.
""" """
item = self._find_one(usage_key) item = self._find_one(usage_key)
@@ -994,7 +994,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
For this modulestore, ``name`` is a commonly provided key (Location based stores) For this modulestore, ``name`` is a commonly provided key (Location based stores)
This modulestore does not allow searching dates by comparison or edited_by, previous_version, This modulestore does not allow searching dates by comparison or edited_by, previous_version,
update_version info. update_version info.
using_descriptor_system (CachingDescriptorSystem): The existing CachingDescriptorSystem using_descriptor_system (SplitModuleStoreRuntime): The existing SplitModuleStoreRuntime
to add data to, and to load the XBlocks from. to add data to, and to load the XBlocks from.
""" """
qualifiers = qualifiers.copy() if qualifiers else {} # copy the qualifiers (destructively manipulated here) qualifiers = qualifiers.copy() if qualifiers else {} # copy the qualifiers (destructively manipulated here)
@@ -1104,7 +1104,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
services["partitions"] = PartitionService(course_key) services["partitions"] = PartitionService(course_key)
runtime = CachingDescriptorSystem( runtime = OldModuleStoreRuntime(
modulestore=self, modulestore=self,
module_data={}, module_data={},
course_key=course_key, course_key=course_key,

View File

@@ -73,7 +73,7 @@ class DraftModuleStore(MongoModuleStore):
Note: If the item is in DIRECT_ONLY_CATEGORIES, then returns only the PUBLISHED Note: If the item is in DIRECT_ONLY_CATEGORIES, then returns only the PUBLISHED
version regardless of the revision. version regardless of the revision.
using_descriptor_system (CachingDescriptorSystem): The existing CachingDescriptorSystem using_descriptor_system (ModuleStoreRuntime): The existing runtime
to add data to, and to load the XBlocks from. to add data to, and to load the XBlocks from.
Raises: Raises:

View File

@@ -14,19 +14,19 @@ from xmodule.x_module import AsideKeyGenerator, OpaqueKeyReader
class SplitMongoIdManager(OpaqueKeyReader, AsideKeyGenerator): # pylint: disable=abstract-method class SplitMongoIdManager(OpaqueKeyReader, AsideKeyGenerator): # pylint: disable=abstract-method
""" """
An IdManager that knows how to retrieve the DefinitionLocator, given An IdManager that knows how to retrieve the DefinitionLocator, given
a usage_id and a :class:`.CachingDescriptorSystem`. a usage_id and a :class:`.SplitModuleStoreRuntime`.
""" """
def __init__(self, caching_descriptor_system): def __init__(self, runtime):
self._cds = caching_descriptor_system self._runtime = runtime
def get_definition_id(self, usage_id): def get_definition_id(self, usage_id):
if isinstance(usage_id.block_id, LocalId): if isinstance(usage_id.block_id, LocalId):
# a LocalId indicates that this block hasn't been persisted yet, and is instead stored # a LocalId indicates that this block hasn't been persisted yet, and is instead stored
# in-memory in the local_modules dictionary. # in-memory in the local_modules dictionary.
return self._cds.local_modules[usage_id].scope_ids.def_id return self._runtime.local_modules[usage_id].scope_ids.def_id
else: else:
block_key = BlockKey.from_usage_key(usage_id) block_key = BlockKey.from_usage_key(usage_id)
module_data = self._cds.get_module_data(block_key, usage_id.course_key) module_data = self._runtime.get_module_data(block_key, usage_id.course_key)
if module_data.definition is not None: if module_data.definition is not None:
return DefinitionLocator(usage_id.block_type, module_data.definition) return DefinitionLocator(usage_id.block_type, module_data.definition)

View File

@@ -14,7 +14,6 @@ from xblock.runtime import KeyValueStore, KvsFieldData
from xmodule.error_block import ErrorBlock from xmodule.error_block import ErrorBlock
from xmodule.errortracker import exc_info_to_str from xmodule.errortracker import exc_info_to_str
from xmodule.library_tools import LegacyLibraryToolsService from xmodule.library_tools import LegacyLibraryToolsService
from xmodule.mako_block import MakoDescriptorSystem
from xmodule.modulestore import BlockData from xmodule.modulestore import BlockData
from xmodule.modulestore.edit_info import EditInfoRuntimeMixin from xmodule.modulestore.edit_info import EditInfoRuntimeMixin
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError
@@ -24,12 +23,12 @@ from xmodule.modulestore.split_mongo.definition_lazy_loader import DefinitionLaz
from xmodule.modulestore.split_mongo.id_manager import SplitMongoIdManager from xmodule.modulestore.split_mongo.id_manager import SplitMongoIdManager
from xmodule.modulestore.split_mongo.split_mongo_kvs import SplitMongoKVS from xmodule.modulestore.split_mongo.split_mongo_kvs import SplitMongoKVS
from xmodule.util.misc import get_library_or_course_attribute from xmodule.util.misc import get_library_or_course_attribute
from xmodule.x_module import XModuleMixin from xmodule.x_module import XModuleMixin, ModuleStoreRuntime
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin): # lint-amnesty, pylint: disable=abstract-method class SplitModuleStoreRuntime(ModuleStoreRuntime, EditInfoRuntimeMixin): # pylint: disable=abstract-method
""" """
A system that has a cache of a course version's json that it will use to load blocks A system that has a cache of a course version's json that it will use to load blocks
from, with a backup of calling to the underlying modulestore for more data. from, with a backup of calling to the underlying modulestore for more data.

View File

@@ -106,7 +106,7 @@ from xmodule.util.misc import get_library_or_course_attribute
from xmodule.util.keys import BlockKey, derive_key from xmodule.util.keys import BlockKey, derive_key
from ..exceptions import ItemNotFoundError from ..exceptions import ItemNotFoundError
from .caching_descriptor_system import CachingDescriptorSystem from .runtime import SplitModuleStoreRuntime
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -715,7 +715,7 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
per course per fetch operations are done. per course per fetch operations are done.
Arguments: Arguments:
system: a CachingDescriptorSystem system: a SplitModuleStoreRuntime
base_block_ids: list of BlockIds to fetch base_block_ids: list of BlockIds to fetch
course_key: the destination course providing the context course_key: the destination course providing the context
depth: how deep below these to prefetch depth: how deep below these to prefetch
@@ -3290,7 +3290,7 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
if not isinstance(course_entry.course_key, LibraryLocator): if not isinstance(course_entry.course_key, LibraryLocator):
services["partitions"] = PartitionService(course_entry.course_key) services["partitions"] = PartitionService(course_entry.course_key)
return CachingDescriptorSystem( return SplitModuleStoreRuntime(
modulestore=self, modulestore=self,
course_entry=course_entry, course_entry=course_entry,
module_data={}, module_data={},

View File

@@ -33,7 +33,10 @@ class TestAsidesXmlStore(TestCase):
Test Asides sourced from xml store Test Asides sourced from xml store
""" """
@patch('xmodule.modulestore.xml.ImportSystem.applicable_aside_types', lambda self, block: ['test_aside']) @patch(
'xmodule.modulestore.xml.XMLImportingModuleStoreRuntime.applicable_aside_types',
lambda self, block: ['test_aside']
)
@XBlockAside.register_temp_plugin(AsideTestType, 'test_aside') @XBlockAside.register_temp_plugin(AsideTestType, 'test_aside')
def test_xml_aside(self): def test_xml_aside(self):
""" """

View File

@@ -3349,7 +3349,7 @@ class TestPublishOverExportImport(CommonMixedModuleStoreSetup):
@ddt.data(ModuleStoreEnum.Type.split) @ddt.data(ModuleStoreEnum.Type.split)
@XBlockAside.register_temp_plugin(AsideTestType, 'test_aside') @XBlockAside.register_temp_plugin(AsideTestType, 'test_aside')
@patch('xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.applicable_aside_types', @patch('xmodule.modulestore.split_mongo.runtime.SplitModuleStoreRuntime.applicable_aside_types',
lambda self, block: ['test_aside']) lambda self, block: ['test_aside'])
def test_aside_crud(self, default_store): def test_aside_crud(self, default_store):
""" """
@@ -3423,7 +3423,7 @@ class TestPublishOverExportImport(CommonMixedModuleStoreSetup):
@ddt.data(ModuleStoreEnum.Type.split) @ddt.data(ModuleStoreEnum.Type.split)
@XBlockAside.register_temp_plugin(AsideTestType, 'test_aside') @XBlockAside.register_temp_plugin(AsideTestType, 'test_aside')
@patch('xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.applicable_aside_types', @patch('xmodule.modulestore.split_mongo.runtime.SplitModuleStoreRuntime.applicable_aside_types',
lambda self, block: ['test_aside']) lambda self, block: ['test_aside'])
def test_export_course_with_asides(self, default_store): def test_export_course_with_asides(self, default_store):
if default_store == ModuleStoreEnum.Type.mongo: if default_store == ModuleStoreEnum.Type.mongo:
@@ -3510,7 +3510,7 @@ class TestPublishOverExportImport(CommonMixedModuleStoreSetup):
@ddt.data(ModuleStoreEnum.Type.split) @ddt.data(ModuleStoreEnum.Type.split)
@XBlockAside.register_temp_plugin(AsideTestType, 'test_aside') @XBlockAside.register_temp_plugin(AsideTestType, 'test_aside')
@patch('xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.applicable_aside_types', @patch('xmodule.modulestore.split_mongo.runtime.SplitModuleStoreRuntime.applicable_aside_types',
lambda self, block: ['test_aside']) lambda self, block: ['test_aside'])
def test_export_course_after_creating_new_items_with_asides(self, default_store): # pylint: disable=too-many-statements def test_export_course_after_creating_new_items_with_asides(self, default_store): # pylint: disable=too-many-statements
if default_store == ModuleStoreEnum.Type.mongo: if default_store == ModuleStoreEnum.Type.mongo:
@@ -3645,7 +3645,7 @@ class TestAsidesWithMixedModuleStore(CommonMixedModuleStoreSetup):
@ddt.data(ModuleStoreEnum.Type.split) @ddt.data(ModuleStoreEnum.Type.split)
@XBlockAside.register_temp_plugin(AsideFoo, 'test_aside1') @XBlockAside.register_temp_plugin(AsideFoo, 'test_aside1')
@XBlockAside.register_temp_plugin(AsideBar, 'test_aside2') @XBlockAside.register_temp_plugin(AsideBar, 'test_aside2')
@patch('xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.applicable_aside_types', @patch('xmodule.modulestore.split_mongo.runtime.SplitModuleStoreRuntime.applicable_aside_types',
lambda self, block: ['test_aside1', 'test_aside2']) lambda self, block: ['test_aside1', 'test_aside2'])
def test_get_and_update_asides(self, default_store): def test_get_and_update_asides(self, default_store):
""" """
@@ -3709,7 +3709,7 @@ class TestAsidesWithMixedModuleStore(CommonMixedModuleStoreSetup):
@ddt.data(ModuleStoreEnum.Type.split) @ddt.data(ModuleStoreEnum.Type.split)
@XBlockAside.register_temp_plugin(AsideFoo, 'test_aside1') @XBlockAside.register_temp_plugin(AsideFoo, 'test_aside1')
@patch('xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.applicable_aside_types', @patch('xmodule.modulestore.split_mongo.runtime.SplitModuleStoreRuntime.applicable_aside_types',
lambda self, block: ['test_aside1']) lambda self, block: ['test_aside1'])
def test_clone_course_with_asides(self, default_store): def test_clone_course_with_asides(self, default_store):
""" """
@@ -3757,7 +3757,7 @@ class TestAsidesWithMixedModuleStore(CommonMixedModuleStoreSetup):
@ddt.data(ModuleStoreEnum.Type.split) @ddt.data(ModuleStoreEnum.Type.split)
@XBlockAside.register_temp_plugin(AsideFoo, 'test_aside1') @XBlockAside.register_temp_plugin(AsideFoo, 'test_aside1')
@patch('xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.applicable_aside_types', @patch('xmodule.modulestore.split_mongo.runtime.SplitModuleStoreRuntime.applicable_aside_types',
lambda self, block: ['test_aside1']) lambda self, block: ['test_aside1'])
def test_delete_item_with_asides(self, default_store): def test_delete_item_with_asides(self, default_store):
""" """
@@ -3806,7 +3806,7 @@ class TestAsidesWithMixedModuleStore(CommonMixedModuleStoreSetup):
@ddt.data((ModuleStoreEnum.Type.split, 1, 0)) @ddt.data((ModuleStoreEnum.Type.split, 1, 0))
@XBlockAside.register_temp_plugin(AsideFoo, 'test_aside1') @XBlockAside.register_temp_plugin(AsideFoo, 'test_aside1')
@patch('xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.applicable_aside_types', @patch('xmodule.modulestore.split_mongo.runtime.SplitModuleStoreRuntime.applicable_aside_types',
lambda self, block: ['test_aside1']) lambda self, block: ['test_aside1'])
@ddt.unpack @ddt.unpack
def test_published_and_unpublish_item_with_asides(self, default_store, max_find, max_send): def test_published_and_unpublish_item_with_asides(self, default_store, max_find, max_send):

View File

@@ -415,14 +415,14 @@ class TestSplitDirectOnlyCategorySemantics(DirectOnlyCategorySemantics):
@ddt.data(*TESTABLE_BLOCK_TYPES) @ddt.data(*TESTABLE_BLOCK_TYPES)
@XBlockAside.register_temp_plugin(AsideTest, 'test_aside') @XBlockAside.register_temp_plugin(AsideTest, 'test_aside')
@patch('xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.applicable_aside_types', @patch('xmodule.modulestore.split_mongo.runtime.SplitModuleStoreRuntime.applicable_aside_types',
lambda self, block: ['test_aside']) lambda self, block: ['test_aside'])
def test_create_with_asides(self, block_type): def test_create_with_asides(self, block_type):
self._do_create(block_type, with_asides=True) self._do_create(block_type, with_asides=True)
@ddt.data(*TESTABLE_BLOCK_TYPES) @ddt.data(*TESTABLE_BLOCK_TYPES)
@XBlockAside.register_temp_plugin(AsideTest, 'test_aside') @XBlockAside.register_temp_plugin(AsideTest, 'test_aside')
@patch('xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.applicable_aside_types', @patch('xmodule.modulestore.split_mongo.runtime.SplitModuleStoreRuntime.applicable_aside_types',
lambda self, block: ['test_aside']) lambda self, block: ['test_aside'])
def test_update_asides(self, block_type): def test_update_asides(self, block_type):
block_usage_key = self._do_create(block_type, with_asides=True) block_usage_key = self._do_create(block_type, with_asides=True)

View File

@@ -15,24 +15,29 @@ from importlib import import_module
from fs.osfs import OSFS from fs.osfs import OSFS
from lxml import etree from lxml import etree
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey, UsageKey
from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator, LibraryLocator from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator, LibraryLocator
from path import Path as path from path import Path as path
from xblock.core import XBlockAside
from xblock.field_data import DictFieldData from xblock.field_data import DictFieldData
from xblock.fields import ScopeIds from xblock.fields import (
Reference,
ReferenceList,
ReferenceValueDict,
ScopeIds,
)
from xblock.runtime import DictKeyValueStore from xblock.runtime import DictKeyValueStore
from common.djangoapps.util.monitoring import monitor_import_failure from common.djangoapps.util.monitoring import monitor_import_failure
from xmodule.error_block import ErrorBlock from xmodule.error_block import ErrorBlock
from xmodule.errortracker import exc_info_to_str, make_error_tracker from xmodule.errortracker import exc_info_to_str, make_error_tracker
from xmodule.mako_block import MakoDescriptorSystem
from xmodule.modulestore import COURSE_ROOT, LIBRARY_ROOT, ModuleStoreEnum, ModuleStoreReadBase from xmodule.modulestore import COURSE_ROOT, LIBRARY_ROOT, ModuleStoreEnum, ModuleStoreReadBase
from xmodule.modulestore.xml_exporter import DEFAULT_CONTENT_FIELDS from xmodule.modulestore.xml_exporter import DEFAULT_CONTENT_FIELDS
from xmodule.tabs import CourseTabList from xmodule.tabs import CourseTabList
from xmodule.x_module import ( # lint-amnesty, pylint: disable=unused-import from xmodule.x_module import (
AsideKeyGenerator, AsideKeyGenerator,
OpaqueKeyReader, OpaqueKeyReader,
XMLParsingSystem, ModuleStoreRuntime,
policy_key policy_key
) )
@@ -46,12 +51,129 @@ etree.set_default_parser(edx_xml_parser)
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class ImportSystem(XMLParsingSystem, MakoDescriptorSystem): # lint-amnesty, pylint: disable=abstract-method, missing-class-docstring class XMLParsingModuleStoreRuntime(ModuleStoreRuntime):
"""
ModuleStoreRuntime with some tweaks for XML processing.
"""
def __init__(self, process_xml, **kwargs):
"""
process_xml: Takes an xml string, and returns a XModuleDescriptor
created from that xml
"""
super().__init__(**kwargs)
self.process_xml = process_xml
def _usage_id_from_node(self, node, parent_id):
"""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
Returns:
UsageKey: the usage key for the new xblock
"""
return self.xblock_from_node(node, parent_id, self.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:
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))
aside_children = self.parse_asides(node, def_id, usage_id, id_generator)
asides_tags = [x.tag for x in aside_children]
block = block_class.parse_xml(node, self, keys)
self._convert_reference_fields_to_keys(block) # difference from XBlock.runtime
block.parent = parent_id
block.save()
asides = self.get_asides(block)
for asd in asides:
if asd.scope_ids.block_type in asides_tags:
block.add_aside(asd)
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)
node.remove(child)
return aside_children
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
usage_key = UsageKey.from_string(value)
return usage_key.map_into_course(course_key)
def _convert_reference_fields_to_keys(self, xblock):
"""
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.values():
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.items():
assert isinstance(subvalue, str)
field_value[key] = self._make_usage_key(course_key, subvalue)
setattr(xblock, field.name, field_value)
class XMLImportingModuleStoreRuntime(XMLParsingModuleStoreRuntime): # pylint: disable=abstract-method
"""
A runtime for importing OLX into ModuleStore.
"""
def __init__(self, xmlstore, course_id, course_dir, # lint-amnesty, pylint: disable=too-many-statements def __init__(self, xmlstore, course_id, course_dir, # lint-amnesty, pylint: disable=too-many-statements
error_tracker, error_tracker,
load_error_blocks=True, target_course_id=None, **kwargs): load_error_blocks=True, target_course_id=None, **kwargs):
""" """
A class that handles loading from xml. Does some munging to ensure that A class that handles loading from xml to ModuleStore. Does some munging to ensure that
all elements have unique slugs. all elements have unique slugs.
xmlstore: the XMLModuleStore to store the loaded blocks in xmlstore: the XMLModuleStore to store the loaded blocks in
@@ -257,7 +379,7 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem): # lint-amnesty, pyl
) )
return super().construct_xblock_from_class(cls, scope_ids, field_data, *args, **kwargs) return super().construct_xblock_from_class(cls, scope_ids, field_data, *args, **kwargs)
# id_generator is ignored, because each ImportSystem is already local to # id_generator is ignored, because each XMLImportingModuleStoreRuntime is already local to
# a course, and has it's own id_generator already in place # a course, and has it's own id_generator already in place
def add_node_as_child(self, block, node): # lint-amnesty, pylint: disable=signature-differs def add_node_as_child(self, block, node): # lint-amnesty, pylint: disable=signature-differs
child_block = self.process_xml(etree.tostring(node)) child_block = self.process_xml(etree.tostring(node))
@@ -532,7 +654,7 @@ class XMLModuleStore(ModuleStoreReadBase):
if self.user_service: if self.user_service:
services['user'] = self.user_service services['user'] = self.user_service
system = ImportSystem( system = XMLImportingModuleStoreRuntime(
xmlstore=self, xmlstore=self,
course_id=course_id, course_id=course_id,
course_dir=course_dir, course_dir=course_dir,

View File

@@ -52,7 +52,7 @@ from xmodule.modulestore.django import ASSET_IGNORE_REGEX
from xmodule.modulestore.exceptions import DuplicateCourseError from xmodule.modulestore.exceptions import DuplicateCourseError
from xmodule.modulestore.mongo.base import MongoRevisionKey from xmodule.modulestore.mongo.base import MongoRevisionKey
from xmodule.modulestore.store_utilities import draft_node_constructor, get_draft_subtree_roots from xmodule.modulestore.store_utilities import draft_node_constructor, get_draft_subtree_roots
from xmodule.modulestore.xml import ImportSystem, LibraryXMLModuleStore, XMLModuleStore from xmodule.modulestore.xml import XMLImportingModuleStoreRuntime, LibraryXMLModuleStore, XMLModuleStore
from xmodule.tabs import CourseTabList from xmodule.tabs import CourseTabList
from xmodule.util.misc import escape_invalid_characters from xmodule.util.misc import escape_invalid_characters
from xmodule.x_module import XModuleMixin from xmodule.x_module import XModuleMixin
@@ -993,8 +993,8 @@ def _import_course_draft(
# create a new 'System' object which will manage the importing # create a new 'System' object which will manage the importing
errorlog = make_error_tracker() errorlog = make_error_tracker()
# The course_dir as passed to ImportSystem is expected to just be relative, not # The course_dir as passed to XMLImportingModuleStoreRuntime is expected to just be relative, not
# the complete path including data_dir. ImportSystem will concatenate the two together. # the complete path including data_dir. XMLImportingModuleStoreRuntime will concatenate the two together.
data_dir = xml_module_store.data_dir data_dir = xml_module_store.data_dir
# Whether or not data_dir ends with a "/" differs in production vs. test. # Whether or not data_dir ends with a "/" differs in production vs. test.
if not data_dir.endswith("/"): if not data_dir.endswith("/"):
@@ -1002,7 +1002,7 @@ def _import_course_draft(
# Remove absolute path, leaving relative <course_name>/drafts. # Remove absolute path, leaving relative <course_name>/drafts.
draft_course_dir = draft_dir.replace(data_dir, '', 1) draft_course_dir = draft_dir.replace(data_dir, '', 1)
system = ImportSystem( system = XMLImportingModuleStoreRuntime(
xmlstore=xml_module_store, xmlstore=xml_module_store,
course_id=source_course_id, course_id=source_course_id,
course_dir=draft_course_dir, course_dir=draft_course_dir,

View File

@@ -24,14 +24,13 @@ from xblock.fields import Reference, ReferenceList, ReferenceValueDict, ScopeIds
from xmodule.capa.xqueue_interface import XQueueService from xmodule.capa.xqueue_interface import XQueueService
from xmodule.assetstore import AssetMetadata from xmodule.assetstore import AssetMetadata
from xmodule.contentstore.django import contentstore from xmodule.contentstore.django import contentstore
from xmodule.mako_block import MakoDescriptorSystem
from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.draft_and_published import ModuleStoreDraftAndPublished from xmodule.modulestore.draft_and_published import ModuleStoreDraftAndPublished
from xmodule.modulestore.inheritance import InheritanceMixin from xmodule.modulestore.inheritance import InheritanceMixin
from xmodule.modulestore.xml import CourseLocationManager from xmodule.modulestore.xml import CourseLocationManager
from xmodule.tests.helpers import StubReplaceURLService, mock_render_template, StubMakoService, StubUserService from xmodule.tests.helpers import StubReplaceURLService, mock_render_template, StubMakoService, StubUserService
from xmodule.util.sandboxing import SandboxService from xmodule.util.sandboxing import SandboxService
from xmodule.x_module import DoNothingCache, XModuleMixin from xmodule.x_module import DoNothingCache, XModuleMixin, ModuleStoreRuntime
from openedx.core.lib.cache_utils import CacheService from openedx.core.lib.cache_utils import CacheService
@@ -64,12 +63,12 @@ def get_asides(block):
@property @property
def resources_fs(): def resources_fs():
return Mock(name='TestDescriptorSystem.resources_fs', root_path='.') return Mock(name='TestModuleStoreRuntime.resources_fs', root_path='.')
class TestDescriptorSystem(MakoDescriptorSystem): # pylint: disable=abstract-method class TestModuleStoreRuntime(ModuleStoreRuntime): # pylint: disable=abstract-method
""" """
DescriptorSystem for testing ModuleStore-based XBlock Runtime for testing
""" """
def handler_url(self, block, handler, suffix='', query='', thirdparty=False): # lint-amnesty, pylint: disable=arguments-differ def handler_url(self, block, handler, suffix='', query='', thirdparty=False): # lint-amnesty, pylint: disable=arguments-differ
return '{usage_id}/{handler}{suffix}?{query}'.format( return '{usage_id}/{handler}{suffix}?{query}'.format(
@@ -90,7 +89,7 @@ class TestDescriptorSystem(MakoDescriptorSystem): # pylint: disable=abstract-me
return [] return []
def resources_fs(self): # lint-amnesty, pylint: disable=method-hidden def resources_fs(self): # lint-amnesty, pylint: disable=method-hidden
return Mock(name='TestDescriptorSystem.resources_fs', root_path='.') return Mock(name='TestModuleStoreRuntime.resources_fs', root_path='.')
def __repr__(self): def __repr__(self):
""" """
@@ -117,7 +116,7 @@ def get_test_system(
add_get_block_overrides=False add_get_block_overrides=False
): ):
""" """
Construct a test DescriptorSystem instance. Construct a test ModuleStoreRuntime instance.
By default, the descriptor system's render_template() method simply returns the repr of the By default, the descriptor system's render_template() method simply returns the repr of the
context it is passed. You can override this by passing in a different render_template argument. context it is passed. You can override this by passing in a different render_template argument.
@@ -239,11 +238,11 @@ def prepare_block_runtime(
def get_test_descriptor_system(render_template=None, **kwargs): def get_test_descriptor_system(render_template=None, **kwargs):
""" """
Construct a test DescriptorSystem instance. Construct a test ModuleStoreRuntime instance.
""" """
field_data = DictFieldData({}) field_data = DictFieldData({})
descriptor_system = TestDescriptorSystem( descriptor_system = TestModuleStoreRuntime(
load_item=Mock(name='get_test_descriptor_system.load_item'), load_item=Mock(name='get_test_descriptor_system.load_item'),
resources_fs=Mock(name='get_test_descriptor_system.resources_fs'), resources_fs=Mock(name='get_test_descriptor_system.resources_fs'),
error_tracker=Mock(name='get_test_descriptor_system.error_tracker'), error_tracker=Mock(name='get_test_descriptor_system.error_tracker'),

View File

@@ -9,7 +9,7 @@ import pprint
import pytest import pytest
from path import Path as path from path import Path as path
from xblock.reference.user_service import UserService, XBlockUser from xblock.reference.user_service import UserService, XBlockUser
from xmodule.x_module import DescriptorSystem from xmodule.x_module import ModuleStoreRuntime
def directories_equal(directory1, directory2): def directories_equal(directory1, directory2):
@@ -132,7 +132,7 @@ class StubReplaceURLService:
@pytest.fixture @pytest.fixture
def override_descriptor_system(monkeypatch): def override_descriptor_system(monkeypatch):
""" """
Fixture to override get_block method of DescriptorSystem Fixture to override get_block method of ModuleStoreRuntime
""" """
def get_block(self, usage_id, for_parent=None): def get_block(self, usage_id, for_parent=None):
@@ -140,4 +140,4 @@ def override_descriptor_system(monkeypatch):
block = self.load_item(usage_id, for_parent=for_parent) block = self.load_item(usage_id, for_parent=for_parent)
return block return block
monkeypatch.setattr(DescriptorSystem, "get_block", get_block) monkeypatch.setattr(ModuleStoreRuntime, "get_block", get_block)

View File

@@ -15,7 +15,7 @@ from xblock.fields import ScopeIds
from xmodule.conditional_block import ConditionalBlock from xmodule.conditional_block import ConditionalBlock
from xmodule.error_block import ErrorBlock from xmodule.error_block import ErrorBlock
from xmodule.modulestore.xml import CourseLocationManager, ImportSystem, XMLModuleStore from xmodule.modulestore.xml import CourseLocationManager, XMLImportingModuleStoreRuntime, XMLModuleStore
from xmodule.tests import DATA_DIR, get_test_system, prepare_block_runtime from xmodule.tests import DATA_DIR, get_test_system, prepare_block_runtime
from xmodule.tests.xml import XModuleXmlImportTest from xmodule.tests.xml import XModuleXmlImportTest
from xmodule.tests.xml import factories as xml from xmodule.tests.xml import factories as xml
@@ -26,7 +26,10 @@ ORG = 'test_org'
COURSE = 'conditional' # name of directory with course data COURSE = 'conditional' # name of directory with course data
class DummySystem(ImportSystem): # lint-amnesty, pylint: disable=abstract-method, missing-class-docstring class DummyModuleStoreRuntime(XMLImportingModuleStoreRuntime): # pylint: disable=abstract-method
"""
Minimal modulestore runtime for tests
"""
@patch('xmodule.modulestore.xml.OSFS', lambda directory: MemoryFS()) @patch('xmodule.modulestore.xml.OSFS', lambda directory: MemoryFS())
def __init__(self, load_error_blocks): def __init__(self, load_error_blocks):

View File

@@ -21,7 +21,7 @@ from openedx.core.lib.teams_config import TeamsConfig, DEFAULT_COURSE_RUN_MAX_TE
import xmodule.course_block import xmodule.course_block
from xmodule.course_metadata_utils import DEFAULT_START_DATE from xmodule.course_metadata_utils import DEFAULT_START_DATE
from xmodule.data import CertificatesDisplayBehaviors from xmodule.data import CertificatesDisplayBehaviors
from xmodule.modulestore.xml import ImportSystem, XMLModuleStore from xmodule.modulestore.xml import XMLImportingModuleStoreRuntime, XMLModuleStore
from xmodule.modulestore.exceptions import InvalidProctoringProvider from xmodule.modulestore.exceptions import InvalidProctoringProvider
ORG = 'test_org' ORG = 'test_org'
@@ -52,7 +52,10 @@ class CourseFieldsTestCase(unittest.TestCase): # lint-amnesty, pylint: disable=
assert xmodule.course_block.CourseFields.enrollment_start.default == expected assert xmodule.course_block.CourseFields.enrollment_start.default == expected
class DummySystem(ImportSystem): # lint-amnesty, pylint: disable=abstract-method, missing-class-docstring class DummyModuleStoreRuntime(XMLImportingModuleStoreRuntime): # pylint: disable=abstract-method
"""
Minimal modulestore runtime for tests.
"""
@patch('xmodule.modulestore.xml.OSFS', lambda dir: MemoryFS()) @patch('xmodule.modulestore.xml.OSFS', lambda dir: MemoryFS())
def __init__(self, load_error_blocks, course_id=None): def __init__(self, load_error_blocks, course_id=None):
@@ -83,7 +86,7 @@ def get_dummy_course(
): ):
"""Get a dummy course""" """Get a dummy course"""
system = DummySystem(load_error_blocks=True) system = DummyModuleStoreRuntime(load_error_blocks=True)
def to_attrb(n, v): def to_attrb(n, v):
return '' if v is None else f'{n}="{v}"'.lower() return '' if v is None else f'{n}="{v}"'.lower()
@@ -126,7 +129,7 @@ class HasEndedMayCertifyTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
system = DummySystem(load_error_blocks=True) # lint-amnesty, pylint: disable=unused-variable system = DummyModuleStoreRuntime(load_error_blocks=True) # lint-amnesty, pylint: disable=unused-variable
past_end = (datetime.now() - timedelta(days=12)).strftime("%Y-%m-%dT%H:%M:00") past_end = (datetime.now() - timedelta(days=12)).strftime("%Y-%m-%dT%H:%M:00")
future_end = (datetime.now() + timedelta(days=12)).strftime("%Y-%m-%dT%H:%M:00") future_end = (datetime.now() + timedelta(days=12)).strftime("%Y-%m-%dT%H:%M:00")

View File

@@ -18,7 +18,7 @@ from xblock.runtime import DictKeyValueStore, KvsFieldData
from xmodule.fields import Date from xmodule.fields import Date
from xmodule.modulestore.inheritance import InheritanceMixin, compute_inherited_metadata from xmodule.modulestore.inheritance import InheritanceMixin, compute_inherited_metadata
from xmodule.modulestore.xml import ImportSystem, LibraryXMLModuleStore, XMLModuleStore from xmodule.modulestore.xml import XMLImportingModuleStoreRuntime, LibraryXMLModuleStore, XMLModuleStore
from xmodule.tests import DATA_DIR from xmodule.tests import DATA_DIR
from xmodule.x_module import XModuleMixin from xmodule.x_module import XModuleMixin
from xmodule.xml_block import is_pointer_tag from xmodule.xml_block import is_pointer_tag
@@ -28,7 +28,10 @@ COURSE = 'test_course'
RUN = 'test_run' RUN = 'test_run'
class DummySystem(ImportSystem): # lint-amnesty, pylint: disable=abstract-method, missing-class-docstring class DummyModuleStoreRuntime(XMLImportingModuleStoreRuntime): # pylint: disable=abstract-method, missing-class-docstring
"""
Minimal modulestore runtime for tests
"""
@patch('xmodule.modulestore.xml.OSFS', lambda dir: OSFS(mkdtemp())) @patch('xmodule.modulestore.xml.OSFS', lambda dir: OSFS(mkdtemp()))
def __init__(self, load_error_blocks, library=False): def __init__(self, load_error_blocks, library=False):
@@ -58,7 +61,7 @@ class BaseCourseTestCase(TestCase):
@staticmethod @staticmethod
def get_system(load_error_blocks=True, library=False): def get_system(load_error_blocks=True, library=False):
'''Get a dummy system''' '''Get a dummy system'''
return DummySystem(load_error_blocks, library=library) return DummyModuleStoreRuntime(load_error_blocks, library=library)
def get_course(self, name): def get_course(self, name):
"""Get a test course by directory name. If there's more than one, error.""" """Get a test course by directory name. If there's more than one, error."""

View File

@@ -21,7 +21,7 @@ from xmodule.capa_block import ProblemBlock
from common.djangoapps.student.tests.factories import UserFactory from common.djangoapps.student.tests.factories import UserFactory
from ..item_bank_block import ItemBankBlock from ..item_bank_block import ItemBankBlock
from .test_course_block import DummySystem as TestImportSystem from .test_course_block import DummyModuleStoreRuntime
dummy_render = lambda block, _: Fragment(block.data) # pylint: disable=invalid-name dummy_render = lambda block, _: Fragment(block.data) # pylint: disable=invalid-name
@@ -121,7 +121,7 @@ class TestItemBankForCms(ItemBankTestBase):
olx_element = etree.fromstring(actual_olx_export) olx_element = etree.fromstring(actual_olx_export)
# Re-import the OLX. # Re-import the OLX.
runtime = TestImportSystem(load_error_blocks=True, course_id=self.item_bank.context_key) runtime = DummyModuleStoreRuntime(load_error_blocks=True, course_id=self.item_bank.context_key)
runtime.resources_fs = export_fs runtime.resources_fs = export_fs
imported_item_bank = ItemBankBlock.parse_xml(olx_element, runtime, None) imported_item_bank = ItemBankBlock.parse_xml(olx_element, runtime, None)
@@ -168,11 +168,11 @@ class TestItemBankForCms(ItemBankTestBase):
assert self.item_bank.validate() assert self.item_bank.validate()
@patch( @patch(
'xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.render', 'xmodule.modulestore.split_mongo.runtime.SplitModuleStoreRuntime.render',
VanillaRuntime.render, VanillaRuntime.render,
) )
@patch('xmodule.capa_block.ProblemBlock.author_view', dummy_render, create=True) @patch('xmodule.capa_block.ProblemBlock.author_view', dummy_render, create=True)
@patch('xmodule.x_module.DescriptorSystem.applicable_aside_types', lambda self, block: []) @patch('xmodule.x_module.ModuleStoreRuntime.applicable_aside_types', lambda self, block: [])
def test_preview_view(self): def test_preview_view(self):
""" Test preview view rendering """ """ Test preview view rendering """
self._bind_course_block(self.item_bank) self._bind_course_block(self.item_bank)
@@ -183,11 +183,11 @@ class TestItemBankForCms(ItemBankTestBase):
assert '<p>Hello world from problem 3</p>' in rendered.content assert '<p>Hello world from problem 3</p>' in rendered.content
@patch( @patch(
'xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.render', 'xmodule.modulestore.split_mongo.runtime.SplitModuleStoreRuntime.render',
VanillaRuntime.render, VanillaRuntime.render,
) )
@patch('xmodule.capa_block.ProblemBlock.author_view', dummy_render, create=True) @patch('xmodule.capa_block.ProblemBlock.author_view', dummy_render, create=True)
@patch('xmodule.x_module.DescriptorSystem.applicable_aside_types', lambda self, block: []) @patch('xmodule.x_module.ModuleStoreRuntime.applicable_aside_types', lambda self, block: [])
def test_author_view(self): def test_author_view(self):
""" Test author view rendering """ """ Test author view rendering """
self._bind_course_block(self.item_bank) self._bind_course_block(self.item_bank)

View File

@@ -27,7 +27,7 @@ from xmodule.tests import prepare_block_runtime
from xmodule.validation import StudioValidationMessage from xmodule.validation import StudioValidationMessage
from xmodule.x_module import AUTHOR_VIEW from xmodule.x_module import AUTHOR_VIEW
from .test_course_block import DummySystem as TestImportSystem from .test_course_block import DummyModuleStoreRuntime
dummy_render = lambda block, _: Fragment(block.data) # pylint: disable=invalid-name dummy_render = lambda block, _: Fragment(block.data) # pylint: disable=invalid-name
@@ -167,7 +167,7 @@ class TestLibraryContentExportImport(LegacyLibraryContentTest):
self.lc_block.runtime.export_fs = self.export_fs # pylint: disable=protected-access self.lc_block.runtime.export_fs = self.export_fs # pylint: disable=protected-access
# Prepare runtime for the import. # Prepare runtime for the import.
self.runtime = TestImportSystem(load_error_blocks=True, course_id=self.lc_block.location.course_key) self.runtime = DummyModuleStoreRuntime(load_error_blocks=True, course_id=self.lc_block.location.course_key)
self.runtime.resources_fs = self.export_fs self.runtime.resources_fs = self.export_fs
self.id_generator = Mock() self.id_generator = Mock()
@@ -521,10 +521,10 @@ class TestLegacyLibraryContentBlockWithSearchIndex(LegacyLibraryContentBlockTest
@patch( @patch(
'xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.render', VanillaRuntime.render 'xmodule.modulestore.split_mongo.runtime.SplitModuleStoreRuntime.render', VanillaRuntime.render
) )
@patch('xmodule.html_block.HtmlBlock.author_view', dummy_render, create=True) @patch('xmodule.html_block.HtmlBlock.author_view', dummy_render, create=True)
@patch('xmodule.x_module.DescriptorSystem.applicable_aside_types', lambda self, block: []) @patch('xmodule.x_module.ModuleStoreRuntime.applicable_aside_types', lambda self, block: [])
class TestLibraryContentRender(LegacyLibraryContentTest): class TestLibraryContentRender(LegacyLibraryContentTest):
""" """
Rendering unit tests for LegacyLibraryContentBlock Rendering unit tests for LegacyLibraryContentBlock
@@ -732,10 +732,10 @@ class TestLibraryContentAnalytics(LegacyLibraryContentTest):
@patch( @patch(
'xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.render', VanillaRuntime.render 'xmodule.modulestore.split_mongo.runtime.SplitModuleStoreRuntime.render', VanillaRuntime.render
) )
@patch('xmodule.html_block.HtmlBlock.author_view', dummy_render, create=True) @patch('xmodule.html_block.HtmlBlock.author_view', dummy_render, create=True)
@patch('xmodule.x_module.DescriptorSystem.applicable_aside_types', lambda self, block: []) @patch('xmodule.x_module.ModuleStoreRuntime.applicable_aside_types', lambda self, block: [])
class TestMigratedLibraryContentRender(LegacyLibraryContentTest): class TestMigratedLibraryContentRender(LegacyLibraryContentTest):
""" """
Rendering unit tests for LegacyLibraryContentBlock Rendering unit tests for LegacyLibraryContentBlock
@@ -825,7 +825,7 @@ class TestMigratedLibraryContentRender(LegacyLibraryContentTest):
assert exported_olx == expected_olx_export assert exported_olx == expected_olx_export
# Now import it. # Now import it.
runtime = TestImportSystem(load_error_blocks=True, course_id=self.lc_block.location.course_key) runtime = DummyModuleStoreRuntime(load_error_blocks=True, course_id=self.lc_block.location.course_key)
runtime.resources_fs = export_fs runtime.resources_fs = export_fs
olx_element = etree.fromstring(exported_olx) olx_element = etree.fromstring(exported_olx)
imported_lc_block = LegacyLibraryContentBlock.parse_xml(olx_element, runtime, None) imported_lc_block = LegacyLibraryContentBlock.parse_xml(olx_element, runtime, None)

View File

@@ -15,11 +15,11 @@ dummy_render = lambda block, _: Fragment(block.data) # pylint: disable=invalid-
@patch( @patch(
'xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.render', VanillaRuntime.render 'xmodule.modulestore.split_mongo.runtime.SplitModuleStoreRuntime.render', VanillaRuntime.render
) )
@patch('xmodule.html_block.HtmlBlock.author_view', dummy_render, create=True) @patch('xmodule.html_block.HtmlBlock.author_view', dummy_render, create=True)
@patch('xmodule.html_block.HtmlBlock.has_author_view', True, create=True) @patch('xmodule.html_block.HtmlBlock.has_author_view', True, create=True)
@patch('xmodule.x_module.DescriptorSystem.applicable_aside_types', lambda self, block: []) @patch('xmodule.x_module.ModuleStoreRuntime.applicable_aside_types', lambda self, block: [])
class TestLibraryRoot(MixedSplitTestCase): class TestLibraryRoot(MixedSplitTestCase):
""" """
Basic unit tests for LibraryRoot (library_root_xblock.py) Basic unit tests for LibraryRoot (library_root_xblock.py)

View File

@@ -11,7 +11,7 @@ from django.test import TestCase
from openedx.core.lib.safe_lxml import etree from openedx.core.lib.safe_lxml import etree
from xmodule import poll_block from xmodule import poll_block
from . import get_test_system from . import get_test_system
from .test_import import DummySystem from .test_import import DummyModuleStoreRuntime
class _PollBlockTestBase(TestCase): class _PollBlockTestBase(TestCase):
@@ -67,7 +67,7 @@ class _PollBlockTestBase(TestCase):
Make sure that poll_block will export fine if its xml contains Make sure that poll_block will export fine if its xml contains
unescaped characters. unescaped characters.
""" """
module_system = DummySystem(load_error_blocks=True) module_system = DummyModuleStoreRuntime(load_error_blocks=True)
module_system.id_generator.target_course_id = self.xblock.course_id module_system.id_generator.target_course_id = self.xblock.course_id
sample_poll_xml = ''' sample_poll_xml = '''
<poll_question display_name="Poll Question"> <poll_question display_name="Poll Question">

View File

@@ -11,7 +11,7 @@ from xmodule.modulestore.tests.utils import MixedSplitTestCase
from xmodule.randomize_block import RandomizeBlock from xmodule.randomize_block import RandomizeBlock
from xmodule.tests import prepare_block_runtime from xmodule.tests import prepare_block_runtime
from .test_course_block import DummySystem as TestImportSystem from .test_course_block import DummyModuleStoreRuntime
class RandomizeBlockTest(MixedSplitTestCase): class RandomizeBlockTest(MixedSplitTestCase):
@@ -78,7 +78,7 @@ class RandomizeBlockTest(MixedSplitTestCase):
# And compare. # And compare.
assert exported_olx == expected_olx assert exported_olx == expected_olx
runtime = TestImportSystem(load_error_blocks=True, course_id=randomize_block.location.course_key) runtime = DummyModuleStoreRuntime(load_error_blocks=True, course_id=randomize_block.location.course_key)
runtime.resources_fs = export_fs runtime.resources_fs = export_fs
# Now import it. # Now import it.

View File

@@ -19,7 +19,7 @@ from xmodule.split_test_block import (
user_partition_values, user_partition_values,
) )
from xmodule.tests import prepare_block_runtime from xmodule.tests import prepare_block_runtime
from xmodule.tests.test_course_block import DummySystem as TestImportSystem from xmodule.tests.test_course_block import DummyModuleStoreRuntime
from xmodule.tests.xml import XModuleXmlImportTest from xmodule.tests.xml import XModuleXmlImportTest
from xmodule.tests.xml import factories as xml from xmodule.tests.xml import factories as xml
from xmodule.validation import StudioValidationMessage from xmodule.validation import StudioValidationMessage
@@ -581,7 +581,7 @@ class SplitTestBlockExportImportTest(MixedSplitTestCase):
# And compare. # And compare.
assert exported_olx == expected_olx assert exported_olx == expected_olx
runtime = TestImportSystem(load_error_blocks=True, course_id=split_test_block.location.course_key) runtime = DummyModuleStoreRuntime(load_error_blocks=True, course_id=split_test_block.location.course_key)
runtime.resources_fs = export_fs runtime.resources_fs = export_fs
# Now import it. # Now import it.

View File

@@ -40,7 +40,7 @@ from xmodule.video_block.transcripts_utils import save_to_store
from xblock.core import XBlockAside from xblock.core import XBlockAside
from xmodule.modulestore.tests.test_asides import AsideTestType from xmodule.modulestore.tests.test_asides import AsideTestType
from .test_import import DummySystem from .test_import import DummyModuleStoreRuntime
SRT_FILEDATA = ''' SRT_FILEDATA = '''
0 0
@@ -283,7 +283,7 @@ class VideoBlockImportTestCase(TestCase):
}) })
def test_parse_xml(self): def test_parse_xml(self):
module_system = DummySystem(load_error_blocks=True) module_system = DummyModuleStoreRuntime(load_error_blocks=True)
xml_data = ''' xml_data = '''
<video display_name="Test Video" <video display_name="Test Video"
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8" youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
@@ -324,7 +324,7 @@ class VideoBlockImportTestCase(TestCase):
@ddt.data(True, False) @ddt.data(True, False)
def test_parse_xml_with_asides(self, video_xml_has_aside, mock_is_pointer_tag, mock_load_file): def test_parse_xml_with_asides(self, video_xml_has_aside, mock_is_pointer_tag, mock_load_file):
"""Test that `parse_xml` parses asides from the video xml""" """Test that `parse_xml` parses asides from the video xml"""
runtime = DummySystem(load_error_blocks=True) runtime = DummyModuleStoreRuntime(load_error_blocks=True)
if video_xml_has_aside: if video_xml_has_aside:
xml_data = ''' xml_data = '''
<video url_name="a16643fa63234fef8f6ebbc1902e2253"> <video url_name="a16643fa63234fef8f6ebbc1902e2253">
@@ -358,7 +358,7 @@ class VideoBlockImportTestCase(TestCase):
""" """
Test that if handout link is course_asset then it will contain targeted course_id in handout link. Test that if handout link is course_asset then it will contain targeted course_id in handout link.
""" """
module_system = DummySystem(load_error_blocks=True) module_system = DummyModuleStoreRuntime(load_error_blocks=True)
course_id = CourseKey.from_string(course_id_string) course_id = CourseKey.from_string(course_id_string)
xml_data = ''' xml_data = '''
<video display_name="Test Video" <video display_name="Test Video"
@@ -401,7 +401,7 @@ class VideoBlockImportTestCase(TestCase):
Ensure that attributes have the right values if they aren't Ensure that attributes have the right values if they aren't
explicitly set in XML. explicitly set in XML.
""" """
module_system = DummySystem(load_error_blocks=True) module_system = DummyModuleStoreRuntime(load_error_blocks=True)
xml_data = ''' xml_data = '''
<video display_name="Test Video" <video display_name="Test Video"
youtube="1.0:p2Q6BrNhdh8,1.25:1EeWXzPdhSA" youtube="1.0:p2Q6BrNhdh8,1.25:1EeWXzPdhSA"
@@ -432,7 +432,7 @@ class VideoBlockImportTestCase(TestCase):
Ensure that attributes have the right values if they aren't Ensure that attributes have the right values if they aren't
explicitly set in XML. explicitly set in XML.
""" """
module_system = DummySystem(load_error_blocks=True) module_system = DummyModuleStoreRuntime(load_error_blocks=True)
xml_data = ''' xml_data = '''
<video display_name="Test Video" <video display_name="Test Video"
youtube="1.0:p2Q6BrNhdh8,1.25:1EeWXzPdhSA" youtube="1.0:p2Q6BrNhdh8,1.25:1EeWXzPdhSA"
@@ -463,7 +463,7 @@ class VideoBlockImportTestCase(TestCase):
""" """
Make sure settings are correct if none are explicitly set in XML. Make sure settings are correct if none are explicitly set in XML.
""" """
module_system = DummySystem(load_error_blocks=True) module_system = DummyModuleStoreRuntime(load_error_blocks=True)
xml_data = '<video></video>' xml_data = '<video></video>'
xml_object = etree.fromstring(xml_data) xml_object = etree.fromstring(xml_data)
output = VideoBlock.parse_xml(xml_object, module_system, None) output = VideoBlock.parse_xml(xml_object, module_system, None)
@@ -489,7 +489,7 @@ class VideoBlockImportTestCase(TestCase):
Make sure we can handle the double-quoted string format (which was used for exporting for Make sure we can handle the double-quoted string format (which was used for exporting for
a few weeks). a few weeks).
""" """
module_system = DummySystem(load_error_blocks=True) module_system = DummyModuleStoreRuntime(load_error_blocks=True)
xml_data = ''' xml_data = '''
<video display_name="&quot;display_name&quot;" <video display_name="&quot;display_name&quot;"
html5_sources="[&quot;source_1&quot;, &quot;source_2&quot;]" html5_sources="[&quot;source_1&quot;, &quot;source_2&quot;]"
@@ -524,7 +524,7 @@ class VideoBlockImportTestCase(TestCase):
}) })
def test_parse_xml_double_quote_concatenated_youtube(self): def test_parse_xml_double_quote_concatenated_youtube(self):
module_system = DummySystem(load_error_blocks=True) module_system = DummyModuleStoreRuntime(load_error_blocks=True)
xml_data = ''' xml_data = '''
<video display_name="Test Video" <video display_name="Test Video"
youtube="1.0:&quot;p2Q6BrNhdh8&quot;,1.25:&quot;1EeWXzPdhSA&quot;"> youtube="1.0:&quot;p2Q6BrNhdh8&quot;,1.25:&quot;1EeWXzPdhSA&quot;">
@@ -552,7 +552,7 @@ class VideoBlockImportTestCase(TestCase):
""" """
Test backwards compatibility with VideoBlock's XML format. Test backwards compatibility with VideoBlock's XML format.
""" """
module_system = DummySystem(load_error_blocks=True) module_system = DummyModuleStoreRuntime(load_error_blocks=True)
xml_data = """ xml_data = """
<video display_name="Test Video" <video display_name="Test Video"
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8" youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
@@ -584,7 +584,7 @@ class VideoBlockImportTestCase(TestCase):
""" """
Ensure that Video is able to read VideoBlock's model data. Ensure that Video is able to read VideoBlock's model data.
""" """
module_system = DummySystem(load_error_blocks=True) module_system = DummyModuleStoreRuntime(load_error_blocks=True)
xml_data = """ xml_data = """
<video display_name="Test Video" <video display_name="Test Video"
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8" youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
@@ -615,7 +615,7 @@ class VideoBlockImportTestCase(TestCase):
""" """
Ensure that Video is able to read VideoBlock's model data. Ensure that Video is able to read VideoBlock's model data.
""" """
module_system = DummySystem(load_error_blocks=True) module_system = DummyModuleStoreRuntime(load_error_blocks=True)
xml_data = """ xml_data = """
<video display_name="Test Video" <video display_name="Test Video"
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8" youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
@@ -660,7 +660,7 @@ class VideoBlockImportTestCase(TestCase):
edx_video_id = 'test_edx_video_id' edx_video_id = 'test_edx_video_id'
mock_val_api.import_from_xml = Mock(wraps=mock_val_import) mock_val_api.import_from_xml = Mock(wraps=mock_val_import)
module_system = DummySystem(load_error_blocks=True) module_system = DummyModuleStoreRuntime(load_error_blocks=True)
# Create static directory in import file system and place transcript files inside it. # Create static directory in import file system and place transcript files inside it.
module_system.resources_fs.makedirs(EXPORT_IMPORT_STATIC_DIR, recreate=True) module_system.resources_fs.makedirs(EXPORT_IMPORT_STATIC_DIR, recreate=True)
@@ -691,7 +691,7 @@ class VideoBlockImportTestCase(TestCase):
def test_import_val_data_invalid(self, mock_val_api): def test_import_val_data_invalid(self, mock_val_api):
mock_val_api.ValCannotCreateError = _MockValCannotCreateError mock_val_api.ValCannotCreateError = _MockValCannotCreateError
mock_val_api.import_from_xml = Mock(side_effect=mock_val_api.ValCannotCreateError) mock_val_api.import_from_xml = Mock(side_effect=mock_val_api.ValCannotCreateError)
module_system = DummySystem(load_error_blocks=True) module_system = DummyModuleStoreRuntime(load_error_blocks=True)
# Negative duration is invalid # Negative duration is invalid
xml_data = """ xml_data = """

View File

@@ -11,14 +11,13 @@ from lxml import etree
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
from xblock.runtime import DictKeyValueStore, KvsFieldData from xblock.runtime import DictKeyValueStore, KvsFieldData
from xmodule.mako_block import MakoDescriptorSystem from xmodule.modulestore.xml import XMLParsingModuleStoreRuntime, CourseLocationManager
from xmodule.modulestore.xml import CourseLocationManager from xmodule.x_module import policy_key
from xmodule.x_module import XMLParsingSystem, policy_key
class InMemorySystem(XMLParsingSystem, MakoDescriptorSystem): # pylint: disable=abstract-method class InMemoryModuleStoreRuntime(XMLParsingModuleStoreRuntime): # pylint: disable=abstract-method
""" """
The simplest possible XMLParsingSystem The simplest possible ModuleStoreRuntime
""" """
def __init__(self, xml_import_data): def __init__(self, xml_import_data):
self.course_id = CourseKey.from_string(xml_import_data.course_id) self.course_id = CourseKey.from_string(xml_import_data.course_id)
@@ -63,5 +62,5 @@ class XModuleXmlImportTest(TestCase):
@classmethod @classmethod
def process_xml(cls, xml_import_data): def process_xml(cls, xml_import_data):
"""Use the `xml_import_data` to import an :class:`XBlock` from XML.""" """Use the `xml_import_data` to import an :class:`XBlock` from XML."""
system = InMemorySystem(xml_import_data) system = InMemoryModuleStoreRuntime(xml_import_data)
return system.process_xml(xml_import_data.xml_string) return system.process_xml(xml_import_data.xml_string)

View File

@@ -17,17 +17,13 @@ from opaque_keys.edx.keys import UsageKey
from web_fragments.fragment import Fragment from web_fragments.fragment import Fragment
from webob import Response from webob import Response
from webob.multidict import MultiDict from webob.multidict import MultiDict
from xblock.core import XBlock, XBlockAside from xblock.core import XBlock
from xblock.fields import ( from xblock.fields import (
Dict, Dict,
Float, Float,
Integer, Integer,
List, List,
Reference,
ReferenceList,
ReferenceValueDict,
Scope, Scope,
ScopeIds,
String, String,
UserScope UserScope
) )
@@ -267,17 +263,17 @@ class XModuleMixin(XModuleFields, XBlock):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self._asides = [] self._asides = []
# Initialization data used by CachingDescriptorSystem to defer FieldData initialization # Initialization data used by SplitModuleStoreRuntime to defer FieldData initialization
self._cds_init_args = kwargs.pop("cds_init_args", None) self._cds_init_args = kwargs.pop("cds_init_args", None)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def get_cds_init_args(self): def get_cds_init_args(self):
""" Get initialization data used by CachingDescriptorSystem to defer FieldData initialization """ """ Get initialization data used by SplitModuleStoreRuntime to defer FieldData initialization """
if self._cds_init_args is None: if self._cds_init_args is None:
raise KeyError("cds_init_args was not provided for this XBlock") raise KeyError("cds_init_args was not provided for this XBlock")
if self._cds_init_args is False: if self._cds_init_args is False:
raise RuntimeError("Tried to get CachingDescriptorSystem cds_init_args twice for the same XBlock.") raise RuntimeError("Tried to get SplitModuleStoreRuntime cds_init_args twice for the same XBlock.")
args = self._cds_init_args args = self._cds_init_args
# Free the memory and set this False to flag any double-access bugs. This only needs to be read once. # Free the memory and set this False to flag any double-access bugs. This only needs to be read once.
self._cds_init_args = False self._cds_init_args = False
@@ -617,8 +613,8 @@ class XModuleMixin(XModuleFields, XBlock):
wrapped_field_data = wrapper(wrapped_field_data) wrapped_field_data = wrapper(wrapped_field_data)
self._bound_field_data = wrapped_field_data self._bound_field_data = wrapped_field_data
if getattr(self.runtime, "uses_deprecated_field_data", False): if getattr(self.runtime, "uses_deprecated_field_data", False):
# This approach is deprecated but old mongo's CachingDescriptorSystem still requires it. # This approach is deprecated but OldModuleStoreRuntime still requires it.
# For Split mongo's CachingDescriptor system, don't set ._field_data this way. # For SplitModuleStoreRuntime, don't set ._field_data this way.
self._field_data = wrapped_field_data self._field_data = wrapped_field_data
@property @property
@@ -919,7 +915,7 @@ class ResourceTemplates:
return cls._load_template(abs_path, template_id) return cls._load_template(abs_path, template_id)
class ConfigurableFragmentWrapper: class _ConfigurableFragmentWrapper:
""" """
Runtime mixin that allows for composition of many `wrap_xblock` wrappers Runtime mixin that allows for composition of many `wrap_xblock` wrappers
""" """
@@ -985,9 +981,9 @@ def block_global_local_resource_url(block, uri):
raise NotImplementedError("Applications must monkey-patch this function before using local_resource_url for studio_view") # lint-amnesty, pylint: disable=line-too-long raise NotImplementedError("Applications must monkey-patch this function before using local_resource_url for studio_view") # lint-amnesty, pylint: disable=line-too-long
class MetricsMixin: class _MetricsMixin:
""" """
Mixin for adding metric logging for render and handle methods in the DescriptorSystem. Mixin for adding metric logging for render and handle methods in the ModuleStoreRuntime.
""" """
def render(self, block, view_name, context=None): # lint-amnesty, pylint: disable=missing-function-docstring def render(self, block, view_name, context=None): # lint-amnesty, pylint: disable=missing-function-docstring
@@ -1022,7 +1018,7 @@ class MetricsMixin:
) )
class ModuleSystemShim: class _ModuleSystemShim:
""" """
This shim provides the properties formerly available from ModuleSystem which are now being provided by services. This shim provides the properties formerly available from ModuleSystem which are now being provided by services.
@@ -1346,12 +1342,19 @@ class ModuleSystemShim:
self._deprecated_course_id = course_id self._deprecated_course_id = course_id
class DescriptorSystem(MetricsMixin, ConfigurableFragmentWrapper, ModuleSystemShim, Runtime): class ModuleStoreRuntime(_MetricsMixin, _ConfigurableFragmentWrapper, _ModuleSystemShim, Runtime):
""" """
Base class for :class:`Runtime`s to be used with :class:`XModuleDescriptor`s Base class for :class:`Runtime`s to be used with :class:`XBlock`s loaded from ModuleStore.
""" """
def __init__( def __init__(
self, load_item, resources_fs, error_tracker, get_policy=None, disabled_xblock_types=lambda: [], **kwargs self,
load_item,
resources_fs,
error_tracker,
get_policy=None,
render_template=None,
disabled_xblock_types=lambda: [],
**kwargs
): ):
""" """
load_item: Takes a Location and returns an XModuleDescriptor load_item: Takes a Location and returns an XModuleDescriptor
@@ -1385,6 +1388,19 @@ class DescriptorSystem(MetricsMixin, ConfigurableFragmentWrapper, ModuleSystemSh
self.get_policy = get_policy self.get_policy = get_policy
else: else:
self.get_policy = lambda u: {} self.get_policy = lambda u: {}
if render_template:
self.render_template = render_template
# Add the MakoService to the runtime services. If it already exists, do not attempt to reinitialize it;
# otherwise, this could override the `namespace_prefix` of the `MakoService`, breaking template rendering in
# Studio.
#
# This is not needed by most XBlocks, because the MakoService is added to their runtimes. However, there are
# a few cases where the MakoService is not added to the XBlock's runtime. Specifically: * in the Instructor
# Dashboard bulk emails tab, when rendering the HtmlBlock for its WYSIWYG editor. * during testing, when
# fetching factory-created blocks.
if 'mako' not in self._services:
from common.djangoapps.edxmako.services import MakoService
self._services['mako'] = MakoService()
self.disabled_xblock_types = disabled_xblock_types self.disabled_xblock_types = disabled_xblock_types
@@ -1439,7 +1455,7 @@ class DescriptorSystem(MetricsMixin, ConfigurableFragmentWrapper, ModuleSystemSh
return result return result
def handler_url(self, block, handler_name, suffix='', query='', thirdparty=False): def handler_url(self, block, handler_name, suffix='', query='', thirdparty=False):
# When the Modulestore instantiates DescriptorSystems, we will reference a # When the Modulestore instantiates ModuleStoreRuntime, we will reference a
# global function that the application can override, unless a specific function is # global function that the application can override, unless a specific function is
# defined for LMS/CMS through the handler_url_override property. # defined for LMS/CMS through the handler_url_override property.
if getattr(self, 'handler_url_override', None): if getattr(self, 'handler_url_override', None):
@@ -1450,8 +1466,8 @@ class DescriptorSystem(MetricsMixin, ConfigurableFragmentWrapper, ModuleSystemSh
""" """
See :meth:`xblock.runtime.Runtime:local_resource_url` for documentation. See :meth:`xblock.runtime.Runtime:local_resource_url` for documentation.
""" """
# Currently, Modulestore is responsible for instantiating DescriptorSystems # Currently, Modulestore is responsible for instantiating ModuleStoreRuntime
# This means that LMS/CMS don't have a way to define a subclass of DescriptorSystem # This means that LMS/CMS don't have a way to define a subclass of ModuleStoreRuntime
# that implements the correct local_resource_url. So, for now, instead, we will reference a # that implements the correct local_resource_url. So, for now, instead, we will reference a
# global function that the application can override. # global function that the application can override.
return block_global_local_resource_url(block, uri) return block_global_local_resource_url(block, uri)
@@ -1522,120 +1538,6 @@ class DescriptorSystem(MetricsMixin, ConfigurableFragmentWrapper, ModuleSystemSh
return super().layout_asides(block, context, frag, view_name, aside_frag_fns) return super().layout_asides(block, context, frag, view_name, aside_frag_fns)
class XMLParsingSystem(DescriptorSystem): # lint-amnesty, pylint: disable=abstract-method, missing-class-docstring
def __init__(self, process_xml, **kwargs):
"""
process_xml: Takes an xml string, and returns a XModuleDescriptor
created from that xml
"""
super().__init__(**kwargs)
self.process_xml = process_xml
def _usage_id_from_node(self, node, parent_id):
"""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
Returns:
UsageKey: the usage key for the new xblock
"""
return self.xblock_from_node(node, parent_id, self.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))
aside_children = self.parse_asides(node, def_id, usage_id, id_generator)
asides_tags = [x.tag for x in aside_children]
block = block_class.parse_xml(node, self, keys)
self._convert_reference_fields_to_keys(block) # difference from XBlock.runtime
block.parent = parent_id
block.save()
asides = self.get_asides(block)
for asd in asides:
if asd.scope_ids.block_type in asides_tags:
block.add_aside(asd)
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)
node.remove(child)
return aside_children
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
usage_key = UsageKey.from_string(value)
return usage_key.map_into_course(course_key)
def _convert_reference_fields_to_keys(self, xblock):
"""
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.values():
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.items():
assert isinstance(subvalue, str)
field_value[key] = self._make_usage_key(course_key, subvalue)
setattr(xblock, field.name, field_value)
class DoNothingCache: class DoNothingCache:
"""A duck-compatible object to use in ModuleSystemShim when there's no cache.""" """A duck-compatible object to use in ModuleSystemShim when there's no cache."""
def get(self, _key): def get(self, _key):

View File

@@ -304,7 +304,7 @@ class XmlMixin:
Returns (XBlock): The newly parsed XBlock Returns (XBlock): The newly parsed XBlock
""" """
from xmodule.modulestore.xml import ImportSystem # done here to avoid circular import from xmodule.modulestore.xml import XMLImportingModuleStoreRuntime # done here to avoid circular import
if keys is None: if keys is None:
# Passing keys=None is against the XBlock API but some platform tests do it. # Passing keys=None is against the XBlock API but some platform tests do it.
@@ -361,9 +361,10 @@ class XmlMixin:
if "filename" in field_data: if "filename" in field_data:
del field_data["filename"] # filename should only be in xml_attributes. del field_data["filename"] # filename should only be in xml_attributes.
if isinstance(runtime, ImportSystem): if isinstance(runtime, XMLImportingModuleStoreRuntime):
# we shouldn't be instantiating our own field data instance here, but there are complex inter-depenencies # we shouldn't be instantiating our own field data instance here, but there are complex
# between this mixin and ImportSystem that currently seem to require it for proper metadata inheritance. # inter-depenencies between this mixin and XMLImportingModuleStoreRuntime that currently
# seem to require it for proper metadata inheritance.
kvs = InheritanceKeyValueStore(initial_values=field_data) kvs = InheritanceKeyValueStore(initial_values=field_data)
field_data = KvsFieldData(kvs) field_data = KvsFieldData(kvs)
xblock = runtime.construct_xblock_from_class(cls, keys, field_data) xblock = runtime.construct_xblock_from_class(cls, keys, field_data)