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

193 by mattgiuca
Apps: Added stubs for the 3 new apps, Editor, Console and Tutorial.
1
# IVLE
2
# Copyright (C) 2007-2008 The University of Melbourne
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
18
# Author: Matt Giuca, Will Grant
19
20
'''Tutorial/worksheet/exercise application.
21
22
Displays tutorial content with editable exercises, allowing students to test
23
and submit their solutions to exercises and have them auto-tested.
24
'''
287 by mattgiuca
setup.py: Added new conf.py variable: subjects_base. This is for storing the
25
26
import os
291 by mattgiuca
tutorial: Added code to handle top-level menu and subject menu (reads dir
27
import urllib
28
import re
1099.1.36 by William Grant
ivle.webapp.tutorial: Clean up.
29
import mimetypes
30
from datetime import datetime
291 by mattgiuca
tutorial: Added code to handle top-level menu and subject menu (reads dir
31
from xml.dom import minidom
193 by mattgiuca
Apps: Added stubs for the 3 new apps, Editor, Console and Tutorial.
32
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
33
import genshi
307 by mattgiuca
tutorial: Now each problem div has an ID. Added submit buttons which call
34
1099.1.36 by William Grant
ivle.webapp.tutorial: Clean up.
35
import ivle.util
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
36
import ivle.conf
1080.1.32 by me at id
www/app/{subjects,tutorial}: Use the new Storm API to get enrolled subjects.
37
import ivle.database
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
38
from ivle.database import Subject, Offering, Semester, Exercise, ExerciseSave
39
from ivle.database import Worksheet as DBWorksheet
1080.1.56 by Matt Giuca
Added new module: ivle.worksheet. This will contain general functions for
40
import ivle.worksheet
1099.1.34 by William Grant
Split up ivle.webapp.base.views into ivle.webapp.base.{rest,xhtml}, as it was
41
from ivle.webapp.base.views import BaseView
42
from ivle.webapp.base.xhtml import XHTMLView
1099.1.99 by William Grant
Require that plugins providing media subclass MediaPlugin.
43
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
44
from ivle.webapp.media import BaseMediaFileView
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
45
from ivle.webapp.errors import NotFound, Forbidden
1099.1.89 by William Grant
Don't shadow ivle.webapp.tutorial.rst (we were importing
46
from ivle.webapp.tutorial.rst import rst as rstfunc
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
47
from ivle.webapp.tutorial.service import AttemptsRESTView, \
48
                                        AttemptRESTView, ExerciseRESTView
523 by stevenbird
Adding ReStructured Text preprocessing of exercise descriptions,
49
291 by mattgiuca
tutorial: Added code to handle top-level menu and subject menu (reads dir
50
# Regex for valid identifiers (subject/worksheet names)
51
re_ident = re.compile("[0-9A-Za-z_]+")
52
53
class Worksheet:
734 by mattgiuca
tutorial: BEHAVIOUR CHANGE
54
    def __init__(self, id, name, assessable):
291 by mattgiuca
tutorial: Added code to handle top-level menu and subject menu (reads dir
55
        self.id = id
56
        self.name = name
734 by mattgiuca
tutorial: BEHAVIOUR CHANGE
57
        self.assessable = assessable
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
58
        self.loc = urllib.quote(id)
59
        self.complete_class = ''
60
        self.optional_message = ''
61
        self.total = 0
62
        self.mand_done = 0
291 by mattgiuca
tutorial: Added code to handle top-level menu and subject menu (reads dir
63
    def __repr__(self):
734 by mattgiuca
tutorial: BEHAVIOUR CHANGE
64
        return ("Worksheet(id=%s, name=%s, assessable=%s)"
65
                % (repr(self.id), repr(self.name), repr(self.assessable)))
291 by mattgiuca
tutorial: Added code to handle top-level menu and subject menu (reads dir
66
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
67
class OfferingView(XHTMLView):
68
    '''The view of the index of worksheets for an offering.'''
1099.1.35 by William Grant
ivle.webapp.base.xhtml#XHTMLView: Rename app_template to template (the things
69
    template = 'subjectmenu.html'
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
70
    appname = 'tutorial' # XXX
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
71
    permission = 'view'
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
72
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
73
    def __init__(self, req, subject, year, semester):
74
        self.context = req.store.find(Offering,
75
            Offering.subject_id == Subject.id,
76
            Subject.code == subject,
77
            Offering.semester_id == Semester.id,
78
            Semester.year == year,
79
            Semester.semester == semester).one()
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
80
81
    def populate(self, req, ctx):
1099.1.64 by William Grant
Move ivle.webapp.tutorial's media to the new framework. This also fixes the
82
        self.plugin_styles[Plugin] = ['tutorial.css']
83
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
84
        if not self.context:
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
85
            raise NotFound()
86
87
        # Subject names must be valid identifiers
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
88
        if not is_valid_subjname(self.context.subject.code):
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
89
            raise NotFound()
90
91
        # Parse the subject description file
92
        # The subject directory must have a file "subject.xml" in it,
93
        # or it does not exist (404 error).
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
94
        ctx['subject'] = self.context.subject.code
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
95
        try:
96
            subjectfile = open(os.path.join(ivle.conf.subjects_base,
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
97
                                    self.context.subject.code, "subject.xml")).read()
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
98
        except:
99
            raise NotFound()
100
101
        subjectfile = genshi.Stream(list(genshi.XML(subjectfile)))
102
103
        ctx['worksheets'] = get_worksheets(subjectfile)
104
105
        # As we go, calculate the total score for this subject
106
        # (Assessable worksheets only, mandatory problems only)
107
        problems_done = 0
108
        problems_total = 0
109
        for worksheet in ctx['worksheets']:
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
110
            stored_worksheet = req.store.find(DBWorksheet,
111
                DBWorksheet.offering_id == self.context.id,
112
                DBWorksheet.name == worksheet.id).one()
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
113
            # If worksheet is not in database yet, we'll simply not display
114
            # data about it yet (it should be added as soon as anyone visits
115
            # the worksheet itself).
116
            if stored_worksheet is not None:
117
                # If the assessable status of this worksheet has changed,
118
                # update the DB
119
                # (Note: This fails the try block if the worksheet is not yet
120
                # in the DB, which is fine. The author should visit the
121
                # worksheet page to get it into the DB).
122
                if worksheet.assessable != stored_worksheet.assessable:
123
                    # XXX If statement to avoid unnecessary database writes.
124
                    # Is this necessary, or will Storm check for us?
125
                    stored_worksheet.assessable = worksheet.assessable
126
                if worksheet.assessable:
127
                    # Calculate the user's score for this worksheet
128
                    mand_done, mand_total, opt_done, opt_total = (
129
                        ivle.worksheet.calculate_score(req.store, req.user,
130
                            stored_worksheet))
131
                    if opt_total > 0:
132
                        optional_message = " (excluding optional exercises)"
133
                    else:
134
                        optional_message = ""
135
                    if mand_done >= mand_total:
136
                        worksheet.complete_class = "complete"
137
                    elif mand_done > 0:
138
                        worksheet.complete_class = "semicomplete"
139
                    else:
140
                        worksheet.complete_class = "incomplete"
141
                    problems_done += mand_done
142
                    problems_total += mand_total
143
                    worksheet.mand_done = mand_done
144
                    worksheet.total = mand_total
145
                    worksheet.optional_message = optional_message
146
147
148
        ctx['problems_total'] = problems_total
149
        ctx['problems_done'] = problems_done
150
        if problems_total > 0:
151
            if problems_done >= problems_total:
152
                ctx['complete_class'] = "complete"
153
            elif problems_done > 0:
154
                ctx['complete_class'] = "semicomplete"
155
            else:
156
                ctx['complete_class'] = "incomplete"
157
            ctx['problems_pct'] = (100 * problems_done) / problems_total
158
            # TODO: Put this somewhere else! What is this on about? Why 16?
159
            # XXX Marks calculation (should be abstracted out of here!)
160
            # percent / 16, rounded down, with a maximum mark of 5
161
            ctx['max_mark'] = 5
162
            ctx['mark'] = min(ctx['problems_pct'] / 16, ctx['max_mark'])
163
164
class WorksheetView(XHTMLView):
165
    '''The view of a worksheet with exercises.'''
1099.1.35 by William Grant
ivle.webapp.base.xhtml#XHTMLView: Rename app_template to template (the things
166
    template = 'worksheet.html'
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
167
    appname = 'tutorial' # XXX
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
168
    permission = 'view'
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
169
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
170
    def __init__(self, req, subject, year, semester, worksheet):
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
171
        # XXX: Worksheet is actually context, but it's not really there yet.
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
172
        self.context = req.store.find(DBWorksheet,
173
            DBWorksheet.offering_id == Offering.id,
174
            Offering.subject_id == Subject.id,
175
            Subject.code == subject,
176
            Offering.semester_id == Semester.id,
177
            Semester.year == year,
178
            Semester.semester == semester,
179
            DBWorksheet.name == worksheet).one()
180
        
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
181
        self.worksheetname = worksheet
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
182
        self.year = year
183
        self.semester = semester
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
184
185
    def populate(self, req, ctx):
1099.1.64 by William Grant
Move ivle.webapp.tutorial's media to the new framework. This also fixes the
186
        self.plugin_scripts[Plugin] = ['tutorial.js']
187
        self.plugin_styles[Plugin] = ['tutorial.css']
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
188
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
189
        if not self.context:
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
190
            raise NotFound()
191
192
        # Read in worksheet data
193
        worksheetfilename = os.path.join(ivle.conf.subjects_base,
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
194
                               self.context.offering.subject.code, self.worksheetname + ".xml")
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
195
        try:
196
            worksheetfile = open(worksheetfilename)
197
            worksheetmtime = os.path.getmtime(worksheetfilename)
198
        except:
199
            raise NotFound()
200
201
        worksheetmtime = datetime.fromtimestamp(worksheetmtime)
202
        worksheetfile = worksheetfile.read()
203
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
204
        ctx['subject'] = self.context.offering.subject.code
1099.1.58 by Nick Chadwick
Updated the Worksheets to use a new tutorialservice, hosted in the
205
        ctx['worksheet'] = self.worksheetname
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
206
        ctx['semester'] = self.semester
207
        ctx['year'] = self.year
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
208
        ctx['worksheetstream'] = genshi.Stream(list(genshi.XML(worksheetfile)))
209
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
210
        generate_worksheet_data(ctx, req, self.context)
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
211
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
212
        update_db_worksheet(req.store, self.context.offering.subject.code, self.worksheetname,
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
213
            worksheetmtime, ctx['exerciselist'])
214
215
        ctx['worksheetstream'] = add_exercises(ctx['worksheetstream'], ctx, req)
216
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
217
class SubjectMediaView(BaseMediaFileView):
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
218
    '''The view of subject media files.
219
220
    URIs pointing here will just be served directly, from the subject's
221
    media directory.
222
    '''
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
223
    permission = 'view'
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
224
225
    def __init__(self, req, subject, path):
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
226
        self.context = req.store.find(Subject, code=subject).one()
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
227
        self.path = os.path.normpath(path)
228
1099.1.65 by William Grant
ivle.webapp.tutorial#SubjectMediaView now subclasses
229
    def _make_filename(self, req):
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
230
        # If the subject doesn't exist, self.subject will be None. Die.
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
231
        if not self.context:
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
232
            raise NotFound()
233
234
        subjectdir = os.path.join(ivle.conf.subjects_base,
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
235
                                  self.context.code, 'media')
1099.1.65 by William Grant
ivle.webapp.tutorial#SubjectMediaView now subclasses
236
        return os.path.join(subjectdir, self.path)
291 by mattgiuca
tutorial: Added code to handle top-level menu and subject menu (reads dir
237
238
def is_valid_subjname(subject):
239
    m = re_ident.match(subject)
240
    return m is not None and m.end() == len(subject)
287 by mattgiuca
setup.py: Added new conf.py variable: subjects_base. This is for storing the
241
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
242
def get_worksheets(subjectfile):
243
    '''Given a subject stream, get all the worksheets and put them in ctx'''
244
    worksheets = []
245
    for kind, data, pos in subjectfile:
246
        if kind is genshi.core.START:
247
            if data[0] == 'worksheet':
248
                worksheetid = ''
249
                worksheetname = ''
250
                worksheetasses = False
251
                for attr in data[1]:
252
                    if attr[0] == 'id':
253
                        worksheetid = attr[1]
254
                    elif attr[0] == 'name':
255
                        worksheetname = attr[1]
256
                    elif attr[0] == 'assessable':
257
                        worksheetasses = attr[1] == 'true'
258
                worksheets.append(Worksheet(worksheetid, worksheetname, \
259
                                                            worksheetasses))
260
    return worksheets
261
262
# This generator adds in the exercises as they are required. This is returned    
263
def add_exercises(stream, ctx, req):
1099.1.42 by Nick Chadwick
Fixed an oversight in the tutorial code which was printing <worksheet>
264
    """A filter which adds exercises into the stream."""
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
265
    exid = 0
266
    for kind, data, pos in stream:
267
        if kind is genshi.core.START:
1099.1.42 by Nick Chadwick
Fixed an oversight in the tutorial code which was printing <worksheet>
268
            # Remove the worksheet tags, as they are not xhtml valid.
269
            if data[0] == 'worksheet':
270
                continue
271
            # If we have an exercise node, replace it with the content of the
272
            # exercise.
273
            elif data[0] == 'exercise':
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
274
                new_stream = ctx['exercises'][exid]['stream']
275
                exid += 1
276
                for item in new_stream:
277
                    yield item
278
            else:
279
                yield kind, data, pos
1099.1.42 by Nick Chadwick
Fixed an oversight in the tutorial code which was printing <worksheet>
280
        # Remove the end tags for exercises and worksheets
281
        elif kind is genshi.core.END:
282
            if data == 'exercise':
283
                continue
284
            elif data == 'worksheet':
285
                continue
286
            else:
287
                yield kind, data, pos
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
288
        else:
289
            yield kind, data, pos
290
291
# This function runs through the worksheet, to get data on the exercises to
292
# build a Table of Contents, as well as fill in details in ctx
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
293
def generate_worksheet_data(ctx, req, worksheet):
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
294
    """Runs through the worksheetstream, generating the exericises"""
295
    ctx['exercises'] = []
296
    ctx['exerciselist'] = []
297
    for kind, data, pos in ctx['worksheetstream']:
298
        if kind is genshi.core.START:
299
            if data[0] == 'exercise':
300
                src = ""
301
                optional = False
302
                for attr in data[1]:
303
                    if attr[0] == 'src':
304
                        src = attr[1]
305
                    if attr[0] == 'optional':
306
                        optional = attr[1] == 'true'
307
                # Each item in toc is of type (name, complete, stream)
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
308
                ctx['exercises'].append(present_exercise(req, src, worksheet))
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
309
                ctx['exerciselist'].append((src, optional))
310
            elif data[0] == 'worksheet':
311
                ctx['worksheetname'] = 'bob'
312
                for attr in data[1]:
313
                    if attr[0] == 'name':
314
                        ctx['worksheetname'] = attr[1]
425 by mattgiuca
tutorial: Refactored present_worksheet so it has a separate function for
315
297 by mattgiuca
tutorial: Now presents problems correctly, by parsing XML source and writing
316
def innerXML(elem):
317
    """Given an element, returns its children as XML strings concatenated
318
    together."""
319
    s = ""
320
    for child in elem.childNodes:
321
        s += child.toxml()
322
    return s
323
324
def getTextData(element):
325
    """ Get the text and cdata inside an element
326
    Leading and trailing whitespace are stripped
327
    """
328
    data = ''
329
    for child in element.childNodes:
330
        if child.nodeType == child.CDATA_SECTION_NODE:
331
            data += child.data
710 by mattgiuca
Tutorial: The tutorial system now presents a table of contents at the top.
332
        elif child.nodeType == child.TEXT_NODE:
297 by mattgiuca
tutorial: Now presents problems correctly, by parsing XML source and writing
333
            data += child.data
710 by mattgiuca
Tutorial: The tutorial system now presents a table of contents at the top.
334
        elif child.nodeType == child.ELEMENT_NODE:
335
            data += getTextData(child)
297 by mattgiuca
tutorial: Now presents problems correctly, by parsing XML source and writing
336
337
    return data.strip()
338
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
339
#TODO: This needs to be re-written, to stop using minidom, and get the data
340
# about the worksheet directly from the database
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
341
def present_exercise(req, exercisesrc, worksheet):
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
342
    """Open a exercise file, and write out the exercise to the request in HTML.
343
    exercisesrc: "src" of the exercise file. A path relative to the top-level
344
        exercises base directory, as configured in conf.
291 by mattgiuca
tutorial: Added code to handle top-level menu and subject menu (reads dir
345
    """
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
346
    # Exercise-specific context is used here, as we already have all the data
347
    # we need
348
    curctx = genshi.template.Context()
349
    curctx['filename'] = exercisesrc
350
351
    # Retrieve the exercise details from the database
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
352
    exercise = req.store.find(Exercise, Exercise.id == exercisesrc).one()
353
    
354
    if exercise is None:
1099.1.93 by William Grant
Remove remaining uses of req.throw_error in the new webapps.
355
        raise NotFound()
356
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
357
    # Read exercise file and present the exercise
297 by mattgiuca
tutorial: Now presents problems correctly, by parsing XML source and writing
358
    # Note: We do not use the testing framework because it does a lot more
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
359
    # work than we need. We just need to get the exercise name and a few other
297 by mattgiuca
tutorial: Now presents problems correctly, by parsing XML source and writing
360
    # fields from the XML.
361
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
362
    #TODO: Replace calls to minidom with calls to the database directly
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
363
    curctx['exercise'] = exercise
364
    if exercise.description is not None:
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
365
        curctx['description'] = genshi.XML('<div id="description">' + 
366
                                           exercise.description + '</div>')
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
367
    else:
368
        curctx['description'] = None
297 by mattgiuca
tutorial: Now presents problems correctly, by parsing XML source and writing
369
702 by mattgiuca
tutorial:
370
    # If the user has already saved some text for this problem, or submitted
371
    # an attempt, then use that text instead of the supplied "partial".
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
372
    save = req.store.find(ExerciseSave, 
373
                          ExerciseSave.exercise_id == exercise.id,
374
                          ExerciseSave.worksheetid == worksheet.id,
375
                          ExerciseSave.user_id == req.user.id
376
                          ).one()
1080.1.56 by Matt Giuca
Added new module: ivle.worksheet. This will contain general functions for
377
    # Also get the number of attempts taken and whether this is complete.
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
378
    complete, curctx['attempts'] = \
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
379
            ivle.worksheet.get_exercise_status(req.store, req.user, 
380
                                               exercise, worksheet)
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
381
    if save is not None:
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
382
        curctx['exercisesave'] = save.text
383
    else:
384
        curctx['exercisesave']= exercise.partial
1099.1.45 by William Grant
ivle.webapp.tutorial: Recapitalise 'Complete' and 'Incomplete' in body text.
385
    curctx['complete'] = 'Complete' if complete else 'Incomplete'
386
    curctx['complete_class'] = curctx['complete'].lower()
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
387
388
    #Save the exercise details to the Table of Contents
389
390
    loader = genshi.template.TemplateLoader(".", auto_reload=True)
1099.1.23 by root
ivle.webapp.tutorial#present_exericse: Use the new template path.
391
    tmpl = loader.load(os.path.join(os.path.dirname(__file__), "exercise.html"))
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
392
    ex_stream = tmpl.generate(curctx)
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
393
    return {'name': exercise.name,
1099.1.45 by William Grant
ivle.webapp.tutorial: Recapitalise 'Complete' and 'Incomplete' in body text.
394
            'complete': curctx['complete_class'],
395
            'stream': ex_stream,
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
396
            'exid': exercise.id}
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
397
725 by mattgiuca
The database now stores a cache of all the worksheets and what problems
398
1080.1.47 by Matt Giuca
ivle.database: Added Worksheet.get_by_name method.
399
def update_db_worksheet(store, subject, worksheetname, file_mtime,
732 by mattgiuca
db/tutorial refactoring:
400
    exercise_list=None, assessable=None):
725 by mattgiuca
The database now stores a cache of all the worksheets and what problems
401
    """
402
    Determines if the database is missing this worksheet or out of date,
403
    and inserts or updates its details about the worksheet.
1080.1.47 by Matt Giuca
ivle.database: Added Worksheet.get_by_name method.
404
    file_mtime is a datetime.datetime with the modification time of the XML
732 by mattgiuca
db/tutorial refactoring:
405
    file. The database will not be updated unless worksheetmtime is newer than
406
    the mtime in the database.
725 by mattgiuca
The database now stores a cache of all the worksheets and what problems
407
    exercise_list is a list of (filename, optional) pairs as returned by
408
    present_table_of_contents.
409
    assessable is boolean.
732 by mattgiuca
db/tutorial refactoring:
410
    exercise_list and assessable are optional, and if omitted, will not change
411
    the existing data. If the worksheet does not yet exist, and assessable
412
    is omitted, it defaults to False.
725 by mattgiuca
The database now stores a cache of all the worksheets and what problems
413
    """
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
414
"""    worksheet = ivle.database.Worksheet.get_by_name(store, subject,
1080.1.47 by Matt Giuca
ivle.database: Added Worksheet.get_by_name method.
415
                                                    worksheetname)
1080.1.51 by Matt Giuca
tutorial: Replaced call to ivle.db.create_worksheet with local code (roughly
416
417
    updated_database = False
418
    if worksheet is None:
419
        # If assessable is not supplied, default to False.
420
        if assessable is None:
421
            assessable = False
422
        # Create a new Worksheet
1091 by chadnickbok
Fixed a small issue with non-unicode strings being passed
423
        worksheet = ivle.database.Worksheet(subject=unicode(subject),
424
            name=unicode(worksheetname), assessable=assessable,
425
            mtime=datetime.now())
1080.1.51 by Matt Giuca
tutorial: Replaced call to ivle.db.create_worksheet with local code (roughly
426
        store.add(worksheet)
427
        updated_database = True
428
    else:
429
        if file_mtime > worksheet.mtime:
430
            # File on disk is newer than database. Need to update.
431
            worksheet.mtime = datetime.now()
432
            if exercise_list is not None:
433
                # exercise_list is supplied, so delete any existing problems
434
                worksheet.remove_all_exercises(store)
435
            if assessable is not None:
436
                worksheet.assessable = assessable
437
            updated_database = True
438
439
    if updated_database and exercise_list is not None:
440
        # Insert each exercise into the worksheet
1080.1.53 by Matt Giuca
tutorial: Simplified update_db_worksheet. Now expects a list of pairs, rather
441
        for exercise_name, optional in exercise_list:
1080.1.51 by Matt Giuca
tutorial: Replaced call to ivle.db.create_worksheet with local code (roughly
442
            # Get the Exercise from the DB
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
443
            exercise = store.find(Exercise, Exercise.id == exercise_name).one()
1080.1.51 by Matt Giuca
tutorial: Replaced call to ivle.db.create_worksheet with local code (roughly
444
            # Create a new binding between the worksheet and the exercise
445
            worksheetexercise = ivle.database.WorksheetExercise(
446
                    worksheet=worksheet, exercise=exercise, optional=optional)
447
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
448
    store.commit()"""
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
449
1099.1.99 by William Grant
Require that plugins providing media subclass MediaPlugin.
450
class Plugin(ViewPlugin, MediaPlugin):
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
451
    urls = [
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
452
        ('subjects/:subject/:year/:semester/+worksheets', OfferingView),
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
453
        ('subjects/:subject/+worksheets/+media/*(path)', SubjectMediaView),
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
454
        ('subjects/:subject/:year/:semester/+worksheets/:worksheet', WorksheetView),
455
        ('api/subjects/:subject/:year/:semester/+worksheets/:worksheet/*exercise/'
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
456
            '+attempts/:username', AttemptsRESTView),
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
457
        ('api/subjects/:subject/:year/:semester/+worksheets/:worksheet/*exercise/'
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
458
                '+attempts/:username/:date', AttemptRESTView),
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
459
        ('api/subjects/:subject/:year/:semester/+worksheets/:worksheet/*exercise', ExerciseRESTView),
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
460
    ]
1099.1.64 by William Grant
Move ivle.webapp.tutorial's media to the new framework. This also fixes the
461
1099.1.115 by William Grant
Add tabs to the new framework. Move the app icons into the apps themselves.
462
    tabs = [
463
        ('tutorial', 'Worksheets',
464
         'Online tutorials and exercises for lab work.', 'tutorial.png',
465
         'tutorial', 2)
466
    ]
467
1099.1.64 by William Grant
Move ivle.webapp.tutorial's media to the new framework. This also fixes the
468
    media = 'media'
1099.1.100 by Nick Chadwick
Created a new help system.
469
    help = {'Tutorial': 'help.html'}