Skip to content

Commit

Permalink
Do recursive tree generation for IMSCP nodes.
Browse files Browse the repository at this point in the history
Write basic tests to confirm behaviour.
  • Loading branch information
rtibbles committed Feb 19, 2024
1 parent db9c255 commit 7500585
Show file tree
Hide file tree
Showing 3 changed files with 252 additions and 168 deletions.
115 changes: 75 additions & 40 deletions ricecooker/classes/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from ..exceptions import InvalidNodeException
from ..utils.utils import is_valid_uuid_string
from .licenses import License
from ricecooker.utils.SCORM_metadata import update_node_from_metadata

MASTERY_MODELS = [id for id, name in exercises.MASTERY_MODELS]
ROLES = [id for id, name in roles.choices]
Expand All @@ -31,6 +32,7 @@ class Node(object):

license = None
language = None
kind = None

def __init__(
self,
Expand All @@ -40,7 +42,7 @@ def __init__(
thumbnail=None,
files=None,
derive_thumbnail=False,
node_modifications={},
node_modifications=None,
extra_fields=None,
):
self.files = []
Expand All @@ -60,7 +62,7 @@ def __init__(

self.set_thumbnail(thumbnail)
# save modifications passed in by csv
self.node_modifications = node_modifications
self.node_modifications = node_modifications or {}

def set_language(self, language):
"""Set self.language to internal lang. repr. code from str or Language object."""
Expand Down Expand Up @@ -1649,54 +1651,87 @@ def to_dict(self):
RemoteContentNode = StudioContentNode


def get_by_path(obj, *args):
for arg in args:
obj = obj.get(arg, {})
if obj == {}:
return None
return obj


QTI_PLACEHOLDER_SOURCE_ID = "A SOURCE ID THAT WILL BE REMOVED BEFORE UPLOAD"

class IMSCPNode(TreeNode):

class QTINode(ContentNode):
"""
Node representing QTI exercise
"""
kind = content_kinds.HTML5

kind = content_kinds.EXERCISE
@classmethod
def _recurse_and_add_children(cls, parent, item_data, license):
source_id = item_data.get("identifier", item_data["title"])
if item_data.get("children") is not None:
node = TopicNode(
source_id,
item_data["title"],
files=parent.files,
)
for child in item_data["children"]:
cls._recurse_and_add_children(node, child, license)
else:
node = ContentNode(
source_id,
item_data["title"],
license,
files=parent.files,
extra_fields={
"options": {
"entry": item_data["href"] + item_data.get("parameters", "")
}
},
)
node.kind = cls.kind
update_node_from_metadata(node, item_data["metadata"])
parent.add_child(node)

def __init__(
self, source_id=QTI_PLACEHOLDER_SOURCE_ID, title=None, license=None, **kwargs
):
super(QTINode, self).__init__(source_id, title, license, **kwargs)
from .files import QTIZipFile
def __new__(cls, *args, **kwargs):
from .files import IMSCPZipFile

qti_files = [f for f in self.files if isinstance(f, QTIZipFile)]
qti_file = qti_files[0]
qti_file.process_file()
metadata = qti_file.extract_metadata()
self.source_id = (
metadata.get("identifer")
if self.source_id == QTI_PLACEHOLDER_SOURCE_ID
else self.source_id
)
if self.source_id is None:
imscp_files = [f for f in kwargs["files"] if isinstance(f, IMSCPZipFile)]
if not imscp_files or len(imscp_files) > 1:
raise InvalidNodeException(
"IMSCPNode must be instantiated with exactly one IMSCPZipFile"
)
imscp_file = imscp_files[0]
metadata = imscp_file.extract_metadata()
if "title" in metadata["metadata"] and kwargs.get("title") is None:
kwargs["title"] = metadata["metadata"]["title"]
if kwargs.get("title") is None:
raise InvalidNodeException(
"No source_id was provided and the QTI file {} does not have an identifier".format(
qti_file.path
"No title was provided and the IMSCP file {} does not have a title".format(
imscp_file.path
)
)
self.title = self.title or get_by_path(
metadata, "metadata", "general", "title", "string"
)
if self.title is None:

if "identifier" in metadata and kwargs.get("source_id") is None:
kwargs["source_id"] = metadata["identifier"]
if kwargs.get("source_id") is None:
raise InvalidNodeException(
"No title was provided and the QTI file {} does not have a title".format(
qti_file.path
"No source_id was provided and the IMSCP file {} does not have an identifier".format(
imscp_file.path
)
)
license = kwargs.pop("license")
if license is None:
raise InvalidNodeException(
"No license was provided and we cannot infer license from an IMSCP file"
)
if metadata.get("organizations"):
node = TopicNode(*args, **kwargs)

for child in metadata["organizations"]:
cls._recurse_and_add_children(node, child, license)
else:
node = ContentNode(*args, **kwargs)
node.kind = cls.kind
update_node_from_metadata(node, metadata["metadata"])
return node


class QTINode(IMSCPNode):
"""
Node representing QTI exercise
"""

kind = content_kinds.EXERCISE

def validate(self):
"""validate: Makes sure QTI is valid
Expand Down
Loading

0 comments on commit 7500585

Please sign in to comment.