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