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

« back to all changes in this revision

Viewing changes to ivle/webapp/tutorial/__init__.py

Merge from new-dispatch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
35
35
import ivle.util
36
36
import ivle.conf
37
37
import ivle.database
38
 
from ivle.database import Subject
 
38
from ivle.database import Subject, Offering, Semester, Exercise, ExerciseSave
 
39
from ivle.database import Worksheet as DBWorksheet
39
40
import ivle.worksheet
40
41
from ivle.webapp.base.views import BaseView
41
42
from ivle.webapp.base.xhtml import XHTMLView
63
64
        return ("Worksheet(id=%s, name=%s, assessable=%s)"
64
65
                % (repr(self.id), repr(self.name), repr(self.assessable)))
65
66
 
66
 
class SubjectView(XHTMLView):
67
 
    '''The view of the index of worksheets for a subject.'''
 
67
class OfferingView(XHTMLView):
 
68
    '''The view of the index of worksheets for an offering.'''
68
69
    template = 'subjectmenu.html'
69
70
    appname = 'tutorial' # XXX
70
71
    permission = 'view'
71
72
 
72
 
    def __init__(self, req, subject):
73
 
        self.context = req.store.find(Subject, code=subject).one()
 
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()
74
80
 
75
81
    def populate(self, req, ctx):
76
82
        self.plugin_styles[Plugin] = ['tutorial.css']
79
85
            raise NotFound()
80
86
 
81
87
        # Subject names must be valid identifiers
82
 
        if not is_valid_subjname(self.context.code):
 
88
        if not is_valid_subjname(self.context.subject.code):
83
89
            raise NotFound()
84
90
 
85
91
        # Parse the subject description file
86
92
        # The subject directory must have a file "subject.xml" in it,
87
93
        # or it does not exist (404 error).
88
 
        ctx['subject'] = self.context.code
 
94
        ctx['subject'] = self.context.subject.code
89
95
        try:
90
96
            subjectfile = open(os.path.join(ivle.conf.subjects_base,
91
 
                                    self.context.code, "subject.xml")).read()
 
97
                                    self.context.subject.code, "subject.xml")).read()
92
98
        except:
93
99
            raise NotFound()
94
100
 
102
108
        problems_total = 0
103
109
        for worksheet in ctx['worksheets']:
104
110
            stored_worksheet = ivle.database.Worksheet.get_by_name(req.store,
105
 
                self.context.code, worksheet.id)
 
111
                self.context.subject.code, worksheet.id)
106
112
            # If worksheet is not in database yet, we'll simply not display
107
113
            # data about it yet (it should be added as soon as anyone visits
108
114
            # the worksheet itself).
160
166
    appname = 'tutorial' # XXX
161
167
    permission = 'view'
162
168
 
163
 
    def __init__(self, req, subject, worksheet):
 
169
    def __init__(self, req, subject, year, semester, worksheet):
164
170
        # XXX: Worksheet is actually context, but it's not really there yet.
165
 
        self.context = req.store.find(Subject, code=subject).one()
 
171
        self.context = req.store.find(DBWorksheet,
 
172
            DBWorksheet.offering_id == Offering.id,
 
173
            Offering.subject_id == Subject.id,
 
174
            Subject.code == subject,
 
175
            Offering.semester_id == Semester.id,
 
176
            Semester.year == year,
 
177
            Semester.semester == semester,
 
178
            DBWorksheet.name == worksheet).one()
 
179
        
166
180
        self.worksheetname = worksheet
 
181
        self.year = year
 
182
        self.semester = semester
167
183
 
168
184
    def populate(self, req, ctx):
169
185
        self.plugin_scripts[Plugin] = ['tutorial.js']
172
188
        if not self.context:
173
189
            raise NotFound()
174
190
 
175
 
        # Subject and worksheet names must be valid identifiers
176
 
        if not is_valid_subjname(self.context.code) or \
177
 
           not is_valid_subjname(self.worksheetname):
178
 
            raise NotFound()
179
 
 
180
191
        # Read in worksheet data
181
192
        worksheetfilename = os.path.join(ivle.conf.subjects_base,
182
 
                               self.context.code, self.worksheetname + ".xml")
 
193
                               self.context.offering.subject.code, self.worksheetname + ".xml")
183
194
        try:
184
195
            worksheetfile = open(worksheetfilename)
185
196
            worksheetmtime = os.path.getmtime(worksheetfilename)
189
200
        worksheetmtime = datetime.fromtimestamp(worksheetmtime)
190
201
        worksheetfile = worksheetfile.read()
191
202
 
192
 
        ctx['subject'] = self.context.code
 
203
        ctx['subject'] = self.context.offering.subject.code
193
204
        ctx['worksheet'] = self.worksheetname
 
205
        ctx['semester'] = self.semester
 
206
        ctx['year'] = self.year
194
207
        ctx['worksheetstream'] = genshi.Stream(list(genshi.XML(worksheetfile)))
195
208
 
196
209
        #TODO: Replace this with a nice way, possibly a match template
197
210
        generate_worksheet_data(ctx, req)
198
211
 
199
 
        update_db_worksheet(req.store, self.context.code, self.worksheetname,
 
212
        update_db_worksheet(req.store, self.context.offering.subject.code, self.worksheetname,
200
213
            worksheetmtime, ctx['exerciselist'])
201
214
 
202
215
        ctx['worksheetstream'] = add_exercises(ctx['worksheetstream'], ctx, req)
279
292
# build a Table of Contents, as well as fill in details in ctx
280
293
def generate_worksheet_data(ctx, req):
281
294
    """Runs through the worksheetstream, generating the exericises"""
282
 
    exid = 0
283
295
    ctx['exercises'] = []
284
296
    ctx['exerciselist'] = []
285
297
    for kind, data, pos in ctx['worksheetstream']:
286
298
        if kind is genshi.core.START:
287
299
            if data[0] == 'exercise':
288
 
                exid += 1
289
300
                src = ""
290
301
                optional = False
291
302
                for attr in data[1]:
294
305
                    if attr[0] == 'optional':
295
306
                        optional = attr[1] == 'true'
296
307
                # Each item in toc is of type (name, complete, stream)
297
 
                ctx['exercises'].append(present_exercise(req, src, exid))
 
308
                ctx['exercises'].append(present_exercise(req, src))
298
309
                ctx['exerciselist'].append((src, optional))
299
310
            elif data[0] == 'worksheet':
300
311
                ctx['worksheetname'] = 'bob'
327
338
 
328
339
#TODO: This needs to be re-written, to stop using minidom, and get the data
329
340
# about the worksheet directly from the database
330
 
def present_exercise(req, exercisesrc, exerciseid):
 
341
def present_exercise(req, exercisesrc):
331
342
    """Open a exercise file, and write out the exercise to the request in HTML.
332
343
    exercisesrc: "src" of the exercise file. A path relative to the top-level
333
344
        exercises base directory, as configured in conf.
336
347
    # we need
337
348
    curctx = genshi.template.Context()
338
349
    curctx['filename'] = exercisesrc
339
 
    curctx['exerciseid'] = exerciseid
340
350
 
341
351
    # Retrieve the exercise details from the database
342
 
    exercise = ivle.database.Exercise.get_by_name(req.store, exercisesrc)
343
 
    #Open the exercise, and double-check that it exists
344
 
    exercisefile = ivle.util.open_exercise_file(exercisesrc)
345
 
    if exercisefile is None:
 
352
    exercise = req.store.find(Exercise, Exercise.id == exercisesrc).one()
 
353
    
 
354
    if exercise is None:
346
355
        raise NotFound()
347
356
 
348
357
    # Read exercise file and present the exercise
351
360
    # fields from the XML.
352
361
 
353
362
    #TODO: Replace calls to minidom with calls to the database directly
354
 
    exercisedom = minidom.parse(exercisefile)
355
 
    exercisefile.close()
356
 
    exercisedom = exercisedom.documentElement
357
 
    assert exercisedom.tagName == "exercise", \
358
 
           "Exercise file top-level element must be <exercise>."
359
 
    curctx['exercisename'] = exercisedom.getAttribute("name")
360
 
 
361
 
    curctx['rows'] = exercisedom.getAttribute("rows")
362
 
    if not curctx['rows']:
363
 
        curctx['rows'] = "12"
364
 
    # Look for some other fields we need, which are elements:
365
 
    # - desc
366
 
    # - partial
367
 
    curctx['exercisedesc'] = None
368
 
    curctx['exercisepartial'] = ""
369
 
    for elem in exercisedom.childNodes:
370
 
        if elem.nodeType == elem.ELEMENT_NODE:
371
 
            if elem.tagName == "desc":
372
 
                curctx['exercisedesc'] = genshi.XML(
373
 
                                              rstfunc(innerXML(elem).strip()))
374
 
            if elem.tagName == "partial":
375
 
                curctx['exercisepartial'] = getTextData(elem) + '\n'
376
 
    curctx['exercisepartial_backup'] = curctx['exercisepartial']
 
363
    curctx['exercise'] = exercise
 
364
    if exercise.description is not None:
 
365
        curctx['description'] = genshi.XML('<div id="description">' + exercise.description + '</div>')
 
366
    else:
 
367
        curctx['description'] = None
377
368
 
378
369
    # If the user has already saved some text for this problem, or submitted
379
370
    # an attempt, then use that text instead of the supplied "partial".
380
 
    saved_text = ivle.worksheet.get_exercise_stored_text(req.store,
381
 
        req.user, exercise)
 
371
    save = req.store.find(ExerciseSave, ExerciseSave.exercise_id == exercise.id).one()
382
372
    # Also get the number of attempts taken and whether this is complete.
383
373
    complete, curctx['attempts'] = \
384
374
            ivle.worksheet.get_exercise_status(req.store, req.user, exercise)
385
 
    if saved_text is not None:
386
 
        curctx['exercisepartial'] = saved_text.text
 
375
    if save is not None:
 
376
        curctx['exercisepartial'] = save.text
387
377
    curctx['complete'] = 'Complete' if complete else 'Incomplete'
388
378
    curctx['complete_class'] = curctx['complete'].lower()
389
379
 
392
382
    loader = genshi.template.TemplateLoader(".", auto_reload=True)
393
383
    tmpl = loader.load(os.path.join(os.path.dirname(__file__), "exercise.html"))
394
384
    ex_stream = tmpl.generate(curctx)
395
 
    return {'name': curctx['exercisename'],
 
385
    return {'name': exercise.name,
396
386
            'complete': curctx['complete_class'],
397
387
            'stream': ex_stream,
398
 
            'exid': exerciseid}
 
388
            'exid': exercise.id}
399
389
 
400
390
 
401
391
def update_db_worksheet(store, subject, worksheetname, file_mtime,
442
432
        # Insert each exercise into the worksheet
443
433
        for exercise_name, optional in exercise_list:
444
434
            # Get the Exercise from the DB
445
 
            exercise = ivle.database.Exercise.get_by_name(store,exercise_name)
 
435
            exercise = store.find(Exercise, Exercise.id == exercise_name).one()
446
436
            # Create a new binding between the worksheet and the exercise
447
437
            worksheetexercise = ivle.database.WorksheetExercise(
448
438
                    worksheet=worksheet, exercise=exercise, optional=optional)
451
441
 
452
442
class Plugin(ViewPlugin, MediaPlugin):
453
443
    urls = [
454
 
        ('subjects/:subject/+worksheets', SubjectView),
 
444
        ('subjects/:subject/:year/:semester/+worksheets', OfferingView),
455
445
        ('subjects/:subject/+worksheets/+media/*(path)', SubjectMediaView),
456
 
        ('subjects/:subject/+worksheets/:worksheet', WorksheetView),
457
 
        ('api/subjects/:subject/+worksheets/:worksheet/*exercise/'
 
446
        ('subjects/:subject/:year/:semester/+worksheets/:worksheet', WorksheetView),
 
447
        ('api/subjects/:subject/:year/:semester/+worksheets/:worksheet/*exercise/'
458
448
            '+attempts/:username', AttemptsRESTView),
459
 
        ('api/subjects/:subject/+worksheets/:worksheet/*exercise/'
 
449
        ('api/subjects/:subject/:year/:semester/+worksheets/:worksheet/*exercise/'
460
450
                '+attempts/:username/:date', AttemptRESTView),
461
 
        ('api/subjects/:subject/+worksheets/:worksheet/*exercise', ExerciseRESTView),
 
451
        ('api/subjects/:subject/:year/:semester/+worksheets/:worksheet/*exercise', ExerciseRESTView),
462
452
    ]
463
453
 
464
454
    tabs = [