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

1099.1.216 by Nick Chadwick
Started adding in add and save options in the exercise edit view, to
1
# IVLE - Informatics Virtual Learning Environment
2
# Copyright (C) 2007-2009 The University of Melbourne
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
18
# Author: Nick Chadwick
19
20
21
import ivle.database
22
from ivle.database import Exercise, TestSuite, TestCase, \
23
                          TestSuiteVar, TestCasePart
1606.1.7 by William Grant
Verify that new exercise URL names comply, and fail nicely if they fail or the name is a duplicate.
24
from ivle.webapp.base.forms import VALID_URL_NAME
1796.1.2 by William Grant
Replace @named_operation with @read_operation and @write_operation. Allow execution of read operations with a GET rather than a POST.
25
from ivle.webapp.base.rest import (JSONRESTView, write_operation,
1099.1.216 by Nick Chadwick
Started adding in add and save options in the exercise edit view, to
26
                                   require_permission)
1099.6.3 by Nick Chadwick
Edited the exercise service to delete individual parts of an exercise.
27
from ivle.webapp.errors import NotFound, BadRequest
1413 by William Grant
Display TestErrors in test mode, much like TestCreationErrors.
28
from ivle.webapp.tutorial.test.TestFramework import (
29
    TestCreationError, TestError)
1099.1.216 by Nick Chadwick
Started adding in add and save options in the exercise edit view, to
30
31
32
class ExercisesRESTView(JSONRESTView):
33
    """Rest view for adding an exercise."""
34
    
1315 by William Grant
Allow tutors to add exercises.
35
    #Only lecturers, tutors and admins can add exercises
1544 by Matt Giuca
Added an argument 'config' to every single get_permissions method throughout the program. All calls to get_permissions pass a config. This is to allow per-site policy configurations on permissions.
36
    def get_permissions(self, user, config):
1099.1.216 by Nick Chadwick
Started adding in add and save options in the exercise edit view, to
37
        if user is not None:
1099.1.221 by Nick Chadwick
added in extra parts to the exercise edit view. Now almost all
38
            if user.admin:
39
                return set(['save'])
1099.1.237 by Nick Chadwick
Updated exercise_service to make use of the new deletion methods
40
            elif 'lecturer' in set((e.role for e in user.active_enrolments)):
1099.1.216 by Nick Chadwick
Started adding in add and save options in the exercise edit view, to
41
                return set(['save'])
1315 by William Grant
Allow tutors to add exercises.
42
            elif 'tutor' in set((e.role for e in user.active_enrolments)):
43
                return set(['save'])
1099.1.216 by Nick Chadwick
Started adding in add and save options in the exercise edit view, to
44
            else:
45
                return set()
46
        else:
47
            return set()
48
    
1796.1.2 by William Grant
Replace @named_operation with @read_operation and @write_operation. Allow execution of read operations with a GET rather than a POST.
49
    @write_operation('save')
1099.1.216 by Nick Chadwick
Started adding in add and save options in the exercise edit view, to
50
    def add_exercise(self, req, identifier, name, description, partial, solution, include, num_rows):
1606.1.7 by William Grant
Verify that new exercise URL names comply, and fail nicely if they fail or the name is a duplicate.
51
        if not VALID_URL_NAME.match(identifier):
52
            raise BadRequest(
1606.1.16 by William Grant
Fix validation messages to inform users that alphanumerics in names must be lowercase.
53
                "Exercise names must consist of a lowercase alphanumeric "
54
                "character followed by any number of lowercase alphanumerics, "
55
                "., +, - or _.")
1606.1.7 by William Grant
Verify that new exercise URL names comply, and fail nicely if they fail or the name is a duplicate.
56
57
        if req.store.find(Exercise, id=unicode(identifier)).one():
58
            raise BadRequest("An exercise with that URL name already exists.")
59
1099.1.216 by Nick Chadwick
Started adding in add and save options in the exercise edit view, to
60
        new_exercise = Exercise()
61
        new_exercise.id = unicode(identifier)
1099.1.229 by Nick Chadwick
Fixed a slight oversight, which meant there was no way to add a new
62
        new_exercise.name = unicode(name)
1720.1.3 by William Grant
Abstract worksheet and exercise data setting.
63
        new_exercise.set_description(unicode(description))
1099.1.229 by Nick Chadwick
Fixed a slight oversight, which meant there was no way to add a new
64
        new_exercise.partial = unicode(partial)
65
        new_exercise.solution = unicode(solution)
66
        new_exercise.include = unicode(include)
67
        new_exercise.num_rows = int(num_rows)
1099.1.216 by Nick Chadwick
Started adding in add and save options in the exercise edit view, to
68
        
69
        req.store.add(new_exercise)
70
        
71
        return {'result': 'ok'}
72
        
73
        
74
75
class ExerciseRESTView(JSONRESTView):
76
    """View for updating Exercises"""
77
    
1796.1.2 by William Grant
Replace @named_operation with @read_operation and @write_operation. Allow execution of read operations with a GET rather than a POST.
78
    @write_operation(u'edit')
1099.1.217 by Nick Chadwick
working on making the exercise editor complete
79
    def edit_exercise(self, req, name, description, partial, 
1099.1.216 by Nick Chadwick
Started adding in add and save options in the exercise edit view, to
80
                      solution, include, num_rows):
81
        
82
        self.context.name = unicode(name)
1720.1.3 by William Grant
Abstract worksheet and exercise data setting.
83
        self.context.set_description(unicode(description))
1099.1.216 by Nick Chadwick
Started adding in add and save options in the exercise edit view, to
84
        self.context.partial = unicode(partial)
85
        self.context.solution = unicode(solution)
86
        self.context.include = unicode(include)
87
        self.context.num_rows = int(num_rows)
1099.1.237 by Nick Chadwick
Updated exercise_service to make use of the new deletion methods
88
        return {'result': 'ok'}
1099.6.3 by Nick Chadwick
Edited the exercise service to delete individual parts of an exercise.
89
    
1796.1.2 by William Grant
Replace @named_operation with @read_operation and @write_operation. Allow execution of read operations with a GET rather than a POST.
90
    @write_operation(u'edit')
1099.6.3 by Nick Chadwick
Edited the exercise service to delete individual parts of an exercise.
91
    def delete_exercise(self, req, id):
92
        
1099.1.242 by Nick Chadwick
Fixed a problem with exercise editor, which wasn't editing or adding
93
        self.context.delete()
94
        return {'result': 'ok'}
95
1796.1.2 by William Grant
Replace @named_operation with @read_operation and @write_operation. Allow execution of read operations with a GET rather than a POST.
96
    @write_operation(u'edit')
1099.1.217 by Nick Chadwick
working on making the exercise editor complete
97
    def add_suite(self, req, description, function, stdin):
1099.1.216 by Nick Chadwick
Started adding in add and save options in the exercise edit view, to
98
        
99
        new_suite = TestSuite()
1099.1.217 by Nick Chadwick
working on making the exercise editor complete
100
        new_suite.description = unicode(description)
101
        new_suite.seq_no = self.context.test_suites.count()
102
        new_suite.function = unicode(function)
103
        new_suite.stdin = unicode(stdin)
1099.1.216 by Nick Chadwick
Started adding in add and save options in the exercise edit view, to
104
        new_suite.exercise = self.context
105
        
106
        req.store.add(new_suite)
107
        
108
        return {'result': 'ok'}
109
        
1796.1.2 by William Grant
Replace @named_operation with @read_operation and @write_operation. Allow execution of read operations with a GET rather than a POST.
110
    @write_operation(u'edit')
1099.1.217 by Nick Chadwick
working on making the exercise editor complete
111
    def edit_suite(self, req, suiteid, description, function, stdin):
112
        
113
        suite = req.store.find(TestSuite,
114
            TestSuite.suiteid == int(suiteid),
115
            TestSuite.exercise_id == self.context.id).one()
116
        
117
        if suite is None:
118
            raise NotFound()
119
        
120
        suite.description = unicode(description)
121
        suite.function = unicode(function)
122
        suite.stdin = unicode(stdin)
123
        
124
        return {'result': 'ok'}
1099.6.3 by Nick Chadwick
Edited the exercise service to delete individual parts of an exercise.
125
    
1796.1.2 by William Grant
Replace @named_operation with @read_operation and @write_operation. Allow execution of read operations with a GET rather than a POST.
126
    @write_operation(u'edit')
1099.6.3 by Nick Chadwick
Edited the exercise service to delete individual parts of an exercise.
127
    def delete_suite(self, req, suiteid):
128
        
129
        suite = req.store.find(TestSuite,
130
            TestSuite.suiteid == int(suiteid),
131
            TestSuite.exercise_id == self.context.id).one()
132
        if suite is None:
133
            raise NotFound()
134
        
1099.1.242 by Nick Chadwick
Fixed a problem with exercise editor, which wasn't editing or adding
135
        suite.delete()
1099.6.3 by Nick Chadwick
Edited the exercise service to delete individual parts of an exercise.
136
        
137
        return {'result': 'ok'}
1099.1.217 by Nick Chadwick
working on making the exercise editor complete
138
      
1796.1.2 by William Grant
Replace @named_operation with @read_operation and @write_operation. Allow execution of read operations with a GET rather than a POST.
139
    @write_operation(u'edit')
1099.1.217 by Nick Chadwick
working on making the exercise editor complete
140
    def add_var(self, req, suiteid, var_type, var_name, var_val, argno):
141
142
        suite = req.store.find(TestSuite,
143
            TestSuite.suiteid == int(suiteid),
144
            TestSuite.exercise_id == self.context.id).one()
145
        
146
        if suite is None:
147
            raise NotFound()
148
        
149
        new_var = TestSuiteVar()
150
        new_var.var_type = unicode(var_type)
151
        new_var.var_name = unicode(var_name)
1409 by William Grant
Unbreak variable adding and editing.
152
        new_var.var_value = unicode(var_val)
153
        new_var.arg_no = int(argno) if len(argno) else None
1099.1.217 by Nick Chadwick
working on making the exercise editor complete
154
        new_var.suite = suite
155
        
156
        req.store.add(new_var)
157
        
158
        return {'result': 'ok'}
1099.1.220 by Nick Chadwick
Merged from trunk
159
1796.1.2 by William Grant
Replace @named_operation with @read_operation and @write_operation. Allow execution of read operations with a GET rather than a POST.
160
    @write_operation(u'edit')
1099.1.220 by Nick Chadwick
Merged from trunk
161
    def edit_var(self, req, suiteid, varid, var_type, var_name, var_val, argno):
162
        var = req.store.find(TestSuiteVar,
163
            TestSuiteVar.varid == int(varid),
164
            TestSuiteVar.suiteid == int(suiteid)
165
        ).one()
166
        
167
        if var is None:
1099.1.242 by Nick Chadwick
Fixed a problem with exercise editor, which wasn't editing or adding
168
            raise NotFound("Var not found.")
1099.1.220 by Nick Chadwick
Merged from trunk
169
            
170
        var.var_type = unicode(var_type)
171
        var.var_name = unicode(var_name)
1409 by William Grant
Unbreak variable adding and editing.
172
        var.var_value = unicode(var_val)
173
        var.arg_no = int(argno) if len(argno) else None
1099.1.220 by Nick Chadwick
Merged from trunk
174
        
175
        return {'result': 'ok'}
176
    
1796.1.2 by William Grant
Replace @named_operation with @read_operation and @write_operation. Allow execution of read operations with a GET rather than a POST.
177
    @write_operation(u'edit')
1099.6.3 by Nick Chadwick
Edited the exercise service to delete individual parts of an exercise.
178
    def delete_var(self, req, suiteid, varid):
179
        var = req.store.find(TestSuiteVar,
180
            TestSuiteVar.varid == int(varid),
181
            TestSuiteVar.suiteid == int(suiteid)).one()
182
        if var is None:
183
            raise NotFound()
184
        
1099.1.242 by Nick Chadwick
Fixed a problem with exercise editor, which wasn't editing or adding
185
        var.delete()
1099.6.3 by Nick Chadwick
Edited the exercise service to delete individual parts of an exercise.
186
        
187
        return {'result': 'ok'}
1099.1.237 by Nick Chadwick
Updated exercise_service to make use of the new deletion methods
188
        
1796.1.2 by William Grant
Replace @named_operation with @read_operation and @write_operation. Allow execution of read operations with a GET rather than a POST.
189
    @write_operation(u'edit')
1394.1.5 by William Grant
Drop TestSuite file match default from the UI -- we don't support file tests any more.
190
    def add_testcase(self, req, suiteid, passmsg, failmsg):
1099.1.221 by Nick Chadwick
added in extra parts to the exercise edit view. Now almost all
191
        
192
        suite = req.store.find(TestSuite,
193
            TestSuite.suiteid == int(suiteid),
194
            TestSuite.exercise_id == self.context.id).one()
195
        
196
        if suite is None:
197
            raise NotFound()
198
        
199
        new_case = TestCase()
200
        new_case.passmsg = unicode(passmsg)
201
        new_case.failmsg = unicode(failmsg)
202
        new_case.seq_no = suite.test_cases.count()
1412 by William Grant
Disable support for testcases with a default other than 'ignore'.
203
        # XXX: Force this for now, since we don't support the
204
        #      'match' default. It might make sense to support
205
        #      this again once file testing support returns.
206
        new_case.test_default = u'ignore'
1099.1.221 by Nick Chadwick
added in extra parts to the exercise edit view. Now almost all
207
        suite.test_cases.add(new_case)
208
        
209
        req.store.add(new_case)
210
        
211
        return {'result': 'ok'}
1099.6.1 by Nick Chadwick
Exercise-ui is now able to create an entire exercise.
212
    
1796.1.2 by William Grant
Replace @named_operation with @read_operation and @write_operation. Allow execution of read operations with a GET rather than a POST.
213
    @write_operation(u'edit')
1394.1.5 by William Grant
Drop TestSuite file match default from the UI -- we don't support file tests any more.
214
    def edit_testcase(self, req, suiteid, testid, passmsg, failmsg):
1099.6.1 by Nick Chadwick
Exercise-ui is now able to create an entire exercise.
215
        
216
        suite = req.store.find(TestSuite,
217
            TestSuite.suiteid == int(suiteid),
218
            TestSuite.exercise_id == self.context.id).one()
219
        if suite is None:
1099.1.238 by Nick Chadwick
Removed some extraneous output in exercise REST views, when encountering
220
            raise NotFound()
1099.6.1 by Nick Chadwick
Exercise-ui is now able to create an entire exercise.
221
        
222
        test_case = req.store.find(TestCase,
223
            TestCase.suiteid == suite.suiteid,
224
            TestCase.testid == int(testid)).one()
225
        if test_case is None:
1099.1.238 by Nick Chadwick
Removed some extraneous output in exercise REST views, when encountering
226
            raise NotFound()
1099.6.1 by Nick Chadwick
Exercise-ui is now able to create an entire exercise.
227
        
228
        test_case.passmsg = unicode(passmsg)
229
        test_case.failmsg = unicode(failmsg)
230
        
231
        return {'result': 'ok'}
232
    
1796.1.2 by William Grant
Replace @named_operation with @read_operation and @write_operation. Allow execution of read operations with a GET rather than a POST.
233
    @write_operation(u'edit')
1099.6.3 by Nick Chadwick
Edited the exercise service to delete individual parts of an exercise.
234
    def delete_testcase(self, req, suiteid, testid):
235
        
236
        suite = req.store.find(TestSuite,
237
            TestSuite.suiteid == int(suiteid),
238
            TestSuite.exercise_id == self.context.id).one()
239
        if suite is None:
240
            raise NotFound()
241
        
242
        test_case = req.store.find(TestCase,
243
            TestCase.suiteid == suite.suiteid,
244
            TestCase.testid == int(testid)).one()
1099.6.4 by Nick Chadwick
Exercise UI is now ready to be merged into trunk.
245
        if test_case is None:   
1099.6.3 by Nick Chadwick
Edited the exercise service to delete individual parts of an exercise.
246
            raise NotFound()
1099.1.237 by Nick Chadwick
Updated exercise_service to make use of the new deletion methods
247
1099.1.242 by Nick Chadwick
Fixed a problem with exercise editor, which wasn't editing or adding
248
        test_case.delete()
1099.1.237 by Nick Chadwick
Updated exercise_service to make use of the new deletion methods
249
1099.6.3 by Nick Chadwick
Edited the exercise service to delete individual parts of an exercise.
250
        return {'result': 'ok'}
251
    
1796.1.2 by William Grant
Replace @named_operation with @read_operation and @write_operation. Allow execution of read operations with a GET rather than a POST.
252
    @write_operation(u'edit')
1099.6.1 by Nick Chadwick
Exercise-ui is now able to create an entire exercise.
253
    def edit_testpart(self, req, suiteid, testid, partid, part_type, test_type, 
1394.1.7 by William Grant
Drop TestCasePart filename UI.
254
                      data):
1099.6.1 by Nick Chadwick
Exercise-ui is now able to create an entire exercise.
255
    
256
        suite = req.store.find(TestSuite,
257
            TestSuite.suiteid == int(suiteid),
258
            TestSuite.exercise_id == self.context.id).one()
259
        if suite is None:
1099.1.238 by Nick Chadwick
Removed some extraneous output in exercise REST views, when encountering
260
            raise NotFound()
1099.6.1 by Nick Chadwick
Exercise-ui is now able to create an entire exercise.
261
        
262
        test_case = req.store.find(TestCase,
263
            TestCase.suiteid == suite.suiteid,
264
            TestCase.testid == int(testid)).one()
265
        if test_case is None:
1099.1.238 by Nick Chadwick
Removed some extraneous output in exercise REST views, when encountering
266
            raise NotFound()
1099.6.1 by Nick Chadwick
Exercise-ui is now able to create an entire exercise.
267
        
268
        test_part = req.store.find(TestCasePart,
269
            TestCasePart.testid == test_case.testid,
270
            TestCasePart.partid == int(partid)).one()
271
        if test_part is None:
272
            raise NotFound('testcasepart')
273
        
274
        test_part.part_type = unicode(part_type)
275
        test_part.test_type = unicode(test_type)
276
        test_part.data = unicode(data)
277
        
278
        return {'result': 'ok'}
279
    
1796.1.2 by William Grant
Replace @named_operation with @read_operation and @write_operation. Allow execution of read operations with a GET rather than a POST.
280
    @write_operation(u'edit')
1099.6.1 by Nick Chadwick
Exercise-ui is now able to create an entire exercise.
281
    def add_testpart(self, req, suiteid, testid, part_type, test_type, 
1394.1.7 by William Grant
Drop TestCasePart filename UI.
282
                      data):
1099.6.1 by Nick Chadwick
Exercise-ui is now able to create an entire exercise.
283
    
284
        suite = req.store.find(TestSuite,
285
            TestSuite.suiteid == int(suiteid),
286
            TestSuite.exercise_id == self.context.id).one()
287
        if suite is None:
1099.1.238 by Nick Chadwick
Removed some extraneous output in exercise REST views, when encountering
288
            raise NotFound()
1099.6.1 by Nick Chadwick
Exercise-ui is now able to create an entire exercise.
289
        
290
        test_case = req.store.find(TestCase,
291
            TestCase.suiteid == suite.suiteid,
292
            TestCase.testid == int(testid)).one()
293
        if test_case is None:
1099.1.238 by Nick Chadwick
Removed some extraneous output in exercise REST views, when encountering
294
            raise NotFound()
1099.6.1 by Nick Chadwick
Exercise-ui is now able to create an entire exercise.
295
        
296
        test_part = TestCasePart()
297
        test_part.part_type = unicode(part_type)
298
        test_part.test_type = unicode(test_type)
299
        test_part.data = unicode(data)
300
        
301
        test_case.parts.add(test_part)
302
        
303
        return {'result': 'ok'}
1099.6.3 by Nick Chadwick
Edited the exercise service to delete individual parts of an exercise.
304
    
1796.1.2 by William Grant
Replace @named_operation with @read_operation and @write_operation. Allow execution of read operations with a GET rather than a POST.
305
    @write_operation(u'edit')
1099.6.3 by Nick Chadwick
Edited the exercise service to delete individual parts of an exercise.
306
    def delete_testpart(self, req, suiteid, testid, partid):
307
        suite = req.store.find(TestSuite,
308
            TestSuite.suiteid == int(suiteid),
309
            TestSuite.exercise_id == self.context.id).one()
310
        if suite is None:
311
            raise NotFound()
312
        
313
        test_case = req.store.find(TestCase,
314
            TestCase.suiteid == suite.suiteid,
315
            TestCase.testid == int(testid)).one()
316
        if test_case is None:
317
            raise NotFound()
318
        
319
        test_part = req.store.find(TestCasePart,
320
            TestCasePart.testid == test_case.testid,
321
            TestCasePart.partid == int(partid)).one()
322
        if test_part is None:
1099.1.238 by Nick Chadwick
Removed some extraneous output in exercise REST views, when encountering
323
            raise NotFound()
1099.6.3 by Nick Chadwick
Edited the exercise service to delete individual parts of an exercise.
324
        
1099.1.242 by Nick Chadwick
Fixed a problem with exercise editor, which wasn't editing or adding
325
        test_part.delete()
1099.6.3 by Nick Chadwick
Edited the exercise service to delete individual parts of an exercise.
326
        
327
        return {'result': 'ok'}
1394.2.7 by William Grant
Hook up a new ExerciseRESTView.test into the JS.
328
1796.1.2 by William Grant
Replace @named_operation with @read_operation and @write_operation. Allow execution of read operations with a GET rather than a POST.
329
    @write_operation(u'edit')
1394.2.7 by William Grant
Hook up a new ExerciseRESTView.test into the JS.
330
    def test(self, req, code):
1602 by William Grant
Fix circular import in exercise_service.
331
        from ivle.worksheet.utils import test_exercise_submission
1397.1.1 by William Grant
Return a TestCreationError if one occurs while testing an exercise standalone.
332
        try:
333
            return test_exercise_submission(
334
                req.config, req.user, self.context, code)
335
        except TestCreationError, e:
1397.1.5 by William Grant
Report TestCreationErrors as a critical error during testing.
336
            return {'critical_error': {'name': 'TestCreationError', 'detail': e._reason}}
1413 by William Grant
Display TestErrors in test mode, much like TestCreationErrors.
337
        except TestError, e:
338
            return {'critical_error': {'name': 'TestError', 'detail': str(e)}}