Don't break exports for uninstalled XBlock content

When an unknown content type is encountered, it's imported as a
RawDescriptor, which will preserve the OLX and export it back out. But
if we import a course while an XBlock is installed and then export it
after that XBlock is removed, we export RawDescriptors that never got to
save the original OLX and have a blank "data" field. Attempting to
export this used to fail and break export altogether. We now test that
the export continues to complete, and just skips over anything it can't
serialize out.

Note that this will stil export pointers in the export, so if you
uninstalled a "AmazingBlock" and exported, you might see something like
the following in a vertical's XML::

<vertical display_name="Unit">
  <amazing url_name="2edebb68d5734395a06b8a62b9bb677e"/>
</vertical>

However there would be no corresponding file at:
  /amazing/2edebb68d5734395a06b8a62b9bb677e.xml

In fact, there would be no /amazing directory at all in the export.

The better long term solution is probably to leave the pointer as-is
and export some generic file that can't be mistaken for OLX (say a
JSON file) that represents the raw key-value data we have in
Modulstore for the now unknown XBlock type. However, this commit at
least keeps export from crashing out entirely.
This commit is contained in:
David Ormsbee
2019-02-01 18:19:28 -05:00
parent b62d70441b
commit d0c353609d
3 changed files with 110 additions and 13 deletions

View File

@@ -24,6 +24,25 @@ class RawDescriptor(XmlDescriptor, XMLEditingDescriptor):
return {'data': etree.tostring(xml_object, pretty_print=True, encoding='unicode')}, []
def definition_to_xml(self, resource_fs):
"""
Return an Element if we've kept the import OLX, or None otherwise.
"""
# If there's no self.data, it means that an XBlock/XModule originally
# existed for this data at the time of import/editing, but was later
# uninstalled. RawDescriptor therefore never got to preserve the
# original OLX that came in, and we have no idea how it should be
# serialized for export. It's possible that we could do some smarter
# fallback here and attempt to extract the data, but it's reasonable
# and simpler to just skip this node altogether.
if not self.data:
log.warning(
"Could not serialize %s: No XBlock installed for '%s' tag.",
self.location,
self.location.block_type,
)
return None
# Normal case: Just echo back the original OLX we saved.
try:
return etree.fromstring(self.data)
except etree.XMLSyntaxError as err:

View File

@@ -429,6 +429,12 @@ class XmlParserMixin(object):
"""
# Get the definition
xml_object = self.definition_to_xml(self.runtime.export_fs)
# If xml_object is None, we don't know how to serialize this node, but
# we shouldn't crash out the whole export for it.
if xml_object is None:
return
for aside in self.runtime.get_asides(self):
if aside.needs_serialization():
aside_node = etree.Element("unknown_root", nsmap=XML_NAMESPACES)