~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.util
27
import ivle.console
1080.1.58 by Matt Giuca
ivle.worksheet: Added get_exercise_attempts and get_exercise_attempt.
28
import ivle.database
1099.4.3 by Nick Chadwick
Updated the tutorial service, to now allow users to edit worksheets
29
from ivle.database import Exercise, ExerciseAttempt, ExerciseSave, Worksheet, \
30
                          Offering, Subject, Semester, WorksheetExercise
1080.1.56 by Matt Giuca
Added new module: ivle.worksheet. This will contain general functions for
31
import ivle.worksheet
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
32
import ivle.conf
1099.1.70 by William Grant
Clean up ivle.webapp.tutorial.service.
33
import ivle.webapp.tutorial.test
301 by mattgiuca
tutorialservice: Now parses and executes the students code using the test
34
1099.1.113 by William Grant
Give console and tutorial services security declarations.
35
from ivle.webapp.base.rest import (JSONRESTView, named_operation,
36
                                   require_permission)
1099.1.87 by William Grant
Fix views in ivle.webapp.admin and ivle.webapp.tutorial to return 404 wherever
37
from ivle.webapp.errors import NotFound
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
38
1025 by mattgiuca
tutorialservice: Added two new GET actions: getattempts and getattempt.
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
1080.1.88 by William Grant
ivle.worksheet: Add a save_exercise function.
43
TIMESTAMP_FORMAT = '%Y-%m-%d %H:%M:%S'
44
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
45
class AttemptsRESTView(JSONRESTView):
1099.1.70 by William Grant
Clean up ivle.webapp.tutorial.service.
46
    '''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
47
    
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
48
    def __init__(self, req, subject, year, semester, worksheet, 
49
                                                exercise, username):
1099.1.87 by William Grant
Fix views in ivle.webapp.admin and ivle.webapp.tutorial to return 404 wherever
50
        self.user = ivle.database.User.get_by_login(req.store, username)
51
        if self.user is None:
52
            raise NotFound()
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
53
        
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
54
        self.worksheet_exercise = req.store.find(WorksheetExercise,
55
            WorksheetExercise.exercise_id == exercise,
56
            WorksheetExercise.worksheet_id == Worksheet.id,
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
57
            Worksheet.offering_id == Offering.id,
58
            Offering.subject_id == Subject.id,
1135 by William Grant
Subject URLs now contain the short name (eg. info1) rather than the code
59
            Subject.short_name == subject,
1099.1.150 by Nick Chadwick
Modified worksheets to properly link attempts to worksheets and
60
            Offering.semester_id == Semester.id,
61
            Semester.year == year,
62
            Semester.semester == semester).one()
63
        
1099.1.113 by William Grant
Give console and tutorial services security declarations.
64
        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
65
1099.1.113 by William Grant
Give console and tutorial services security declarations.
66
    @require_permission('edit')
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
67
    def GET(self, req):
68
        """Handles a GET Attempts action."""
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
69
        attempts = req.store.find(ExerciseAttempt, 
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
70
                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.
71
                ExerciseAttempt.user_id == self.user.id)
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
72
        # attempts is a list of ExerciseAttempt objects. Convert to dictionaries
73
        time_fmt = lambda dt: datetime.datetime.strftime(dt, TIMESTAMP_FORMAT)
74
        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.
75
                for a in attempts]
1099.1.70 by William Grant
Clean up ivle.webapp.tutorial.service.
76
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
77
        return attempts
1099.1.70 by William Grant
Clean up ivle.webapp.tutorial.service.
78
79
1099.1.113 by William Grant
Give console and tutorial services security declarations.
80
    @require_permission('edit')
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
81
    def PUT(self, req, data):
1099.1.114 by Nick Chadwick
Modified the database so that exercises are now stored in the database, rather
82
        """ Tests the given submission """
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
83
        exercise = req.store.find(Exercise, 
84
            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.
85
        if exercise is None:
1099.1.93 by William Grant
Remove remaining uses of req.throw_error in the new webapps.
86
            raise NotFound()
1099.1.49 by Nick Chadwick
Began moving tutorialservice over to webapp.
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)
1099.1.70 by William Grant
Clean up ivle.webapp.tutorial.service.
91
        cons = ivle.console.Console(req.user.unixid, jail_path, 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.
116
        completed, attempts = ivle.worksheet.get_exercise_status(req.store,
1099.1.180 by Nick Chadwick
This commit changes the tutorial service, which now almost exclusively
117
            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.198 by William Grant
Fix retrieval of old exercise attempts.
159
        attempt = ivle.worksheet.get_exercise_attempt(req.store, user,
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
1131 by William Grant
Offerings now give 'view' only to user enrolled in them. 'edit' is granted
173
class WorksheetExerciseRESTView(JSONRESTView):
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
215
# Note that this is the view of an existing worksheet. Creation is handled
216
# by OfferingRESTView (as offerings have worksheets)
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
217
class WorksheetRESTView(JSONRESTView):
218
    """View used to update a worksheet."""
219
220
    def __init__(self, req, **kwargs):
221
    
222
        self.worksheet = kwargs['worksheet']
223
        self.subject = kwargs['subject']
224
        self.year = kwargs['year']
225
        self.semester = kwargs['semester']
226
    
227
        self.context = req.store.find(Worksheet,
1099.4.3 by Nick Chadwick
Updated the tutorial service, to now allow users to edit worksheets
228
            Worksheet.identifier == self.worksheet,
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
229
            Worksheet.offering_id == Offering.id,
230
            Offering.subject_id == Subject.id,
1135 by William Grant
Subject URLs now contain the short name (eg. info1) rather than the code
231
            Subject.short_name == self.subject,
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
232
            Offering.semester_id == Semester.id,
233
            Semester.year == self.year,
234
            Semester.semester == self.semester).one()
1099.4.3 by Nick Chadwick
Updated the tutorial service, to now allow users to edit worksheets
235
        
236
        if self.context is None:
237
            raise NotFound()
1099.4.1 by Nick Chadwick
Working on putting worksheets into the database.
238
    
1131 by William Grant
Offerings now give 'view' only to user enrolled in them. 'edit' is granted
239
    @named_operation('edit')
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
240
    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
241
        """Takes worksheet data and saves it."""
242
        self.context.name = unicode(name)
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
243
        self.context.assessable = self.convert_bool(assessable)
1099.4.3 by Nick Chadwick
Updated the tutorial service, to now allow users to edit worksheets
244
        self.context.data = unicode(data)
1099.1.194 by Nick Chadwick
Updated worksheet_edit to correctly set the format.
245
        self.context.format = unicode(format)
1102 by William Grant
Move ivle.webapp.tutorial.service.generate_exerciselist() to ivle.worksheet,
246
        ivle.worksheet.update_exerciselist(self.context)
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
247
        
248
        return {"result": "ok"}
249
1099.1.189 by Nick Chadwick
Updated service.py to correctly add a seq_no to a worksheet before
250
class WorksheetsRESTView(JSONRESTView):
251
    """View used to update and create Worksheets."""
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
252
    
253
    def __init__(self, req, **kwargs):
254
    
255
        self.subject = kwargs['subject']
256
        self.year = kwargs['year']
257
        self.semester = kwargs['semester']
258
    
259
        self.context = req.store.find(Offering,
260
            Offering.subject_id == Subject.id,
1135 by William Grant
Subject URLs now contain the short name (eg. info1) rather than the code
261
            Subject.short_name == self.subject,
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
262
            Offering.semester_id == Semester.id,
263
            Semester.year == self.year,
264
            Semester.semester == self.semester).one()
265
        
266
        if self.context is None:
267
            raise NotFound()
268
1099.1.197 by Nick Chadwick
Modified worksheets edit view, so now there are links to edit, add,
269
    @named_operation('edit')
1099.1.189 by Nick Chadwick
Updated service.py to correctly add a seq_no to a worksheet before
270
    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
271
        """Takes worksheet data and adds it."""
272
        
273
        new_worksheet = Worksheet()
1099.1.189 by Nick Chadwick
Updated service.py to correctly add a seq_no to a worksheet before
274
        new_worksheet.seq_no = self.context.worksheets.count()
275
        # Setting new_worksheet.offering implicitly adds new_worksheet,
276
        # hence worksheets.count MUST be called above it
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
277
        new_worksheet.offering = self.context
278
        new_worksheet.identifier = unicode(identifier)
279
        new_worksheet.name = unicode(name)
280
        new_worksheet.assessable = self.convert_bool(assessable)
281
        new_worksheet.data = unicode(data)
282
        new_worksheet.format = unicode(format)
1099.1.189 by Nick Chadwick
Updated service.py to correctly add a seq_no to a worksheet before
283
        
284
        # 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
285
        req.store.add(new_worksheet)
286
        
1102 by William Grant
Move ivle.webapp.tutorial.service.generate_exerciselist() to ivle.worksheet,
287
        ivle.worksheet.update_exerciselist(new_worksheet)
1099.1.182 by Nick Chadwick
Added a view to allow admins to edit worksheets
288
        
289
        return {"result": "ok"}
290
1099.1.197 by Nick Chadwick
Modified worksheets edit view, so now there are links to edit, add,
291
    @named_operation('edit')
292
    def move_up(self, req, worksheetid):
293
        """Takes a list of worksheet-seq_no pairs and updates their 
294
        corresponding Worksheet objects to match."""
295
        
296
        worksheet_below = req.store.find(Worksheet,
297
            Worksheet.offering_id == self.context.id,
298
            Worksheet.identifier == unicode(worksheetid)).one()
299
        if worksheet_below is None:
300
            raise NotFound('worksheet_below')
301
        worksheet_above = req.store.find(Worksheet,
302
            Worksheet.offering_id == self.context.id,
303
            Worksheet.seq_no == (worksheet_below.seq_no - 1)).one()
304
        if worksheet_above is None:
305
            raise NotFound('worksheet_above')
306
307
        worksheet_below.seq_no = worksheet_below.seq_no - 1
308
        worksheet_above.seq_no = worksheet_above.seq_no + 1
309
        
310
        return {'result': 'ok'}
311
312
    @named_operation('edit')
313
    def move_down(self, req, worksheetid):
314
        """Takes a list of worksheet-seq_no pairs and updates their 
315
        corresponding Worksheet objects to match."""
316
        
317
        worksheet_above = req.store.find(Worksheet,
318
            Worksheet.offering_id == self.context.id,
319
            Worksheet.identifier == unicode(worksheetid)).one()
320
        if worksheet_above is None:
321
            raise NotFound('worksheet_below')
322
        worksheet_below = req.store.find(Worksheet,
323
            Worksheet.offering_id == self.context.id,
324
            Worksheet.seq_no == (worksheet_above.seq_no + 1)).one()
325
        if worksheet_below is None:
326
            raise NotFound('worksheet_above')
327
328
        worksheet_below.seq_no = worksheet_below.seq_no - 1
329
        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
330
        
331
        return {'result': 'ok'}