35
from xml.dom import minidom
34
37
from common import util
42
# Regex for valid identifiers (subject/worksheet names)
43
re_ident = re.compile("[0-9A-Za-z_]+")
46
def __init__(self, id, name):
50
return ("Worksheet(id=" + repr(self.id) + ", name=" + repr(self.name)
53
def make_tutorial_path(subject=None, worksheet=None):
54
"""Creates an absolute (site-relative) path to a tutorial sheet.
55
Subject or worksheet can be None.
56
Ensures that top-level or subject-level URLs end in a '/', because they
57
are represented as directories.
60
return util.make_path(THIS_APP + '/')
63
return util.make_path(os.path.join(THIS_APP, subject + '/'))
65
return util.make_path(os.path.join(THIS_APP, subject, worksheet))
37
68
"""Handler for the Tutorial application."""
39
70
# Set request attributes
40
71
req.content_type = "text/html"
41
req.write_html_head_foot = True # Have dispatch print head and foot
73
"media/tutorial/tutorial.css",
75
# Note: Don't print write_html_head_foot just yet
76
# If we encounter errors later we do not want this
43
78
path_segs = req.path.split(os.sep)
58
93
handle_worksheet(req, subject, worksheet)
60
95
def handle_toplevel_menu(req):
96
# This is represented as a directory. Redirect and add a slash if it is
98
if req.uri[-1] != os.sep:
99
req.throw_redirect(make_tutorial_path())
100
req.write_html_head_foot = True
61
101
req.write("<h1>IVLE Tutorials</h1>\n")
62
req.write("<p>TODO: Top-level tutorial menu</p>\n")
102
req.write("""<p>Welcome to the IVLE tutorial system.
103
Please select a subject from the list below to take a tutorial problem sheet
104
for that subject.</p>\n""")
105
# Get list of subjects
106
# TODO: Fetch from DB. For now, just get directory listing
107
subjects = os.listdir(conf.subjects_base)
109
req.write("<h2>Subjects</h2>\n<ul>\n")
110
for subject in subjects:
111
req.write(' <li><a href="%s">%s</a></li>\n'
112
% (urllib.quote(subject) + '/', cgi.escape(subject)))
115
def is_valid_subjname(subject):
116
m = re_ident.match(subject)
117
return m is not None and m.end() == len(subject)
64
119
def handle_subject_menu(req, subject):
120
# This is represented as a directory. Redirect and add a slash if it is
122
if req.uri[-1] != os.sep:
123
req.throw_redirect(make_tutorial_path(subject))
124
# Subject names must be valid identifiers
125
if not is_valid_subjname(subject):
126
req.throw_error(req.HTTP_NOT_FOUND)
127
# Parse the subject description file
128
# The subject directory must have a file "subject.xml" in it,
129
# or it does not exist (404 error).
131
subjectfile = open(os.path.join(conf.subjects_base, subject,
134
req.throw_error(req.HTTP_NOT_FOUND)
136
# Read in data about the subject
137
subjectdom = minidom.parse(subjectfile)
139
# TEMP: All of this is for a temporary XML format, which will later
141
worksheetsdom = subjectdom.documentElement
142
worksheets = [] # List of string IDs
143
for worksheetdom in worksheetsdom.childNodes:
144
if worksheetdom.nodeType == worksheetdom.ELEMENT_NODE:
145
worksheet = Worksheet(worksheetdom.getAttribute("id"),
146
worksheetdom.getAttribute("name"))
147
worksheets.append(worksheet)
149
# Now all the errors are out the way, we can begin writing
150
req.write_html_head_foot = True
65
151
req.write("<h1>IVLE Tutorials - %s</h1>\n" % cgi.escape(subject))
66
req.write("<p>TODO: Subject-level menu</p>\n")
152
req.write("<h2>Worksheets</h2>\n<ul>\n")
153
for worksheet in worksheets:
154
req.write(' <li><a href="%s">%s</a></li>\n'
155
% (urllib.quote(worksheet.id), cgi.escape(worksheet.name)))
68
158
def handle_worksheet(req, subject, worksheet):
159
# Subject and worksheet names must be valid identifiers
160
if not is_valid_subjname(subject) or not is_valid_subjname(worksheet):
161
req.throw_error(req.HTTP_NOT_FOUND)
163
# Read in worksheet data
165
worksheetfile = open(os.path.join(conf.subjects_base, subject,
168
req.throw_error(req.HTTP_NOT_FOUND)
170
worksheetdom = minidom.parse(worksheetfile)
171
worksheetfile.close()
172
# TEMP: All of this is for a temporary XML format, which will later
174
worksheetdom = worksheetdom.documentElement
175
if worksheetdom.tagName != "worksheet":
176
# TODO: Nicer error message, to help authors
177
req.throw_error(req.HTTP_INTERNAL_SERVER_ERROR)
178
worksheetname = worksheetdom.getAttribute("name")
179
elements = [] # List of DOM elements
180
for elem in worksheetdom.childNodes:
181
if elem.nodeType == elem.ELEMENT_NODE:
182
elements.append(elem)
184
# Now all the errors are out the way, we can begin writing
185
req.write_html_head_foot = True
69
186
req.write("<h1>IVLE Tutorials - %s</h1>\n<h2>%s</h2>\n"
70
% (cgi.escape(subject), cgi.escape(worksheet)))
71
req.write("<p>TODO: Worksheet content</p>\n")
187
% (cgi.escape(subject), cgi.escape(worksheetname)))
189
for elem in elements:
190
if elem.tagName == "problem":
191
present_problem(req, subject, elem.getAttribute("src"))
193
# Just treat this as a normal HTML element
194
req.write(elem.toxml() + '\n')
196
def present_problem(req, subject, problemsrc):
197
"""Open a problem file, and write out the problem to the request in HTML.
198
subject: Subject name.
199
problemfile: "src" of the problem file. A path relative to the subject
202
req.write('<div class="tuteproblem">\n')
203
# First normalise the path
204
problemsrc = os.path.normpath(problemsrc)
205
# Now if it begins with ".." or separator, then it's illegal
206
if problemsrc.startswith("..") or problemsrc.startswith(os.sep):
209
problemfile = os.path.join(conf.subjects_base, subject,
213
problemfile = open(problemfile)
214
except (TypeError, IOError): # TypeError if problemfile == None
215
req.write("<p><b>Server Error</b>: "
216
+ "Problem file could not be opened.</p>\n")
217
req.write("</div>\n")
220
# TODO: Read problem file and present the problem
221
# TEMP: Print out the problem XML source
222
req.write("<p><b>Problem:</b></p>\n")
223
req.write("<pre>%s</pre>\n" % cgi.escape(problemfile.read()))
224
req.write("</div>\n")