~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)
1099.1.70 by William Grant
Clean up ivle.webapp.tutorial.service.
90
        cons = ivle.console.Console(req.user.unixid, jail_path, working_dir)
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
91
92
        # 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
93
        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.
94
                                                            exercise, cons)
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
95
96
        # Run the test cases. Get the result back as a JSONable object.
97
        # Return it.
1099.1.58 by Nick Chadwick
Updated the Worksheets to use a new tutorialservice, hosted in the
98
        test_results = exercise_obj.run_tests(data['code'])
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
99
100
        # Close the console
101
        cons.close()
102
103
        attempt = ivle.database.ExerciseAttempt(user=req.user,
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
104
            worksheet_exercise = self.worksheet_exercise,
105
            date = datetime.datetime.now(),
106
            complete = test_results['passed'],
107
            text = unicode(data['code'])
108
        )
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
109
110
        req.store.add(attempt)
1099.1.70 by William Grant
Clean up ivle.webapp.tutorial.service.
111
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
112
        # Query the DB to get an updated score on whether or not this problem
113
        # has EVER been completed (may be different from "passed", if it has
114
        # been completed before), and the total number of attempts.
1099.1.220 by Nick Chadwick
Merged from trunk
115
        completed, attempts = ivle.worksheet.utils.get_exercise_status(
116
                req.store, req.user, self.worksheet_exercise)
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
117
        test_results["completed"] = completed
118
        test_results["attempts"] = attempts
119
120
        return test_results
1099.1.70 by William Grant
Clean up ivle.webapp.tutorial.service.
121
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
122
123
class AttemptRESTView(JSONRESTView):
1099.1.70 by William Grant
Clean up ivle.webapp.tutorial.service.
124
    '''REST view of an exercise attempt.'''
125
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
126
    def __init__(self, req, subject, year, semester, worksheet, exercise, 
127
                 username, date):
1099.1.87 by William Grant
Fix views in ivle.webapp.admin and ivle.webapp.tutorial to return 404 wherever
128
        # TODO: Find exercise within worksheet.
129
        user = ivle.database.User.get_by_login(req.store, username)
130
        if user is None:
131
            raise NotFound()
132
1099.1.198 by William Grant
Fix retrieval of old exercise attempts.
133
        try:
134
            date = datetime.datetime.strptime(date, TIMESTAMP_FORMAT)
135
        except ValueError:
136
            raise NotFound()
1099.1.87 by William Grant
Fix views in ivle.webapp.admin and ivle.webapp.tutorial to return 404 wherever
137
1128 by William Grant
Hack around Google Code issue 87 by adding one second to attempt dates
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
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
147
        worksheet_exercise = req.store.find(WorksheetExercise,
148
            WorksheetExercise.exercise_id == exercise,
149
            WorksheetExercise.worksheet_id == Worksheet.id,
150
            Worksheet.identifier == worksheet,
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
151
            Worksheet.offering_id == Offering.id,
152
            Offering.subject_id == Subject.id,
1135 by William Grant
Subject URLs now contain the short name (eg. info1) rather than the code
153
            Subject.short_name == subject,
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
154
            Offering.semester_id == Semester.id,
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
155
            Semester.year == year,
156
            Semester.semester == semester).one()
157
            
1099.1.220 by Nick Chadwick
Merged from trunk
158
        attempt = ivle.worksheet.utils.get_exercise_attempt(req.store, user,
1099.1.198 by William Grant
Fix retrieval of old exercise attempts.
159
                        worksheet_exercise, as_of=date,
160
                        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
161
162
        if attempt is None:
163
            raise NotFound()
164
165
        self.context = attempt
166
1099.1.113 by William Grant
Give console and tutorial services security declarations.
167
    @require_permission('view')
1099.1.87 by William Grant
Fix views in ivle.webapp.admin and ivle.webapp.tutorial to return 404 wherever
168
    def GET(self, req):
169
        return {'code': self.context.text}
1099.1.70 by William Grant
Clean up ivle.webapp.tutorial.service.
170
171
1099.1.216 by Nick Chadwick
Started adding in add and save options in the exercise edit view, to
172
class WorksheetExerciseRESTView(JSONRESTView):
1131 by William Grant
Offerings now give 'view' only to user enrolled in them. 'edit' is granted
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,
1135 by William Grant
Subject URLs now contain the short name (eg. info1) rather than the code
181
            Subject.short_name == subject,
1131 by William Grant
Offerings now give 'view' only to user enrolled in them. 'edit' is granted
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')
1099.1.58 by Nick Chadwick
Updated the Worksheets to use a new tutorialservice, hosted in the
190
    def save(self, req, text):
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
191
        # Find the appropriate WorksheetExercise to save to. If its not found,
192
        # the user is submitting against a non-existant worksheet/exercise
193
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
194
        old_save = req.store.find(ExerciseSave,
1131 by William Grant
Offerings now give 'view' only to user enrolled in them. 'edit' is granted
195
            ExerciseSave.ws_ex_id == self.context.id,
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
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
        
1131 by William Grant
Offerings now give 'view' only to user enrolled in them. 'edit' is granted
205
        new_save.worksheet_exercise = self.context
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
206
        new_save.user = req.user
207
        new_save.text = unicode(text)
208
        new_save.date = datetime.datetime.now()
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
209
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
210
        return {"result": "ok"}
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
211
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
212
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
213
# Note that this is the view of an existing worksheet. Creation is handled
214
# by OfferingRESTView (as offerings have worksheets)
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
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,
1099.4.3 by Nick Chadwick
Updated the tutorial service, to now allow users to edit worksheets
226
            Worksheet.identifier == self.worksheet,
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
227
            Worksheet.offering_id == Offering.id,
228
            Offering.subject_id == Subject.id,
1135 by William Grant
Subject URLs now contain the short name (eg. info1) rather than the code
229
            Subject.short_name == self.subject,
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
230
            Offering.semester_id == Semester.id,
231
            Semester.year == self.year,
232
            Semester.semester == self.semester).one()
1099.4.3 by Nick Chadwick
Updated the tutorial service, to now allow users to edit worksheets
233
        
234
        if self.context is None:
235
            raise NotFound()
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
236
    
1131 by William Grant
Offerings now give 'view' only to user enrolled in them. 'edit' is granted
237
    @named_operation('edit')
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
238
    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
239
        """Takes worksheet data and saves it."""
240
        self.context.name = unicode(name)
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
241
        self.context.assessable = self.convert_bool(assessable)
1099.4.3 by Nick Chadwick
Updated the tutorial service, to now allow users to edit worksheets
242
        self.context.data = unicode(data)
1099.1.194 by Nick Chadwick
Updated worksheet_edit to correctly set the format.
243
        self.context.format = unicode(format)
1099.1.220 by Nick Chadwick
Merged from trunk
244
        ivle.worksheet.utils.update_exerciselist(self.context)
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
245
        
246
        return {"result": "ok"}
247
1099.1.189 by Nick Chadwick
Updated service.py to correctly add a seq_no to a worksheet before
248
class WorksheetsRESTView(JSONRESTView):
249
    """View used to update and create Worksheets."""
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit 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,
1135 by William Grant
Subject URLs now contain the short name (eg. info1) rather than the code
259
            Subject.short_name == self.subject,
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
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
1099.1.197 by Nick Chadwick
Modified worksheets edit view, so now there are links to edit, add,
267
    @named_operation('edit')
1099.1.189 by Nick Chadwick
Updated service.py to correctly add a seq_no to a worksheet before
268
    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
269
        """Takes worksheet data and adds it."""
270
        
271
        new_worksheet = Worksheet()
1099.1.189 by Nick Chadwick
Updated service.py to correctly add a seq_no to a worksheet before
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
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
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)
1099.1.189 by Nick Chadwick
Updated service.py to correctly add a seq_no to a worksheet before
281
        
282
        # 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
283
        req.store.add(new_worksheet)
1099.1.220 by Nick Chadwick
Merged from trunk
284
285
        ivle.worksheet.utils.update_exerciselist(new_worksheet)
286
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
287
        return {"result": "ok"}
288
1099.1.197 by Nick Chadwick
Modified worksheets edit view, so now there are links to edit, add,
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
1099.1.189 by Nick Chadwick
Updated service.py to correctly add a seq_no to a worksheet before
328
        
329
        return {'result': 'ok'}