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

294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
1
# IVLE - Informatics Virtual Learning Environment
1099.1.70 by William Grant
Clean up ivle.webapp.tutorial.service.
2
# Copyright (C) 2007-2009 The University of Melbourne
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
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.70 by William Grant
Clean up ivle.webapp.tutorial.service.
18
# Author: Matt Giuca, Nick Chadwick
19
20
'''AJAX backend for the tutorial application.'''
300 by mattgiuca
conf/apps: Added TutorialService as a registered app.
21
301 by mattgiuca
tutorialservice: Now parses and executes the students code using the test
22
import os
1080.1.58 by Matt Giuca
ivle.worksheet: Added get_exercise_attempts and get_exercise_attempt.
23
import datetime
1099.4.3 by Nick Chadwick
Updated the tutorial service, to now allow users to edit worksheets
24
import genshi
301 by mattgiuca
tutorialservice: Now parses and executes the students code using the test
25
1099.1.70 by William Grant
Clean up ivle.webapp.tutorial.service.
26
import ivle.console
1080.1.58 by Matt Giuca
ivle.worksheet: Added get_exercise_attempts and get_exercise_attempt.
27
import ivle.database
1099.4.3 by Nick Chadwick
Updated the tutorial service, to now allow users to edit worksheets
28
from ivle.database import Exercise, ExerciseAttempt, ExerciseSave, Worksheet, \
29
                          Offering, Subject, Semester, WorksheetExercise
1099.1.220 by Nick Chadwick
Merged from trunk
30
import ivle.worksheet.utils
1099.1.70 by William Grant
Clean up ivle.webapp.tutorial.service.
31
import ivle.webapp.tutorial.test
1099.1.113 by William Grant
Give console and tutorial services security declarations.
32
from ivle.webapp.base.rest import (JSONRESTView, named_operation,
33
                                   require_permission)
1099.1.87 by William Grant
Fix views in ivle.webapp.admin and ivle.webapp.tutorial to return 404 wherever
34
from ivle.webapp.errors import NotFound
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
35
1025 by mattgiuca
tutorialservice: Added two new GET actions: getattempts and getattempt.
36
# If True, getattempts or getattempt will allow browsing of inactive/disabled
37
# attempts. If False, will not allow this.
38
HISTORY_ALLOW_INACTIVE = False
39
1080.1.88 by William Grant
ivle.worksheet: Add a save_exercise function.
40
TIMESTAMP_FORMAT = '%Y-%m-%d %H:%M:%S'
41
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
42
class AttemptsRESTView(JSONRESTView):
1099.1.70 by William Grant
Clean up ivle.webapp.tutorial.service.
43
    '''REST view of a user's attempts at an exercise.'''
1099.1.192 by Nick Chadwick
Moved the tutorial templates in a new directory to keep tutorial cleaner
44
    
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
45
    def __init__(self, req, subject, year, semester, worksheet, 
46
                                                exercise, username):
1099.1.87 by William Grant
Fix views in ivle.webapp.admin and ivle.webapp.tutorial to return 404 wherever
47
        self.user = ivle.database.User.get_by_login(req.store, username)
48
        if self.user is None:
49
            raise NotFound()
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
50
        
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
51
        self.worksheet_exercise = req.store.find(WorksheetExercise,
1099.1.221 by Nick Chadwick
added in extra parts to the exercise edit view. Now almost all
52
            WorksheetExercise.exercise_id == unicode(exercise),
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
53
            WorksheetExercise.worksheet_id == Worksheet.id,
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
54
            Worksheet.offering_id == Offering.id,
1099.1.221 by Nick Chadwick
added in extra parts to the exercise edit view. Now almost all
55
            Worksheet.identifier == unicode(worksheet),
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
56
            Offering.subject_id == Subject.id,
1135 by William Grant
Subject URLs now contain the short name (eg. info1) rather than the code
57
            Subject.short_name == subject,
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
58
            Offering.semester_id == Semester.id,
59
            Semester.year == year,
60
            Semester.semester == semester).one()
61
        
1099.1.113 by William Grant
Give console and tutorial services security declarations.
62
        self.context = self.user # XXX: Not quite right.
1099.1.87 by William Grant
Fix views in ivle.webapp.admin and ivle.webapp.tutorial to return 404 wherever
63
1099.1.113 by William Grant
Give console and tutorial services security declarations.
64
    @require_permission('edit')
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
65
    def GET(self, req):
66
        """Handles a GET Attempts action."""
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
67
        attempts = req.store.find(ExerciseAttempt, 
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
68
                ExerciseAttempt.ws_ex_id == self.worksheet_exercise.id,
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
69
                ExerciseAttempt.user_id == self.user.id)
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
70
        # attempts is a list of ExerciseAttempt objects. Convert to dictionaries
71
        time_fmt = lambda dt: datetime.datetime.strftime(dt, TIMESTAMP_FORMAT)
72
        attempts = [{'date': time_fmt(a.date), 'complete': a.complete}
1080.1.58 by Matt Giuca
ivle.worksheet: Added get_exercise_attempts and get_exercise_attempt.
73
                for a in attempts]
1099.1.70 by William Grant
Clean up ivle.webapp.tutorial.service.
74
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
75
        return attempts
1099.1.70 by William Grant
Clean up ivle.webapp.tutorial.service.
76
77
1099.1.113 by William Grant
Give console and tutorial services security declarations.
78
    @require_permission('edit')
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
79
    def PUT(self, req, data):
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
80
        """ Tests the given submission """
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
81
        exercise = req.store.find(Exercise, 
82
            Exercise.id == self.worksheet_exercise.exercise_id).one()
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
83
        if exercise is None:
1099.1.93 by William Grant
Remove remaining uses of req.throw_error in the new webapps.
84
            raise NotFound()
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
85
86
        # Start a console to run the tests on
1219 by William Grant
Remove ivle.conf dependency from ivle.webapp.tutorial.service.
87
        jail_path = os.path.join(req.config['paths']['jails']['mounts'],
88
                                 req.user.login)
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
89
        working_dir = os.path.join("/home", req.user.login)
1275 by William Grant
Kill ivle.console's dependency on ivle.conf.
90
        cons = ivle.console.Console(req.config, req.user.unixid, jail_path,
91
                                    working_dir)
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
92
93
        # Parse the file into a exercise object using the test suite
1099.1.58 by Nick Chadwick
Updated the Worksheets to use a new tutorialservice, hosted in the
94
        exercise_obj = ivle.webapp.tutorial.test.parse_exercise_file(
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
95
                                                            exercise, cons)
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
96
97
        # Run the test cases. Get the result back as a JSONable object.
98
        # Return it.
1099.1.58 by Nick Chadwick
Updated the Worksheets to use a new tutorialservice, hosted in the
99
        test_results = exercise_obj.run_tests(data['code'])
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
100
101
        # Close the console
102
        cons.close()
103
104
        attempt = ivle.database.ExerciseAttempt(user=req.user,
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
105
            worksheet_exercise = self.worksheet_exercise,
106
            date = datetime.datetime.now(),
107
            complete = test_results['passed'],
108
            text = unicode(data['code'])
109
        )
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
110
111
        req.store.add(attempt)
1099.1.70 by William Grant
Clean up ivle.webapp.tutorial.service.
112
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
113
        # Query the DB to get an updated score on whether or not this problem
114
        # has EVER been completed (may be different from "passed", if it has
115
        # been completed before), and the total number of attempts.
1099.1.220 by Nick Chadwick
Merged from trunk
116
        completed, attempts = ivle.worksheet.utils.get_exercise_status(
117
                req.store, req.user, self.worksheet_exercise)
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
118
        test_results["completed"] = completed
119
        test_results["attempts"] = attempts
120
121
        return test_results
1099.1.70 by William Grant
Clean up ivle.webapp.tutorial.service.
122
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
123
124
class AttemptRESTView(JSONRESTView):
1099.1.70 by William Grant
Clean up ivle.webapp.tutorial.service.
125
    '''REST view of an exercise attempt.'''
126
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
127
    def __init__(self, req, subject, year, semester, worksheet, exercise, 
128
                 username, date):
1099.1.87 by William Grant
Fix views in ivle.webapp.admin and ivle.webapp.tutorial to return 404 wherever
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
1099.1.198 by William Grant
Fix retrieval of old exercise attempts.
134
        try:
135
            date = datetime.datetime.strptime(date, TIMESTAMP_FORMAT)
136
        except ValueError:
137
            raise NotFound()
1099.1.87 by William Grant
Fix views in ivle.webapp.admin and ivle.webapp.tutorial to return 404 wherever
138
1128 by William Grant
Hack around Google Code issue 87 by adding one second to attempt dates
139
        # XXX Hack around Google Code issue #87
140
        # Query from the given date +1 secnod.
141
        # Date is in seconds (eg. 3:47:12), while the data is in finer time
142
        # (eg. 3:47:12.3625). The query "date <= 3:47:12" will fail because
143
        # 3:47:12.3625 is greater. Hence we do the query from +1 second,
144
        # "date <= 3:47:13", and it finds the correct submission, UNLESS there
145
        # are multiple submissions inside the same second.
146
        date += datetime.timedelta(seconds=1)
147
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
148
        worksheet_exercise = req.store.find(WorksheetExercise,
149
            WorksheetExercise.exercise_id == exercise,
150
            WorksheetExercise.worksheet_id == Worksheet.id,
151
            Worksheet.identifier == worksheet,
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
152
            Worksheet.offering_id == Offering.id,
153
            Offering.subject_id == Subject.id,
1135 by William Grant
Subject URLs now contain the short name (eg. info1) rather than the code
154
            Subject.short_name == subject,
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
155
            Offering.semester_id == Semester.id,
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
156
            Semester.year == year,
157
            Semester.semester == semester).one()
158
            
1099.1.220 by Nick Chadwick
Merged from trunk
159
        attempt = ivle.worksheet.utils.get_exercise_attempt(req.store, user,
1099.1.198 by William Grant
Fix retrieval of old exercise attempts.
160
                        worksheet_exercise, as_of=date,
161
                        allow_inactive=HISTORY_ALLOW_INACTIVE) 
1099.1.87 by William Grant
Fix views in ivle.webapp.admin and ivle.webapp.tutorial to return 404 wherever
162
163
        if attempt is None:
164
            raise NotFound()
165
166
        self.context = attempt
167
1099.1.113 by William Grant
Give console and tutorial services security declarations.
168
    @require_permission('view')
1099.1.87 by William Grant
Fix views in ivle.webapp.admin and ivle.webapp.tutorial to return 404 wherever
169
    def GET(self, req):
170
        return {'code': self.context.text}
1099.1.70 by William Grant
Clean up ivle.webapp.tutorial.service.
171
172
1099.1.216 by Nick Chadwick
Started adding in add and save options in the exercise edit view, to
173
class WorksheetExerciseRESTView(JSONRESTView):
1131 by William Grant
Offerings now give 'view' only to user enrolled in them. 'edit' is granted
174
    '''REST view of a worksheet exercise.'''
175
176
    def __init__(self, req, subject, year, semester, worksheet, exercise):
177
        self.context = req.store.find(WorksheetExercise,
178
            WorksheetExercise.exercise_id == exercise,
179
            WorksheetExercise.worksheet_id == Worksheet.id,
180
            Worksheet.offering_id == Offering.id,
181
            Offering.subject_id == Subject.id,
1135 by William Grant
Subject URLs now contain the short name (eg. info1) rather than the code
182
            Subject.short_name == subject,
1131 by William Grant
Offerings now give 'view' only to user enrolled in them. 'edit' is granted
183
            Offering.semester_id == Semester.id,
184
            Semester.year == year,
185
            Semester.semester == semester).one()
186
        
187
        if self.context is None:
188
            raise NotFound()
189
190
    @named_operation('view')
1099.1.58 by Nick Chadwick
Updated the Worksheets to use a new tutorialservice, hosted in the
191
    def save(self, req, text):
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
192
        # Find the appropriate WorksheetExercise to save to. If its not found,
193
        # the user is submitting against a non-existant worksheet/exercise
194
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
195
        old_save = req.store.find(ExerciseSave,
1131 by William Grant
Offerings now give 'view' only to user enrolled in them. 'edit' is granted
196
            ExerciseSave.ws_ex_id == self.context.id,
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
197
            ExerciseSave.user == req.user).one()
198
        
199
        #Overwrite the old, or create a new if there isn't one
200
        if old_save is None:
201
            new_save = ExerciseSave()
202
            req.store.add(new_save)
203
        else:
204
            new_save = old_save
205
        
1131 by William Grant
Offerings now give 'view' only to user enrolled in them. 'edit' is granted
206
        new_save.worksheet_exercise = self.context
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
207
        new_save.user = req.user
208
        new_save.text = unicode(text)
209
        new_save.date = datetime.datetime.now()
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
210
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
211
        return {"result": "ok"}
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
212
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
213
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
214
# Note that this is the view of an existing worksheet. Creation is handled
215
# by OfferingRESTView (as offerings have worksheets)
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
216
class WorksheetRESTView(JSONRESTView):
217
    """View used to update a worksheet."""
218
219
    def __init__(self, req, **kwargs):
220
    
221
        self.worksheet = kwargs['worksheet']
222
        self.subject = kwargs['subject']
223
        self.year = kwargs['year']
224
        self.semester = kwargs['semester']
225
    
226
        self.context = req.store.find(Worksheet,
1099.4.3 by Nick Chadwick
Updated the tutorial service, to now allow users to edit worksheets
227
            Worksheet.identifier == self.worksheet,
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
228
            Worksheet.offering_id == Offering.id,
229
            Offering.subject_id == Subject.id,
1135 by William Grant
Subject URLs now contain the short name (eg. info1) rather than the code
230
            Subject.short_name == self.subject,
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
231
            Offering.semester_id == Semester.id,
232
            Semester.year == self.year,
233
            Semester.semester == self.semester).one()
1099.4.3 by Nick Chadwick
Updated the tutorial service, to now allow users to edit worksheets
234
        
235
        if self.context is None:
236
            raise NotFound()
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
237
    
1131 by William Grant
Offerings now give 'view' only to user enrolled in them. 'edit' is granted
238
    @named_operation('edit')
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
239
    def save(self, req, name, assessable, data, format):
1099.4.3 by Nick Chadwick
Updated the tutorial service, to now allow users to edit worksheets
240
        """Takes worksheet data and saves it."""
241
        self.context.name = unicode(name)
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
242
        self.context.assessable = self.convert_bool(assessable)
1099.4.3 by Nick Chadwick
Updated the tutorial service, to now allow users to edit worksheets
243
        self.context.data = unicode(data)
1099.1.194 by Nick Chadwick
Updated worksheet_edit to correctly set the format.
244
        self.context.format = unicode(format)
1099.1.220 by Nick Chadwick
Merged from trunk
245
        ivle.worksheet.utils.update_exerciselist(self.context)
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
246
        
247
        return {"result": "ok"}
248
1099.1.189 by Nick Chadwick
Updated service.py to correctly add a seq_no to a worksheet before
249
class WorksheetsRESTView(JSONRESTView):
250
    """View used to update and create Worksheets."""
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
251
    
252
    def __init__(self, req, **kwargs):
253
    
254
        self.subject = kwargs['subject']
255
        self.year = kwargs['year']
256
        self.semester = kwargs['semester']
257
    
258
        self.context = req.store.find(Offering,
259
            Offering.subject_id == Subject.id,
1135 by William Grant
Subject URLs now contain the short name (eg. info1) rather than the code
260
            Subject.short_name == self.subject,
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
261
            Offering.semester_id == Semester.id,
262
            Semester.year == self.year,
263
            Semester.semester == self.semester).one()
264
        
265
        if self.context is None:
266
            raise NotFound()
267
1099.1.197 by Nick Chadwick
Modified worksheets edit view, so now there are links to edit, add,
268
    @named_operation('edit')
1099.1.189 by Nick Chadwick
Updated service.py to correctly add a seq_no to a worksheet before
269
    def add_worksheet(self, req, identifier, name, assessable, data, format):
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
270
        """Takes worksheet data and adds it."""
271
        
272
        new_worksheet = Worksheet()
1099.1.189 by Nick Chadwick
Updated service.py to correctly add a seq_no to a worksheet before
273
        new_worksheet.seq_no = self.context.worksheets.count()
274
        # Setting new_worksheet.offering implicitly adds new_worksheet,
275
        # hence worksheets.count MUST be called above it
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
276
        new_worksheet.offering = self.context
277
        new_worksheet.identifier = unicode(identifier)
278
        new_worksheet.name = unicode(name)
279
        new_worksheet.assessable = self.convert_bool(assessable)
280
        new_worksheet.data = unicode(data)
281
        new_worksheet.format = unicode(format)
1099.1.189 by Nick Chadwick
Updated service.py to correctly add a seq_no to a worksheet before
282
        
283
        # This call is added for clarity, as the worksheet is implicitly added.        
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
284
        req.store.add(new_worksheet)
1099.1.220 by Nick Chadwick
Merged from trunk
285
286
        ivle.worksheet.utils.update_exerciselist(new_worksheet)
287
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
288
        return {"result": "ok"}
289
1099.1.197 by Nick Chadwick
Modified worksheets edit view, so now there are links to edit, add,
290
    @named_operation('edit')
291
    def move_up(self, req, worksheetid):
292
        """Takes a list of worksheet-seq_no pairs and updates their 
293
        corresponding Worksheet objects to match."""
294
        
295
        worksheet_below = req.store.find(Worksheet,
296
            Worksheet.offering_id == self.context.id,
297
            Worksheet.identifier == unicode(worksheetid)).one()
298
        if worksheet_below is None:
299
            raise NotFound('worksheet_below')
300
        worksheet_above = req.store.find(Worksheet,
301
            Worksheet.offering_id == self.context.id,
302
            Worksheet.seq_no == (worksheet_below.seq_no - 1)).one()
303
        if worksheet_above is None:
304
            raise NotFound('worksheet_above')
305
306
        worksheet_below.seq_no = worksheet_below.seq_no - 1
307
        worksheet_above.seq_no = worksheet_above.seq_no + 1
308
        
309
        return {'result': 'ok'}
310
311
    @named_operation('edit')
312
    def move_down(self, req, worksheetid):
313
        """Takes a list of worksheet-seq_no pairs and updates their 
314
        corresponding Worksheet objects to match."""
315
        
316
        worksheet_above = req.store.find(Worksheet,
317
            Worksheet.offering_id == self.context.id,
318
            Worksheet.identifier == unicode(worksheetid)).one()
319
        if worksheet_above is None:
320
            raise NotFound('worksheet_below')
321
        worksheet_below = req.store.find(Worksheet,
322
            Worksheet.offering_id == self.context.id,
323
            Worksheet.seq_no == (worksheet_above.seq_no + 1)).one()
324
        if worksheet_below is None:
325
            raise NotFound('worksheet_above')
326
327
        worksheet_below.seq_no = worksheet_below.seq_no - 1
328
        worksheet_above.seq_no = worksheet_above.seq_no + 1
1099.1.189 by Nick Chadwick
Updated service.py to correctly add a seq_no to a worksheet before
329
        
330
        return {'result': 'ok'}