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

« back to all changes in this revision

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

Error views are now retrieved from a class method of the view, not the plugin
registry. They also now discriminate based on exception type.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# IVLE - Informatics Virtual Learning Environment
2
 
# Copyright (C) 2007-2008 The University of Melbourne
 
2
# Copyright (C) 2007-2009 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
 
# 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.
 
18
# Author: Matt Giuca, Nick Chadwick
 
19
 
 
20
'''AJAX backend for the tutorial application.'''
43
21
 
44
22
import os
45
 
import time
46
23
import datetime
47
24
 
48
 
import cjson
49
 
 
50
 
from ivle import db
51
 
from ivle import util
52
 
from ivle import console
 
25
import ivle.util
 
26
import ivle.console
53
27
import ivle.database
54
28
import ivle.worksheet
55
29
import ivle.conf
56
 
import test # XXX: Really .test, not real test.
 
30
import ivle.webapp.tutorial.test
 
31
 
 
32
from ivle.webapp.base.rest import JSONRESTView, named_operation
 
33
from ivle.webapp.errors import NotFound
57
34
 
58
35
# If True, getattempts or getattempt will allow browsing of inactive/disabled
59
36
# attempts. If False, will not allow this.
60
37
HISTORY_ALLOW_INACTIVE = False
61
38
 
62
 
def handle(req):
63
 
    """Handler for Ajax backend TutorialService app."""
64
 
    # Set request attributes
65
 
    req.write_html_head_foot = False     # No HTML
66
 
 
67
 
    if req.path != "":
68
 
        req.throw_error(req.HTTP_BAD_REQUEST)
69
 
    fields = req.get_fieldstorage()
70
 
    act = fields.getfirst('action')
71
 
    exercise = fields.getfirst('exercise')
72
 
    if act is None or exercise is None:
73
 
        req.throw_error(req.HTTP_BAD_REQUEST)
74
 
    act = act.value
75
 
    exercise = exercise.value
76
 
 
77
 
    if act == 'save' or act == 'test':
78
 
        # Must be POST
79
 
        if req.method != 'POST':
80
 
            req.throw_error(req.HTTP_BAD_REQUEST)
81
 
 
82
 
        code = fields.getfirst('code')
83
 
        if code is None:
84
 
            req.throw_error(req.HTTP_BAD_REQUEST)
85
 
        code = code.value
86
 
 
87
 
        if act == 'save':
88
 
            handle_save(req, exercise, code, fields)
89
 
        else:   # act == "test"
90
 
            handle_test(req, exercise, code, fields)
91
 
    elif act == 'getattempts':
92
 
        handle_getattempts(req, exercise)
93
 
    elif act == 'getattempt':
94
 
        date = fields.getfirst('date')
95
 
        if date is None:
96
 
            req.throw_error(req.HTTP_BAD_REQUEST)
97
 
        date = date.value
98
 
        # Convert into a struct_time
99
 
        # The time *should* be in the same format as the DB (since it should
100
 
        # be bounced back to us from the getattempts output). Assume this.
 
39
TIMESTAMP_FORMAT = '%Y-%m-%d %H:%M:%S'
 
40
 
 
41
 
 
42
class AttemptsRESTView(JSONRESTView):
 
43
    '''REST view of a user's attempts at an exercise.'''
 
44
    def __init__(self, req, subject, worksheet, exercise, username):
 
45
        # TODO: Find exercise within worksheet.
 
46
        self.user = ivle.database.User.get_by_login(req.store, username)
 
47
        if self.user is None:
 
48
            raise NotFound()
 
49
        self.exercise = exercise
 
50
 
 
51
    def GET(self, req):
 
52
        """Handles a GET Attempts action."""
 
53
        exercise = ivle.database.Exercise.get_by_name(req.store, 
 
54
                                                        self.exercise)
 
55
 
 
56
        attempts = ivle.worksheet.get_exercise_attempts(req.store, self.user,
 
57
                            exercise, allow_inactive=HISTORY_ALLOW_INACTIVE)
 
58
        # attempts is a list of ExerciseAttempt objects. Convert to dictionaries
 
59
        time_fmt = lambda dt: datetime.datetime.strftime(dt, TIMESTAMP_FORMAT)
 
60
        attempts = [{'date': time_fmt(a.date), 'complete': a.complete}
 
61
                for a in attempts]
 
62
 
 
63
        return attempts
 
64
 
 
65
 
 
66
    def PUT(self, req, data):
 
67
        ''' Tests the given submission '''
 
68
        exercisefile = ivle.util.open_exercise_file(self.exercise)
 
69
        if exercisefile is None:
 
70
            raise NotFound()
 
71
 
 
72
        # Start a console to run the tests on
 
73
        jail_path = os.path.join(ivle.conf.jail_base, req.user.login)
 
74
        working_dir = os.path.join("/home", req.user.login)
 
75
        cons = ivle.console.Console(req.user.unixid, jail_path, working_dir)
 
76
 
 
77
        # Parse the file into a exercise object using the test suite
 
78
        exercise_obj = ivle.webapp.tutorial.test.parse_exercise_file(
 
79
                                                            exercisefile, cons)
 
80
        exercisefile.close()
 
81
 
 
82
        # Run the test cases. Get the result back as a JSONable object.
 
83
        # Return it.
 
84
        test_results = exercise_obj.run_tests(data['code'])
 
85
 
 
86
        # Close the console
 
87
        cons.close()
 
88
 
 
89
        # Get the Exercise from the database
 
90
        exercise = ivle.database.Exercise.get_by_name(req.store, self.exercise)
 
91
 
 
92
        attempt = ivle.database.ExerciseAttempt(user=req.user,
 
93
                                                exercise=exercise,
 
94
                                                date=datetime.datetime.now(),
 
95
                                                complete=test_results['passed'],
 
96
                                                # XXX
 
97
                                                text=unicode(data['code']))
 
98
 
 
99
        req.store.add(attempt)
 
100
 
 
101
        # Query the DB to get an updated score on whether or not this problem
 
102
        # has EVER been completed (may be different from "passed", if it has
 
103
        # been completed before), and the total number of attempts.
 
104
        completed, attempts = ivle.worksheet.get_exercise_status(req.store,
 
105
            req.user, exercise)
 
106
        test_results["completed"] = completed
 
107
        test_results["attempts"] = attempts
 
108
 
 
109
        return test_results
 
110
 
 
111
 
 
112
class AttemptRESTView(JSONRESTView):
 
113
    '''REST view of an exercise attempt.'''
 
114
 
 
115
    def __init__(self, req, subject, worksheet, exercise, username, date):
 
116
        # TODO: Find exercise within worksheet.
 
117
        user = ivle.database.User.get_by_login(req.store, username)
 
118
        if user is None:
 
119
            raise NotFound()
 
120
 
101
121
        try:
102
 
            date = datetime.datetime.strptime(date, db.TIMESTAMP_FORMAT)
 
122
            date = datetime.datetime.strptime(date, TIMESTAMP_FORMAT)
103
123
        except ValueError:
104
 
            # Date was not in correct format
105
 
            req.throw_error(req.HTTP_BAD_REQUEST)
106
 
        handle_getattempt(req, exercise, date)
107
 
    else:
108
 
        req.throw_error(req.HTTP_BAD_REQUEST)
109
 
 
110
 
def handle_save(req, exercise, code, fields):
111
 
    """Handles a save action. This saves the user's code without executing it.
112
 
    """
113
 
    # Need to open JUST so we know this is a real exercise.
114
 
    # (This avoids users submitting code for bogus exercises).
115
 
    exercisefile = util.open_exercise_file(exercise)
116
 
    if exercisefile is None:
117
 
        req.throw_error(req.HTTP_NOT_FOUND,
118
 
            "The exercise was not found.")
119
 
    exercisefile.close()
120
 
 
121
 
    req.write('{"result": "ok"}')
122
 
 
123
 
    conn = db.DB()
124
 
 
125
 
    try:
126
 
        conn.write_problem_save(
127
 
            user = req.user,
128
 
            exercisename = exercise,
129
 
            date = time.localtime(),
130
 
            text = code)
131
 
    finally:
132
 
        conn.close()
133
 
 
134
 
def handle_test(req, exercisesrc, code, fields):
135
 
    """Handles a test action."""
136
 
 
137
 
    exercisefile = util.open_exercise_file(exercisesrc)
138
 
    if exercisefile is None:
139
 
        req.throw_error(req.HTTP_NOT_FOUND,
140
 
            "The exercise was not found.")
141
 
 
142
 
    # Start a console to run the tests on
143
 
    jail_path = os.path.join(ivle.conf.jail_base, req.user.login)
144
 
    working_dir = os.path.join("/home", req.user.login)
145
 
    cons = console.Console(req.user.unixid, jail_path, working_dir)
146
 
 
147
 
    # Parse the file into a exercise object using the test suite
148
 
    exercise_obj = test.parse_exercise_file(exercisefile, cons)
149
 
    exercisefile.close()
150
 
 
151
 
    # Run the test cases. Get the result back as a JSONable object.
152
 
    # Return it.
153
 
    test_results = exercise_obj.run_tests(code)
154
 
 
155
 
    # Close the console
156
 
    cons.close()
157
 
 
158
 
    # Get the Exercise from the database
159
 
    exercise = ivle.database.Exercise.get_by_name(req.store, exercisesrc)
160
 
 
161
 
    conn = db.DB()
162
 
    try:
163
 
        conn.insert_problem_attempt(
164
 
            user = req.user,
165
 
            exercisename = exercisesrc,
166
 
            date = time.localtime(),
167
 
            complete = test_results['passed'],
168
 
            attempt = code)
169
 
    finally:
170
 
        conn.close()
171
 
 
172
 
    # Query the DB to get an updated score on whether or not this problem
173
 
    # has EVER been completed (may be different from "passed", if it has
174
 
    # been completed before), and the total number of attempts.
175
 
    completed, attempts = ivle.worksheet.get_exercise_status(req.store,
176
 
        req.user, exercise)
177
 
    test_results["completed"] = completed
178
 
    test_results["attempts"] = attempts
179
 
 
180
 
    req.write(cjson.encode(test_results))
181
 
 
182
 
def handle_getattempts(req, exercisename):
183
 
    """Handles a getattempts action."""
184
 
    exercise = ivle.database.Exercise.get_by_name(req.store, exercisename)
185
 
    attempts = ivle.worksheet.get_exercise_attempts(req.store, req.user,
186
 
        exercise, allow_inactive=HISTORY_ALLOW_INACTIVE)
187
 
    # attempts is a list of ExerciseAttempt objects. Convert to dictionaries.
188
 
    time_fmt = lambda dt: datetime.datetime.strftime(dt, db.TIMESTAMP_FORMAT)
189
 
    attempts = [{'date': time_fmt(a.date), 'complete': a.complete}
190
 
                for a in attempts]
191
 
    req.write(cjson.encode(attempts))
192
 
 
193
 
def handle_getattempt(req, exercisename, date):
194
 
    """Handles a getattempts action. Date is a datetime.datetime."""
195
 
    conn = db.DB()
196
 
    exercise = ivle.database.Exercise.get_by_name(req.store, exercisename)
197
 
    attempt = ivle.worksheet.get_exercise_attempt(req.store, req.user,
198
 
        exercise, as_of=date, allow_inactive=HISTORY_ALLOW_INACTIVE)
199
 
    if attempt is not None:
200
 
        attempt = attempt.text
201
 
    # attempt may be None; will write "null"
202
 
    req.write(cjson.encode({'code': attempt}))
 
124
            raise NotFound()
 
125
 
 
126
        exercise = ivle.database.Exercise.get_by_name(req.store, exercise)
 
127
        attempt = ivle.worksheet.get_exercise_attempt(req.store, user,
 
128
            exercise, as_of=date, allow_inactive=HISTORY_ALLOW_INACTIVE)
 
129
 
 
130
        if attempt is None:
 
131
            raise NotFound()
 
132
 
 
133
        self.context = attempt
 
134
 
 
135
    def GET(self, req):
 
136
        return {'code': self.context.text}
 
137
 
 
138
 
 
139
class ExerciseRESTView(JSONRESTView):
 
140
    '''REST view of an exercise.'''
 
141
    @named_operation
 
142
    def save(self, req, text):
 
143
        # Need to open JUST so we know this is a real exercise.
 
144
        # (This avoids users submitting code for bogus exercises).
 
145
        exercisefile = ivle.util.open_exercise_file(self.exercise)
 
146
        if exercisefile is None:
 
147
            raise NotFound()
 
148
        exercisefile.close()
 
149
 
 
150
        exercise = ivle.database.Exercise.get_by_name(req.store, self.exercise)
 
151
        ivle.worksheet.save_exercise(req.store, req.user, exercise,
 
152
                                     unicode(text), datetime.datetime.now())
 
153
        return {"result": "ok"}