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

« back to all changes in this revision

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

  • Committer: Matt Giuca
  • Date: 2010-07-27 06:53:03 UTC
  • Revision ID: matt.giuca@gmail.com-20100727065303-gs7fn3gc3ccaqfux
Changed database schema 'semester' table. 'year' and 'semester' fields now allow any length, not just 4 or 1 respectively. (LP: #610330).

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
 
20
20
'''AJAX backend for the tutorial application.'''
21
21
 
22
 
import os
23
22
import datetime
 
23
 
24
24
import genshi
 
25
from storm.locals import Store
25
26
 
26
 
import ivle.util
27
 
import ivle.console
28
27
import ivle.database
29
28
from ivle.database import Exercise, ExerciseAttempt, ExerciseSave, Worksheet, \
30
 
                          Offering, Subject, Semester, WorksheetExercise
31
 
import ivle.worksheet
32
 
import ivle.conf
33
 
import ivle.webapp.tutorial.test
34
 
 
35
 
from ivle.webapp.base.rest import (JSONRESTView, named_operation,
 
29
                          Offering, Subject, Semester, User, WorksheetExercise
 
30
import ivle.worksheet.utils
 
31
from ivle.webapp.base.rest import (JSONRESTView, write_operation,
36
32
                                   require_permission)
37
33
from ivle.webapp.errors import NotFound
38
34
 
39
 
# If True, getattempts or getattempt will allow browsing of inactive/disabled
40
 
# attempts. If False, will not allow this.
41
 
HISTORY_ALLOW_INACTIVE = False
42
35
 
43
36
TIMESTAMP_FORMAT = '%Y-%m-%d %H:%M:%S'
44
37
 
 
38
 
45
39
class AttemptsRESTView(JSONRESTView):
46
40
    '''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
41
 
66
42
    @require_permission('edit')
67
43
    def GET(self, req):
68
44
        """Handles a GET Attempts action."""
69
45
        attempts = req.store.find(ExerciseAttempt, 
70
 
                ExerciseAttempt.ws_ex_id == self.worksheet_exercise.id,
71
 
                ExerciseAttempt.user_id == self.user.id)
 
46
                ExerciseAttempt.ws_ex_id == self.context.worksheet_exercise.id,
 
47
                ExerciseAttempt.user_id == self.context.user.id)
72
48
        # attempts is a list of ExerciseAttempt objects. Convert to dictionaries
73
49
        time_fmt = lambda dt: datetime.datetime.strftime(dt, TIMESTAMP_FORMAT)
74
50
        attempts = [{'date': time_fmt(a.date), 'complete': a.complete}
80
56
    @require_permission('edit')
81
57
    def PUT(self, req, data):
82
58
        """ 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()
87
 
 
88
 
        # Start a console to run the tests on
89
 
        jail_path = os.path.join(ivle.conf.jail_base, req.user.login)
90
 
        working_dir = os.path.join("/home", req.user.login)
91
 
        cons = ivle.console.Console(req.user.unixid, jail_path, working_dir)
92
 
 
93
 
        # Parse the file into a exercise object using the test suite
94
 
        exercise_obj = ivle.webapp.tutorial.test.parse_exercise_file(
95
 
                                                            exercise, cons)
96
 
 
97
 
        # Run the test cases. Get the result back as a JSONable object.
98
 
        # Return it.
99
 
        test_results = exercise_obj.run_tests(data['code'])
100
 
 
101
 
        # Close the console
102
 
        cons.close()
 
59
        # Trim off any trailing whitespace (can cause syntax errors in python)
 
60
        # While technically this is a user error, it causes a lot of confusion 
 
61
        # for student since it's "invisible".
 
62
        code = data['code'].rstrip()
 
63
 
 
64
        test_results = ivle.worksheet.utils.test_exercise_submission(
 
65
            req.config, req.user, self.context.worksheet_exercise.exercise,
 
66
            code)
103
67
 
104
68
        attempt = ivle.database.ExerciseAttempt(user=req.user,
105
 
            worksheet_exercise = self.worksheet_exercise,
 
69
            worksheet_exercise = self.context.worksheet_exercise,
106
70
            date = datetime.datetime.now(),
107
71
            complete = test_results['passed'],
108
 
            text = unicode(data['code'])
 
72
            text = unicode(code)
109
73
        )
110
74
 
111
75
        req.store.add(attempt)
113
77
        # Query the DB to get an updated score on whether or not this problem
114
78
        # has EVER been completed (may be different from "passed", if it has
115
79
        # been completed before), and the total number of attempts.
116
 
        completed, attempts = ivle.worksheet.get_exercise_status(req.store,
117
 
            req.user, self.worksheet_exercise)
 
80
        completed, attempts = ivle.worksheet.utils.get_exercise_status(
 
81
                req.store, req.user, self.context.worksheet_exercise)
118
82
        test_results["completed"] = completed
119
83
        test_results["attempts"] = attempts
120
84
 
124
88
class AttemptRESTView(JSONRESTView):
125
89
    '''REST view of an exercise attempt.'''
126
90
 
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 = req.store.find(ExerciseAttempt,
151
 
            ExerciseAttempt.user_id == user.id,
152
 
            ExerciseAttempt.ws_ex_id == worksheet_exercise.id,
153
 
            ExerciseAttempt.date == date
154
 
        ).one()
155
 
 
156
 
        if attempt is None:
157
 
            raise NotFound()
158
 
 
159
 
        self.context = attempt
160
 
 
161
91
    @require_permission('view')
162
92
    def GET(self, req):
163
93
        return {'code': self.context.text}
164
94
 
165
95
 
166
 
class ExerciseRESTView(JSONRESTView):
167
 
    '''REST view of an exercise.'''
168
 
 
169
 
    def get_permissions(self, user):
170
 
        # XXX: Do it properly.
171
 
        # XXX: Does any user have the ability to save as themselves?
172
 
        # XXX: Does a user EVER have permission to save as another user?
173
 
        if user is not None:
174
 
            return set(['save'])
175
 
        else:
176
 
            return set()
177
 
 
178
 
    @named_operation('save')
 
96
class WorksheetExerciseRESTView(JSONRESTView):
 
97
    '''REST view of a worksheet exercise.'''
 
98
 
 
99
    @write_operation('view')
179
100
    def save(self, req, text):
180
101
        # Find the appropriate WorksheetExercise to save to. If its not found,
181
102
        # the user is submitting against a non-existant worksheet/exercise
182
 
        worksheet_exercise = req.store.find(WorksheetExercise,
183
 
            WorksheetExercise.exercise_id == self.exercise,
184
 
            WorksheetExercise.worksheet_id == Worksheet.id,
185
 
            Worksheet.offering_id == Offering.id,
186
 
            Offering.subject_id == Subject.id,
187
 
            Subject.code == self.subject,
188
 
            Offering.semester_id == Semester.id,
189
 
            Semester.year == self.year,
190
 
            Semester.semester == self.semester).one()
191
 
        
192
 
        if worksheet_exercise is None:
193
 
            raise NotFound()
194
103
 
195
104
        old_save = req.store.find(ExerciseSave,
196
 
            ExerciseSave.ws_ex_id == worksheet_exercise.id,
 
105
            ExerciseSave.ws_ex_id == self.context.id,
197
106
            ExerciseSave.user == req.user).one()
198
107
        
199
108
        #Overwrite the old, or create a new if there isn't one
203
112
        else:
204
113
            new_save = old_save
205
114
        
206
 
        new_save.worksheet_exercise = worksheet_exercise
 
115
        new_save.worksheet_exercise = self.context
207
116
        new_save.user = req.user
208
117
        new_save.text = unicode(text)
209
118
        new_save.date = datetime.datetime.now()
211
120
        return {"result": "ok"}
212
121
 
213
122
 
214
 
def generate_exerciselist(worksheet, req, worksheetdata):
215
 
    """Runs through the worksheetstream, generating the appropriate
216
 
    WorksheetExercises, and de-activating the old ones."""
217
 
    exercises = []
218
 
    # Turns the worksheet into an xml stream, and then finds all the 
219
 
    # exercise nodes in the stream.
220
 
    worksheetdata = genshi.XML(worksheetdata)
221
 
    for kind, data, pos in worksheetdata:
222
 
        if kind is genshi.core.START:
223
 
            # Data is a tuple of tag name and a list of name->value tuples
224
 
            if data[0] == 'exercise':
225
 
                src = ""
226
 
                optional = False
227
 
                for attr in data[1]:
228
 
                    if attr[0] == 'src':
229
 
                        src = attr[1]
230
 
                    if attr[0] == 'optional':
231
 
                        optional = attr[1] == 'true'
232
 
                if src != "":
233
 
                    exercises.append((src, optional))
234
 
    ex_num = 0
235
 
    # Set all current worksheet_exercises to be inactive
236
 
    db_worksheet_exercises = req.store.find(WorksheetExercise,
237
 
        WorksheetExercise.worksheet_id == worksheet.id)
238
 
    for worksheet_exercise in db_worksheet_exercises:
239
 
        worksheet_exercise.active = False
240
 
    
241
 
    for exerciseid, optional in exercises:
242
 
        worksheet_exercise = req.store.find(WorksheetExercise,
243
 
            WorksheetExercise.worksheet_id == worksheet.id,
244
 
            Exercise.id == WorksheetExercise.exercise_id,
245
 
            Exercise.id == exerciseid).one()
246
 
        if worksheet_exercise is None:
247
 
            exercise = req.store.find(Exercise,
248
 
                Exercise.id == exerciseid
249
 
            ).one()
250
 
            if exercise is None:
251
 
                raise NotFound()
252
 
            worksheet_exercise = WorksheetExercise()
253
 
            worksheet_exercise.worksheet_id = worksheet.id
254
 
            worksheet_exercise.exercise_id = exercise.id
255
 
            req.store.add(worksheet_exercise)
256
 
        worksheet_exercise.active = True
257
 
        worksheet_exercise.seq_no = ex_num
258
 
        worksheet_exercise.optional = optional
259
 
 
260
 
 
261
 
# Note that this is the view of an existing worksheet. Creation is handled
262
 
# by OfferingRESTView (as offerings have worksheets)
263
 
class WorksheetRESTView(JSONRESTView):
264
 
    """View used to update a worksheet."""
265
 
 
266
 
    def get_permissions(self, user):
267
 
        # XXX: Do it properly.
268
 
        # XXX: Lecturers should be allowed to add worksheets Only to subjects
269
 
        #      under their control
270
 
        if user is not None:
271
 
            if user.rolenm == 'admin':
272
 
                return set(['save'])
273
 
            else:
274
 
                return set()
275
 
        else:
276
 
            return set()    
277
 
 
278
 
    def __init__(self, req, **kwargs):
279
 
    
280
 
        self.worksheet = kwargs['worksheet']
281
 
        self.subject = kwargs['subject']
282
 
        self.year = kwargs['year']
283
 
        self.semester = kwargs['semester']
284
 
    
285
 
        self.context = req.store.find(Worksheet,
286
 
            Worksheet.identifier == self.worksheet,
287
 
            Worksheet.offering_id == Offering.id,
288
 
            Offering.subject_id == Subject.id,
289
 
            Subject.code == self.subject,
290
 
            Offering.semester_id == Semester.id,
291
 
            Semester.year == self.year,
292
 
            Semester.semester == self.semester).one()
293
 
        
294
 
        if self.context is None:
295
 
            raise NotFound()
296
 
    
297
 
    @named_operation('save')
298
 
    def save(self, req, name, assessable, data, format):
299
 
        """Takes worksheet data and saves it."""
300
 
        self.generate_exerciselist(self.context, req, data)
301
 
        
302
 
        self.context.name = unicode(name)
303
 
        self.context.assessable = self.convert_bool(assessable)
304
 
        self.context.data = unicode(data)
305
 
        
306
 
        return {"result": "ok"}
307
 
 
308
123
class WorksheetsRESTView(JSONRESTView):
309
124
    """View used to update and create Worksheets."""
310
 
    
311
 
    def get_permissions(self, user):
312
 
        # XXX: Do it properly.
313
 
        # XXX: Lecturers should be allowed to add worksheets Only to subjects
314
 
        #      under their control
315
 
        if user is not None:
316
 
            if user.rolenm == 'admin':
317
 
                return set(['add_worksheet', 'set_sequence'])
318
 
            else:
319
 
                return set()
320
 
        else:
321
 
            return set()
322
 
 
323
 
    def __init__(self, req, **kwargs):
324
 
    
325
 
        self.subject = kwargs['subject']
326
 
        self.year = kwargs['year']
327
 
        self.semester = kwargs['semester']
328
 
    
329
 
        self.context = req.store.find(Offering,
330
 
            Offering.subject_id == Subject.id,
331
 
            Subject.code == self.subject,
332
 
            Offering.semester_id == Semester.id,
333
 
            Semester.year == self.year,
334
 
            Semester.semester == self.semester).one()
335
 
        
336
 
        if self.context is None:
337
 
            raise NotFound()
338
 
 
339
 
    @named_operation('add_worksheet')
340
 
    def add_worksheet(self, req, identifier, name, assessable, data, format):
341
 
        """Takes worksheet data and adds it."""
342
 
        
343
 
        new_worksheet = Worksheet()
344
 
        new_worksheet.seq_no = self.context.worksheets.count()
345
 
        # Setting new_worksheet.offering implicitly adds new_worksheet,
346
 
        # hence worksheets.count MUST be called above it
347
 
        new_worksheet.offering = self.context
348
 
        new_worksheet.identifier = unicode(identifier)
349
 
        new_worksheet.name = unicode(name)
350
 
        new_worksheet.assessable = self.convert_bool(assessable)
351
 
        new_worksheet.data = unicode(data)
352
 
        new_worksheet.format = unicode(format)
353
 
        
354
 
        # This call is added for clarity, as the worksheet is implicitly added.        
355
 
        req.store.add(new_worksheet)
356
 
        
357
 
        generate_exerciselist(new_worksheet, req, data)
358
 
        
359
 
        return {"result": "ok"}
360
 
 
361
 
    @named_operation('set_sequence')
362
 
    def seq_sequence(self, req, worksheet_list):
363
 
        """Takes a list of worksheet-seq_no pairs and updates their 
364
 
        corresponding Worksheet objects to match."""
365
 
        
366
 
        for worksheetid, seq_no in worksheet_list:
367
 
            worksheet = req.store.find(Worksheet,
368
 
                Worksheet.offering_id == self.context.id,
369
 
                Worksheet.identifier == worksheetid).one()
370
 
            if worksheet is None:
371
 
                raise NotFound(worksheet)
372
 
            worksheet.seq_no = seq_no
 
125
 
 
126
    @write_operation('edit_worksheets')
 
127
    def move_up(self, req, worksheetid):
 
128
        """Takes a list of worksheet-seq_no pairs and updates their 
 
129
        corresponding Worksheet objects to match."""
 
130
        
 
131
        worksheet_below = req.store.find(Worksheet,
 
132
            Worksheet.offering_id == self.context.id,
 
133
            Worksheet.identifier == unicode(worksheetid)).one()
 
134
        if worksheet_below is None:
 
135
            raise NotFound('worksheet_below')
 
136
        worksheet_above = req.store.find(Worksheet,
 
137
            Worksheet.offering_id == self.context.id,
 
138
            Worksheet.seq_no == (worksheet_below.seq_no - 1)).one()
 
139
        if worksheet_above is None:
 
140
            raise NotFound('worksheet_above')
 
141
 
 
142
        worksheet_below.seq_no = worksheet_below.seq_no - 1
 
143
        worksheet_above.seq_no = worksheet_above.seq_no + 1
 
144
        
 
145
        return {'result': 'ok'}
 
146
 
 
147
    @write_operation('edit_worksheets')
 
148
    def move_down(self, req, worksheetid):
 
149
        """Takes a list of worksheet-seq_no pairs and updates their 
 
150
        corresponding Worksheet objects to match."""
 
151
        
 
152
        worksheet_above = req.store.find(Worksheet,
 
153
            Worksheet.offering_id == self.context.id,
 
154
            Worksheet.identifier == unicode(worksheetid)).one()
 
155
        if worksheet_above is None:
 
156
            raise NotFound('worksheet_below')
 
157
        worksheet_below = req.store.find(Worksheet,
 
158
            Worksheet.offering_id == self.context.id,
 
159
            Worksheet.seq_no == (worksheet_above.seq_no + 1)).one()
 
160
        if worksheet_below is None:
 
161
            raise NotFound('worksheet_above')
 
162
 
 
163
        worksheet_below.seq_no = worksheet_below.seq_no - 1
 
164
        worksheet_above.seq_no = worksheet_above.seq_no + 1
373
165
        
374
166
        return {'result': 'ok'}