213
216
worksheetdom.getAttribute("assessable") == "true")
214
217
worksheets.append(worksheet)
218
# Now all the errors are out the way, we can begin writing
219
req.title = "Tutorial - %s" % subject
220
req.write_html_head_foot = True
221
req.write('<div id="ivle_padding">\n')
222
req.write("<h1>IVLE Tutorials - %s</h1>\n" % cgi.escape(subject))
223
req.write('<h2>Worksheets</h2>\n<ul id="tutorial-toc">\n')
224
# As we go, calculate the total score for this subject
225
# (Assessable worksheets only, mandatory problems only)
228
for worksheet in worksheets:
229
req.write(' <li><a href="%s">%s</a>'
230
% (urllib.quote(worksheet.id), cgi.escape(worksheet.name)))
232
# If the assessable status of this worksheet has changed,
234
# (Note: This fails the try block if the worksheet is not yet
235
# in the DB, which is fine. The author should visit the
236
# worksheet page to get it into the DB).
237
if (db.worksheet_is_assessable(subject, worksheet.id) !=
238
worksheet.assessable):
239
db.set_worksheet_assessable(subject, worksheet.id,
240
assessable=worksheet.assessable)
241
if worksheet.assessable:
242
mand_done, mand_total, opt_done, opt_total = (
243
db.calculate_score_worksheet(req.user.login, subject,
246
optional_message = " (excluding optional exercises)"
248
optional_message = ""
249
if mand_done >= mand_total:
250
complete_class = "complete"
252
complete_class = "semicomplete"
254
complete_class = "incomplete"
255
problems_done += mand_done
256
problems_total += mand_total
257
req.write('\n <ul><li class="%s">'
258
'Completed %d/%d%s</li></ul>\n '
259
% (complete_class, mand_done, mand_total,
261
except ivle.db.DBException:
262
# Worksheet is probably not in database yet
266
if problems_total > 0:
267
if problems_done >= problems_total:
268
complete_class = "complete"
269
elif problems_done > 0:
270
complete_class = "semicomplete"
272
complete_class = "incomplete"
273
problems_pct = (100 * problems_done) / problems_total # int
274
req.write('<ul><li class="%s">Total exercises completed: %d/%d '
276
% (complete_class, problems_done, problems_total,
278
# XXX Marks calculation (should be abstracted out of here!)
279
# percent / 16, rounded down, with a maximum mark of 5
281
mark = min(problems_pct / 16, max_mark)
282
req.write('<p style="font-weight: bold">Worksheet mark: %d/%d'
283
'</p>\n' % (mark, max_mark))
284
req.write("</div>\n") # tutorialbody
219
# Now all the errors are out the way, we can begin writing
220
req.title = "Tutorial - %s" % subject
221
req.write_html_head_foot = True
222
req.write('<div id="ivle_padding">\n')
223
req.write("<h1>IVLE Tutorials - %s</h1>\n" % cgi.escape(subject))
224
req.write('<h2>Worksheets</h2>\n<ul id="tutorial-toc">\n')
225
# As we go, calculate the total score for this subject
226
# (Assessable worksheets only, mandatory problems only)
229
for worksheet_from_xml in worksheets:
230
worksheet = ivle.database.Worksheet.get_by_name(req.store,
231
subject, worksheet_from_xml.id)
232
# If worksheet is not in database yet, we'll simply not display
233
# data about it yet (it should be added as soon as anyone visits
234
# the worksheet itself).
235
req.write(' <li><a href="%s">%s</a>'
236
% (urllib.quote(worksheet_from_xml.id),
237
cgi.escape(worksheet_from_xml.name)))
238
if worksheet is not None:
239
# If the assessable status of this worksheet has changed,
241
# (Note: This fails the try block if the worksheet is not yet
242
# in the DB, which is fine. The author should visit the
243
# worksheet page to get it into the DB).
244
if worksheet.assessable != worksheet_from_xml.assessable:
245
# XXX If statement to avoid unnecessary database writes.
246
# Is this necessary, or will Storm check for us?
247
worksheet.assessable = worksheet_from_xml.assessable
249
if worksheet.assessable:
250
# Calculate the user's score for this worksheet
251
mand_done, mand_total, opt_done, opt_total = (
252
ivle.worksheet.calculate_score(req.store, req.user,
255
optional_message = " (excluding optional exercises)"
257
optional_message = ""
258
if mand_done >= mand_total:
259
complete_class = "complete"
261
complete_class = "semicomplete"
263
complete_class = "incomplete"
264
problems_done += mand_done
265
problems_total += mand_total
266
req.write('\n <ul><li class="%s">'
267
'Completed %d/%d%s</li></ul>\n '
268
% (complete_class, mand_done, mand_total,
272
if problems_total > 0:
273
if problems_done >= problems_total:
274
complete_class = "complete"
275
elif problems_done > 0:
276
complete_class = "semicomplete"
278
complete_class = "incomplete"
279
problems_pct = (100 * problems_done) / problems_total # int
280
req.write('<ul><li class="%s">Total exercises completed: %d/%d '
282
% (complete_class, problems_done, problems_total,
284
# XXX Marks calculation (should be abstracted out of here!)
285
# percent / 16, rounded down, with a maximum mark of 5
287
mark = min(problems_pct / 16, max_mark)
288
req.write('<p style="font-weight: bold">Worksheet mark: %d/%d'
289
'</p>\n' % (mark, max_mark))
290
req.write("</div>\n") # tutorialbody
288
292
def handle_worksheet(req, subject, worksheet):
289
293
# Subject and worksheet names must be valid identifiers
352
357
<h2>Worksheet Contents</h2>
357
for tag, xml in find_all_nodes(req, node):
360
# Fragment ID is an accumulating exerciseid
361
# (The same algorithm is employed when presenting exercises)
362
fragment_id = "exercise%d" % exerciseid
364
exercisesrc = xml.getAttribute("src")
365
# Optionality: Defaults to False
366
exerciseoptional = xml.getAttribute("optional") == "true"
367
# Record the name and optionality for returning in the list
368
exercise_list.append((exercisesrc, exerciseoptional))
369
# TODO: Get proper exercise title
371
# Get the completion status of this exercise
372
complete, _ = db.get_problem_status(req.user.login,
374
req.write(' <li class="%s" id="toc_li_%s"><a href="#%s">%s'
376
% ("complete" if complete else "incomplete",
377
fragment_id, fragment_id, cgi.escape(title)))
380
fragment_id = getID(xml)
381
title = getTextData(xml)
382
req.write(' <li><a href="#%s">%s</a></li>\n'
383
% (fragment_id, cgi.escape(title)))
360
for tag, xml in find_all_nodes(req, node):
363
# Fragment ID is an accumulating exerciseid
364
# (The same algorithm is employed when presenting exercises)
365
fragment_id = "exercise%d" % exerciseid
367
exercisesrc = xml.getAttribute("src")
368
# Optionality: Defaults to False
369
exerciseoptional = xml.getAttribute("optional") == "true"
370
# Record the name and optionality for returning in the list
371
exercise_list.append((exercisesrc, exerciseoptional))
372
# TODO: Get proper exercise title
374
# Get the completion status of this exercise
375
exercise = ivle.database.Exercise.get_by_name(req.store,
377
complete, _ = ivle.worksheet.get_exercise_status(req.store,
379
req.write(' <li class="%s" id="toc_li_%s"><a href="#%s">%s'
381
% ("complete" if complete else "incomplete",
382
fragment_id, fragment_id, cgi.escape(title)))
385
fragment_id = getID(xml)
386
title = getTextData(xml)
387
req.write(' <li><a href="#%s">%s</a></li>\n'
388
% (fragment_id, cgi.escape(title)))
386
389
req.write('</ul>\n</div>\n')
387
390
return exercise_list
625
622
the existing data. If the worksheet does not yet exist, and assessable
626
623
is omitted, it defaults to False.
630
db_mtime = db.get_worksheet_mtime(subject, worksheet)
631
if db_mtime is None or file_mtime > db_mtime:
632
db.create_worksheet(subject, worksheet, exercise_list, assessable)
625
worksheet = ivle.database.Worksheet.get_by_name(store, subject,
628
updated_database = False
629
if worksheet is None:
630
# If assessable is not supplied, default to False.
631
if assessable is None:
633
# Create a new Worksheet
634
worksheet = ivle.database.Worksheet(subject=subject,
635
name=worksheetname, assessable=assessable, mtime=datetime.now())
637
updated_database = True
639
if file_mtime > worksheet.mtime:
640
# File on disk is newer than database. Need to update.
641
worksheet.mtime = datetime.now()
642
if exercise_list is not None:
643
# exercise_list is supplied, so delete any existing problems
644
worksheet.remove_all_exercises(store)
645
if assessable is not None:
646
worksheet.assessable = assessable
647
updated_database = True
649
if updated_database and exercise_list is not None:
650
# Insert each exercise into the worksheet
651
for exercise_name, optional in exercise_list:
652
# Get the Exercise from the DB
653
exercise = ivle.database.Exercise.get_by_name(store,exercise_name)
654
# Create a new binding between the worksheet and the exercise
655
worksheetexercise = ivle.database.WorksheetExercise(
656
worksheet=worksheet, exercise=exercise, optional=optional)