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

« back to all changes in this revision

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

  • Committer: chadnickbok
  • Date: 2009-02-02 04:00:25 UTC
  • Revision ID: svn-v4:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:1189
Adding the changes from my genshi branch into trunk.

Most apps now use the Genshi templating engine, in preparation
for future changes to dispatch

Show diffs side-by-side

added added

removed removed

Lines of Context:
46
46
 
47
47
from rst import rst
48
48
 
 
49
import genshi
 
50
import genshi.core
 
51
import genshi.template
 
52
 
49
53
THIS_APP = "tutorial"
50
54
 
51
55
# Regex for valid identifiers (subject/worksheet names)
56
60
        self.id = id
57
61
        self.name = name
58
62
        self.assessable = assessable
 
63
        self.loc = urllib.quote(id)
 
64
        self.complete_class = ''
 
65
        self.optional_message = ''
 
66
        self.total = 0
 
67
        self.mand_done = 0
59
68
    def __repr__(self):
60
69
        return ("Worksheet(id=%s, name=%s, assessable=%s)"
61
70
                % (repr(self.id), repr(self.name), repr(self.assessable)))
77
86
def handle(req):
78
87
    """Handler for the Tutorial application."""
79
88
 
 
89
    # TODO: Take this as an argument instead (refactor dispatch)
 
90
    ctx = genshi.template.Context()
 
91
 
80
92
    # Set request attributes
81
93
    req.content_type = "text/html"
82
94
    req.scripts = [
107
119
            worksheet = path_segs[1]
108
120
 
109
121
    if subject == None:
110
 
        handle_toplevel_menu(req)
 
122
        ctx['whichmenu'] = 'toplevel'
 
123
        handle_toplevel_menu(req, ctx)
111
124
    elif worksheet == None:
112
 
        handle_subject_menu(req, subject)
 
125
        ctx['whichmenu'] = 'subjectmenu'
 
126
        handle_subject_menu(req, ctx, subject)
113
127
    else:
114
 
        handle_worksheet(req, subject, worksheet)
 
128
        ctx['whichmenu'] = 'worksheet'
 
129
        handle_worksheet(req, ctx, subject, worksheet)
 
130
 
 
131
    # Use Genshi to render out the template
 
132
    # TODO: Dispatch should do this instead
 
133
    loader = genshi.template.TemplateLoader(".", auto_reload=True)
 
134
    tmpl = loader.load(util.make_local_path("apps/tutorial/template.html"))
 
135
    req.write(tmpl.generate(ctx).render('html')) #'xhtml', doctype='xhtml'))
115
136
 
116
137
def handle_media_path(req):
117
138
    """
139
160
    req.content_type = type
140
161
    req.sendfile(filename)
141
162
 
142
 
def handle_toplevel_menu(req):
 
163
def handle_toplevel_menu(req, ctx):
143
164
    # This is represented as a directory. Redirect and add a slash if it is
144
165
    # missing.
145
166
    if req.uri[-1] != '/':
146
167
        req.throw_redirect(make_tutorial_path())
147
168
    req.write_html_head_foot = True
148
 
    req.write('<div id="ivle_padding">\n')
149
 
    req.write("<h1>IVLE Tutorials</h1>\n")
150
 
    req.write("""<p>Welcome to the IVLE tutorial system.
151
 
  Please select a subject from the list below to select a worksheet
152
 
  for that subject.</p>\n""")
153
169
 
154
 
    enrolled_subjects = req.user.subjects
155
 
    unenrolled_subjects = [subject for subject in
 
170
    ctx['enrolled_subjects'] = req.user.subjects
 
171
    ctx['unenrolled_subjects'] = [subject for subject in
156
172
                           req.store.find(ivle.database.Subject)
157
 
                           if subject not in enrolled_subjects]
158
 
 
159
 
    def print_subject(subject):
160
 
        req.write('  <li><a href="%s">%s</a></li>\n'
161
 
            % (urllib.quote(subject.code) + '/',
162
 
               cgi.escape(subject.name)))
163
 
 
164
 
    req.write("<h2>Subjects</h2>\n<ul>\n")
165
 
    for subject in enrolled_subjects:
166
 
        print_subject(subject)
167
 
    req.write("</ul>\n")
168
 
    if len(unenrolled_subjects) > 0:
169
 
        req.write("<h3>Other Subjects</h3>\n")
170
 
        req.write("<p>You are not currently enrolled in these subjects.\n"
171
 
                  "   Your marks will not be counted.</p>\n")
172
 
        req.write("<ul>\n")
173
 
        for subject in unenrolled_subjects:
174
 
            print_subject(subject)
175
 
        req.write("</ul>\n")
176
 
    req.write("</div>\n")   # tutorialbody
 
173
                           if subject not in ctx['enrolled_subjects']]
177
174
 
178
175
def is_valid_subjname(subject):
179
176
    m = re_ident.match(subject)
180
177
    return m is not None and m.end() == len(subject)
181
178
 
182
 
def handle_subject_menu(req, subject):
 
179
def handle_subject_menu(req, ctx, subject):
183
180
    # This is represented as a directory. Redirect and add a slash if it is
184
181
    # missing.
185
182
    if req.uri[-1] != '/':
191
188
    # Parse the subject description file
192
189
    # The subject directory must have a file "subject.xml" in it,
193
190
    # or it does not exist (404 error).
 
191
 
 
192
    ctx['subject'] = subject
194
193
    try:
195
194
        subjectfile = open(os.path.join(ivle.conf.subjects_base, subject,
196
 
            "subject.xml"))
 
195
            "subject.xml")).read()
197
196
    except:
198
197
        req.throw_error(req.HTTP_NOT_FOUND,
199
198
            "Subject %s not found." % repr(subject))
200
199
 
201
 
    # Read in data about the subject
202
 
    subjectdom = minidom.parse(subjectfile)
203
 
    subjectfile.close()
204
 
    # TEMP: All of this is for a temporary XML format, which will later
205
 
    # change.
206
 
    worksheetsdom = subjectdom.documentElement
207
 
    worksheets = []     # List of string IDs
208
 
    for worksheetdom in worksheetsdom.childNodes:
209
 
        if worksheetdom.nodeType == worksheetdom.ELEMENT_NODE:
210
 
            # Get the 3 attributes for this node and construct a Worksheet
211
 
            # object.
212
 
            # (Note: assessable will default to False, unless it is explicitly
213
 
            # set to "true").
214
 
            worksheet = Worksheet(worksheetdom.getAttribute("id"),
215
 
                worksheetdom.getAttribute("name"),
216
 
                worksheetdom.getAttribute("assessable") == "true")
217
 
            worksheets.append(worksheet)
 
200
    subjectfile = genshi.Stream(list(genshi.XML(subjectfile)))
218
201
 
 
202
    ctx['worksheets'] = get_worksheets(subjectfile)
 
203
    
219
204
    # Now all the errors are out the way, we can begin writing
220
 
    req.title = "Tutorial - %s" % subject
 
205
 
221
206
    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
207
    # As we go, calculate the total score for this subject
226
208
    # (Assessable worksheets only, mandatory problems only)
227
209
    problems_done = 0
228
210
    problems_total = 0
229
 
    for worksheet_from_xml in worksheets:
 
211
    for worksheet_from_xml in ctx['worksheets']:
230
212
        worksheet = ivle.database.Worksheet.get_by_name(req.store,
231
213
            subject, worksheet_from_xml.id)
232
214
        # If worksheet is not in database yet, we'll simply not display
233
215
        # data about it yet (it should be added as soon as anyone visits
234
216
        # 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
217
        if worksheet is not None:
239
218
            # If the assessable status of this worksheet has changed,
240
219
            # update the DB
256
235
                else:
257
236
                    optional_message = ""
258
237
                if mand_done >= mand_total:
259
 
                    complete_class = "complete"
 
238
                    worksheet.complete_class = "complete"
260
239
                elif mand_done > 0:
261
 
                    complete_class = "semicomplete"
 
240
                    worksheet.complete_class = "semicomplete"
262
241
                else:
263
 
                    complete_class = "incomplete"
 
242
                    worksheet.complete_class = "incomplete"
264
243
                problems_done += mand_done
265
244
                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")
 
245
                worksheet.mand_done = mand_done
 
246
                worksheet.total = mand_total
 
247
                worksheet.optional_message = optional_message
 
248
 
 
249
    ctx['problems_total'] = problems_total
 
250
    ctx['problems_done'] = problems_done
272
251
    if problems_total > 0:
273
252
        if problems_done >= problems_total:
274
 
            complete_class = "complete"
 
253
            ctx['complete_class'] = "complete"
275
254
        elif problems_done > 0:
276
 
            complete_class = "semicomplete"
 
255
            ctx['complete_class'] = "semicomplete"
277
256
        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))
 
257
            ctx['complete_class'] = "incomplete"
 
258
        ctx['problems_pct'] = (100 * problems_done) / problems_total
 
259
        # TODO: Put this somewhere else! What is this on about? Why 16?
284
260
        # XXX Marks calculation (should be abstracted out of here!)
285
261
        # 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
291
 
 
292
 
def handle_worksheet(req, subject, worksheet):
 
262
        ctx['max_mark'] = 5
 
263
        ctx['mark'] = min(problems_pct / 16, max_mark)
 
264
 
 
265
def get_worksheets(subjectfile):
 
266
    '''Given a subject stream, get all the worksheets and put them in ctx'''
 
267
    worksheets = []
 
268
    for kind, data, pos in subjectfile:
 
269
        if kind is genshi.core.START:
 
270
            if data[0] == 'worksheet':
 
271
                worksheetid = ''
 
272
                worksheetname = ''
 
273
                worksheetasses = False
 
274
                for attr in data[1]:
 
275
                    if attr[0] == 'id':
 
276
                        worksheetid = attr[1]
 
277
                    elif attr[0] == 'name':
 
278
                        worksheetname = attr[1]
 
279
                    elif attr[0] == 'assessable':
 
280
                        worksheetasses = attr[1] == 'true'
 
281
                worksheets.append(Worksheet(worksheetid, worksheetname, \
 
282
                                                            worksheetasses))
 
283
    return worksheets
 
284
 
 
285
def handle_worksheet(req, ctx, subject, worksheet):
293
286
    # Subject and worksheet names must be valid identifiers
294
287
    if not is_valid_subjname(subject) or not is_valid_subjname(worksheet):
295
288
        req.throw_error(req.HTTP_NOT_FOUND,
306
299
        req.throw_error(req.HTTP_NOT_FOUND,
307
300
            "Worksheet file not found.")
308
301
    worksheetmtime = datetime.fromtimestamp(worksheetmtime)
309
 
 
310
 
    worksheetdom = minidom.parse(worksheetfile)
311
 
    worksheetfile.close()
312
 
    # TEMP: All of this is for a temporary XML format, which will later
313
 
    # change.
314
 
    worksheetdom = worksheetdom.documentElement
315
 
    if worksheetdom.tagName != "worksheet":
316
 
        req.throw_error(req.HTTP_INTERNAL_SERVER_ERROR,
317
 
            "The worksheet XML file's top-level element must be <worksheet>.")
318
 
    worksheetname = worksheetdom.getAttribute("name")
319
 
 
320
 
    # Now all the errors are out the way, we can begin writing
321
 
    req.title = "Tutorial - %s" % worksheetname
 
302
    worksheetfile = worksheetfile.read()
 
303
    
 
304
    ctx['worksheetstream'] = genshi.Stream(list(genshi.XML(worksheetfile)))
 
305
 
322
306
    req.write_html_head_foot = True
323
 
    req.write('<div id="ivle_padding">\n')
324
 
    req.write("<h1>IVLE Tutorials - %s</h1>\n<h2>%s</h2>\n"
325
 
        % (cgi.escape(subject), cgi.escape(worksheetname)))
326
 
    exercise_list = present_table_of_contents(req, worksheetdom, 0)
327
 
    # If the database is missing this worksheet or out of date, update its
328
 
    # details about this worksheet
329
 
    # Note: Do NOT set assessable (this is done at the subject level).
 
307
 
 
308
    ctx['subject'] = subject
 
309
    
 
310
    #TODO: Replace this with a nice way, possibly a match template
 
311
    generate_worksheet_data(ctx, req)
 
312
    
330
313
    update_db_worksheet(req.store, subject, worksheet, worksheetmtime,
331
 
        exercise_list)
332
 
 
333
 
    # Write each element
334
 
    exerciseid = 0
335
 
    for node in worksheetdom.childNodes:
336
 
        exerciseid = present_worksheet_node(req, node, exerciseid)
337
 
    req.write("</div>\n")   # tutorialbody
338
 
 
339
 
def present_table_of_contents(req, node, exerciseid):
340
 
    """Given a node of a worksheet XML document, writes out a table of
341
 
    contents to the request. This recursively searches for "excercise"
342
 
    and heading elements to write out.
343
 
 
344
 
    When exercise elements are encountered, the DB is queried for their
345
 
    completion status, and the ball is shown of the appropriate colour.
346
 
 
347
 
    exerciseid is the ID to use for the first exercise.
348
 
 
349
 
    As a secondary feature, this records the identifier (xml filename) and
350
 
    optionality of each exercise in a list of pairs [(str, bool)], and returns
351
 
    this list. This can be used to cache this information in the database.
352
 
    """
353
 
    exercise_list = []
354
 
    # XXX This means the DB is queried twice for each element.
355
 
    # Consider caching these results for lookup later.
356
 
    req.write("""<div id="tutorial-toc">
357
 
<h2>Worksheet Contents</h2>
358
 
<ul>
359
 
""")
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)))
389
 
    req.write('</ul>\n</div>\n')
390
 
    return exercise_list
391
 
 
392
 
def find_all_nodes(req, node):
393
 
    """Generator. Searches through a node and yields all headings and
394
 
    exercises. (Recursive).
395
 
    When finding a heading, yields a pair ("hx", headingnode), where "hx" is
396
 
    the element name, such as "h1", "h2", etc.
397
 
    When finding an exercise, yields a pair ("ex", exercisenode), where
398
 
    exercisenode is the DOM node for this exercise.
399
 
    """
400
 
    if node.nodeType == node.ELEMENT_NODE:
401
 
        if node.tagName == "exercise":
402
 
            yield "ex", node
403
 
        elif (node.tagName == "h1" or node.tagName == "h2"
404
 
            or node.tagName == "h3"):
405
 
            yield node.tagName, node
406
 
        else:
407
 
            # Some other element. Recurse.
408
 
            for childnode in node.childNodes:
409
 
                for yieldval in find_all_nodes(req, childnode):
410
 
                    yield yieldval
411
 
 
412
 
def present_worksheet_node(req, node, exerciseid):
413
 
    """Given a node of a worksheet XML document, writes it out to the
414
 
    request. This recursively searches for "exercise" elements and handles
415
 
    those specially (presenting their XML exercise spec and input box), and
416
 
    just dumps the other elements as regular HTML.
417
 
 
418
 
    exerciseid is the ID to use for the first exercise.
419
 
    Returns the new exerciseid after all the exercises have been written
420
 
    (since we need unique IDs for each exercise).
421
 
    """
422
 
    if node.nodeType == node.ELEMENT_NODE:
423
 
        if node.tagName == "exercise":
424
 
            present_exercise(req, node.getAttribute("src"), exerciseid)
425
 
            exerciseid += 1
426
 
        else:
427
 
            # Some other element. Write out its head and foot, and recurse.
428
 
            req.write("<" + node.tagName)
429
 
            # Attributes
430
 
            attrs = map(lambda (k,v): '%s="%s"'
431
 
                    % (cgi.escape(k), cgi.escape(v)), node.attributes.items())
432
 
            if len(attrs) > 0:
433
 
                req.write(" " + ' '.join(attrs))
434
 
            req.write(">")
435
 
            for childnode in node.childNodes:
436
 
                exerciseid = present_worksheet_node(req, childnode, exerciseid)
437
 
            req.write("</" + node.tagName + ">")
438
 
    else:
439
 
        # No need to recurse, so just print this node's contents
440
 
        req.write(node.toxml())
441
 
    return exerciseid
 
314
        ctx['exerciselist'])
 
315
    
 
316
    ctx['worksheetstream'] = add_exercises(ctx['worksheetstream'], ctx, req)
 
317
 
 
318
# This generator adds in the exercises as they are required. This is returned    
 
319
def add_exercises(stream, ctx, req):
 
320
    """A filter adds exercises into the stream."""
 
321
    exid = 0
 
322
    for kind, data, pos in stream:
 
323
        if kind is genshi.core.START:
 
324
            if data[0] == 'exercise':
 
325
                new_stream = ctx['exercises'][exid]['stream']
 
326
                exid += 1
 
327
                for item in new_stream:
 
328
                    yield item
 
329
            else:
 
330
                yield kind, data, pos
 
331
        else:
 
332
            yield kind, data, pos
 
333
 
 
334
# This function runs through the worksheet, to get data on the exercises to
 
335
# build a Table of Contents, as well as fill in details in ctx
 
336
def generate_worksheet_data(ctx, req):
 
337
    """Runs through the worksheetstream, generating the exericises"""
 
338
    exid = 0
 
339
    ctx['exercises'] = []
 
340
    ctx['exerciselist'] = []
 
341
    for kind, data, pos in ctx['worksheetstream']:
 
342
        if kind is genshi.core.START:
 
343
            if data[0] == 'exercise':
 
344
                exid += 1
 
345
                src = ""
 
346
                optional = False
 
347
                for attr in data[1]:
 
348
                    if attr[0] == 'src':
 
349
                        src = attr[1]
 
350
                    if attr[0] == 'optional':
 
351
                        optional = attr[1] == 'true'
 
352
                # Each item in toc is of type (name, complete, stream)
 
353
                ctx['exercises'].append(present_exercise(req, src, exid))
 
354
                ctx['exerciselist'].append((src, optional))
 
355
            elif data[0] == 'worksheet':
 
356
                ctx['worksheetname'] = 'bob'
 
357
                for attr in data[1]:
 
358
                    if attr[0] == 'name':
 
359
                        ctx['worksheetname'] = attr[1]
442
360
 
443
361
def innerXML(elem):
444
362
    """Given an element, returns its children as XML strings concatenated
448
366
        s += child.toxml()
449
367
    return s
450
368
 
451
 
def getID(element):
452
 
    """Get the first ID attribute found when traversing a node and its
453
 
    children. (This is used to make fragment links to a particular element).
454
 
    Returns None if no ID is found.
455
 
    """
456
 
    id = element.getAttribute("id")
457
 
    if id is not None and id != '':
458
 
        return id
459
 
    for child in element.childNodes:
460
 
        if child.nodeType == child.ELEMENT_NODE:
461
 
            id = getID(child)
462
 
            if id is not None:
463
 
                return id
464
 
    return None
465
 
 
466
369
def getTextData(element):
467
370
    """ Get the text and cdata inside an element
468
371
    Leading and trailing whitespace are stripped
478
381
 
479
382
    return data.strip()
480
383
 
 
384
#TODO: This needs to be re-written, to stop using minidom, and get the data
 
385
# about the worksheet directly from the database
481
386
def present_exercise(req, exercisesrc, exerciseid):
482
387
    """Open a exercise file, and write out the exercise to the request in HTML.
483
388
    exercisesrc: "src" of the exercise file. A path relative to the top-level
484
389
        exercises base directory, as configured in conf.
485
390
    """
486
 
    req.write('<div class="exercise" id="exercise%d">\n'
487
 
        % exerciseid)
 
391
    # Exercise-specific context is used here, as we already have all the data
 
392
    # we need
 
393
    curctx = genshi.template.Context()
 
394
    curctx['filename'] = exercisesrc
 
395
    curctx['exerciseid'] = exerciseid
 
396
 
 
397
    # Retrieve the exercise details from the database
488
398
    exercise = ivle.database.Exercise.get_by_name(req.store, exercisesrc)
 
399
    #Open the exercise, and double-check that it exists
489
400
    exercisefile = util.open_exercise_file(exercisesrc)
490
401
    if exercisefile is None:
491
 
        req.write("<p><b>Server Error</b>: "
492
 
            + "Exercise file could not be opened.</p>\n")
493
 
        req.write("</div>\n")
494
 
        return
 
402
        req.throw_error(req.HTTP_EXPECTATION_FAILED, \
 
403
                                        "Exercise file could not be opened")
495
404
    
496
405
    # Read exercise file and present the exercise
497
406
    # Note: We do not use the testing framework because it does a lot more
498
407
    # work than we need. We just need to get the exercise name and a few other
499
408
    # fields from the XML.
500
409
 
 
410
    #TODO: Replace calls to minidom with calls to the database directly
501
411
    exercisedom = minidom.parse(exercisefile)
502
412
    exercisefile.close()
503
413
    exercisedom = exercisedom.documentElement
504
414
    if exercisedom.tagName != "exercise":
505
415
        req.throw_error(req.HTTP_INTERNAL_SERVER_ERROR,
506
416
            "The exercise XML file's top-level element must be <exercise>.")
507
 
    exercisename = exercisedom.getAttribute("name")
508
 
    rows = exercisedom.getAttribute("rows")
509
 
    if not rows:
510
 
        rows = "12"
 
417
    curctx['exercisename'] = exercisedom.getAttribute("name")
 
418
    
 
419
    curctx['rows'] = exercisedom.getAttribute("rows")
 
420
    if not curctx['rows']:
 
421
        curctx['rows'] = "12"
511
422
    # Look for some other fields we need, which are elements:
512
423
    # - desc
513
424
    # - partial
514
 
    exercisedesc = None
515
 
    exercisepartial= ""
 
425
    curctx['exercisedesc'] = None
 
426
    curctx['exercisepartial'] = ""
516
427
    for elem in exercisedom.childNodes:
517
428
        if elem.nodeType == elem.ELEMENT_NODE:
518
429
            if elem.tagName == "desc":
519
 
                exercisedesc = rst(innerXML(elem).strip())
 
430
                curctx['exercisedesc'] = genshi.XML(rst(innerXML(elem).strip()))
520
431
            if elem.tagName == "partial":
521
 
                exercisepartial= getTextData(elem) + '\n'
522
 
    exercisepartial_backup = exercisepartial
 
432
                curctx['exercisepartial'] = getTextData(elem) + '\n'
 
433
    curctx['exercisepartial_backup'] = curctx['exercisepartial']
523
434
 
524
435
    # If the user has already saved some text for this problem, or submitted
525
436
    # an attempt, then use that text instead of the supplied "partial".
526
437
    saved_text = ivle.worksheet.get_exercise_stored_text(req.store,
527
438
        req.user, exercise)
528
439
    # 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)
 
440
    complete, curctx['attempts'] = \
 
441
            ivle.worksheet.get_exercise_status(req.store, req.user, exercise)
531
442
    if saved_text is not None:
532
 
        exercisepartial = saved_text.text
533
 
 
534
 
    # Print this exercise out to HTML 
535
 
    req.write("<p><b>Exercise:</b> %s</p>\n" % cgi.escape(exercisename))
536
 
    if exercisedesc is not None:
537
 
        req.write("<div>%s</div>\n" % exercisedesc)
538
 
    filename = cgi.escape(cjson.encode(exercisesrc), quote=True)
539
 
    req.write("""<input id="input_resettext_exercise%d" type="hidden"
540
 
    value="%s" />"""
541
 
        % (exerciseid, urllib.quote(exercisepartial_backup)))
542
 
    req.write("""<textarea id="textarea_exercise%d" class="exercisebox"
543
 
    onkeypress="return catch_textbox_input(&quot;exercise%d&quot;, %s,
544
 
        event.keyCode)"
545
 
    onchange="set_saved_status(&quot;exercise%d&quot;, %s,
546
 
        &quot;Save&quot;)"
547
 
    cols="80" rows="%s">%s</textarea>"""
548
 
        % (exerciseid, exerciseid, filename, exerciseid, filename,
549
 
            rows, cgi.escape(exercisepartial)))
550
 
    req.write("""\n<div class="exercisebuttons">\n""")
551
 
    req.write("""  <input type="button" value="Saved" disabled="disabled"
552
 
    id="savebutton_exercise%d"
553
 
    onclick="saveexercise(&quot;exercise%d&quot;, %s)"
554
 
    title="Save your solution to this exercise" />\n"""
555
 
        % (exerciseid, exerciseid, filename))
556
 
    req.write("""  <input type="button" value="Reset"
557
 
    id="resetbutton_exercise%d"
558
 
    onclick="resetexercise(&quot;exercise%d&quot;, %s)"
559
 
    title="Reload the original partial solution for this exercise" />\n"""
560
 
        % (exerciseid, exerciseid, filename))
561
 
    req.write("""  <input type="button" value="Run"
562
 
    onclick="runexercise(&quot;exercise%d&quot;, %s)"
563
 
    title="Run this program in the console" />\n"""
564
 
        % (exerciseid, filename))
565
 
    req.write("""  <input type="button" value="Submit"
566
 
    id="submitbutton_exercise%d"
567
 
    onclick="submitexercise(&quot;exercise%d&quot;, %s)"
568
 
    title="Submit this solution for evaluation" />\n"""
569
 
        % (exerciseid, exerciseid, filename))
570
 
    req.write("""</div>
571
 
<div class="testoutput">
572
 
</div>
573
 
""")
574
 
    # Write the "summary" - whether this problem is complete and how many
575
 
    # attempts it has taken.
576
 
    req.write("""<div class="problem_summary">
577
 
  <ul><li id="summaryli_exercise%d" class="%s">
578
 
    <b><span id="summarycomplete_exercise%d">%s</span>.</b>
579
 
    Attempts: <span id="summaryattempts_exercise%d">%d</span>.
580
 
  </li></ul>
581
 
</div>
582
 
""" % (exerciseid, "complete" if complete else "incomplete",
583
 
        exerciseid, "Complete" if complete else "Incomplete",
584
 
        exerciseid, attempts))
585
 
    # Write the attempt history infrastructure
586
 
    req.write("""<div class="attempthistory">
587
 
  <p><a title="Click to view previous submissions you have made for this \
588
 
exercise" onclick="open_previous(&quot;exercise%d&quot;, %s)">View previous \
589
 
attempts</a></p>
590
 
  <div style="display: none">
591
 
    <h3>Previous attempts</h3>
592
 
    <p><a title="Close the previous attempts" \
593
 
onclick="close_previous(&quot;exercise%d&quot;)">Close attempts</a></p>
594
 
    <p>
595
 
      <select title="Select an attempt's time stamp from the list">
596
 
        <option></option>
597
 
      </select>
598
 
      <input type="button" value="View"
599
 
        onclick="select_attempt(&quot;exercise%d&quot;, %s)" />
600
 
    </p>
601
 
    <p><textarea readonly="readonly" class="exercisebox" cols="80" rows="%s"
602
 
        title="You submitted this code on a previous attempt">
603
 
       </textarea>
604
 
    </p>
605
 
  </div>
606
 
</div>
607
 
""" % (exerciseid, filename, exerciseid, exerciseid, filename, rows))
608
 
    req.write("</div>\n")
 
443
        curctx['exercisepartial'] = saved_text.text
 
444
    if complete:
 
445
        curctx['complete'] = 'complete'
 
446
    else:
 
447
        curctx['complete'] = 'incomplete'
 
448
 
 
449
    #Save the exercise details to the Table of Contents
 
450
 
 
451
    loader = genshi.template.TemplateLoader(".", auto_reload=True)
 
452
    tmpl = loader.load(util.make_local_path("apps/tutorial/exercise.html"))
 
453
    ex_stream = tmpl.generate(curctx)
 
454
    return {'name': curctx['exercisename'], 'complete': curctx['complete'], \
 
455
              'stream': ex_stream, 'exid': exerciseid}
 
456
 
609
457
 
610
458
def update_db_worksheet(store, subject, worksheetname, file_mtime,
611
459
    exercise_list=None, assessable=None):