|
12 | 12 | import markupsafe |
13 | 13 | from django.utils.translation import gettext_noop as _ |
14 | 14 | from lxml import etree |
| 15 | +from opaque_keys.edx.keys import UsageKey |
15 | 16 | from web_fragments.fragment import Fragment |
16 | 17 | from xblock.core import XBlock |
17 | 18 | from xblock.fields import Scope, String, XMLString |
18 | 19 | from xblock.utils.resources import ResourceLoader |
19 | 20 |
|
| 21 | +from xblocks_contrib.common.xml_utils import LegacyXmlMixin |
| 22 | + |
20 | 23 | log = logging.getLogger(__name__) |
21 | 24 |
|
22 | 25 | resource_loader = ResourceLoader(__name__) |
23 | 26 |
|
24 | 27 |
|
| 28 | +class SerializationError(Exception): |
| 29 | + """ |
| 30 | + Thrown when a module cannot be exported to XML |
| 31 | + """ |
| 32 | + def __init__(self, location, msg): |
| 33 | + super().__init__(msg) |
| 34 | + self.location = location |
| 35 | + |
| 36 | + |
25 | 37 | @XBlock.needs("i18n") |
26 | | -class AnnotatableBlock(XBlock): |
| 38 | +class AnnotatableBlock(LegacyXmlMixin, XBlock): |
27 | 39 | """ |
28 | 40 | AnnotatableXBlock allows instructors to create annotated content that students can view interactively. |
29 | 41 | Annotations can be styled and customized, with internationalization support for multilingual environments. |
@@ -84,6 +96,18 @@ class AnnotatableBlock(XBlock): |
84 | 96 | # List of supported highlight colors for annotations |
85 | 97 | HIGHLIGHT_COLORS = ["yellow", "orange", "purple", "blue", "green"] |
86 | 98 |
|
| 99 | + @property |
| 100 | + def location(self): |
| 101 | + return self.scope_ids.usage_id |
| 102 | + |
| 103 | + @location.setter |
| 104 | + def location(self, value): |
| 105 | + assert isinstance(value, UsageKey) |
| 106 | + self.scope_ids = self.scope_ids._replace( |
| 107 | + def_id=value, # Note: assigning a UsageKey as def_id is OK in old mongo / import system but wrong in split |
| 108 | + usage_id=value, |
| 109 | + ) |
| 110 | + |
87 | 111 | def _get_annotation_class_attr(self, index, el): # pylint: disable=unused-argument |
88 | 112 | """Returns a dict with the CSS class attribute to set on the annotation |
89 | 113 | and an XML key to delete from the element. |
@@ -234,3 +258,45 @@ def workbench_scenarios(): |
234 | 258 | """, |
235 | 259 | ), |
236 | 260 | ] |
| 261 | + |
| 262 | + @classmethod |
| 263 | + def definition_from_xml(cls, xml_object, system): |
| 264 | + if len(xml_object) == 0 and len(list(xml_object.items())) == 0: |
| 265 | + return {'data': ''}, [] |
| 266 | + return {'data': etree.tostring(xml_object, pretty_print=True, encoding='unicode')}, [] |
| 267 | + |
| 268 | + def definition_to_xml(self, resource_fs): |
| 269 | + """ |
| 270 | + Return an Element if we've kept the import OLX, or None otherwise. |
| 271 | + """ |
| 272 | + # If there's no self.data, it means that an XBlock/XModule originally |
| 273 | + # existed for this data at the time of import/editing, but was later |
| 274 | + # uninstalled. RawDescriptor therefore never got to preserve the |
| 275 | + # original OLX that came in, and we have no idea how it should be |
| 276 | + # serialized for export. It's possible that we could do some smarter |
| 277 | + # fallback here and attempt to extract the data, but it's reasonable |
| 278 | + # and simpler to just skip this node altogether. |
| 279 | + if not self.data: |
| 280 | + log.warning( |
| 281 | + "Could not serialize %s: No XBlock installed for '%s' tag.", |
| 282 | + self.location, |
| 283 | + self.location.block_type, |
| 284 | + ) |
| 285 | + return None |
| 286 | + |
| 287 | + # Normal case: Just echo back the original OLX we saved. |
| 288 | + try: |
| 289 | + return etree.fromstring(self.data) |
| 290 | + except etree.XMLSyntaxError as err: |
| 291 | + # Can't recover here, so just add some info and |
| 292 | + # re-raise |
| 293 | + lines = self.data.split('\n') |
| 294 | + line, offset = err.position # lint-amnesty, pylint: disable=unpacking-non-sequence |
| 295 | + msg = ( |
| 296 | + "Unable to create xml for block {loc}. " |
| 297 | + "Context: '{context}'" |
| 298 | + ).format( |
| 299 | + context=lines[line - 1][offset - 40:offset + 40], |
| 300 | + loc=self.location, |
| 301 | + ) |
| 302 | + raise SerializationError(self.location, msg) from err |
0 commit comments