Merge pull request #34398 from openedx/xblock2

upgrade xblock==2.0
References:
- https://github.com/openedx/public-engineering/issues/15
- https://github.com/openedx/XBlock/pull/680
This commit is contained in:
Irtaza Akram
2024-04-08 14:30:38 +05:00
committed by GitHub
25 changed files with 92 additions and 99 deletions

View File

@@ -1166,8 +1166,8 @@ class CourseBlock(
return policy_str
@classmethod
def parse_xml(cls, node, runtime, keys, id_generator):
instance = super().parse_xml(node, runtime, keys, id_generator)
def parse_xml(cls, node, runtime, keys):
instance = super().parse_xml(node, runtime, keys)
policy_dir = None
url_name = node.get('url_name')

View File

@@ -233,7 +233,7 @@ class DiscussionXBlock(XBlock, StudioEditableXBlockMixin, XmlMixin): # lint-amn
return {'topic_id': self.discussion_id}
@classmethod
def parse_xml(cls, node, runtime, keys, id_generator):
def parse_xml(cls, node, runtime, keys):
"""
Parses OLX into XBlock.
@@ -246,7 +246,7 @@ class DiscussionXBlock(XBlock, StudioEditableXBlockMixin, XmlMixin): # lint-amn
XBlock.parse_xml. Otherwise this method parses file in "discussion" folder (known as definition_xml), applies
policy.json and updates fields accordingly.
"""
block = super().parse_xml(node, runtime, keys, id_generator)
block = super().parse_xml(node, runtime, keys)
cls._apply_metadata_and_policy(block, node, runtime)

View File

@@ -178,13 +178,13 @@ class ErrorBlock(
return cls._construct(system, xml_data, error_msg, location=id_generator.create_definition('error'))
@classmethod
def parse_xml(cls, node, runtime, keys, id_generator): # lint-amnesty, pylint: disable=unused-argument
def parse_xml(cls, node, runtime, keys): # lint-amnesty, pylint: disable=unused-argument
"""
Interpret the parsed XML in `node`, creating an XModuleDescriptor.
"""
# It'd be great to not reserialize and deserialize the xml
xml = etree.tostring(node).decode('utf-8')
block = cls.from_xml(xml, runtime, id_generator)
block = cls.from_xml(xml, runtime, runtime.id_generator)
return block
def export_to_xml(self, resource_fs):

View File

@@ -259,7 +259,7 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem): # lint-amnesty, pyl
# id_generator is ignored, because each ImportSystem is already local to
# a course, and has it's own id_generator already in place
def add_node_as_child(self, block, node, id_generator): # 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))
block.children.append(child_block.scope_ids.usage_id)

View File

@@ -78,7 +78,7 @@ class RawMixin:
try:
block = super().parse_xml_new_runtime(node, runtime, keys)
except AttributeError:
block = super().parse_xml(node, runtime, keys, id_generator=None)
block = super().parse_xml(node, runtime, keys)
block.data = data_field_value
return block

View File

@@ -130,7 +130,7 @@ class TranslateCustomTagBlock( # pylint: disable=abstract-method
resources_dir = None
@classmethod
def parse_xml(cls, node, runtime, _keys, _id_generator):
def parse_xml(cls, node, runtime, _keys):
"""
Transforms the xml_data from <$custom_tag attr="" attr=""/> to
<customtag attr="" attr="" impl="$custom_tag"/>

View File

@@ -69,7 +69,6 @@ class DiscussionXBlockImportExportTests(TestCase):
self.runtime_mock = mock.Mock(spec=Runtime)
self.runtime_mock.construct_xblock_from_class = mock.Mock(side_effect=self._construct_xblock_mock)
self.runtime_mock.get_policy = mock.Mock(return_value={})
self.id_gen_mock = mock.Mock()
def _construct_xblock_mock(self, cls, keys): # pylint: disable=unused-argument
"""
@@ -102,7 +101,7 @@ class DiscussionXBlockImportExportTests(TestCase):
patched_load_definition_xml.side_effect = Exception("Irrelevant")
block = DiscussionXBlock.parse_xml(node, self.runtime_mock, self.keys, self.id_gen_mock)
block = DiscussionXBlock.parse_xml(node, self.runtime_mock, self.keys)
try:
assert block.discussion_id == id_pair.value
assert block.discussion_category == category_pair.value
@@ -134,7 +133,7 @@ class DiscussionXBlockImportExportTests(TestCase):
patched_load_definition_xml.return_value = (definition_node, "irrelevant")
block = DiscussionXBlock.parse_xml(node, self.runtime_mock, self.keys, self.id_gen_mock)
block = DiscussionXBlock.parse_xml(node, self.runtime_mock, self.keys)
try:
assert block.discussion_id == id_pair.value
assert block.discussion_category == category_pair.value

View File

@@ -195,7 +195,7 @@ class TestLibraryContentExportImport(LibraryContentTest):
# Now import it.
olx_element = etree.fromstring(exported_olx)
imported_lc_block = LibraryContentBlock.parse_xml(olx_element, self.runtime, None, self.id_generator)
imported_lc_block = LibraryContentBlock.parse_xml(olx_element, self.runtime, None)
self._verify_xblock_properties(imported_lc_block)
@@ -219,7 +219,7 @@ class TestLibraryContentExportImport(LibraryContentTest):
# Import the olx.
olx_element = etree.fromstring(olx_with_comments)
imported_lc_block = LibraryContentBlock.parse_xml(olx_element, self.runtime, None, self.id_generator)
imported_lc_block = LibraryContentBlock.parse_xml(olx_element, self.runtime, None)
self._verify_xblock_properties(imported_lc_block)

View File

@@ -2,7 +2,6 @@
import json
import unittest
from unittest.mock import Mock
from opaque_keys.edx.keys import CourseKey
from xblock.field_data import DictFieldData
@@ -62,8 +61,7 @@ class PollBlockTest(unittest.TestCase):
unescaped characters.
"""
module_system = DummySystem(load_error_blocks=True)
id_generator = Mock()
id_generator.target_course_id = self.xblock.course_id
module_system.id_generator.target_course_id = self.xblock.course_id
sample_poll_xml = '''
<poll_question display_name="Poll Question">
<p>How old are you?</p>
@@ -72,7 +70,7 @@ class PollBlockTest(unittest.TestCase):
'''
node = etree.fromstring(sample_poll_xml)
output = PollBlock.parse_xml(node, module_system, self.scope_ids, id_generator)
output = PollBlock.parse_xml(node, module_system, self.scope_ids)
# Update the answer with invalid character.
invalid_characters_poll_answer = output.answers[0]
# Invalid less-than character.

View File

@@ -83,8 +83,7 @@ class RandomizeBlockTest(MixedSplitTestCase):
# Now import it.
olx_element = etree.fromstring(exported_olx)
id_generator = Mock()
imported_randomize_block = RandomizeBlock.parse_xml(olx_element, runtime, None, id_generator)
imported_randomize_block = RandomizeBlock.parse_xml(olx_element, runtime, None)
# Check the new XBlock has the same properties as the old one.
assert imported_randomize_block.display_name == randomize_block.display_name

View File

@@ -586,8 +586,7 @@ class SplitTestBlockExportImportTest(MixedSplitTestCase):
# Now import it.
olx_element = lxml.etree.fromstring(exported_olx)
id_generator = Mock()
imported_split_test_block = SplitTestBlock.parse_xml(olx_element, runtime, None, id_generator)
imported_split_test_block = SplitTestBlock.parse_xml(olx_element, runtime, None)
# Check the new XBlock has the same properties as the old one.
assert imported_split_test_block.display_name == split_test_block.display_name

View File

@@ -298,7 +298,7 @@ class VideoBlockImportTestCase(TestCase):
</video>
'''
xml_object = etree.fromstring(xml_data)
output = VideoBlock.parse_xml(xml_object, module_system, None, Mock())
output = VideoBlock.parse_xml(xml_object, module_system, None)
self.assert_attributes_equal(output, {
'youtube_id_0_75': 'izygArpw-Qo',
'youtube_id_1_0': 'p2Q6BrNhdh8',
@@ -344,10 +344,9 @@ class VideoBlockImportTestCase(TestCase):
</video>
'''
xml_object = etree.fromstring(xml_data)
id_generator = Mock()
id_generator.target_course_id = course_id
module_system.id_generator.target_course_id = course_id
output = VideoBlock.parse_xml(xml_object, module_system, None, id_generator)
output = VideoBlock.parse_xml(xml_object, module_system, None)
self.assert_attributes_equal(output, {
'youtube_id_0_75': 'izygArpw-Qo',
'youtube_id_1_0': 'p2Q6BrNhdh8',
@@ -379,7 +378,7 @@ class VideoBlockImportTestCase(TestCase):
</video>
'''
xml_object = etree.fromstring(xml_data)
output = VideoBlock.parse_xml(xml_object, module_system, None, Mock())
output = VideoBlock.parse_xml(xml_object, module_system, None)
self.assert_attributes_equal(output, {
'youtube_id_0_75': '',
'youtube_id_1_0': 'p2Q6BrNhdh8',
@@ -411,7 +410,7 @@ class VideoBlockImportTestCase(TestCase):
</video>
'''
xml_object = etree.fromstring(xml_data)
output = VideoBlock.parse_xml(xml_object, module_system, None, Mock())
output = VideoBlock.parse_xml(xml_object, module_system, None)
self.assert_attributes_equal(output, {
'youtube_id_0_75': '',
'youtube_id_1_0': 'p2Q6BrNhdh8',
@@ -435,7 +434,7 @@ class VideoBlockImportTestCase(TestCase):
module_system = DummySystem(load_error_blocks=True)
xml_data = '<video></video>'
xml_object = etree.fromstring(xml_data)
output = VideoBlock.parse_xml(xml_object, module_system, None, Mock())
output = VideoBlock.parse_xml(xml_object, module_system, None)
self.assert_attributes_equal(output, {
'youtube_id_0_75': '',
'youtube_id_1_0': '3_yD_cEKoCk',
@@ -475,7 +474,7 @@ class VideoBlockImportTestCase(TestCase):
/>
'''
xml_object = etree.fromstring(xml_data)
output = VideoBlock.parse_xml(xml_object, module_system, None, Mock())
output = VideoBlock.parse_xml(xml_object, module_system, None)
self.assert_attributes_equal(output, {
'youtube_id_0_75': 'OEoXaMPEzf65',
'youtube_id_1_0': 'OEoXaMPEzf10',
@@ -500,7 +499,7 @@ class VideoBlockImportTestCase(TestCase):
</video>
'''
xml_object = etree.fromstring(xml_data)
output = VideoBlock.parse_xml(xml_object, module_system, None, Mock())
output = VideoBlock.parse_xml(xml_object, module_system, None)
self.assert_attributes_equal(output, {
'youtube_id_0_75': '',
'youtube_id_1_0': 'p2Q6BrNhdh8',
@@ -534,7 +533,7 @@ class VideoBlockImportTestCase(TestCase):
</video>
"""
xml_object = etree.fromstring(xml_data)
output = VideoBlock.parse_xml(xml_object, module_system, None, Mock())
output = VideoBlock.parse_xml(xml_object, module_system, None)
self.assert_attributes_equal(output, {
'youtube_id_0_75': 'izygArpw-Qo',
'youtube_id_1_0': 'p2Q6BrNhdh8',
@@ -565,7 +564,7 @@ class VideoBlockImportTestCase(TestCase):
</video>
"""
xml_object = etree.fromstring(xml_data)
video = VideoBlock.parse_xml(xml_object, module_system, None, Mock())
video = VideoBlock.parse_xml(xml_object, module_system, None)
self.assert_attributes_equal(video, {
'youtube_id_0_75': 'izygArpw-Qo',
'youtube_id_1_0': 'p2Q6BrNhdh8',
@@ -596,7 +595,7 @@ class VideoBlockImportTestCase(TestCase):
</video>
"""
xml_object = etree.fromstring(xml_data)
video = VideoBlock.parse_xml(xml_object, module_system, None, Mock())
video = VideoBlock.parse_xml(xml_object, module_system, None)
self.assert_attributes_equal(video, {
'youtube_id_0_75': 'izygArpw-Qo',
'youtube_id_1_0': 'p2Q6BrNhdh8',
@@ -643,9 +642,8 @@ class VideoBlockImportTestCase(TestCase):
edx_video_id=edx_video_id
)
xml_object = etree.fromstring(xml_data)
id_generator = Mock()
id_generator.target_course_id = 'test_course_id'
video = VideoBlock.parse_xml(xml_object, module_system, None, id_generator)
module_system.id_generator.target_course_id = 'test_course_id'
video = VideoBlock.parse_xml(xml_object, module_system, None)
self.assert_attributes_equal(video, {'edx_video_id': edx_video_id})
mock_val_api.import_from_xml.assert_called_once_with(
@@ -671,7 +669,7 @@ class VideoBlockImportTestCase(TestCase):
"""
xml_object = etree.fromstring(xml_data)
with pytest.raises(mock_val_api.ValCannotCreateError):
VideoBlock.parse_xml(xml_object, module_system, None, Mock())
VideoBlock.parse_xml(xml_object, module_system, None)
class VideoExportTestCase(VideoBlockTestBase):

View File

@@ -41,8 +41,8 @@ class WordCloudBlockTest(TestCase):
)
olx_element = etree.fromstring(original_xml)
id_generator = Mock()
block = WordCloudBlock.parse_xml(olx_element, runtime, None, id_generator)
runtime.id_generator = Mock()
block = WordCloudBlock.parse_xml(olx_element, runtime, None)
block.location = BlockUsageLocator(
CourseLocator('org', 'course', 'run', branch='revision'), 'word_cloud', 'block_id'
)

View File

@@ -40,6 +40,7 @@ class InMemorySystem(XMLParsingSystem, MakoDescriptorSystem): # pylint: disable
render_template=lambda template, context: pprint.pformat((template, context)),
services={'field-data': KvsFieldData(DictKeyValueStore())},
)
self.id_generator = Mock()
def process_xml(self, xml): # pylint: disable=method-hidden
"""Parse `xml` as an XBlock, and add it to `self._blocks`"""

View File

@@ -727,7 +727,7 @@ class VideoBlock(
return video_block
@classmethod
def parse_xml(cls, node, runtime, _keys, id_generator):
def parse_xml(cls, node, runtime, _keys):
"""
Use `node` to construct a new block.
@@ -735,13 +735,13 @@ class VideoBlock(
"""
url_name = node.get('url_name')
block_type = 'video'
definition_id = id_generator.create_definition(block_type, url_name)
usage_id = id_generator.create_usage(definition_id)
definition_id = runtime.id_generator.create_definition(block_type, url_name)
usage_id = runtime.id_generator.create_usage(definition_id)
if is_pointer_tag(node):
filepath = cls._format_filepath(node.tag, name_to_pathname(url_name))
node = cls.load_file(filepath, runtime.resources_fs, usage_id)
runtime.parse_asides(node, definition_id, usage_id, id_generator)
field_data = cls.parse_video_xml(node, id_generator)
runtime.parse_asides(node, definition_id, usage_id, runtime.id_generator)
field_data = cls.parse_video_xml(node, runtime.id_generator)
kvs = InheritanceKeyValueStore(initial_values=field_data)
field_data = KvsFieldData(kvs)
video = runtime.construct_xblock_from_class(
@@ -757,7 +757,7 @@ class VideoBlock(
video.edx_video_id = video.import_video_info_into_val(
node,
runtime.resources_fs,
getattr(id_generator, 'target_course_id', None)
getattr(runtime.id_generator, 'target_course_id', None)
)
return video

View File

@@ -1529,18 +1529,16 @@ class XMLParsingSystem(DescriptorSystem): # lint-amnesty, pylint: disable=abstr
super().__init__(**kwargs)
self.process_xml = process_xml
def _usage_id_from_node(self, node, parent_id, id_generator=None):
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
id_generator (IdGenerator): The :class:`.IdGenerator` to use
for creating ids
Returns:
UsageKey: the usage key for the new xblock
"""
return self.xblock_from_node(node, parent_id, id_generator).scope_ids.usage_id
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):
"""
@@ -1575,7 +1573,7 @@ class XMLParsingSystem(DescriptorSystem): # lint-amnesty, pylint: disable=abstr
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, id_generator)
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()
@@ -1599,7 +1597,7 @@ class XMLParsingSystem(DescriptorSystem): # lint-amnesty, pylint: disable=abstr
aside_children.append(child)
# now process them & remove them from the xml payload
for child in aside_children:
self._aside_from_xml(child, def_id, usage_id, id_generator)
self._aside_from_xml(child, def_id, usage_id)
node.remove(child)
return aside_children

View File

@@ -285,7 +285,7 @@ class XmlMixin:
metadata[attr] = value
@classmethod
def parse_xml(cls, node, runtime, keys, id_generator): # pylint: disable=too-many-statements
def parse_xml(cls, node, runtime, keys): # pylint: disable=too-many-statements
"""
Use `node` to construct a new block.
@@ -297,20 +297,15 @@ class XmlMixin:
keys (:class:`.ScopeIds`): The keys identifying where this block
will store its data.
id_generator (:class:`.IdGenerator`): An object that will allow the
runtime to generate correct definition and usage ids for
children of this block.
Returns (XBlock): The newly parsed XBlock
"""
from xmodule.modulestore.xml import ImportSystem # done here to avoid circular import
if id_generator is None:
id_generator = runtime.id_generator
if keys is None:
# Passing keys=None is against the XBlock API but some platform tests do it.
def_id = id_generator.create_definition(node.tag, node.get('url_name'))
keys = ScopeIds(None, node.tag, def_id, id_generator.create_usage(def_id))
def_id = runtime.id_generator.create_definition(node.tag, node.get('url_name'))
keys = ScopeIds(None, node.tag, def_id, runtime.id_generator.create_usage(def_id))
aside_children = []
# VS[compat]
@@ -324,13 +319,13 @@ class XmlMixin:
# new style:
# read the actual definition file--named using url_name.replace(':','/')
definition_xml, filepath = cls.load_definition_xml(node, runtime, keys.def_id)
aside_children = runtime.parse_asides(definition_xml, keys.def_id, keys.usage_id, id_generator)
aside_children = runtime.parse_asides(definition_xml, keys.def_id, keys.usage_id, runtime.id_generator)
else:
filepath = None
definition_xml = node
# Note: removes metadata.
definition, children = cls.load_definition(definition_xml, runtime, keys.def_id, id_generator)
definition, children = cls.load_definition(definition_xml, runtime, keys.def_id, runtime.id_generator)
# VS[compat]
# Make Ike's github preview links work in both old and new file layouts.
@@ -399,7 +394,7 @@ class XmlMixin:
try:
return super().parse_xml_new_runtime(node, runtime, keys)
except AttributeError:
return super().parse_xml(node, runtime, keys, id_generator=None)
return super().parse_xml(node, runtime, keys)
@classmethod
def load_definition_xml(cls, node, runtime, def_id):