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

« back to all changes in this revision

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

Error views are now retrieved from a class method of the view, not the plugin
registry. They also now discriminate based on exception type.

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
 
22
22
import os
23
23
import datetime
24
 
import genshi
25
24
 
 
25
import ivle.util
26
26
import ivle.console
27
27
import ivle.database
28
 
from ivle.database import Exercise, ExerciseAttempt, ExerciseSave, Worksheet, \
29
 
                          Offering, Subject, Semester, WorksheetExercise
30
 
import ivle.worksheet.utils
 
28
import ivle.worksheet
 
29
import ivle.conf
31
30
import ivle.webapp.tutorial.test
32
 
from ivle.webapp.base.rest import (JSONRESTView, named_operation,
33
 
                                   require_permission)
 
31
 
 
32
from ivle.webapp.base.rest import JSONRESTView, named_operation
34
33
from ivle.webapp.errors import NotFound
35
34
 
36
35
# If True, getattempts or getattempt will allow browsing of inactive/disabled
39
38
 
40
39
TIMESTAMP_FORMAT = '%Y-%m-%d %H:%M:%S'
41
40
 
 
41
 
42
42
class AttemptsRESTView(JSONRESTView):
43
43
    '''REST view of a user's attempts at an exercise.'''
44
 
    
45
 
    def __init__(self, req, subject, year, semester, worksheet, 
46
 
                                                exercise, username):
 
44
    def __init__(self, req, subject, worksheet, exercise, username):
 
45
        # TODO: Find exercise within worksheet.
47
46
        self.user = ivle.database.User.get_by_login(req.store, username)
48
47
        if self.user is None:
49
48
            raise NotFound()
50
 
        
51
 
        self.worksheet_exercise = req.store.find(WorksheetExercise,
52
 
            WorksheetExercise.exercise_id == unicode(exercise),
53
 
            WorksheetExercise.worksheet_id == Worksheet.id,
54
 
            Worksheet.offering_id == Offering.id,
55
 
            Worksheet.identifier == unicode(worksheet),
56
 
            Offering.subject_id == Subject.id,
57
 
            Subject.short_name == subject,
58
 
            Offering.semester_id == Semester.id,
59
 
            Semester.year == year,
60
 
            Semester.semester == semester).one()
61
 
        
62
 
        self.context = self.user # XXX: Not quite right.
 
49
        self.exercise = exercise
63
50
 
64
 
    @require_permission('edit')
65
51
    def GET(self, req):
66
52
        """Handles a GET Attempts action."""
67
 
        attempts = req.store.find(ExerciseAttempt, 
68
 
                ExerciseAttempt.ws_ex_id == self.worksheet_exercise.id,
69
 
                ExerciseAttempt.user_id == self.user.id)
 
53
        exercise = ivle.database.Exercise.get_by_name(req.store, 
 
54
                                                        self.exercise)
 
55
 
 
56
        attempts = ivle.worksheet.get_exercise_attempts(req.store, self.user,
 
57
                            exercise, allow_inactive=HISTORY_ALLOW_INACTIVE)
70
58
        # attempts is a list of ExerciseAttempt objects. Convert to dictionaries
71
59
        time_fmt = lambda dt: datetime.datetime.strftime(dt, TIMESTAMP_FORMAT)
72
60
        attempts = [{'date': time_fmt(a.date), 'complete': a.complete}
75
63
        return attempts
76
64
 
77
65
 
78
 
    @require_permission('edit')
79
66
    def PUT(self, req, data):
80
 
        """ Tests the given submission """
81
 
        exercise = req.store.find(Exercise, 
82
 
            Exercise.id == self.worksheet_exercise.exercise_id).one()
83
 
        if exercise is None:
 
67
        ''' Tests the given submission '''
 
68
        exercisefile = ivle.util.open_exercise_file(self.exercise)
 
69
        if exercisefile is None:
84
70
            raise NotFound()
85
71
 
86
72
        # Start a console to run the tests on
87
 
        jail_path = os.path.join(req.config['paths']['jails']['mounts'],
88
 
                                 req.user.login)
 
73
        jail_path = os.path.join(ivle.conf.jail_base, req.user.login)
89
74
        working_dir = os.path.join("/home", req.user.login)
90
75
        cons = ivle.console.Console(req.user.unixid, jail_path, working_dir)
91
76
 
92
77
        # Parse the file into a exercise object using the test suite
93
78
        exercise_obj = ivle.webapp.tutorial.test.parse_exercise_file(
94
 
                                                            exercise, cons)
 
79
                                                            exercisefile, cons)
 
80
        exercisefile.close()
95
81
 
96
82
        # Run the test cases. Get the result back as a JSONable object.
97
83
        # Return it.
100
86
        # Close the console
101
87
        cons.close()
102
88
 
 
89
        # Get the Exercise from the database
 
90
        exercise = ivle.database.Exercise.get_by_name(req.store, self.exercise)
 
91
 
103
92
        attempt = ivle.database.ExerciseAttempt(user=req.user,
104
 
            worksheet_exercise = self.worksheet_exercise,
105
 
            date = datetime.datetime.now(),
106
 
            complete = test_results['passed'],
107
 
            text = unicode(data['code'])
108
 
        )
 
93
                                                exercise=exercise,
 
94
                                                date=datetime.datetime.now(),
 
95
                                                complete=test_results['passed'],
 
96
                                                # XXX
 
97
                                                text=unicode(data['code']))
109
98
 
110
99
        req.store.add(attempt)
111
100
 
112
101
        # Query the DB to get an updated score on whether or not this problem
113
102
        # has EVER been completed (may be different from "passed", if it has
114
103
        # been completed before), and the total number of attempts.
115
 
        completed, attempts = ivle.worksheet.utils.get_exercise_status(
116
 
                req.store, req.user, self.worksheet_exercise)
 
104
        completed, attempts = ivle.worksheet.get_exercise_status(req.store,
 
105
            req.user, exercise)
117
106
        test_results["completed"] = completed
118
107
        test_results["attempts"] = attempts
119
108
 
123
112
class AttemptRESTView(JSONRESTView):
124
113
    '''REST view of an exercise attempt.'''
125
114
 
126
 
    def __init__(self, req, subject, year, semester, worksheet, exercise, 
127
 
                 username, date):
 
115
    def __init__(self, req, subject, worksheet, exercise, username, date):
128
116
        # TODO: Find exercise within worksheet.
129
117
        user = ivle.database.User.get_by_login(req.store, username)
130
118
        if user is None:
135
123
        except ValueError:
136
124
            raise NotFound()
137
125
 
138
 
        # XXX Hack around Google Code issue #87
139
 
        # Query from the given date +1 secnod.
140
 
        # Date is in seconds (eg. 3:47:12), while the data is in finer time
141
 
        # (eg. 3:47:12.3625). The query "date <= 3:47:12" will fail because
142
 
        # 3:47:12.3625 is greater. Hence we do the query from +1 second,
143
 
        # "date <= 3:47:13", and it finds the correct submission, UNLESS there
144
 
        # are multiple submissions inside the same second.
145
 
        date += datetime.timedelta(seconds=1)
146
 
 
147
 
        worksheet_exercise = req.store.find(WorksheetExercise,
148
 
            WorksheetExercise.exercise_id == exercise,
149
 
            WorksheetExercise.worksheet_id == Worksheet.id,
150
 
            Worksheet.identifier == worksheet,
151
 
            Worksheet.offering_id == Offering.id,
152
 
            Offering.subject_id == Subject.id,
153
 
            Subject.short_name == subject,
154
 
            Offering.semester_id == Semester.id,
155
 
            Semester.year == year,
156
 
            Semester.semester == semester).one()
157
 
            
158
 
        attempt = ivle.worksheet.utils.get_exercise_attempt(req.store, user,
159
 
                        worksheet_exercise, as_of=date,
160
 
                        allow_inactive=HISTORY_ALLOW_INACTIVE) 
 
126
        exercise = ivle.database.Exercise.get_by_name(req.store, exercise)
 
127
        attempt = ivle.worksheet.get_exercise_attempt(req.store, user,
 
128
            exercise, as_of=date, allow_inactive=HISTORY_ALLOW_INACTIVE)
161
129
 
162
130
        if attempt is None:
163
131
            raise NotFound()
164
132
 
165
133
        self.context = attempt
166
134
 
167
 
    @require_permission('view')
168
135
    def GET(self, req):
169
136
        return {'code': self.context.text}
170
137
 
171
138
 
172
 
class WorksheetExerciseRESTView(JSONRESTView):
173
 
    '''REST view of a worksheet exercise.'''
174
 
 
175
 
    def __init__(self, req, subject, year, semester, worksheet, exercise):
176
 
        self.context = req.store.find(WorksheetExercise,
177
 
            WorksheetExercise.exercise_id == exercise,
178
 
            WorksheetExercise.worksheet_id == Worksheet.id,
179
 
            Worksheet.offering_id == Offering.id,
180
 
            Offering.subject_id == Subject.id,
181
 
            Subject.short_name == subject,
182
 
            Offering.semester_id == Semester.id,
183
 
            Semester.year == year,
184
 
            Semester.semester == semester).one()
185
 
        
186
 
        if self.context is None:
187
 
            raise NotFound()
188
 
 
189
 
    @named_operation('view')
 
139
class ExerciseRESTView(JSONRESTView):
 
140
    '''REST view of an exercise.'''
 
141
    @named_operation
190
142
    def save(self, req, text):
191
 
        # Find the appropriate WorksheetExercise to save to. If its not found,
192
 
        # the user is submitting against a non-existant worksheet/exercise
193
 
 
194
 
        old_save = req.store.find(ExerciseSave,
195
 
            ExerciseSave.ws_ex_id == self.context.id,
196
 
            ExerciseSave.user == req.user).one()
197
 
        
198
 
        #Overwrite the old, or create a new if there isn't one
199
 
        if old_save is None:
200
 
            new_save = ExerciseSave()
201
 
            req.store.add(new_save)
202
 
        else:
203
 
            new_save = old_save
204
 
        
205
 
        new_save.worksheet_exercise = self.context
206
 
        new_save.user = req.user
207
 
        new_save.text = unicode(text)
208
 
        new_save.date = datetime.datetime.now()
209
 
 
210
 
        return {"result": "ok"}
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 __init__(self, req, **kwargs):
219
 
    
220
 
        self.worksheet = kwargs['worksheet']
221
 
        self.subject = kwargs['subject']
222
 
        self.year = kwargs['year']
223
 
        self.semester = kwargs['semester']
224
 
    
225
 
        self.context = req.store.find(Worksheet,
226
 
            Worksheet.identifier == self.worksheet,
227
 
            Worksheet.offering_id == Offering.id,
228
 
            Offering.subject_id == Subject.id,
229
 
            Subject.short_name == self.subject,
230
 
            Offering.semester_id == Semester.id,
231
 
            Semester.year == self.year,
232
 
            Semester.semester == self.semester).one()
233
 
        
234
 
        if self.context is None:
235
 
            raise NotFound()
236
 
    
237
 
    @named_operation('edit')
238
 
    def save(self, req, name, assessable, data, format):
239
 
        """Takes worksheet data and saves it."""
240
 
        self.context.name = unicode(name)
241
 
        self.context.assessable = self.convert_bool(assessable)
242
 
        self.context.data = unicode(data)
243
 
        self.context.format = unicode(format)
244
 
        ivle.worksheet.utils.update_exerciselist(self.context)
245
 
        
246
 
        return {"result": "ok"}
247
 
 
248
 
class WorksheetsRESTView(JSONRESTView):
249
 
    """View used to update and create Worksheets."""
250
 
    
251
 
    def __init__(self, req, **kwargs):
252
 
    
253
 
        self.subject = kwargs['subject']
254
 
        self.year = kwargs['year']
255
 
        self.semester = kwargs['semester']
256
 
    
257
 
        self.context = req.store.find(Offering,
258
 
            Offering.subject_id == Subject.id,
259
 
            Subject.short_name == self.subject,
260
 
            Offering.semester_id == Semester.id,
261
 
            Semester.year == self.year,
262
 
            Semester.semester == self.semester).one()
263
 
        
264
 
        if self.context is None:
265
 
            raise NotFound()
266
 
 
267
 
    @named_operation('edit')
268
 
    def add_worksheet(self, req, identifier, name, assessable, data, format):
269
 
        """Takes worksheet data and adds it."""
270
 
        
271
 
        new_worksheet = Worksheet()
272
 
        new_worksheet.seq_no = self.context.worksheets.count()
273
 
        # Setting new_worksheet.offering implicitly adds new_worksheet,
274
 
        # hence worksheets.count MUST be called above it
275
 
        new_worksheet.offering = self.context
276
 
        new_worksheet.identifier = unicode(identifier)
277
 
        new_worksheet.name = unicode(name)
278
 
        new_worksheet.assessable = self.convert_bool(assessable)
279
 
        new_worksheet.data = unicode(data)
280
 
        new_worksheet.format = unicode(format)
281
 
        
282
 
        # This call is added for clarity, as the worksheet is implicitly added.        
283
 
        req.store.add(new_worksheet)
284
 
 
285
 
        ivle.worksheet.utils.update_exerciselist(new_worksheet)
286
 
 
287
 
        return {"result": "ok"}
288
 
 
289
 
    @named_operation('edit')
290
 
    def move_up(self, req, worksheetid):
291
 
        """Takes a list of worksheet-seq_no pairs and updates their 
292
 
        corresponding Worksheet objects to match."""
293
 
        
294
 
        worksheet_below = req.store.find(Worksheet,
295
 
            Worksheet.offering_id == self.context.id,
296
 
            Worksheet.identifier == unicode(worksheetid)).one()
297
 
        if worksheet_below is None:
298
 
            raise NotFound('worksheet_below')
299
 
        worksheet_above = req.store.find(Worksheet,
300
 
            Worksheet.offering_id == self.context.id,
301
 
            Worksheet.seq_no == (worksheet_below.seq_no - 1)).one()
302
 
        if worksheet_above is None:
303
 
            raise NotFound('worksheet_above')
304
 
 
305
 
        worksheet_below.seq_no = worksheet_below.seq_no - 1
306
 
        worksheet_above.seq_no = worksheet_above.seq_no + 1
307
 
        
308
 
        return {'result': 'ok'}
309
 
 
310
 
    @named_operation('edit')
311
 
    def move_down(self, req, worksheetid):
312
 
        """Takes a list of worksheet-seq_no pairs and updates their 
313
 
        corresponding Worksheet objects to match."""
314
 
        
315
 
        worksheet_above = req.store.find(Worksheet,
316
 
            Worksheet.offering_id == self.context.id,
317
 
            Worksheet.identifier == unicode(worksheetid)).one()
318
 
        if worksheet_above is None:
319
 
            raise NotFound('worksheet_below')
320
 
        worksheet_below = req.store.find(Worksheet,
321
 
            Worksheet.offering_id == self.context.id,
322
 
            Worksheet.seq_no == (worksheet_above.seq_no + 1)).one()
323
 
        if worksheet_below is None:
324
 
            raise NotFound('worksheet_above')
325
 
 
326
 
        worksheet_below.seq_no = worksheet_below.seq_no - 1
327
 
        worksheet_above.seq_no = worksheet_above.seq_no + 1
328
 
        
329
 
        return {'result': 'ok'}
 
143
        # Need to open JUST so we know this is a real exercise.
 
144
        # (This avoids users submitting code for bogus exercises).
 
145
        exercisefile = ivle.util.open_exercise_file(self.exercise)
 
146
        if exercisefile is None:
 
147
            raise NotFound()
 
148
        exercisefile.close()
 
149
 
 
150
        exercise = ivle.database.Exercise.get_by_name(req.store, self.exercise)
 
151
        ivle.worksheet.save_exercise(req.store, req.user, exercise,
 
152
                                     unicode(text), datetime.datetime.now())
 
153
        return {"result": "ok"}