Coverage for e2xgrader/exporters/exporter.py: 54%
81 statements
« prev ^ index » next coverage.py v7.4.2, created at 2024-03-14 13:22 +0100
« prev ^ index » next coverage.py v7.4.2, created at 2024-03-14 13:22 +0100
1import base64
2import glob
3import os
4import os.path
6from bs4 import BeautifulSoup
7from jinja2.filters import pass_context
8from nbconvert.exporters import HTMLExporter
9from nbgrader.server_extensions.formgrader import handlers as nbgrader_handlers
10from traitlets import Unicode
12from ..utils import extra_cells as utils
13from .filters import Highlight2HTMLwithLineNumbers
16class E2xExporter(HTMLExporter):
17 """
18 Custom E2x notebook exporter
19 """
21 extra_cell_field = Unicode(
22 "extended_cell", help="The name of the extra cell metadata field."
23 )
25 def __init__(self, **kwargs):
26 super().__init__(**kwargs)
27 self.extra_template_basedirs = [
28 os.path.abspath(
29 os.path.join(
30 os.path.dirname(__file__),
31 "..",
32 "server_extensions",
33 "apps",
34 "e2xgraderapi",
35 "templates",
36 )
37 ),
38 os.path.abspath(
39 os.path.join(
40 os.path.dirname(__file__),
41 "..",
42 "server_extensions",
43 "apps",
44 "formgrader",
45 "templates",
46 )
47 ),
48 nbgrader_handlers.template_path,
49 ]
50 # The notebook seems to sometimes set exclude_input to true
51 self.exclude_input = False
53 @property
54 def template_paths(self):
55 return super()._template_paths() + [
56 os.path.abspath(
57 os.path.join(
58 os.path.dirname(__file__),
59 "..",
60 "server_extensions",
61 "grader",
62 "apps",
63 "formgrader",
64 "templates",
65 )
66 )
67 ]
69 @pass_context
70 def to_choicecell(self, context, source):
71 cell = context.get("cell", {})
72 soup = BeautifulSoup(source, "html.parser")
73 my_type = None
74 if not soup.ul or not utils.is_extra_cell(cell):
75 return soup.prettify().replace("\n", "")
76 if utils.is_singlechoice(cell):
77 my_type = "radio"
78 elif utils.is_multiplechoice(cell):
79 my_type = "checkbox"
80 form = soup.new_tag("form")
81 form["class"] = "hbrs_checkbox"
83 list_elems = soup.ul.find_all("li")
84 for i in range(len(list_elems)):
85 div = soup.new_tag("div")
86 box = soup.new_tag("input")
87 box["type"] = my_type
88 box["value"] = i
89 box["disabled"] = "disabled"
90 if i in utils.get_choices(cell):
91 box["checked"] = "checked"
92 div.append(box)
93 children = [c for c in list_elems[i].children]
94 for child in children:
95 div.append(child)
97 if utils.has_solution(cell):
98 check = soup.new_tag("span")
99 if i in utils.get_instructor_choices(cell):
100 check.string = "correct"
101 check["style"] = "color:green"
102 else:
103 check.string = "false"
104 check["style"] = "color:red"
105 div.append(check)
106 form.append(div)
107 soup.ul.replaceWith(form)
108 return soup.prettify().replace("\n", "")
110 def default_filters(self):
111 for pair in super(E2xExporter, self).default_filters():
112 yield pair
113 yield ("to_choicecell", self.to_choicecell)
115 def discover_annotations(self, resources):
116 if resources is None:
117 return
118 resources["annotations"] = dict()
119 if "metadata" not in resources or "path" not in resources["metadata"]:
120 return
122 path = resources["metadata"]["path"]
124 for annotation in glob.glob(os.path.join(path, "annotations", "*.png")):
125 cell_id = os.path.splitext(os.path.basename(annotation))[0]
126 with open(annotation, "rb") as f:
127 img = base64.b64encode(f.read()).decode("utf-8")
128 resources["annotations"][cell_id] = f"data:image/png;base64,{img}"
130 def from_notebook_node(self, nb, resources=None, **kw):
131 self.discover_annotations(resources)
133 self.exclude_input = False
134 langinfo = nb.metadata.get("language_info", {})
135 lexer = langinfo.get("pygments_lexer", langinfo.get("name", None))
136 highlight_code = self.filters.get(
137 "highlight_code_with_linenumbers",
138 Highlight2HTMLwithLineNumbers(pygments_lexer=lexer, parent=self),
139 )
140 self.register_filter("highlight_code_with_linenumbers", highlight_code)
141 return super(E2xExporter, self).from_notebook_node(nb, resources, **kw)