Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
| Download
Views: 1833
Image: ubuntu2004
1
from lxml import etree
2
import os, time, json, zipfile, io, csv, shutil
3
from .outcome import Outcome
4
from .xml import CHECKIT_NS, xml_boilerplate
5
6
class Bank():
7
def __init__(self, slug=None):
8
# read manifest for bank
9
xml = etree.parse(os.path.join("banks",slug,"bank.xml")).getroot()
10
if xml.get("version") != "0.1":
11
print("WARNING: Bank configuration doesn't match CheckIt version 0.1; "+
12
"to prevent unexpected behavior please follow bank upgrade instructions. Continuing...")
13
self.title = xml.find(f"{CHECKIT_NS}title").text
14
self.url = xml.find(f"{CHECKIT_NS}url").text
15
self.slug = slug
16
# create each outcome
17
self.outcomes = [
18
Outcome(
19
ele.find(f"{CHECKIT_NS}title").text,
20
ele.find(f"{CHECKIT_NS}slug").text,
21
ele.find(f"{CHECKIT_NS}description").text,
22
self,
23
)
24
for ele in xml.find(f"{CHECKIT_NS}outcomes").iter(f"{CHECKIT_NS}outcome")
25
]
26
27
def generate_exercises(self,public=False,amount=300,regenerate=False):
28
# cache build path
29
self.build_path(public,regenerate)
30
# cache exercises
31
for o in self.outcomes:
32
o.generate_exercises(public,amount,regenerate)
33
34
def build_path(self,public=False,regenerate=False):
35
if not(regenerate):
36
try:
37
return self.__build_path
38
except:
39
pass
40
if public:
41
build_dir = os.path.join("docs")
42
else:
43
build_dir = os.path.join("builds",time.strftime("%Y-%m-%d_%H%M%S", time.localtime()))
44
self.__build_path = os.path.join("banks",self.slug,build_dir)
45
os.makedirs(self.__build_path, exist_ok=True)
46
os.makedirs(os.path.join(self.__build_path,"pretext"), exist_ok=True)
47
return self.__build_path
48
49
def generate_dict(self,public=False,amount=300,regenerate=False):
50
if public:
51
exs = "public exercises"
52
else:
53
exs = "private exercises"
54
olist = [o.generate_dict(public,amount,regenerate) for o in self.outcomes]
55
return {
56
"title": self.title,
57
"slug": self.slug,
58
"url": self.url,
59
"outcomes": olist,
60
}
61
62
def write_json(self,public=False,amount=300,regenerate=False):
63
build_path = os.path.join(self.build_path(public,regenerate),f"bank.json")
64
with open(build_path,'w') as f:
65
json.dump(self.generate_dict(public,amount,regenerate),f)
66
return f"- CheckIt exercise bank JSON written to [{build_path}]({self.build_path(public)})"
67
68
def outcome_csv_list(self):
69
outcome_csv = [[
70
"vendor_guid",
71
"object_type",
72
"title",
73
"description",
74
"display_name",
75
"calculation_method",
76
"calculation_int",
77
"mastery_points",
78
"ratings",
79
]]
80
oid_suffix = time.time()
81
for count,outcome in enumerate(self.outcomes):
82
outcome_csv.append(outcome.csv_row(count,oid_suffix))
83
return outcome_csv
84
85
def write_canvas_outcome_csv(self,public=False,regenerate=False):
86
build_path = os.path.join(self.build_path(public), f"canvas-outcomes.csv")
87
with open(build_path,'w') as f:
88
csv.writer(f).writerows(self.outcome_csv_list())
89
return f"- Canvas outcome CSV written to [{build_path}]({self.build_path(public)})"
90
91
def outcome_from_slug(self,outcome_slug):
92
return [x for x in self.outcomes if x.slug==outcome_slug][0]
93
94
def sample_for_outcome(self,outcome_slug):
95
return self.outcome_from_slug(outcome_slug).generate_exercises(amount=1,regenerate=True,save=False)[0]
96
97
def write_canvas_zip(self,public=False,amount=300,regenerate=False):
98
build_path = os.path.join(self.build_path(public), f"canvas-question-bank.zip")
99
zip_buffer = io.BytesIO()
100
with zipfile.ZipFile(zip_buffer, "a", zipfile.ZIP_DEFLATED, False) as zip_file:
101
for outcome in self.outcomes:
102
zip_file.writestr(
103
f"{outcome.slug}.qti",
104
str(etree.tostring(outcome.canvas_tree(public,amount,regenerate),
105
encoding="UTF-8", xml_declaration=True),"UTF-8")
106
)
107
with open(build_path,'wb') as f:
108
f.write(zip_buffer.getvalue())
109
return f"- Canvas question bank ZIP written to [{build_path}]({self.build_path(public)})"
110
111
def brightspace_manifest_tree(self):
112
IMSMD = "{http://www.imsglobal.org/xsd/imsmd_rootv1p2p1}"
113
tree = xml_boilerplate("brightspace_manifest")
114
for elem in tree.iterfind(f".//{IMSMD}langstring"):
115
elem.text = self.title
116
return tree
117
118
def brightspace_questiondb_tree(self,public=False,amount=300,regenerate=False):
119
tree = xml_boilerplate("brightspace_questiondb")
120
for o in self.outcomes:
121
tree.find("objectbank").append(o.brightspace_tree(public,amount,regenerate).getroot())
122
#print(str(etree.tostring(tree,pretty_print=True), 'utf-8'))
123
return tree
124
125
def write_brightspace_zip(self,public=False,amount=300,regenerate=False):
126
build_path = os.path.join(self.build_path(public), f"brightspace-question-bank.zip")
127
zip_buffer = io.BytesIO()
128
with zipfile.ZipFile(zip_buffer, "a", zipfile.ZIP_DEFLATED, False) as zip_file:
129
zip_file.writestr(
130
f"imsmanifest.xml",
131
str(etree.tostring(self.brightspace_manifest_tree(),
132
encoding="UTF-8", xml_declaration=True),"UTF-8")
133
)
134
zip_file.writestr(
135
f"questiondb.xml",
136
str(etree.tostring(self.brightspace_questiondb_tree(public,amount,regenerate),
137
encoding="UTF-8", xml_declaration=True),"UTF-8")
138
)
139
with open(build_path,'wb') as f:
140
f.write(zip_buffer.getvalue())
141
return f"- Brightspace question bank ZIP written to [{build_path}]({self.build_path(public)})"
142
143
def moodle_xmle(self,public=False,amount=300,regenerate=False):
144
root = etree.Element("quiz")
145
header = etree.SubElement(root,"question")
146
header.set("type","category")
147
category = etree.SubElement(header,"category")
148
category_text = etree.SubElement(category,"text")
149
category_text.text = f"$course$/top/checkit/{self.slug}"
150
info = etree.SubElement(header,"info")
151
info_text = etree.SubElement(info,"text")
152
info_text.text = self.title
153
root.append(header)
154
for o in self.outcomes:
155
for q in o.moodle_xmle(public,amount,regenerate).xpath("question"):
156
root.append(q)
157
return root
158
159
def write_moodle_xml(self,public=False,amount=300,regenerate=False):
160
build_path = os.path.join(self.build_path(public), f"moodle-question-bank.xml")
161
et = etree.ElementTree(self.moodle_xmle(public,amount,regenerate))
162
et.write(build_path)
163
return f"- Moodle question bank XML written to [{build_path}]({self.build_path(public)})"
164
165
def write_pretext_files(self,public=False,amount=300,regenerate=False):
166
for outcome in self.outcomes:
167
for n,exercise in enumerate(outcome.generate_exercises(public=public,amount=amount,regenerate=regenerate)[:10]):
168
build_path = os.path.join(self.build_path(public), "pretext", f"{outcome.slug}-{n}.ptx")
169
et = etree.ElementTree(exercise.pretext_tree())
170
et.write(build_path, pretty_print=True)
171
return f"- Pretext files written to [{self.build_path(public)}/pretext]({self.build_path(public)})"
172
173
def write_outcomes_boilerplate(self):
174
outcomes_path = os.path.join("banks",self.slug,"outcomes")
175
os.makedirs(outcomes_path,exist_ok=True)
176
for outcome in self.outcomes:
177
if not os.path.isfile(os.path.join(outcomes_path,f"{outcome.slug}.xml")):
178
shutil.copyfile(
179
os.path.join("xml","template_boilerplate.xml"),
180
os.path.join(outcomes_path,f"{outcome.slug}.xml"),
181
)
182
if not os.path.isfile(os.path.join(outcomes_path,f"{outcome.slug}.sage")):
183
shutil.copyfile(
184
os.path.join("wrappers","sage_boilerplate.sage"),
185
os.path.join(outcomes_path,f"{outcome.slug}.sage"),
186
)
187
188
def copy_viewer(self,public=False):
189
copy_from_path = os.path.join("viewer")
190
copy_to_path = self.build_path(public)
191
shutil.copytree(copy_from_path,copy_to_path,dirs_exist_ok=True)
192
return f"- Viewer copied to [{copy_to_path}/index.html]({copy_to_path}/index.html)."
193
194
def build(self,public=False,amount=300,regenerate=False):
195
print(self.write_json(public,amount,regenerate))
196
print(self.write_canvas_zip(public,regenerate=False))
197
print(self.write_canvas_outcome_csv(public,regenerate=False))
198
print(self.write_brightspace_zip(public,regenerate=False))
199
print(self.write_moodle_xml(public,regenerate=False))
200
print(self.write_pretext_files(public,regenerate=False))
201
print(self.copy_viewer(public))
202
203