|
58 | 58 | import base64
|
59 | 59 | import datetime
|
60 | 60 | import hashlib
|
| 61 | +import json |
61 | 62 | import logging
|
62 | 63 | import markupsafe
|
63 | 64 | import textwrap
|
|
77 | 78 | from web_fragments.fragment import Fragment
|
78 | 79 | from xblock.core import List, Scope, String, XBlock
|
79 | 80 | from xblock.fields import Boolean, Float, UserScope
|
| 81 | +from xblock.runtime import KvsFieldData |
| 82 | + |
80 | 83 | try:
|
81 | 84 | from xblock.utils.resources import ResourceLoader
|
82 | 85 | from xblock.utils.studio_editable import StudioEditableXBlockMixin
|
|
85 | 88 | from xblockutils.studio_editable import StudioEditableXBlockMixin
|
86 | 89 |
|
87 | 90 | from .lti_2_util import LTI20BlockMixin, LTIError
|
| 91 | +from .xml_mixin import InheritanceKeyValueStore, XmlMixin, is_pointer_tag |
88 | 92 |
|
89 | 93 | # The anonymous user ID for the user in the course.
|
90 | 94 | ATTR_KEY_ANONYMOUS_USER_ID = 'edx-platform.anonymous_user_id'
|
@@ -296,6 +300,7 @@ class LTIBlock(
|
296 | 300 | LTIFields,
|
297 | 301 | LTI20BlockMixin,
|
298 | 302 | StudioEditableXBlockMixin,
|
| 303 | + XmlMixin, |
299 | 304 | XBlock,
|
300 | 305 | ): # pylint: disable=abstract-method
|
301 | 306 | """
|
@@ -1055,3 +1060,78 @@ def bind_for_student(self, user_id, wrappers=None):
|
1055 | 1060 |
|
1056 | 1061 | # Optionally save the state if needed
|
1057 | 1062 | self.save()
|
| 1063 | + |
| 1064 | + @classmethod |
| 1065 | + def parse_xml(cls, node, runtime, keys): # pylint: disable=too-many-statements |
| 1066 | + """ |
| 1067 | + Use `node` to construct a new block. |
| 1068 | +
|
| 1069 | + Arguments: |
| 1070 | + node (etree.Element): The xml node to parse into an xblock. |
| 1071 | +
|
| 1072 | + runtime (:class:`.Runtime`): The runtime to use while parsing. |
| 1073 | +
|
| 1074 | + keys (:class:`.ScopeIds`): The keys identifying where this block |
| 1075 | + will store its data. |
| 1076 | +
|
| 1077 | + Returns (XBlock): The newly parsed XBlock |
| 1078 | +
|
| 1079 | + """ |
| 1080 | + |
| 1081 | + # VS[compat] |
| 1082 | + # In 2012, when the platform didn't have CMS, and all courses were handwritten XML files, problem tags |
| 1083 | + # contained XML problem descriptions withing themselves. Later, when Studio has been created, and "pointer" tags |
| 1084 | + # became the preferred problem format, edX has to add this compatibility code to 1) support both pre- and |
| 1085 | + # post-Studio course formats simulteneously, and 2) be able to migrate 2012-fall courses to Studio. Old style |
| 1086 | + # support supposed to be removed, but the deprecation process have never been initiated, so this |
| 1087 | + # compatibility must stay, probably forever. |
| 1088 | + if is_pointer_tag(node): |
| 1089 | + # new style: |
| 1090 | + # read the actual definition file--named using url_name.replace(':','/') |
| 1091 | + definition_xml, filepath = cls.load_definition_xml(node, runtime, keys.def_id) |
| 1092 | + aside_children = runtime.parse_asides(definition_xml, keys.def_id, keys.usage_id, runtime.id_generator) |
| 1093 | + else: |
| 1094 | + filepath = None |
| 1095 | + definition_xml = node |
| 1096 | + |
| 1097 | + # Note: removes metadata. |
| 1098 | + definition, children = cls.load_definition(definition_xml, runtime, keys.def_id, runtime.id_generator) |
| 1099 | + |
| 1100 | + # VS[compat] |
| 1101 | + # Make Ike's github preview links work in both old and new file layouts. |
| 1102 | + if is_pointer_tag(node): |
| 1103 | + # new style -- contents actually at filepath |
| 1104 | + definition['filename'] = [filepath, filepath] |
| 1105 | + |
| 1106 | + metadata = cls.load_metadata(definition_xml) |
| 1107 | + |
| 1108 | + # move definition metadata into dict |
| 1109 | + dmdata = definition.get('definition_metadata', '') |
| 1110 | + if dmdata: |
| 1111 | + metadata['definition_metadata_raw'] = dmdata |
| 1112 | + try: |
| 1113 | + metadata.update(json.loads(dmdata)) |
| 1114 | + except Exception as err: # lint-amnesty, pylint: disable=broad-except |
| 1115 | + log.debug('Error in loading metadata %r', dmdata, exc_info=True) |
| 1116 | + metadata['definition_metadata_err'] = str(err) |
| 1117 | + |
| 1118 | + definition_aside_children = definition.pop('aside_children', None) |
| 1119 | + if definition_aside_children: |
| 1120 | + aside_children.extend(definition_aside_children) |
| 1121 | + |
| 1122 | + # Set/override any metadata specified by policy |
| 1123 | + cls.apply_policy(metadata, runtime.get_policy(keys.usage_id)) |
| 1124 | + |
| 1125 | + field_data = {**metadata, **definition, "children": children} |
| 1126 | + field_data['xml_attributes']['filename'] = definition.get('filename', ['', None]) # for git link |
| 1127 | + if "filename" in field_data: |
| 1128 | + del field_data["filename"] # filename should only be in xml_attributes. |
| 1129 | + |
| 1130 | + # we shouldn't be instantiating our own field data instance here, but there are complex inter-depenencies |
| 1131 | + # between this mixin and ImportSystem that currently seem to require it for proper metadata inheritance. |
| 1132 | + kvs = InheritanceKeyValueStore(initial_values=field_data) |
| 1133 | + field_data = KvsFieldData(kvs) |
| 1134 | + |
| 1135 | + # super().parse_xml(cls, keys, field_data) |
| 1136 | + xblock = runtime.construct_xblock_from_class(cls, keys, field_data) |
| 1137 | + return xblock |
0 commit comments