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

« back to all changes in this revision

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

ivle.webapp.base.rest#RESTView: Remove broken old render() - it should be
                                implemented by subclasses.
                     #JSONRESTView: Remove an impossible assertion, and fix
                                    some exception message spacing.
ivle.webapp.base.test.test_rest: Add a named operation with both default and
                                 non-default arguments, and check that we die
                                 properly when the non-default is unspecified.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# IVLE - Informatics Virtual Learning Environment
2
 
# Copyright (C) 2007-2009 The University of Melbourne
 
2
# Copyright (C) 2007-2008 The University of Melbourne
3
3
#
4
4
# This program is free software; you can redistribute it and/or modify
5
5
# it under the terms of the GNU General Public License as published by
15
15
# along with this program; if not, write to the Free Software
16
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
17
 
18
 
# Author: Matt Giuca, Nick Chadwick
19
 
 
20
 
'''AJAX backend for the tutorial application.'''
 
18
# Module: TutorialService
 
19
# Author: Matt Giuca
 
20
# Date:   25/1/2008
 
21
 
 
22
# Provides the AJAX backend for the tutorial application.
 
23
# This allows several actions to be performed on the code the student has
 
24
# typed into one of the exercise boxes.
 
25
 
 
26
# Calling syntax
 
27
# Path must be empty.
 
28
# The arguments determine what is to be done on this file.
 
29
 
 
30
# "action". One of the tutorialservice actions.
 
31
# "exercise" - The path to a exercise file (including the .xml extension),
 
32
#              relative to the subjects base directory.
 
33
# action "save" or "test" (POST only):
 
34
#   "code" - Full text of the student's code being submitted.
 
35
# action "getattempts": No arguments. Returns a list of
 
36
#   {'date': 'formatted_date', 'complete': bool} dicts.
 
37
# action "getattempt":
 
38
#   "date" - Formatted date. Gets most recent attempt before (and including)
 
39
#   that date.
 
40
#   Returns JSON string containing code, or null.
 
41
 
 
42
# Returns a JSON response string indicating the results.
21
43
 
22
44
import os
 
45
import time
23
46
import datetime
24
47
 
25
 
import genshi
26
 
from storm.locals import Store
27
 
 
28
 
import ivle.console
29
 
import ivle.database
30
 
from ivle.database import Exercise, ExerciseAttempt, ExerciseSave, Worksheet, \
31
 
                          Offering, Subject, Semester, User, WorksheetExercise
32
 
import ivle.worksheet.utils
33
 
import ivle.webapp.tutorial.test
34
 
from ivle.webapp.base.rest import (JSONRESTView, named_operation,
35
 
                                   require_permission)
36
 
from ivle.webapp.errors import NotFound
37
 
 
 
48
import cjson
 
49
 
 
50
from ivle import util
 
51
from ivle import console
 
52
import ivle.database
 
53
import ivle.worksheet
 
54
import ivle.conf
 
55
import test # XXX: Really .test, not real test.
 
56
 
 
57
from ivle.webapp.base.rest import JSONRESTView
 
58
import ivle.database
 
59
from ivle.webapp.base.rest import named_operation
 
60
 
 
61
# If True, getattempts or getattempt will allow browsing of inactive/disabled
 
62
# attempts. If False, will not allow this.
 
63
HISTORY_ALLOW_INACTIVE = False
38
64
 
39
65
TIMESTAMP_FORMAT = '%Y-%m-%d %H:%M:%S'
40
66
 
 
67
 
41
68
class AttemptsRESTView(JSONRESTView):
42
 
    '''REST view of a user's attempts at an exercise.'''
43
 
 
44
 
    @require_permission('edit')
 
69
    '''
 
70
    Class to return a list of attempts for a given exercise, or add an Attempt
 
71
    '''
45
72
    def GET(self, req):
46
73
        """Handles a GET Attempts action."""
47
 
        attempts = req.store.find(ExerciseAttempt, 
48
 
                ExerciseAttempt.ws_ex_id == self.context.worksheet_exercise.id,
49
 
                ExerciseAttempt.user_id == self.context.user.id)
 
74
        exercise = ivle.database.Exercise.get_by_name(req.store, 
 
75
                                                        self.exercise)
 
76
        user = ivle.database.User.get_by_login(req.store, self.username)
 
77
 
 
78
        attempts = ivle.worksheet.get_exercise_attempts(req.store, user,
 
79
                            exercise, allow_inactive=HISTORY_ALLOW_INACTIVE)
50
80
        # attempts is a list of ExerciseAttempt objects. Convert to dictionaries
51
81
        time_fmt = lambda dt: datetime.datetime.strftime(dt, TIMESTAMP_FORMAT)
52
82
        attempts = [{'date': time_fmt(a.date), 'complete': a.complete}
53
83
                for a in attempts]
54
 
 
 
84
        attempts.append(self.exercise)
 
85
        
55
86
        return attempts
56
 
 
57
 
 
58
 
    @require_permission('edit')
 
87
        
59
88
    def PUT(self, req, data):
60
 
        """ Tests the given submission """
 
89
        
 
90
        exercisefile = util.open_exercise_file(self.exercise)
 
91
        if exercisefile is None:
 
92
            req.throw_error(req.HTTP_NOT_FOUND,
 
93
                "The exercise was not found.")
 
94
 
61
95
        # Start a console to run the tests on
62
 
        jail_path = os.path.join(req.config['paths']['jails']['mounts'],
63
 
                                 req.user.login)
 
96
        jail_path = os.path.join(ivle.conf.jail_base, req.user.login)
64
97
        working_dir = os.path.join("/home", req.user.login)
65
 
        cons = ivle.console.Console(req.config, req.user.unixid, jail_path,
66
 
                                    working_dir)
 
98
        cons = console.Console(req.user.unixid, jail_path, working_dir)
67
99
 
68
100
        # Parse the file into a exercise object using the test suite
69
 
        exercise_obj = ivle.webapp.tutorial.test.parse_exercise_file(
70
 
                            self.context.worksheet_exercise.exercise, cons)
 
101
        exercise_obj = test.parse_exercise_file(exercisefile, cons)
 
102
        exercisefile.close()
71
103
 
72
104
        # Run the test cases. Get the result back as a JSONable object.
73
105
        # Return it.
74
 
        test_results = exercise_obj.run_tests(data['code'])
 
106
        test_results = exercise_obj.run_tests(code)
75
107
 
76
108
        # Close the console
77
109
        cons.close()
78
110
 
 
111
        # Get the Exercise from the database
 
112
        exercise = ivle.database.Exercise.get_by_name(req.store, exercisesrc)
 
113
 
79
114
        attempt = ivle.database.ExerciseAttempt(user=req.user,
80
 
            worksheet_exercise = self.context.worksheet_exercise,
81
 
            date = datetime.datetime.now(),
82
 
            complete = test_results['passed'],
83
 
            text = unicode(data['code'])
84
 
        )
 
115
                                                exercise=exercise,
 
116
                                                date=datetime.datetime.now(),
 
117
                                                complete=test_results['passed'],
 
118
                                                text=unicode(code)) # XXX
85
119
 
86
120
        req.store.add(attempt)
87
 
 
 
121
        req.store.commit()
88
122
        # Query the DB to get an updated score on whether or not this problem
89
123
        # has EVER been completed (may be different from "passed", if it has
90
124
        # been completed before), and the total number of attempts.
91
 
        completed, attempts = ivle.worksheet.utils.get_exercise_status(
92
 
                req.store, req.user, self.context.worksheet_exercise)
 
125
        completed, attempts = ivle.worksheet.get_exercise_status(req.store,
 
126
            req.user, exercise)
93
127
        test_results["completed"] = completed
94
128
        test_results["attempts"] = attempts
95
129
 
96
130
        return test_results
97
 
 
 
131
        
98
132
 
99
133
class AttemptRESTView(JSONRESTView):
100
 
    '''REST view of an exercise attempt.'''
101
 
 
102
 
    @require_permission('view')
 
134
    '''
 
135
    View used to extract the data of a specified attempt
 
136
    '''
 
137
    
103
138
    def GET(self, req):
104
 
        return {'code': self.context.text}
105
 
 
106
 
 
107
 
class WorksheetExerciseRESTView(JSONRESTView):
108
 
    '''REST view of a worksheet exercise.'''
109
 
 
110
 
    @named_operation('view')
111
 
    def save(self, req, text):
112
 
        # Find the appropriate WorksheetExercise to save to. If its not found,
113
 
        # the user is submitting against a non-existant worksheet/exercise
114
 
 
115
 
        old_save = req.store.find(ExerciseSave,
116
 
            ExerciseSave.ws_ex_id == self.context.id,
117
 
            ExerciseSave.user == req.user).one()
118
 
        
119
 
        #Overwrite the old, or create a new if there isn't one
120
 
        if old_save is None:
121
 
            new_save = ExerciseSave()
122
 
            req.store.add(new_save)
123
 
        else:
124
 
            new_save = old_save
125
 
        
126
 
        new_save.worksheet_exercise = self.context
127
 
        new_save.user = req.user
128
 
        new_save.text = unicode(text)
129
 
        new_save.date = datetime.datetime.now()
130
 
 
131
 
        return {"result": "ok"}
132
 
 
133
 
 
134
 
# Note that this is the view of an existing worksheet. Creation is handled
135
 
# by OfferingRESTView (as offerings have worksheets)
136
 
class WorksheetRESTView(JSONRESTView):
137
 
    """View used to update a worksheet."""
138
 
 
139
 
    @named_operation('edit')
140
 
    def save(self, req, name, assessable, data, format):
141
 
        """Takes worksheet data and saves it."""
142
 
        self.context.name = unicode(name)
143
 
        self.context.assessable = self.convert_bool(assessable)
144
 
        self.context.data = unicode(data)
145
 
        self.context.format = unicode(format)
146
 
        ivle.worksheet.utils.update_exerciselist(self.context)
147
 
        
148
 
        return {"result": "ok"}
149
 
 
150
 
class WorksheetsRESTView(JSONRESTView):
151
 
    """View used to update and create Worksheets."""
152
 
 
153
 
    @named_operation('edit')
154
 
    def add_worksheet(self, req, identifier, name, assessable, data, format):
155
 
        """Takes worksheet data and adds it."""
156
 
        
157
 
        new_worksheet = Worksheet()
158
 
        new_worksheet.seq_no = self.context.worksheets.count()
159
 
        # Setting new_worksheet.offering implicitly adds new_worksheet,
160
 
        # hence worksheets.count MUST be called above it
161
 
        new_worksheet.offering = self.context
162
 
        new_worksheet.identifier = unicode(identifier)
163
 
        new_worksheet.name = unicode(name)
164
 
        new_worksheet.assessable = self.convert_bool(assessable)
165
 
        new_worksheet.data = unicode(data)
166
 
        new_worksheet.format = unicode(format)
167
 
        
168
 
        # This call is added for clarity, as the worksheet is implicitly added.        
169
 
        req.store.add(new_worksheet)
170
 
 
171
 
        ivle.worksheet.utils.update_exerciselist(new_worksheet)
172
 
 
173
 
        return {"result": "ok"}
174
 
 
175
 
    @named_operation('edit')
176
 
    def move_up(self, req, worksheetid):
177
 
        """Takes a list of worksheet-seq_no pairs and updates their 
178
 
        corresponding Worksheet objects to match."""
179
 
        
180
 
        worksheet_below = req.store.find(Worksheet,
181
 
            Worksheet.offering_id == self.context.id,
182
 
            Worksheet.identifier == unicode(worksheetid)).one()
183
 
        if worksheet_below is None:
184
 
            raise NotFound('worksheet_below')
185
 
        worksheet_above = req.store.find(Worksheet,
186
 
            Worksheet.offering_id == self.context.id,
187
 
            Worksheet.seq_no == (worksheet_below.seq_no - 1)).one()
188
 
        if worksheet_above is None:
189
 
            raise NotFound('worksheet_above')
190
 
 
191
 
        worksheet_below.seq_no = worksheet_below.seq_no - 1
192
 
        worksheet_above.seq_no = worksheet_above.seq_no + 1
193
 
        
194
 
        return {'result': 'ok'}
195
 
 
196
 
    @named_operation('edit')
197
 
    def move_down(self, req, worksheetid):
198
 
        """Takes a list of worksheet-seq_no pairs and updates their 
199
 
        corresponding Worksheet objects to match."""
200
 
        
201
 
        worksheet_above = req.store.find(Worksheet,
202
 
            Worksheet.offering_id == self.context.id,
203
 
            Worksheet.identifier == unicode(worksheetid)).one()
204
 
        if worksheet_above is None:
205
 
            raise NotFound('worksheet_below')
206
 
        worksheet_below = req.store.find(Worksheet,
207
 
            Worksheet.offering_id == self.context.id,
208
 
            Worksheet.seq_no == (worksheet_above.seq_no + 1)).one()
209
 
        if worksheet_below is None:
210
 
            raise NotFound('worksheet_above')
211
 
 
212
 
        worksheet_below.seq_no = worksheet_below.seq_no - 1
213
 
        worksheet_above.seq_no = worksheet_above.seq_no + 1
214
 
        
215
 
        return {'result': 'ok'}
 
139
        # Get an actual date object, rather than a string
 
140
        date = datetime.datetime.strptime(self.date, TIMESTAMP_FORMAT)
 
141
        
 
142
        exercise = ivle.database.Exercise.get_by_name(req.store, self.exercise)
 
143
        attempt = ivle.worksheet.get_exercise_attempt(req.store, req.user,
 
144
            exercise, as_of=date, allow_inactive=HISTORY_ALLOW_INACTIVE)
 
145
        if attempt is not None:
 
146
            attempt = attempt.text
 
147
        # attempt may be None; will write "null"
 
148
        return {'code': attempt}
 
149
        
 
150
class ExerciseRESTView(JSONRESTView):
 
151
    '''
 
152
    Handles a save action. This saves the user's code without executing it.
 
153
    '''
 
154
    @named_operation
 
155
    def save(self, req, user, text):    
 
156
        # Need to open JUST so we know this is a real exercise.
 
157
        # (This avoids users submitting code for bogus exercises).
 
158
        exercisefile = util.open_exercise_file(self.exercise)
 
159
        if exercisefile is None:
 
160
            req.throw_error(req.HTTP_NOT_FOUND,
 
161
                "The exercise was not found.")
 
162
        exercisefile.close()
 
163
 
 
164
        exercise = ivle.database.Exercise.get_by_name(req.store, self.exercise)
 
165
        ivle.worksheet.save_exercise(req.store, req.user, exercise,
 
166
                                     unicode(code), datetime.datetime.now())
 
167
        req.store.commit()
 
168
        return {"result": "ok"}