~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to www/apps/tutorial/__init__.py

  • Committer: William Grant
  • Date: 2009-01-22 04:47:42 UTC
  • mfrom: (1080.1.93 storm)
  • Revision ID: grantw@unimelb.edu.au-20090122044742-sa8gnww0ma2bm2rv
Merge Storm branch. ivle.db is dead. Watch out for the schema change.

Show diffs side-by-side

added added

removed removed

Lines of Context:
30
30
 
31
31
import os
32
32
import os.path
33
 
import time
 
33
from datetime import datetime
34
34
import cgi
35
35
import urllib
36
36
import re
41
41
 
42
42
from ivle import util
43
43
import ivle.conf
44
 
import ivle.db
 
44
import ivle.database
 
45
import ivle.worksheet
45
46
 
46
47
from rst import rst
47
48
 
150
151
  Please select a subject from the list below to select a worksheet
151
152
  for that subject.</p>\n""")
152
153
 
153
 
    (enrolled_subjects, unenrolled_subjects) = \
154
 
              ivle.db.DB().get_subjects_status(req.user.login)
 
154
    enrolled_subjects = req.user.subjects
 
155
    unenrolled_subjects = [subject for subject in
 
156
                           req.store.find(ivle.database.Subject)
 
157
                           if subject not in enrolled_subjects]
155
158
 
156
159
    def print_subject(subject):
157
160
        req.write('  <li><a href="%s">%s</a></li>\n'
158
 
            % (urllib.quote(subject['subj_code']) + '/',
159
 
               cgi.escape(subject['subj_name'])))
 
161
            % (urllib.quote(subject.code) + '/',
 
162
               cgi.escape(subject.name)))
160
163
 
161
164
    req.write("<h2>Subjects</h2>\n<ul>\n")
162
165
    for subject in enrolled_subjects:
213
216
                worksheetdom.getAttribute("assessable") == "true")
214
217
            worksheets.append(worksheet)
215
218
 
216
 
    db = ivle.db.DB()
217
 
    try:
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)
226
 
        problems_done = 0
227
 
        problems_total = 0
228
 
        for worksheet in worksheets:
229
 
            req.write('  <li><a href="%s">%s</a>'
230
 
                % (urllib.quote(worksheet.id), cgi.escape(worksheet.name)))
231
 
            try:
232
 
                # If the assessable status of this worksheet has changed,
233
 
                # update the DB
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,
244
 
                            worksheet.id))
245
 
                    if opt_total > 0:
246
 
                        optional_message = " (excluding optional exercises)"
247
 
                    else:
248
 
                        optional_message = ""
249
 
                    if mand_done >= mand_total:
250
 
                        complete_class = "complete"
251
 
                    elif mand_done > 0:
252
 
                        complete_class = "semicomplete"
253
 
                    else:
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,
260
 
                                optional_message))
261
 
            except ivle.db.DBException:
262
 
                # Worksheet is probably not in database yet
263
 
                pass
264
 
            req.write('</li>\n')
265
 
        req.write("</ul>\n")
266
 
        if problems_total > 0:
267
 
            if problems_done >= problems_total:
268
 
                complete_class = "complete"
269
 
            elif problems_done > 0:
270
 
                complete_class = "semicomplete"
271
 
            else:
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 '
275
 
                        '(%d%%)</li></ul>\n'
276
 
                % (complete_class, problems_done, problems_total,
277
 
                    problems_pct))
278
 
            # XXX Marks calculation (should be abstracted out of here!)
279
 
            # percent / 16, rounded down, with a maximum mark of 5
280
 
            max_mark = 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
285
 
    finally:
286
 
        db.close()
 
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)
 
227
    problems_done = 0
 
228
    problems_total = 0
 
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,
 
240
            # update the DB
 
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
 
248
                req.store.commit()
 
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,
 
253
                        worksheet))
 
254
                if opt_total > 0:
 
255
                    optional_message = " (excluding optional exercises)"
 
256
                else:
 
257
                    optional_message = ""
 
258
                if mand_done >= mand_total:
 
259
                    complete_class = "complete"
 
260
                elif mand_done > 0:
 
261
                    complete_class = "semicomplete"
 
262
                else:
 
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,
 
269
                            optional_message))
 
270
        req.write('</li>\n')
 
271
    req.write("</ul>\n")
 
272
    if problems_total > 0:
 
273
        if problems_done >= problems_total:
 
274
            complete_class = "complete"
 
275
        elif problems_done > 0:
 
276
            complete_class = "semicomplete"
 
277
        else:
 
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 '
 
281
                    '(%d%%)</li></ul>\n'
 
282
            % (complete_class, problems_done, problems_total,
 
283
                problems_pct))
 
284
        # XXX Marks calculation (should be abstracted out of here!)
 
285
        # percent / 16, rounded down, with a maximum mark of 5
 
286
        max_mark = 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
287
291
 
288
292
def handle_worksheet(req, subject, worksheet):
289
293
    # Subject and worksheet names must be valid identifiers
301
305
    except:
302
306
        req.throw_error(req.HTTP_NOT_FOUND,
303
307
            "Worksheet file not found.")
304
 
    worksheetmtime = time.localtime(worksheetmtime)
 
308
    worksheetmtime = datetime.fromtimestamp(worksheetmtime)
305
309
 
306
310
    worksheetdom = minidom.parse(worksheetfile)
307
311
    worksheetfile.close()
323
327
    # If the database is missing this worksheet or out of date, update its
324
328
    # details about this worksheet
325
329
    # Note: Do NOT set assessable (this is done at the subject level).
326
 
    update_db_worksheet(subject, worksheet, worksheetmtime, exercise_list)
 
330
    update_db_worksheet(req.store, subject, worksheet, worksheetmtime,
 
331
        exercise_list)
327
332
 
328
333
    # Write each element
329
334
    exerciseid = 0
352
357
<h2>Worksheet Contents</h2>
353
358
<ul>
354
359
""")
355
 
    db = ivle.db.DB()
356
 
    try:
357
 
        for tag, xml in find_all_nodes(req, node):
358
 
            if tag == "ex":
359
 
                # Exercise node
360
 
                # Fragment ID is an accumulating exerciseid
361
 
                # (The same algorithm is employed when presenting exercises)
362
 
                fragment_id = "exercise%d" % exerciseid
363
 
                exerciseid += 1
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
370
 
                title = exercisesrc
371
 
                # Get the completion status of this exercise
372
 
                complete, _ = db.get_problem_status(req.user.login,
373
 
                    exercisesrc)
374
 
                req.write('  <li class="%s" id="toc_li_%s"><a href="#%s">%s'
375
 
                    '</a></li>\n'
376
 
                    % ("complete" if complete else "incomplete",
377
 
                        fragment_id, fragment_id, cgi.escape(title)))
378
 
            else:
379
 
                # Heading node
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)))
384
 
    finally:
385
 
        db.close()
 
360
    for tag, xml in find_all_nodes(req, node):
 
361
        if tag == "ex":
 
362
            # Exercise node
 
363
            # Fragment ID is an accumulating exerciseid
 
364
            # (The same algorithm is employed when presenting exercises)
 
365
            fragment_id = "exercise%d" % exerciseid
 
366
            exerciseid += 1
 
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
 
373
            title = exercisesrc
 
374
            # Get the completion status of this exercise
 
375
            exercise = ivle.database.Exercise.get_by_name(req.store,
 
376
                            exercisesrc)
 
377
            complete, _ = ivle.worksheet.get_exercise_status(req.store,
 
378
                            req.user, exercise)
 
379
            req.write('  <li class="%s" id="toc_li_%s"><a href="#%s">%s'
 
380
                '</a></li>\n'
 
381
                % ("complete" if complete else "incomplete",
 
382
                    fragment_id, fragment_id, cgi.escape(title)))
 
383
        else:
 
384
            # Heading node
 
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
388
391
 
482
485
    """
483
486
    req.write('<div class="exercise" id="exercise%d">\n'
484
487
        % exerciseid)
 
488
    exercise = ivle.database.Exercise.get_by_name(req.store, exercisesrc)
485
489
    exercisefile = util.open_exercise_file(exercisesrc)
486
490
    if exercisefile is None:
487
491
        req.write("<p><b>Server Error</b>: "
519
523
 
520
524
    # If the user has already saved some text for this problem, or submitted
521
525
    # an attempt, then use that text instead of the supplied "partial".
522
 
    saved_text = None
523
 
    db = ivle.db.DB()
524
 
    try:
525
 
        saved_text = db.get_problem_stored_text(login=req.user.login,
526
 
            exercisename=exercisesrc)
527
 
        # Also get the number of attempts taken and whether this is complete.
528
 
        complete, attempts = db.get_problem_status(login=req.user.login,
529
 
            exercisename=exercisesrc)
530
 
    finally:
531
 
        db.close()
 
526
    saved_text = ivle.worksheet.get_exercise_stored_text(req.store,
 
527
        req.user, exercise)
 
528
    # Also get the number of attempts taken and whether this is complete.
 
529
    complete, attempts = ivle.worksheet.get_exercise_status(req.store,
 
530
        req.user, exercise)
532
531
    if saved_text is not None:
533
 
        # Important: We got the string from the DB encoded in UTF-8
534
 
        # Make it a unicode string.
535
 
        exercisepartial = saved_text.decode('utf-8')
 
532
        exercisepartial = saved_text.text
536
533
 
537
534
    # Print this exercise out to HTML 
538
535
    req.write("<p><b>Exercise:</b> %s</p>\n" % cgi.escape(exercisename))
610
607
""" % (exerciseid, filename, exerciseid, exerciseid, filename, rows))
611
608
    req.write("</div>\n")
612
609
 
613
 
def update_db_worksheet(subject, worksheet, file_mtime,
 
610
def update_db_worksheet(store, subject, worksheetname, file_mtime,
614
611
    exercise_list=None, assessable=None):
615
612
    """
616
613
    Determines if the database is missing this worksheet or out of date,
617
614
    and inserts or updates its details about the worksheet.
618
 
    file_mtime is a time.struct_time with the modification time of the XML
 
615
    file_mtime is a datetime.datetime with the modification time of the XML
619
616
    file. The database will not be updated unless worksheetmtime is newer than
620
617
    the mtime in the database.
621
618
    exercise_list is a list of (filename, optional) pairs as returned by
625
622
    the existing data. If the worksheet does not yet exist, and assessable
626
623
    is omitted, it defaults to False.
627
624
    """
628
 
    db = ivle.db.DB()
629
 
    try:
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)
633
 
    finally:
634
 
        db.close()
 
625
    worksheet = ivle.database.Worksheet.get_by_name(store, subject,
 
626
                                                    worksheetname)
 
627
 
 
628
    updated_database = False
 
629
    if worksheet is None:
 
630
        # If assessable is not supplied, default to False.
 
631
        if assessable is None:
 
632
            assessable = False
 
633
        # Create a new Worksheet
 
634
        worksheet = ivle.database.Worksheet(subject=subject,
 
635
            name=worksheetname, assessable=assessable, mtime=datetime.now())
 
636
        store.add(worksheet)
 
637
        updated_database = True
 
638
    else:
 
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
 
648
 
 
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)
 
657
 
 
658
    store.commit()