~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.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
38
from ivle.database import Subject, Offering, Semester, Exercise, \
39
                          ExerciseSave, WorksheetExercise
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
40
from ivle.database import Worksheet as DBWorksheet
1099.1.220 by Nick Chadwick
Merged from trunk
41
import ivle.worksheet.utils
1099.1.34 by William Grant
Split up ivle.webapp.base.views into ivle.webapp.base.{rest,xhtml}, as it was
42
from ivle.webapp.base.views import BaseView
43
from ivle.webapp.base.xhtml import XHTMLView
1099.1.99 by William Grant
Require that plugins providing media subclass MediaPlugin.
44
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
1099.1.197 by Nick Chadwick
Modified worksheets edit view, so now there are links to edit, add,
45
from ivle.webapp.media import BaseMediaFileView, media_url
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
46
from ivle.webapp.errors import NotFound, Forbidden
1099.1.220 by Nick Chadwick
Merged from trunk
47
from ivle.worksheet.rst import rst as rstfunc
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
48
from ivle.webapp.tutorial.service import AttemptsRESTView, AttemptRESTView, \
1099.1.216 by Nick Chadwick
Started adding in add and save options in the exercise edit view, to
49
            WorksheetExerciseRESTView, WorksheetRESTView, WorksheetsRESTView
1099.1.228 by Nick Chadwick
Merged from trunk.
50
1099.1.216 by Nick Chadwick
Started adding in add and save options in the exercise edit view, to
51
from ivle.webapp.tutorial.exercise_service import ExercisesRESTView, \
52
                                                  ExerciseRESTView
523 by stevenbird
Adding ReStructured Text preprocessing of exercise descriptions,
53
291 by mattgiuca
tutorial: Added code to handle top-level menu and subject menu (reads dir
54
class Worksheet:
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
55
    """This class represents a worksheet and a particular students progress
56
    through it.
57
    
58
    Do not confuse this with a worksheet in the database. This worksheet
59
    has extra information for use in the output, such as marks."""
734 by mattgiuca
tutorial: BEHAVIOUR CHANGE
60
    def __init__(self, id, name, assessable):
291 by mattgiuca
tutorial: Added code to handle top-level menu and subject menu (reads dir
61
        self.id = id
62
        self.name = name
734 by mattgiuca
tutorial: BEHAVIOUR CHANGE
63
        self.assessable = assessable
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
64
        self.complete_class = ''
65
        self.optional_message = ''
66
        self.total = 0
67
        self.mand_done = 0
291 by mattgiuca
tutorial: Added code to handle top-level menu and subject menu (reads dir
68
    def __repr__(self):
734 by mattgiuca
tutorial: BEHAVIOUR CHANGE
69
        return ("Worksheet(id=%s, name=%s, assessable=%s)"
70
                % (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
71
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
72
class OfferingView(XHTMLView):
73
    '''The view of the index of worksheets for an offering.'''
1099.1.192 by Nick Chadwick
Moved the tutorial templates in a new directory to keep tutorial cleaner
74
    template = 'templates/subjectmenu.html'
1116 by William Grant
Move the old tutorial views into the 'subjects' tab, so they get the right
75
    tab = 'subjects' # XXX
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
76
    permission = 'view'
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
77
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
78
    def __init__(self, req, subject, year, semester):
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
79
        """Find the given offering by subject, year and semester."""
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
80
        self.context = req.store.find(Offering,
81
            Offering.subject_id == Subject.id,
1135 by William Grant
Subject URLs now contain the short name (eg. info1) rather than the code
82
            Subject.short_name == subject,
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
83
            Offering.semester_id == Semester.id,
84
            Semester.year == year,
85
            Semester.semester == semester).one()
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
86
        
87
        if not self.context:
88
            raise NotFound()
89
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
90
91
    def populate(self, req, ctx):
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
92
        """Create the context for the given offering."""
1099.1.64 by William Grant
Move ivle.webapp.tutorial's media to the new framework. This also fixes the
93
        self.plugin_styles[Plugin] = ['tutorial.css']
94
1099.1.207 by William Grant
Replace most of the tutorial headings and titles.
95
        ctx['subject'] = self.context.subject
1140 by William Grant
Show a link to WorksheetsEditView on WorksheetsView if we have privileges.
96
        ctx['offering'] = self.context
97
        ctx['user'] = req.user
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
98
99
        # As we go, calculate the total score for this subject
100
        # (Assessable worksheets only, mandatory problems only)
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
101
102
        ctx['worksheets'] = []
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
103
        problems_done = 0
104
        problems_total = 0
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
105
        # Offering.worksheets is ordered by the worksheets seq_no
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
106
        for worksheet in self.context.worksheets:
107
            new_worksheet = Worksheet(worksheet.identifier, worksheet.name, 
108
                                      worksheet.assessable)
109
            if new_worksheet.assessable:
110
                # Calculate the user's score for this worksheet
111
                mand_done, mand_total, opt_done, opt_total = (
1099.1.220 by Nick Chadwick
Merged from trunk
112
                    ivle.worksheet.utils.calculate_score(req.store, req.user,
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
113
                        worksheet))
114
                if opt_total > 0:
115
                    optional_message = " (excluding optional exercises)"
116
                else:
117
                    optional_message = ""
118
                if mand_done >= mand_total:
119
                    new_worksheet.complete_class = "complete"
120
                elif mand_done > 0:
121
                    new_worksheet.complete_class = "semicomplete"
122
                else:
123
                    new_worksheet.complete_class = "incomplete"
124
                problems_done += mand_done
125
                problems_total += mand_total
126
                new_worksheet.mand_done = mand_done
127
                new_worksheet.total = mand_total
128
                new_worksheet.optional_message = optional_message
129
            ctx['worksheets'].append(new_worksheet)
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
130
131
        ctx['problems_total'] = problems_total
132
        ctx['problems_done'] = problems_done
133
        if problems_total > 0:
134
            if problems_done >= problems_total:
135
                ctx['complete_class'] = "complete"
136
            elif problems_done > 0:
137
                ctx['complete_class'] = "semicomplete"
138
            else:
139
                ctx['complete_class'] = "incomplete"
140
            ctx['problems_pct'] = (100 * problems_done) / problems_total
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
141
142
            # We want to display a students mark out of 5. However, they are
143
            # allowed to skip 1 in 5 questions and still get 'full marks'.
144
            # Hence we divide by 16, essentially making 16 percent worth
145
            # 1 star, and 80 or above worth 5.
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
146
            ctx['max_mark'] = 5
147
            ctx['mark'] = min(ctx['problems_pct'] / 16, ctx['max_mark'])
148
149
class WorksheetView(XHTMLView):
150
    '''The view of a worksheet with exercises.'''
1099.1.192 by Nick Chadwick
Moved the tutorial templates in a new directory to keep tutorial cleaner
151
    template = 'templates/worksheet.html'
1116 by William Grant
Move the old tutorial views into the 'subjects' tab, so they get the right
152
    tab = 'subjects'
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
153
    permission = 'view'
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
154
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
155
    def __init__(self, req, subject, year, semester, worksheet):
156
        self.context = req.store.find(DBWorksheet,
157
            DBWorksheet.offering_id == Offering.id,
158
            Offering.subject_id == Subject.id,
1135 by William Grant
Subject URLs now contain the short name (eg. info1) rather than the code
159
            Subject.short_name == subject,
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
160
            Offering.semester_id == Semester.id,
161
            Semester.year == year,
162
            Semester.semester == semester,
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
163
            DBWorksheet.identifier == worksheet).one()
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
164
        
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
165
        if self.context is None:
166
            raise NotFound(str(worksheet) + " was not found.")
167
        
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
168
        self.year = year
169
        self.semester = semester
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
170
171
    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
172
        self.plugin_scripts[Plugin] = ['tutorial.js']
1099.1.218 by Nick Chadwick
tutorials can now use RST
173
        self.plugin_styles[Plugin] = ['tutorial.css', 'worksheet.css']
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
174
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
175
        if not self.context:
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
176
            raise NotFound()
177
1099.1.207 by William Grant
Replace most of the tutorial headings and titles.
178
        ctx['subject'] = self.context.offering.subject
179
        ctx['worksheet'] = self.context
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
180
        ctx['semester'] = self.semester
181
        ctx['year'] = self.year
1099.1.220 by Nick Chadwick
Merged from trunk
182
183
        ctx['worksheetstream'] = genshi.Stream(list(genshi.XML(self.context.get_xml())))
1141 by William Grant
Display an edit link in WorksheetView, if we have privileges.
184
        ctx['user'] = req.user
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
185
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
186
        generate_worksheet_data(ctx, req, self.context)
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
187
188
        ctx['worksheetstream'] = add_exercises(ctx['worksheetstream'], ctx, req)
189
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
190
class SubjectMediaView(BaseMediaFileView):
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
191
    '''The view of subject media files.
192
193
    URIs pointing here will just be served directly, from the subject's
194
    media directory.
195
    '''
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
196
    permission = 'view'
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
197
198
    def __init__(self, req, subject, path):
1135 by William Grant
Subject URLs now contain the short name (eg. info1) rather than the code
199
        self.context = req.store.find(Subject, short_name=subject).one()
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
200
        self.path = os.path.normpath(path)
201
1099.1.65 by William Grant
ivle.webapp.tutorial#SubjectMediaView now subclasses
202
    def _make_filename(self, req):
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
203
        # 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
204
        if not self.context:
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
205
            raise NotFound()
206
207
        subjectdir = os.path.join(ivle.conf.subjects_base,
1135 by William Grant
Subject URLs now contain the short name (eg. info1) rather than the code
208
                                  self.context.short_name, 'media')
1099.1.65 by William Grant
ivle.webapp.tutorial#SubjectMediaView now subclasses
209
        return os.path.join(subjectdir, self.path)
291 by mattgiuca
tutorial: Added code to handle top-level menu and subject menu (reads dir
210
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
211
def get_worksheets(subjectfile):
212
    '''Given a subject stream, get all the worksheets and put them in ctx'''
213
    worksheets = []
214
    for kind, data, pos in subjectfile:
215
        if kind is genshi.core.START:
216
            if data[0] == 'worksheet':
217
                worksheetid = ''
218
                worksheetname = ''
219
                worksheetasses = False
220
                for attr in data[1]:
221
                    if attr[0] == 'id':
222
                        worksheetid = attr[1]
223
                    elif attr[0] == 'name':
224
                        worksheetname = attr[1]
225
                    elif attr[0] == 'assessable':
226
                        worksheetasses = attr[1] == 'true'
227
                worksheets.append(Worksheet(worksheetid, worksheetname, \
228
                                                            worksheetasses))
229
    return worksheets
230
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
231
# This generator adds in the exercises as they are required. This is returned.
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
232
def add_exercises(stream, ctx, req):
1099.1.42 by Nick Chadwick
Fixed an oversight in the tutorial code which was printing <worksheet>
233
    """A filter which adds exercises into the stream."""
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
234
    exid = 0
235
    for kind, data, pos in stream:
236
        if kind is genshi.core.START:
1099.1.42 by Nick Chadwick
Fixed an oversight in the tutorial code which was printing <worksheet>
237
            # Remove the worksheet tags, as they are not xhtml valid.
238
            if data[0] == 'worksheet':
239
                continue
240
            # If we have an exercise node, replace it with the content of the
241
            # exercise.
242
            elif data[0] == 'exercise':
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
243
                # XXX: Note that we presume ctx['exercises'] has a correct list
244
                #      of exercises. If it doesn't, something has gone wrong.
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
245
                new_stream = ctx['exercises'][exid]['stream']
246
                exid += 1
247
                for item in new_stream:
248
                    yield item
249
            else:
250
                yield kind, data, pos
1099.1.42 by Nick Chadwick
Fixed an oversight in the tutorial code which was printing <worksheet>
251
        # Remove the end tags for exercises and worksheets
252
        elif kind is genshi.core.END:
253
            if data == 'exercise':
254
                continue
255
            elif data == 'worksheet':
256
                continue
257
            else:
258
                yield kind, data, pos
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
259
        else:
260
            yield kind, data, pos
261
262
# This function runs through the worksheet, to get data on the exercises to
263
# 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
264
def generate_worksheet_data(ctx, req, worksheet):
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
265
    """Runs through the worksheetstream, generating the exericises"""
266
    ctx['exercises'] = []
267
    ctx['exerciselist'] = []
268
    for kind, data, pos in ctx['worksheetstream']:
269
        if kind is genshi.core.START:
270
            if data[0] == 'exercise':
271
                src = ""
272
                optional = False
273
                for attr in data[1]:
274
                    if attr[0] == 'src':
275
                        src = attr[1]
276
                    if attr[0] == 'optional':
277
                        optional = attr[1] == 'true'
278
                # Each item in toc is of type (name, complete, stream)
1099.4.3 by Nick Chadwick
Updated the tutorial service, to now allow users to edit worksheets
279
                if src != "":
280
                    ctx['exercises'].append(present_exercise(req, src, worksheet))
281
                    ctx['exerciselist'].append((src, optional))
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
282
            elif data[0] == 'worksheet':
283
                ctx['worksheetname'] = 'bob'
284
                for attr in data[1]:
285
                    if attr[0] == 'name':
286
                        ctx['worksheetname'] = attr[1]
425 by mattgiuca
tutorial: Refactored present_worksheet so it has a separate function for
287
297 by mattgiuca
tutorial: Now presents problems correctly, by parsing XML source and writing
288
def innerXML(elem):
289
    """Given an element, returns its children as XML strings concatenated
290
    together."""
291
    s = ""
292
    for child in elem.childNodes:
293
        s += child.toxml()
294
    return s
295
296
def getTextData(element):
297
    """ Get the text and cdata inside an element
298
    Leading and trailing whitespace are stripped
299
    """
300
    data = ''
301
    for child in element.childNodes:
302
        if child.nodeType == child.CDATA_SECTION_NODE:
303
            data += child.data
710 by mattgiuca
Tutorial: The tutorial system now presents a table of contents at the top.
304
        elif child.nodeType == child.TEXT_NODE:
297 by mattgiuca
tutorial: Now presents problems correctly, by parsing XML source and writing
305
            data += child.data
710 by mattgiuca
Tutorial: The tutorial system now presents a table of contents at the top.
306
        elif child.nodeType == child.ELEMENT_NODE:
307
            data += getTextData(child)
297 by mattgiuca
tutorial: Now presents problems correctly, by parsing XML source and writing
308
309
    return data.strip()
310
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
311
def present_exercise(req, src, worksheet):
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
312
    """Open a exercise file, and write out the exercise to the request in HTML.
313
    exercisesrc: "src" of the exercise file. A path relative to the top-level
314
        exercises base directory, as configured in conf.
291 by mattgiuca
tutorial: Added code to handle top-level menu and subject menu (reads dir
315
    """
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
316
    # Exercise-specific context is used here, as we already have all the data
317
    # we need
318
    curctx = genshi.template.Context()
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
319
320
    worksheet_exercise = req.store.find(WorksheetExercise,
321
        WorksheetExercise.worksheet_id == worksheet.id,
322
        WorksheetExercise.exercise_id == src).one()
323
324
    if worksheet_exercise is None:
325
        raise NotFound()
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
326
327
    # Retrieve the exercise details from the database
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
328
    exercise = req.store.find(Exercise, 
329
        Exercise.id == worksheet_exercise.exercise_id).one()
330
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
331
    if exercise is None:
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
332
        raise NotFound(exercisesrc)
1099.1.93 by William Grant
Remove remaining uses of req.throw_error in the new webapps.
333
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
334
    # Read exercise file and present the exercise
297 by mattgiuca
tutorial: Now presents problems correctly, by parsing XML source and writing
335
    # Note: We do not use the testing framework because it does a lot more
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
336
    # 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
337
    # fields from the XML.
338
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
339
    curctx['exercise'] = exercise
340
    if exercise.description is not None:
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
341
        desc = rstfunc(exercise.description)
342
        curctx['description'] = genshi.XML('<div id="description">' + desc + 
343
                                           '</div>')
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
344
    else:
345
        curctx['description'] = None
297 by mattgiuca
tutorial: Now presents problems correctly, by parsing XML source and writing
346
702 by mattgiuca
tutorial:
347
    # If the user has already saved some text for this problem, or submitted
348
    # an attempt, then use that text instead of the supplied "partial".
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
349
    # Get exercise stored text will return a save, or the most recent attempt,
350
    # whichever is more recent
1099.1.220 by Nick Chadwick
Merged from trunk
351
    save = ivle.worksheet.utils.get_exercise_stored_text(
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
352
                        req.store, req.user, worksheet_exercise)
353
1080.1.56 by Matt Giuca
Added new module: ivle.worksheet. This will contain general functions for
354
    # Also get the number of attempts taken and whether this is complete.
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
355
    complete, curctx['attempts'] = \
1099.1.220 by Nick Chadwick
Merged from trunk
356
            ivle.worksheet.utils.get_exercise_status(req.store, req.user, 
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
357
                                               worksheet_exercise)
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
358
    if save is not None:
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
359
        curctx['exercisesave'] = save.text
360
    else:
361
        curctx['exercisesave']= exercise.partial
1099.1.45 by William Grant
ivle.webapp.tutorial: Recapitalise 'Complete' and 'Incomplete' in body text.
362
    curctx['complete'] = 'Complete' if complete else 'Incomplete'
363
    curctx['complete_class'] = curctx['complete'].lower()
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
364
365
    #Save the exercise details to the Table of Contents
366
367
    loader = genshi.template.TemplateLoader(".", auto_reload=True)
1109 by matt.giuca
tutorial/__init__.py: Fixed path to exercises template
368
    tmpl = loader.load(os.path.join(os.path.dirname(__file__),
369
        "templates/exercise.html"))
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
370
    ex_stream = tmpl.generate(curctx)
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
371
    return {'name': exercise.name,
1099.1.45 by William Grant
ivle.webapp.tutorial: Recapitalise 'Complete' and 'Incomplete' in body text.
372
            'complete': curctx['complete_class'],
373
            'stream': ex_stream,
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
374
            'exid': exercise.id}
1093 by chadnickbok
Adding the changes from my genshi branch into trunk.
375
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
376
class OfferingAdminView(XHTMLView):
377
    """The admin view for an Offering.
378
    
379
    This class is designed to check the user has admin privileges, and
380
    then allow them to edit the RST for the offering, which controls which
381
    worksheets are actually displayed on the page."""
382
    pass
383
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
384
class WorksheetEditView(XHTMLView):
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
385
    """The admin view for an offering.
386
    
387
    This view is designed to replace worksheets.xml, turning them instead
388
    into XML directly from RST."""
389
    permission = "edit"
1099.1.192 by Nick Chadwick
Moved the tutorial templates in a new directory to keep tutorial cleaner
390
    template = "templates/worksheet_edit.html"
1116 by William Grant
Move the old tutorial views into the 'subjects' tab, so they get the right
391
    tab = "subjects"
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
392
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
393
    def __init__(self, req, **kwargs):
394
    
395
        subject = kwargs['subject']
396
        year = kwargs['year']
397
        semester = kwargs['semester']
398
        worksheet = kwargs['worksheet']
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
399
        self.context = req.store.find(DBWorksheet,
400
            DBWorksheet.identifier == worksheet,
401
            DBWorksheet.offering_id == Offering.id,
402
            Offering.semester_id == Semester.id,
403
            Semester.year == year,
404
            Semester.semester == semester,
405
            Offering.subject_id == Subject.id,
1135 by William Grant
Subject URLs now contain the short name (eg. info1) rather than the code
406
            Subject.short_name == subject
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
407
        ).one()
408
        
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
409
        if self.context is None:
410
            raise NotFound()
411
        
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
412
        self.subject = subject
413
        self.year = year
414
        self.semester = semester
1099.4.3 by Nick Chadwick
Updated the tutorial service, to now allow users to edit worksheets
415
        self.worksheet = worksheet
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
416
        
417
            
418
    def populate(self, req, ctx):
419
        self.plugin_styles[Plugin] = ["tutorial_admin.css"]
420
        self.plugin_scripts[Plugin] = ['tutorial_admin.js']
421
        
422
        ctx['worksheet'] = self.context
1099.4.3 by Nick Chadwick
Updated the tutorial service, to now allow users to edit worksheets
423
        ctx['worksheetname'] = self.worksheet
1099.1.207 by William Grant
Replace most of the tutorial headings and titles.
424
        ctx['subject'] = self.context.offering.subject
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
425
        ctx['year'] = self.year
426
        ctx['semester'] = self.semester
1099.1.193 by Nick Chadwick
Modified the edit view of a worksheet to allow editing of the format
427
        #XXX: Get the list of formats from somewhere else
428
        ctx['formats'] = ['xml', 'rst']
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
429
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
430
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
431
class WorksheetAddView(XHTMLView):
432
    """This view allows a user to add a worksheet"""
433
    permission = "edit"
1099.1.192 by Nick Chadwick
Moved the tutorial templates in a new directory to keep tutorial cleaner
434
    template = "templates/worksheet_add.html"
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
435
436
    def __init__(self, req, subject, year, semester):
437
        self.context = req.store.find(Offering,
438
            Offering.semester_id == Semester.id,
439
            Semester.year == year,
440
            Semester.semester == semester,
441
            Offering.subject_id == Subject.id,
1135 by William Grant
Subject URLs now contain the short name (eg. info1) rather than the code
442
            Subject.short_name == subject
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
443
        ).one()
444
        
445
        self.subject = subject
446
        self.year = year
447
        self.semester = semester
448
        
449
        if self.context is None:
450
            raise NotFound()
451
            
452
    def populate(self, req, ctx):
453
        self.plugin_styles[Plugin] = ["tutorial_admin.css"]
454
        self.plugin_scripts[Plugin] = ['tutorial_admin.js']
455
        
1099.1.207 by William Grant
Replace most of the tutorial headings and titles.
456
        ctx['subject'] = self.context.subject
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
457
        ctx['year'] = self.year
458
        ctx['semester'] = self.semester
459
        
460
        #XXX: Get the list of formats from somewhere else
461
        ctx['formats'] = ['xml', 'rst']
462
1099.1.197 by Nick Chadwick
Modified worksheets edit view, so now there are links to edit, add,
463
class WorksheetsEditView(XHTMLView):
464
    """View for arranging worksheets."""
465
    
466
    permission = 'edit'
467
    template = 'templates/worksheets_edit.html'
468
    
469
    def __init__(self, req, subject, year, semester):
470
        self.context = req.store.find(Offering,
471
            Offering.semester_id == Semester.id,
472
            Semester.year == year,
473
            Semester.semester == semester,
474
            Offering.subject_id == Subject.id,
1135 by William Grant
Subject URLs now contain the short name (eg. info1) rather than the code
475
            Subject.short_name == subject
1099.1.197 by Nick Chadwick
Modified worksheets edit view, so now there are links to edit, add,
476
        ).one()
477
        
478
        self.subject = subject
479
        self.year = year
480
        self.semester = semester
481
        
482
        if self.context is None:
483
            raise NotFound()
484
    
485
    def populate(self, req, ctx):
486
        self.plugin_styles[Plugin] = ['tutorial_admin.css']
487
        self.plugin_scripts[Plugin] = ['tutorial_admin.js']
488
        
1099.1.207 by William Grant
Replace most of the tutorial headings and titles.
489
        ctx['subject'] = self.context.subject
1099.1.197 by Nick Chadwick
Modified worksheets edit view, so now there are links to edit, add,
490
        ctx['year'] = self.year
491
        ctx['semester'] = self.semester
492
        
493
        ctx['worksheets'] = self.context.worksheets
494
        
495
        ctx['mediapath'] = media_url(req, Plugin, 'images/')
1099.1.212 by Nick Chadwick
Added a new page to display exercises. This will then be modified to
496
497
498
class ExerciseEditView(XHTMLView):
499
    """View for editing a worksheet."""
500
    
501
    permission = 'edit'
502
    template = 'templates/exercise_edit.html'
503
    
504
    def __init__(self, req, exercise):
505
        self.context = req.store.find(Exercise, 
506
            Exercise.id == exercise).one()
507
508
        if self.context is None:
509
            raise NotFound()
510
    
511
    def populate(self, req, ctx):
512
        self.plugin_styles[Plugin] = ['exercise_admin.css']
513
        self.plugin_scripts[Plugin] = ['exercise_admin.js']
1099.6.4 by Nick Chadwick
Exercise UI is now ready to be merged into trunk.
514
            
515
        ctx['mediapath'] = media_url(req, Plugin, 'images/')
1099.1.212 by Nick Chadwick
Added a new page to display exercises. This will then be modified to
516
        
517
        ctx['exercise'] = self.context
518
        #XXX: These should come from somewhere else
519
520
        ctx['var_types'] = (u'file', u'var', u'arg', u'exception')
521
        ctx['part_types'] = (u'stdout',u'stderr', u'result',
522
                             u'exception', u'file', u'code')
523
        
524
        ctx['test_types'] = ('norm', 'check')
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
525
1099.6.2 by Nick Chadwick
Added a listing of all exercises
526
class ExerciseDeleteView(XHTMLView):
527
    """View for confirming the deletion of an exercise."""
528
    
529
    permission = 'edit'
1099.6.3 by Nick Chadwick
Edited the exercise service to delete individual parts of an exercise.
530
    template = 'templates/exercise_delete.html'
1099.6.2 by Nick Chadwick
Added a listing of all exercises
531
    
532
    def __init__(self, req, exercise):
533
        self.context = req.store.find(Exercise,
534
            Exercise.id == exercise).one()
535
        
536
        if self.context is None:
537
            raise NotFound()
1099.1.233 by Nick Chadwick
Exercise objects in the database module, along with their test cases,
538
1099.6.2 by Nick Chadwick
Added a listing of all exercises
539
    def populate(self, req, ctx):
1099.1.233 by Nick Chadwick
Exercise objects in the database module, along with their test cases,
540
541
        # If post, delete the exercise, or display a message explaining that
542
        # the exercise cannot be deleted
543
        if req.method == 'POST':
544
            ctx['method'] = 'POST'
1099.1.242 by Nick Chadwick
Fixed a problem with exercise editor, which wasn't editing or adding
545
            try:
546
                self.context.delete()
547
                ctx['deleted'] = True
548
            except:
549
                ctx['deleted'] = False
1099.1.233 by Nick Chadwick
Exercise objects in the database module, along with their test cases,
550
551
        # If get, display a delete confirmation page
552
        else:
553
            ctx['method'] = 'GET'
554
            if self.context.worksheet_exercises.count() is not 0:
555
                ctx['has_worksheets'] = True
556
            else:
557
                ctx['has_worksheets'] = False
558
        # Variables for the template
1099.6.2 by Nick Chadwick
Added a listing of all exercises
559
        ctx['exercise'] = self.context
1099.6.3 by Nick Chadwick
Edited the exercise service to delete individual parts of an exercise.
560
        ctx['path'] = "/+exercises/" + self.context.id + "/+delete"
1099.6.2 by Nick Chadwick
Added a listing of all exercises
561
1099.1.229 by Nick Chadwick
Fixed a slight oversight, which meant there was no way to add a new
562
class ExerciseAddView(XHTMLView):
563
    """View for creating a new exercise."""
564
    
565
    permission = 'edit'
566
    template = 'templates/exercise_add.html'
1099.1.234 by Nick Chadwick
Permissions for editing and deleting exercises now come from the
567
    #XXX: This should be done somewhere else
1099.1.229 by Nick Chadwick
Fixed a slight oversight, which meant there was no way to add a new
568
    def authorize(self, req):
569
        for offering in req.store.find(Offering):
570
            if 'edit' in offering.get_permissions(req.user):
571
                return True
572
        return False
573
        
574
    def populate(self, req, ctx):
575
        self.plugin_scripts[Plugin] = ['exercise_admin.js']
576
577
1099.6.2 by Nick Chadwick
Added a listing of all exercises
578
class ExercisesView(XHTMLView):
579
    """View for seeing the list of all exercises"""
580
    
581
    permission = 'edit'
582
    template = 'templates/exercises.html'
1099.1.234 by Nick Chadwick
Permissions for editing and deleting exercises now come from the
583
    #XXX: This should be done somewhere else
1099.6.2 by Nick Chadwick
Added a listing of all exercises
584
    def authorize(self, req):
585
        for offering in req.store.find(Offering):
586
            if 'edit' in offering.get_permissions(req.user):
587
                return True
588
        return False
589
    
590
    def populate(self, req, ctx):
591
        self.plugin_styles[Plugin] = ['exercise_admin.css']
592
        ctx['exercises'] = req.store.find(Exercise).order_by(Exercise.id)
593
        ctx['mediapath'] = media_url(req, Plugin, 'images/')
594
1099.1.99 by William Grant
Require that plugins providing media subclass MediaPlugin.
595
class Plugin(ViewPlugin, MediaPlugin):
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
596
    urls = [
1099.1.212 by Nick Chadwick
Added a new page to display exercises. This will then be modified to
597
        # Worksheet View Urls
1099.1.199 by William Grant
Move WorksheetAddView to +new (was +add), and clean it up a bit.
598
        ('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.
599
        ('subjects/:subject/:year/:semester/+worksheets', OfferingView),
1099.1.199 by William Grant
Move WorksheetAddView to +new (was +add), and clean it up a bit.
600
        ('subjects/:subject/:year/:semester/+worksheets/+new', WorksheetAddView),
1099.1.197 by Nick Chadwick
Modified worksheets edit view, so now there are links to edit, add,
601
        ('subjects/:subject/:year/:semester/+worksheets/+edit', WorksheetsEditView),
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
602
        ('subjects/:subject/:year/:semester/+worksheets/:worksheet', WorksheetView),
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
603
        ('subjects/:subject/:year/:semester/+worksheets/:worksheet/+edit', WorksheetEditView),
1099.1.212 by Nick Chadwick
Added a new page to display exercises. This will then be modified to
604
        
605
        # Worksheet Api Urls
1099.1.190 by Nick Chadwick
Renamed OfferingRESTView to WorksheetsRESTView
606
        ('api/subjects/:subject/:year/:semester/+worksheets', WorksheetsRESTView),
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
607
        ('api/subjects/:subject/:year/:semester/+worksheets/:worksheet/*exercise/'
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
608
            '+attempts/:username', AttemptsRESTView),
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
609
        ('api/subjects/:subject/:year/:semester/+worksheets/:worksheet/*exercise/'
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
610
                '+attempts/:username/:date', AttemptRESTView),
1099.4.3 by Nick Chadwick
Updated the tutorial service, to now allow users to edit worksheets
611
        ('api/subjects/:subject/:year/:semester/+worksheets/:worksheet', WorksheetRESTView),
1099.1.216 by Nick Chadwick
Started adding in add and save options in the exercise edit view, to
612
        ('api/subjects/:subject/:year/:semester/+worksheets/:worksheet/*exercise', WorksheetExerciseRESTView),
1099.1.228 by Nick Chadwick
Merged from trunk.
613
1099.1.212 by Nick Chadwick
Added a new page to display exercises. This will then be modified to
614
        # Exercise View Urls
1099.6.2 by Nick Chadwick
Added a listing of all exercises
615
        ('+exercises', ExercisesView),
1099.1.229 by Nick Chadwick
Fixed a slight oversight, which meant there was no way to add a new
616
        ('+exercises/+add', ExerciseAddView),
1099.1.212 by Nick Chadwick
Added a new page to display exercises. This will then be modified to
617
        ('+exercises/:exercise/+edit', ExerciseEditView),
1099.6.3 by Nick Chadwick
Edited the exercise service to delete individual parts of an exercise.
618
        ('+exercises/:exercise/+delete', ExerciseDeleteView),
1099.1.212 by Nick Chadwick
Added a new page to display exercises. This will then be modified to
619
        
620
        # Exercise Api Urls
1099.1.216 by Nick Chadwick
Started adding in add and save options in the exercise edit view, to
621
        ('api/+exercises', ExercisesRESTView),
1099.1.217 by Nick Chadwick
working on making the exercise editor complete
622
        ('api/+exercises/*exercise', ExerciseRESTView),
1099.1.19 by William Grant
ivle.webapp.tutorial: Port www/apps/tutorial to new framework.
623
    ]
1099.1.64 by William Grant
Move ivle.webapp.tutorial's media to the new framework. This also fixes the
624
625
    media = 'media'
1099.1.100 by Nick Chadwick
Created a new help system.
626
    help = {'Tutorial': 'help.html'}