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

« back to all changes in this revision

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

ivle.webapp.groups: Remove most of the view code; the template now accesses
    the objects directly. Also fix a few double-encodings and typos.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# IVLE - Informatics Virtual Learning Environment
2
 
# Copyright (C) 2007-2009 The University of Melbourne
 
2
# Copyright (C) 2007-2008 The University of Melbourne
3
3
#
4
4
# This program is free software; you can redistribute it and/or modify
5
5
# it under the terms of the GNU General Public License as published by
15
15
# along with this program; if not, write to the Free Software
16
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
17
 
18
 
# Author: Matt Giuca, Nick Chadwick
19
 
 
20
 
'''AJAX backend for the tutorial application.'''
 
18
# Module: TutorialService
 
19
# Author: Matt Giuca
 
20
# Date:   25/1/2008
 
21
 
 
22
# Provides the AJAX backend for the tutorial application.
 
23
# This allows several actions to be performed on the code the student has
 
24
# typed into one of the exercise boxes.
 
25
 
 
26
# Calling syntax
 
27
# Path must be empty.
 
28
# The arguments determine what is to be done on this file.
 
29
 
 
30
# "action". One of the tutorialservice actions.
 
31
# "exercise" - The path to a exercise file (including the .xml extension),
 
32
#              relative to the subjects base directory.
 
33
# action "save" or "test" (POST only):
 
34
#   "code" - Full text of the student's code being submitted.
 
35
# action "getattempts": No arguments. Returns a list of
 
36
#   {'date': 'formatted_date', 'complete': bool} dicts.
 
37
# action "getattempt":
 
38
#   "date" - Formatted date. Gets most recent attempt before (and including)
 
39
#   that date.
 
40
#   Returns JSON string containing code, or null.
 
41
 
 
42
# Returns a JSON response string indicating the results.
21
43
 
22
44
import os
 
45
import time
23
46
import datetime
24
 
import genshi
25
 
 
26
 
import ivle.util
27
 
import ivle.console
 
47
 
 
48
import cjson
 
49
 
 
50
from ivle import util
 
51
from ivle import console
28
52
import ivle.database
29
 
from ivle.database import Exercise, ExerciseAttempt, ExerciseSave, Worksheet, \
30
 
                          Offering, Subject, Semester, WorksheetExercise
31
53
import ivle.worksheet
32
54
import ivle.conf
33
 
import ivle.webapp.tutorial.test
 
55
import test # XXX: Really .test, not real test.
34
56
 
35
 
from ivle.webapp.base.rest import (JSONRESTView, named_operation,
36
 
                                   require_permission)
37
 
from ivle.webapp.errors import NotFound
 
57
from ivle.webapp.base.rest import JSONRESTView
 
58
import ivle.database
 
59
from ivle.webapp.base.rest import named_operation
38
60
 
39
61
# If True, getattempts or getattempt will allow browsing of inactive/disabled
40
62
# attempts. If False, will not allow this.
42
64
 
43
65
TIMESTAMP_FORMAT = '%Y-%m-%d %H:%M:%S'
44
66
 
 
67
 
45
68
class AttemptsRESTView(JSONRESTView):
46
 
    '''REST view of a user's attempts at an exercise.'''
47
 
    
48
 
    def __init__(self, req, subject, year, semester, worksheet, 
49
 
                                                exercise, username):
50
 
        self.user = ivle.database.User.get_by_login(req.store, username)
51
 
        if self.user is None:
52
 
            raise NotFound()
53
 
        
54
 
        self.worksheet_exercise = req.store.find(WorksheetExercise,
55
 
            WorksheetExercise.exercise_id == exercise,
56
 
            WorksheetExercise.worksheet_id == Worksheet.id,
57
 
            Worksheet.offering_id == Offering.id,
58
 
            Offering.subject_id == Subject.id,
59
 
            Subject.code == subject,
60
 
            Offering.semester_id == Semester.id,
61
 
            Semester.year == year,
62
 
            Semester.semester == semester).one()
63
 
        
64
 
        self.context = self.user # XXX: Not quite right.
65
 
 
66
 
    @require_permission('edit')
 
69
    '''
 
70
    Class to return a list of attempts for a given exercise, or add an Attempt
 
71
    '''
67
72
    def GET(self, req):
68
73
        """Handles a GET Attempts action."""
69
 
        attempts = req.store.find(ExerciseAttempt, 
70
 
                ExerciseAttempt.ws_ex_id == self.worksheet_exercise.id,
71
 
                ExerciseAttempt.user_id == self.user.id)
 
74
        exercise = ivle.database.Exercise.get_by_name(req.store, 
 
75
                                                        self.exercise)
 
76
        user = ivle.database.User.get_by_login(req.store, self.username)
 
77
 
 
78
        attempts = ivle.worksheet.get_exercise_attempts(req.store, user,
 
79
                            exercise, allow_inactive=HISTORY_ALLOW_INACTIVE)
72
80
        # attempts is a list of ExerciseAttempt objects. Convert to dictionaries
73
81
        time_fmt = lambda dt: datetime.datetime.strftime(dt, TIMESTAMP_FORMAT)
74
82
        attempts = [{'date': time_fmt(a.date), 'complete': a.complete}
75
83
                for a in attempts]
76
 
 
 
84
        attempts.append(self.exercise)
 
85
        
77
86
        return attempts
78
 
 
79
 
 
80
 
    @require_permission('edit')
 
87
        
81
88
    def PUT(self, req, data):
82
 
        """ Tests the given submission """
83
 
        exercise = req.store.find(Exercise, 
84
 
            Exercise.id == self.worksheet_exercise.exercise_id).one()
85
 
        if exercise is None:
86
 
            raise NotFound()
 
89
        
 
90
        exercisefile = util.open_exercise_file(self.exercise)
 
91
        if exercisefile is None:
 
92
            req.throw_error(req.HTTP_NOT_FOUND,
 
93
                "The exercise was not found.")
87
94
 
88
95
        # Start a console to run the tests on
89
96
        jail_path = os.path.join(ivle.conf.jail_base, req.user.login)
90
97
        working_dir = os.path.join("/home", req.user.login)
91
 
        cons = ivle.console.Console(req.user.unixid, jail_path, working_dir)
 
98
        cons = console.Console(req.user.unixid, jail_path, working_dir)
92
99
 
93
100
        # Parse the file into a exercise object using the test suite
94
 
        exercise_obj = ivle.webapp.tutorial.test.parse_exercise_file(
95
 
                                                            exercise, cons)
 
101
        exercise_obj = test.parse_exercise_file(exercisefile, cons)
 
102
        exercisefile.close()
96
103
 
97
104
        # Run the test cases. Get the result back as a JSONable object.
98
105
        # Return it.
99
 
        test_results = exercise_obj.run_tests(data['code'])
 
106
        test_results = exercise_obj.run_tests(code)
100
107
 
101
108
        # Close the console
102
109
        cons.close()
103
110
 
 
111
        # Get the Exercise from the database
 
112
        exercise = ivle.database.Exercise.get_by_name(req.store, exercisesrc)
 
113
 
104
114
        attempt = ivle.database.ExerciseAttempt(user=req.user,
105
 
            worksheet_exercise = self.worksheet_exercise,
106
 
            date = datetime.datetime.now(),
107
 
            complete = test_results['passed'],
108
 
            text = unicode(data['code'])
109
 
        )
 
115
                                                exercise=exercise,
 
116
                                                date=datetime.datetime.now(),
 
117
                                                complete=test_results['passed'],
 
118
                                                text=unicode(code)) # XXX
110
119
 
111
120
        req.store.add(attempt)
112
 
 
 
121
        req.store.commit()
113
122
        # Query the DB to get an updated score on whether or not this problem
114
123
        # has EVER been completed (may be different from "passed", if it has
115
124
        # been completed before), and the total number of attempts.
116
125
        completed, attempts = ivle.worksheet.get_exercise_status(req.store,
117
 
            req.user, self.worksheet_exercise)
 
126
            req.user, exercise)
118
127
        test_results["completed"] = completed
119
128
        test_results["attempts"] = attempts
120
129
 
121
130
        return test_results
122
 
 
 
131
        
123
132
 
124
133
class AttemptRESTView(JSONRESTView):
125
 
    '''REST view of an exercise attempt.'''
126
 
 
127
 
    def __init__(self, req, subject, year, semester, worksheet, exercise, 
128
 
                 username, date):
129
 
        # TODO: Find exercise within worksheet.
130
 
        user = ivle.database.User.get_by_login(req.store, username)
131
 
        if user is None:
132
 
            raise NotFound()
133
 
 
134
 
        try:
135
 
            date = datetime.datetime.strptime(date, TIMESTAMP_FORMAT)
136
 
        except ValueError:
137
 
            raise NotFound()
138
 
 
139
 
        worksheet_exercise = req.store.find(WorksheetExercise,
140
 
            WorksheetExercise.exercise_id == exercise,
141
 
            WorksheetExercise.worksheet_id == Worksheet.id,
142
 
            Worksheet.identifier == worksheet,
143
 
            Worksheet.offering_id == Offering.id,
144
 
            Offering.subject_id == Subject.id,
145
 
            Subject.code == subject,
146
 
            Offering.semester_id == Semester.id,
147
 
            Semester.year == year,
148
 
            Semester.semester == semester).one()
149
 
            
150
 
        attempt = ivle.worksheet.get_exercise_attempt(req.store, user,
151
 
                        worksheet_exercise, as_of=date,
152
 
                        allow_inactive=HISTORY_ALLOW_INACTIVE) 
153
 
 
154
 
        if attempt is None:
155
 
            raise NotFound()
156
 
 
157
 
        self.context = attempt
158
 
 
159
 
    @require_permission('view')
 
134
    '''
 
135
    View used to extract the data of a specified attempt
 
136
    '''
 
137
    
160
138
    def GET(self, req):
161
 
        return {'code': self.context.text}
162
 
 
163
 
 
 
139
        # Get an actual date object, rather than a string
 
140
        date = datetime.datetime.strptime(self.date, TIMESTAMP_FORMAT)
 
141
        
 
142
        exercise = ivle.database.Exercise.get_by_name(req.store, self.exercise)
 
143
        attempt = ivle.worksheet.get_exercise_attempt(req.store, req.user,
 
144
            exercise, as_of=date, allow_inactive=HISTORY_ALLOW_INACTIVE)
 
145
        if attempt is not None:
 
146
            attempt = attempt.text
 
147
        # attempt may be None; will write "null"
 
148
        return {'code': attempt}
 
149
        
164
150
class ExerciseRESTView(JSONRESTView):
165
 
    '''REST view of an exercise.'''
166
 
 
167
 
    def get_permissions(self, user):
168
 
        # XXX: Do it properly.
169
 
        # XXX: Does any user have the ability to save as themselves?
170
 
        # XXX: Does a user EVER have permission to save as another user?
171
 
        if user is not None:
172
 
            return set(['save'])
173
 
        else:
174
 
            return set()
175
 
 
176
 
    @named_operation('save')
177
 
    def save(self, req, text):
178
 
        # Find the appropriate WorksheetExercise to save to. If its not found,
179
 
        # the user is submitting against a non-existant worksheet/exercise
180
 
        worksheet_exercise = req.store.find(WorksheetExercise,
181
 
            WorksheetExercise.exercise_id == self.exercise,
182
 
            WorksheetExercise.worksheet_id == Worksheet.id,
183
 
            Worksheet.offering_id == Offering.id,
184
 
            Offering.subject_id == Subject.id,
185
 
            Subject.code == self.subject,
186
 
            Offering.semester_id == Semester.id,
187
 
            Semester.year == self.year,
188
 
            Semester.semester == self.semester).one()
189
 
        
190
 
        if worksheet_exercise is None:
191
 
            raise NotFound()
192
 
 
193
 
        old_save = req.store.find(ExerciseSave,
194
 
            ExerciseSave.ws_ex_id == worksheet_exercise.id,
195
 
            ExerciseSave.user == req.user).one()
196
 
        
197
 
        #Overwrite the old, or create a new if there isn't one
198
 
        if old_save is None:
199
 
            new_save = ExerciseSave()
200
 
            req.store.add(new_save)
201
 
        else:
202
 
            new_save = old_save
203
 
        
204
 
        new_save.worksheet_exercise = worksheet_exercise
205
 
        new_save.user = req.user
206
 
        new_save.text = unicode(text)
207
 
        new_save.date = datetime.datetime.now()
208
 
 
209
 
        return {"result": "ok"}
210
 
 
211
 
 
212
 
 
213
 
# Note that this is the view of an existing worksheet. Creation is handled
214
 
# by OfferingRESTView (as offerings have worksheets)
215
 
class WorksheetRESTView(JSONRESTView):
216
 
    """View used to update a worksheet."""
217
 
 
218
 
    def get_permissions(self, user):
219
 
        # XXX: Do it properly.
220
 
        # XXX: Lecturers should be allowed to add worksheets Only to subjects
221
 
        #      under their control
222
 
        if user is not None:
223
 
            if user.admin:
224
 
                return set(['save'])
225
 
            else:
226
 
                return set()
227
 
        else:
228
 
            return set()    
229
 
 
230
 
    def __init__(self, req, **kwargs):
231
 
    
232
 
        self.worksheet = kwargs['worksheet']
233
 
        self.subject = kwargs['subject']
234
 
        self.year = kwargs['year']
235
 
        self.semester = kwargs['semester']
236
 
    
237
 
        self.context = req.store.find(Worksheet,
238
 
            Worksheet.identifier == self.worksheet,
239
 
            Worksheet.offering_id == Offering.id,
240
 
            Offering.subject_id == Subject.id,
241
 
            Subject.code == self.subject,
242
 
            Offering.semester_id == Semester.id,
243
 
            Semester.year == self.year,
244
 
            Semester.semester == self.semester).one()
245
 
        
246
 
        if self.context is None:
247
 
            raise NotFound()
248
 
    
249
 
    @named_operation('save')
250
 
    def save(self, req, name, assessable, data, format):
251
 
        """Takes worksheet data and saves it."""
252
 
        self.context.name = unicode(name)
253
 
        self.context.assessable = self.convert_bool(assessable)
254
 
        self.context.data = unicode(data)
255
 
        self.context.format = unicode(format)
256
 
        ivle.worksheet.update_exerciselist(self.context)
257
 
        
258
 
        return {"result": "ok"}
259
 
 
260
 
class WorksheetsRESTView(JSONRESTView):
261
 
    """View used to update and create Worksheets."""
262
 
    
263
 
    def get_permissions(self, user):
264
 
        # XXX: Do it properly.
265
 
        # XXX: Lecturers should be allowed to add worksheets Only to subjects
266
 
        #      under their control
267
 
        if user is not None:
268
 
            if user.admin:
269
 
                return set(['edit'])
270
 
            else:
271
 
                return set()
272
 
        else:
273
 
            return set()
274
 
 
275
 
    def __init__(self, req, **kwargs):
276
 
    
277
 
        self.subject = kwargs['subject']
278
 
        self.year = kwargs['year']
279
 
        self.semester = kwargs['semester']
280
 
    
281
 
        self.context = req.store.find(Offering,
282
 
            Offering.subject_id == Subject.id,
283
 
            Subject.code == self.subject,
284
 
            Offering.semester_id == Semester.id,
285
 
            Semester.year == self.year,
286
 
            Semester.semester == self.semester).one()
287
 
        
288
 
        if self.context is None:
289
 
            raise NotFound()
290
 
 
291
 
    @named_operation('edit')
292
 
    def add_worksheet(self, req, identifier, name, assessable, data, format):
293
 
        """Takes worksheet data and adds it."""
294
 
        
295
 
        new_worksheet = Worksheet()
296
 
        new_worksheet.seq_no = self.context.worksheets.count()
297
 
        # Setting new_worksheet.offering implicitly adds new_worksheet,
298
 
        # hence worksheets.count MUST be called above it
299
 
        new_worksheet.offering = self.context
300
 
        new_worksheet.identifier = unicode(identifier)
301
 
        new_worksheet.name = unicode(name)
302
 
        new_worksheet.assessable = self.convert_bool(assessable)
303
 
        new_worksheet.data = unicode(data)
304
 
        new_worksheet.format = unicode(format)
305
 
        
306
 
        # This call is added for clarity, as the worksheet is implicitly added.        
307
 
        req.store.add(new_worksheet)
308
 
        
309
 
        ivle.worksheet.update_exerciselist(new_worksheet)
310
 
        
311
 
        return {"result": "ok"}
312
 
 
313
 
    @named_operation('edit')
314
 
    def move_up(self, req, worksheetid):
315
 
        """Takes a list of worksheet-seq_no pairs and updates their 
316
 
        corresponding Worksheet objects to match."""
317
 
        
318
 
        worksheet_below = req.store.find(Worksheet,
319
 
            Worksheet.offering_id == self.context.id,
320
 
            Worksheet.identifier == unicode(worksheetid)).one()
321
 
        if worksheet_below is None:
322
 
            raise NotFound('worksheet_below')
323
 
        worksheet_above = req.store.find(Worksheet,
324
 
            Worksheet.offering_id == self.context.id,
325
 
            Worksheet.seq_no == (worksheet_below.seq_no - 1)).one()
326
 
        if worksheet_above is None:
327
 
            raise NotFound('worksheet_above')
328
 
 
329
 
        worksheet_below.seq_no = worksheet_below.seq_no - 1
330
 
        worksheet_above.seq_no = worksheet_above.seq_no + 1
331
 
        
332
 
        return {'result': 'ok'}
333
 
 
334
 
    @named_operation('edit')
335
 
    def move_down(self, req, worksheetid):
336
 
        """Takes a list of worksheet-seq_no pairs and updates their 
337
 
        corresponding Worksheet objects to match."""
338
 
        
339
 
        worksheet_above = req.store.find(Worksheet,
340
 
            Worksheet.offering_id == self.context.id,
341
 
            Worksheet.identifier == unicode(worksheetid)).one()
342
 
        if worksheet_above is None:
343
 
            raise NotFound('worksheet_below')
344
 
        worksheet_below = req.store.find(Worksheet,
345
 
            Worksheet.offering_id == self.context.id,
346
 
            Worksheet.seq_no == (worksheet_above.seq_no + 1)).one()
347
 
        if worksheet_below is None:
348
 
            raise NotFound('worksheet_above')
349
 
 
350
 
        worksheet_below.seq_no = worksheet_below.seq_no - 1
351
 
        worksheet_above.seq_no = worksheet_above.seq_no + 1
352
 
        
353
 
        return {'result': 'ok'}
 
151
    '''
 
152
    Handles a save action. This saves the user's code without executing it.
 
153
    '''
 
154
    @named_operation
 
155
    def save(self, req, user, text):    
 
156
        # Need to open JUST so we know this is a real exercise.
 
157
        # (This avoids users submitting code for bogus exercises).
 
158
        exercisefile = util.open_exercise_file(self.exercise)
 
159
        if exercisefile is None:
 
160
            req.throw_error(req.HTTP_NOT_FOUND,
 
161
                "The exercise was not found.")
 
162
        exercisefile.close()
 
163
 
 
164
        exercise = ivle.database.Exercise.get_by_name(req.store, self.exercise)
 
165
        ivle.worksheet.save_exercise(req.store, req.user, exercise,
 
166
                                     unicode(code), datetime.datetime.now())
 
167
        req.store.commit()
 
168
        return {"result": "ok"}