Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions src/cc2olx/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
logger = logging.getLogger()

MANIFEST = "imsmanifest.xml"

# canvas-cc course settings
COURSE_SETTINGS_DIR = "course_settings"
MODULE_META = "module_meta.xml"
CANVAS_REPORT = "canvas_export.txt"

DIFFUSE_SHALLOW_SECTIONS = False
DIFFUSE_SHALLOW_SUBSECTIONS = True

Expand Down Expand Up @@ -71,6 +77,9 @@ def __init__(self, cartridge_file, workspace):
self.file_path = cartridge_file
self.directory = None
self.ns = {}
# map by identifier from `course_setting/module_meta.xml`
self.is_canvas_flavor = False
self.module_meta = {}

# List of static files that are outside of `web_resources` directory, but still required
self.extra_static_files = []
Expand All @@ -86,6 +95,47 @@ def __repr__(self):
)
return text

def process_canvas_cc(self, elements):
"""
Perform canvas cc specific processing.

Ex: collapse related items when ContextModuleSubHeader is present.
"""

def collapse_sub_headers(item):
"""
Recursive helper function to collapse related items under subheader.
"""
if item.get("children"):
item_children = []
# track ContextModuleSubHeader.
collapse_to = None
for child in item.get("children", []):
# process each child recusively
child = collapse_sub_headers(child)

meta = self.module_meta.get(child.get("identifier"))
if meta and meta.get("content_type") == "ContextModuleSubHeader":
# if there is a sub header, track it
collapse_to = child
# set `children` property for subheader if not set already
collapse_to["children"] = collapse_to.get("children", [])
item_children.append(collapse_to)
else:
if collapse_to:
# if subheader exists, append consecutive items to it's children property
collapse_to["children"].append(child)
else:
# no subheader, append to item
item_children.append(child)

# reset current item's children property
item["children"] = item_children
return item

elements = [collapse_sub_headers(item) for item in elements]
return elements

def normalize(self):
organizations = self.organizations
count_organizations = len(organizations)
Expand Down Expand Up @@ -123,7 +173,13 @@ def normalize(self):
course_root = course_root[0]
if not course_root:
return

sections = course_root.get("children", [])

if self.is_canvas_flavor:
# If this file exported via canvas-cc, process module meta.
sections = self.process_canvas_cc(sections)

normal_course = {
"children": [],
"identifier": identifier,
Expand Down Expand Up @@ -333,6 +389,12 @@ def get_resource_content(self, identifier):

def load_manifest_extracted(self):
manifest = self._extract()

# load module_meta
self.is_canvas_flavor = self._check_if_canvas_flavor()
if self.is_canvas_flavor:
self.module_meta = self._load_module_meta()

tree = filesystem.get_xml_tree(manifest)
root = tree.getroot()
self._update_namespaces(root)
Expand Down Expand Up @@ -409,6 +471,30 @@ def _extract(self):
manifest = path_extracted / MANIFEST
return manifest

def _check_if_canvas_flavor(self):
"""
Checks if the current file is exported from canvas.
"""
canvas_export_path = self.directory / COURSE_SETTINGS_DIR / CANVAS_REPORT
return os.path.exists(canvas_export_path)

def _load_module_meta(self):
"""
Load module meta from course settings if exists
"""
module_meta_path = self.directory / COURSE_SETTINGS_DIR / MODULE_META
tree = filesystem.get_xml_tree(module_meta_path)
module_meta = {}
if tree:
root = tree.getroot()
items = root.findall(".//{*}item")
for item in items:
if item.attrib.get("identifier"):
module_meta[item.attrib["identifier"]] = {
"content_type": item.find("./{*}content_type").text,
}
return module_meta

def _update_namespaces(self, root):
ns = re.match(r"\{(.*)\}", root.tag).group(1)
version = re.match(r".*/(imsccv\dp\d)/", ns).group(1)
Expand Down
52 changes: 52 additions & 0 deletions tests/fixtures_data/imscc_file/course_settings/module_meta.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,56 @@
</item>
</items>
</module>
<module identifier="sequence2">
<title>Sequence2</title>
<workflow_state>active</workflow_state>
<position>2</position>
<require_sequential_progress>false</require_sequential_progress>
<locked>false</locked>
<items>
<item identifier="vertical1">
<content_type>Webcontent</content_type>
<workflow_state>active</workflow_state>
<title>Vertical</title>
<identifierref>resource_3_vertical</identifierref>
<position>1</position>
<new_tab/>
<indent>0</indent>
</item>
<item identifier="subheader1">
<content_type>ContextModuleSubHeader</content_type>
<workflow_state>active</workflow_state>
<title>Sub Header 1</title>
<position>2</position>
<new_tab>false</new_tab>
<indent>0</indent>
</item>
<item identifier="subheader1_vertical">
<content_type>Webcontent</content_type>
<workflow_state>active</workflow_state>
<title>Vertical</title>
<identifierref>resource_3_vertical</identifierref>
<position>3</position>
<new_tab/>
<indent>0</indent>
</item>
<item identifier="subheader2">
<content_type>ContextModuleSubHeader</content_type>
<workflow_state>active</workflow_state>
<title>Sub Header 1</title>
<position>4</position>
<new_tab>false</new_tab>
<indent>0</indent>
</item>
<item identifier="subheader2_vertical">
<content_type>Webcontent</content_type>
<workflow_state>active</workflow_state>
<title>Vertical</title>
<identifierref>resource_3_vertical</identifierref>
<position>5</position>
<new_tab/>
<indent>0</indent>
</item>
</items>
</module>
</modules>
18 changes: 18 additions & 0 deletions tests/fixtures_data/imscc_file/imsmanifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,24 @@
<title>PDF Outside of Web Resources</title>
</item>
</item>
<item identifier="sequence2">
<title>Sequence2</title>
<item identifier="vertical1" identifierref="resource_3_vertical">
<title>Vertical</title>
</item>
<item identifier="subheader1">
<title>Sub Header 1</title>
</item>
<item identifier="subheader1_vertical" identifierref="resource_3_vertical">
<title>Vertical</title>
</item>
<item identifier="subheader2">
<title>Sub Header 2</title>
</item>
<item identifier="subheader2_vertical" identifierref="resource_3_vertical">
<title>Vertical</title>
</item>
</item>
</item>
</organization>
</organizations>
Expand Down
56 changes: 56 additions & 0 deletions tests/fixtures_data/studio_course_xml/course.xml
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,60 @@
</vertical>
</sequential>
</chapter>
<chapter display_name="Sequence2" url_name="">
<sequential display_name="Vertical" url_name="resource_3_vertical">
<vertical display_name="Vertical" url_name="resource_3_vertical">
<html display_name="Vertical" url_name="resource_3_vertical"><![CDATA[<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Vertical</title>
<meta name="identifier" content="resource_3_vertical"/>
<meta name="editing_roles" content="teachers"/>
<meta name="workflow_state" content="active"/>
</head>
<body>
<img src="/static/QuizImages/fractal.jpg" alt="fractal.jpg" width="500" height="375" />
<p>Fractal Image <a href="/static/QuizImages/fractal.jpg?canvas_download=1" target="_blank">Fractal Image</a></p>
</body>
</html>
]]></html>
</vertical>
</sequential>
<sequential display_name="Sub Header 1" url_name="">
<vertical display_name="Vertical" url_name="">
<html display_name="Vertical" url_name="resource_3_vertical"><![CDATA[<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Vertical</title>
<meta name="identifier" content="resource_3_vertical"/>
<meta name="editing_roles" content="teachers"/>
<meta name="workflow_state" content="active"/>
</head>
<body>
<img src="/static/QuizImages/fractal.jpg" alt="fractal.jpg" width="500" height="375" />
<p>Fractal Image <a href="/static/QuizImages/fractal.jpg?canvas_download=1" target="_blank">Fractal Image</a></p>
</body>
</html>
]]></html>
</vertical>
</sequential>
<sequential display_name="Sub Header 2" url_name="">
<vertical display_name="Vertical" url_name="">
<html display_name="Vertical" url_name="resource_3_vertical"><![CDATA[<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Vertical</title>
<meta name="identifier" content="resource_3_vertical"/>
<meta name="editing_roles" content="teachers"/>
<meta name="workflow_state" content="active"/>
</head>
<body>
<img src="/static/QuizImages/fractal.jpg" alt="fractal.jpg" width="500" height="375" />
<p>Fractal Image <a href="/static/QuizImages/fractal.jpg?canvas_download=1" target="_blank">Fractal Image</a></p>
</body>
</html>
]]></html>
</vertical>
</sequential>
</chapter>
</course>
66 changes: 65 additions & 1 deletion tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,71 @@ def test_cartridge_normalize(imscc_file, settings):
"identifier": "sequence",
"identifierref": None,
"title": "Sequence",
}
},
{
"children": [
{
"children": [
{
"children": [
{
"identifier": "vertical1",
"identifierref": "resource_3_vertical",
"title": "Vertical",
}
],
"identifier": "vertical1",
"identifierref": "resource_3_vertical",
"title": "Vertical",
}
],
"identifier": "vertical1",
"identifierref": "resource_3_vertical",
"title": "Vertical",
},
{
"children": [
{
"children": [
{
"identifier": "subheader1_vertical",
"identifierref": "resource_3_vertical",
"title": "Vertical",
}
],
"identifier": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"identifierref": None,
"title": "Vertical",
}
],
"identifier": "subheader1",
"identifierref": None,
"title": "Sub Header 1",
},
{
"children": [
{
"children": [
{
"identifier": "subheader2_vertical",
"identifierref": "resource_3_vertical",
"title": "Vertical",
}
],
"identifier": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"identifierref": None,
"title": "Vertical",
}
],
"identifier": "subheader2",
"identifierref": None,
"title": "Sub Header 2",
},
],
"identifier": "sequence2",
"identifierref": None,
"title": "Sequence2",
},
],
"identifier": "org_1",
"structure": "rooted-hierarchy",
Expand Down