~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
2
# Copyright (C) 2007-2008 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
# Module: TutorialService
19
# Author: Matt Giuca
20
# Date:   25/1/2008
21
22
# Provides the AJAX backend for the tutorial application.
300 by mattgiuca
conf/apps: Added TutorialService as a registered app.
23
# This allows several actions to be performed on the code the student has
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
24
# typed into one of the exercise boxes.
300 by mattgiuca
conf/apps: Added TutorialService as a registered app.
25
26
# Calling syntax
301 by mattgiuca
tutorialservice: Now parses and executes the students code using the test
27
# Path must be empty.
300 by mattgiuca
conf/apps: Added TutorialService as a registered app.
28
# The arguments determine what is to be done on this file.
29
1024 by mattgiuca
tutorialservice: Refactored the handler for this service, to allow potential
30
# "action". One of the tutorialservice actions.
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
31
# "exercise" - The path to a exercise file (including the .xml extension),
1024 by mattgiuca
tutorialservice: Refactored the handler for this service, to allow potential
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.
1025 by mattgiuca
tutorialservice: Added two new GET actions: getattempts and getattempt.
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.
300 by mattgiuca
conf/apps: Added TutorialService as a registered app.
41
42
# Returns a JSON response string indicating the results.
43
301 by mattgiuca
tutorialservice: Now parses and executes the students code using the test
44
import os
661 by drtomc
tutorialservice: log submitted attempts to the database.
45
import time
1080.1.58 by Matt Giuca
ivle.worksheet: Added get_exercise_attempts and get_exercise_attempt.
46
import datetime
301 by mattgiuca
tutorialservice: Now parses and executes the students code using the test
47
300 by mattgiuca
conf/apps: Added TutorialService as a registered app.
48
import cjson
49
1080.1.58 by Matt Giuca
ivle.worksheet: Added get_exercise_attempts and get_exercise_attempt.
50
from ivle import util
51
from ivle import console
52
import ivle.database
1080.1.56 by Matt Giuca
Added new module: ivle.worksheet. This will contain general functions for
53
import ivle.worksheet
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
54
import ivle.conf
55
import test # XXX: Really .test, not real test.
301 by mattgiuca
tutorialservice: Now parses and executes the students code using the test
56
1025 by mattgiuca
tutorialservice: Added two new GET actions: getattempts and getattempt.
57
# If True, getattempts or getattempt will allow browsing of inactive/disabled
58
# attempts. If False, will not allow this.
59
HISTORY_ALLOW_INACTIVE = False
60
1080.1.88 by William Grant
ivle.worksheet: Add a save_exercise function.
61
TIMESTAMP_FORMAT = '%Y-%m-%d %H:%M:%S'
62
63
300 by mattgiuca
conf/apps: Added TutorialService as a registered app.
64
def handle(req):
65
    """Handler for Ajax backend TutorialService app."""
66
    # Set request attributes
67
    req.write_html_head_foot = False     # No HTML
68
301 by mattgiuca
tutorialservice: Now parses and executes the students code using the test
69
    if req.path != "":
70
        req.throw_error(req.HTTP_BAD_REQUEST)
300 by mattgiuca
conf/apps: Added TutorialService as a registered app.
71
    fields = req.get_fieldstorage()
72
    act = fields.getfirst('action')
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
73
    exercise = fields.getfirst('exercise')
1024 by mattgiuca
tutorialservice: Refactored the handler for this service, to allow potential
74
    if act is None or exercise is None:
300 by mattgiuca
conf/apps: Added TutorialService as a registered app.
75
        req.throw_error(req.HTTP_BAD_REQUEST)
76
    act = act.value
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
77
    exercise = exercise.value
1024 by mattgiuca
tutorialservice: Refactored the handler for this service, to allow potential
78
79
    if act == 'save' or act == 'test':
80
        # Must be POST
81
        if req.method != 'POST':
82
            req.throw_error(req.HTTP_BAD_REQUEST)
1025 by mattgiuca
tutorialservice: Added two new GET actions: getattempts and getattempt.
83
1024 by mattgiuca
tutorialservice: Refactored the handler for this service, to allow potential
84
        code = fields.getfirst('code')
85
        if code is None:
86
            req.throw_error(req.HTTP_BAD_REQUEST)
87
        code = code.value
88
89
        if act == 'save':
90
            handle_save(req, exercise, code, fields)
91
        else:   # act == "test"
92
            handle_test(req, exercise, code, fields)
1025 by mattgiuca
tutorialservice: Added two new GET actions: getattempts and getattempt.
93
    elif act == 'getattempts':
94
        handle_getattempts(req, exercise)
95
    elif act == 'getattempt':
96
        date = fields.getfirst('date')
97
        if date is None:
98
            req.throw_error(req.HTTP_BAD_REQUEST)
99
        date = date.value
100
        # Convert into a struct_time
101
        # The time *should* be in the same format as the DB (since it should
102
        # be bounced back to us from the getattempts output). Assume this.
103
        try:
1080.1.88 by William Grant
ivle.worksheet: Add a save_exercise function.
104
            date = datetime.datetime.strptime(date, TIMESTAMP_FORMAT)
1025 by mattgiuca
tutorialservice: Added two new GET actions: getattempts and getattempt.
105
        except ValueError:
106
            # Date was not in correct format
107
            req.throw_error(req.HTTP_BAD_REQUEST)
108
        handle_getattempt(req, exercise, date)
300 by mattgiuca
conf/apps: Added TutorialService as a registered app.
109
    else:
110
        req.throw_error(req.HTTP_BAD_REQUEST)
111
1080.1.88 by William Grant
ivle.worksheet: Add a save_exercise function.
112
def handle_save(req, exercisename, code, fields):
698 by mattgiuca
Added Save feature to tutorial system.
113
    """Handles a save action. This saves the user's code without executing it.
114
    """
115
    # Need to open JUST so we know this is a real exercise.
116
    # (This avoids users submitting code for bogus exercises).
1080.1.88 by William Grant
ivle.worksheet: Add a save_exercise function.
117
    exercisefile = util.open_exercise_file(exercisename)
698 by mattgiuca
Added Save feature to tutorial system.
118
    if exercisefile is None:
119
        req.throw_error(req.HTTP_NOT_FOUND,
120
            "The exercise was not found.")
121
    exercisefile.close()
122
1080.1.88 by William Grant
ivle.worksheet: Add a save_exercise function.
123
    exercise = ivle.database.Exercise.get_by_name(req.store, exercisename)
124
    ivle.worksheet.save_exercise(req.store, req.user, exercise,
125
                                 unicode(code), datetime.datetime.now())
126
    req.store.commit()
127
698 by mattgiuca
Added Save feature to tutorial system.
128
    req.write('{"result": "ok"}')
129
130
1080.1.56 by Matt Giuca
Added new module: ivle.worksheet. This will contain general functions for
131
def handle_test(req, exercisesrc, code, fields):
698 by mattgiuca
Added Save feature to tutorial system.
132
    """Handles a test action."""
133
1080.1.56 by Matt Giuca
Added new module: ivle.worksheet. This will contain general functions for
134
    exercisefile = util.open_exercise_file(exercisesrc)
698 by mattgiuca
Added Save feature to tutorial system.
135
    if exercisefile is None:
136
        req.throw_error(req.HTTP_NOT_FOUND,
137
            "The exercise was not found.")
301 by mattgiuca
tutorialservice: Now parses and executes the students code using the test
138
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
139
    # Start a console to run the tests on
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
140
    jail_path = os.path.join(ivle.conf.jail_base, req.user.login)
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
141
    working_dir = os.path.join("/home", req.user.login)
142
    cons = console.Console(req.user.unixid, jail_path, working_dir)
143
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
144
    # Parse the file into a exercise object using the test suite
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
145
    exercise_obj = test.parse_exercise_file(exercisefile, cons)
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
146
    exercisefile.close()
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
147
301 by mattgiuca
tutorialservice: Now parses and executes the students code using the test
148
    # Run the test cases. Get the result back as a JSONable object.
149
    # Return it.
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
150
    test_results = exercise_obj.run_tests(code)
325 by mattgiuca
tutorial: Added "run" button which submits the students code to the
151
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
152
    # Close the console
153
    cons.close()
154
1080.1.56 by Matt Giuca
Added new module: ivle.worksheet. This will contain general functions for
155
    # Get the Exercise from the database
156
    exercise = ivle.database.Exercise.get_by_name(req.store, exercisesrc)
157
1080.1.87 by William Grant
www/apps/tutorialservice: Replace the second-last reference ever to ivle.db
158
    attempt = ivle.database.ExerciseAttempt(user=req.user,
159
                                            exercise=exercise,
160
                                            date=datetime.datetime.now(),
161
                                            complete=test_results['passed'],
162
                                            text=unicode(code)) # XXX
1025 by mattgiuca
tutorialservice: Added two new GET actions: getattempts and getattempt.
163
1080.1.87 by William Grant
www/apps/tutorialservice: Replace the second-last reference ever to ivle.db
164
    req.store.add(attempt)
165
    req.store.commit()
1080.1.56 by Matt Giuca
Added new module: ivle.worksheet. This will contain general functions for
166
    # Query the DB to get an updated score on whether or not this problem
167
    # has EVER been completed (may be different from "passed", if it has
168
    # been completed before), and the total number of attempts.
169
    completed, attempts = ivle.worksheet.get_exercise_status(req.store,
170
        req.user, exercise)
171
    test_results["completed"] = completed
172
    test_results["attempts"] = attempts
173
174
    req.write(cjson.encode(test_results))
175
1080.1.58 by Matt Giuca
ivle.worksheet: Added get_exercise_attempts and get_exercise_attempt.
176
def handle_getattempts(req, exercisename):
1025 by mattgiuca
tutorialservice: Added two new GET actions: getattempts and getattempt.
177
    """Handles a getattempts action."""
1080.1.58 by Matt Giuca
ivle.worksheet: Added get_exercise_attempts and get_exercise_attempt.
178
    exercise = ivle.database.Exercise.get_by_name(req.store, exercisename)
179
    attempts = ivle.worksheet.get_exercise_attempts(req.store, req.user,
180
        exercise, allow_inactive=HISTORY_ALLOW_INACTIVE)
181
    # attempts is a list of ExerciseAttempt objects. Convert to dictionaries.
1080.1.88 by William Grant
ivle.worksheet: Add a save_exercise function.
182
    time_fmt = lambda dt: datetime.datetime.strftime(dt, TIMESTAMP_FORMAT)
1080.1.58 by Matt Giuca
ivle.worksheet: Added get_exercise_attempts and get_exercise_attempt.
183
    attempts = [{'date': time_fmt(a.date), 'complete': a.complete}
184
                for a in attempts]
185
    req.write(cjson.encode(attempts))
1025 by mattgiuca
tutorialservice: Added two new GET actions: getattempts and getattempt.
186
1080.1.58 by Matt Giuca
ivle.worksheet: Added get_exercise_attempts and get_exercise_attempt.
187
def handle_getattempt(req, exercisename, date):
188
    """Handles a getattempts action. Date is a datetime.datetime."""
189
    exercise = ivle.database.Exercise.get_by_name(req.store, exercisename)
190
    attempt = ivle.worksheet.get_exercise_attempt(req.store, req.user,
191
        exercise, as_of=date, allow_inactive=HISTORY_ALLOW_INACTIVE)
192
    if attempt is not None:
193
        attempt = attempt.text
194
    # attempt may be None; will write "null"
195
    req.write(cjson.encode({'code': attempt}))