20
20
'''AJAX backend for the tutorial application.'''
25
from storm.locals import Store
27
27
import ivle.database
28
from ivle.database import Exercise, ExerciseAttempt, ExerciseSave, Worksheet, Offering, Subject, Semester
31
import ivle.webapp.tutorial.test
28
from ivle.database import Exercise, ExerciseAttempt, ExerciseSave, Worksheet, \
29
Offering, Subject, Semester, User, WorksheetExercise
30
import ivle.worksheet.utils
33
31
from ivle.webapp.base.rest import (JSONRESTView, named_operation,
34
32
require_permission)
35
33
from ivle.webapp.errors import NotFound
37
# If True, getattempts or getattempt will allow browsing of inactive/disabled
38
# attempts. If False, will not allow this.
39
HISTORY_ALLOW_INACTIVE = False
41
36
TIMESTAMP_FORMAT = '%Y-%m-%d %H:%M:%S'
44
39
class AttemptsRESTView(JSONRESTView):
45
40
'''REST view of a user's attempts at an exercise.'''
46
def __init__(self, req, subject, year, semester, worksheet,
48
self.user = ivle.database.User.get_by_login(req.store, username)
51
self.exercise = exercise
53
self.worksheet = req.store.find(Worksheet,
54
Worksheet.name == worksheet,
55
Worksheet.offering_id == Offering.id,
56
Offering.subject_id == Subject.id,
57
Subject.code == subject,
58
Offering.semester_id == Semester.id,
59
Semester.year == year,
60
Semester.semester == semester).one()
62
self.context = self.user # XXX: Not quite right.
64
42
@require_permission('edit')
65
43
def GET(self, req):
66
44
"""Handles a GET Attempts action."""
67
exercise = req.store.find(Exercise, Exercise.id == self.exercise).one()
69
45
attempts = req.store.find(ExerciseAttempt,
70
ExerciseAttempt.exercise_id == exercise.id,
71
ExerciseAttempt.user_id == self.user.id)
46
ExerciseAttempt.ws_ex_id == self.context.worksheet_exercise.id,
47
ExerciseAttempt.user_id == self.context.user.id)
72
48
# attempts is a list of ExerciseAttempt objects. Convert to dictionaries
73
49
time_fmt = lambda dt: datetime.datetime.strftime(dt, TIMESTAMP_FORMAT)
74
50
attempts = [{'date': time_fmt(a.date), 'complete': a.complete}
80
56
@require_permission('edit')
81
57
def PUT(self, req, data):
82
58
""" Tests the given submission """
83
exercise = req.store.find(Exercise, Exercise.id == self.exercise).one()
87
# Start a console to run the tests on
88
jail_path = os.path.join(ivle.conf.jail_base, req.user.login)
89
working_dir = os.path.join("/home", req.user.login)
90
cons = ivle.console.Console(req.user.unixid, jail_path, working_dir)
92
# Parse the file into a exercise object using the test suite
93
exercise_obj = ivle.webapp.tutorial.test.parse_exercise_file(
96
# Run the test cases. Get the result back as a JSONable object.
98
test_results = exercise_obj.run_tests(data['code'])
59
test_results = ivle.worksheet.utils.test_exercise_submission(
60
req.config, req.user, self.context.worksheet_exercise.exercise,
103
63
attempt = ivle.database.ExerciseAttempt(user=req.user,
105
date=datetime.datetime.now(),
106
complete=test_results['passed'],
107
worksheet=self.worksheet,
108
text=unicode(data['code']))
64
worksheet_exercise = self.context.worksheet_exercise,
65
date = datetime.datetime.now(),
66
complete = test_results['passed'],
67
text = unicode(data['code'])
110
70
req.store.add(attempt)
112
72
# Query the DB to get an updated score on whether or not this problem
113
73
# has EVER been completed (may be different from "passed", if it has
114
74
# been completed before), and the total number of attempts.
115
completed, attempts = ivle.worksheet.get_exercise_status(req.store,
116
req.user, exercise, self.worksheet)
75
completed, attempts = ivle.worksheet.utils.get_exercise_status(
76
req.store, req.user, self.context.worksheet_exercise)
117
77
test_results["completed"] = completed
118
78
test_results["attempts"] = attempts
123
83
class AttemptRESTView(JSONRESTView):
124
84
'''REST view of an exercise attempt.'''
126
def __init__(self, req, subject, worksheet, exercise, username, date):
127
# TODO: Find exercise within worksheet.
128
user = ivle.database.User.get_by_login(req.store, username)
133
date = datetime.datetime.strptime(date, TIMESTAMP_FORMAT)
137
exercise = ivle.database.Exercise.get_by_name(req.store, exercise)
138
worksheet = req.store.find(Worksheet,
139
Worksheet.name == self.worksheet,
140
Worksheet.offering_id == Offering.id,
141
Offering.subject_id == Subject.id,
142
Subject.code == self.subject,
143
Offering.semester_id == Semester.id,
144
Semester.year == self.year,
145
Semester.semester == self.semester).one()
147
attempt = ivle.worksheet.get_exercise_attempt(req.store, user,
148
exercise, worksheet, as_of=date, allow_inactive=HISTORY_ALLOW_INACTIVE)
153
self.context = attempt
155
86
@require_permission('view')
156
87
def GET(self, req):
157
88
return {'code': self.context.text}
160
class ExerciseRESTView(JSONRESTView):
161
'''REST view of an exercise.'''
163
def get_permissions(self, user):
164
# XXX: Do it properly.
91
class WorksheetExerciseRESTView(JSONRESTView):
92
'''REST view of a worksheet exercise.'''
94
@named_operation('view')
95
def save(self, req, text):
96
# Find the appropriate WorksheetExercise to save to. If its not found,
97
# the user is submitting against a non-existant worksheet/exercise
99
old_save = req.store.find(ExerciseSave,
100
ExerciseSave.ws_ex_id == self.context.id,
101
ExerciseSave.user == req.user).one()
103
#Overwrite the old, or create a new if there isn't one
105
new_save = ExerciseSave()
106
req.store.add(new_save)
110
new_save.worksheet_exercise = self.context
111
new_save.user = req.user
112
new_save.text = unicode(text)
113
new_save.date = datetime.datetime.now()
170
@named_operation('save')
171
def save(self, req, text):
172
# Need to open JUST so we know this is a real exercise.
173
# (This avoids users submitting code for bogus exercises).
174
exercise = req.store.find(Exercise,
175
Exercise.id == self.exercise).one()
176
worksheet = req.store.find(Worksheet,
177
Worksheet.name == self.worksheet,
178
Worksheet.offering_id == Offering.id,
179
Offering.subject_id == Subject.id,
180
Subject.code == self.subject,
181
Offering.semester_id == Semester.id,
182
Semester.year == self.year,
183
Semester.semester == self.semester).one()
184
ivle.worksheet.save_exercise(req.store, req.user, exercise, worksheet,
185
unicode(text), datetime.datetime.now())
186
115
return {"result": "ok"}
118
class WorksheetsRESTView(JSONRESTView):
119
"""View used to update and create Worksheets."""
121
@named_operation('edit_worksheets')
122
def move_up(self, req, worksheetid):
123
"""Takes a list of worksheet-seq_no pairs and updates their
124
corresponding Worksheet objects to match."""
126
worksheet_below = req.store.find(Worksheet,
127
Worksheet.offering_id == self.context.id,
128
Worksheet.identifier == unicode(worksheetid)).one()
129
if worksheet_below is None:
130
raise NotFound('worksheet_below')
131
worksheet_above = req.store.find(Worksheet,
132
Worksheet.offering_id == self.context.id,
133
Worksheet.seq_no == (worksheet_below.seq_no - 1)).one()
134
if worksheet_above is None:
135
raise NotFound('worksheet_above')
137
worksheet_below.seq_no = worksheet_below.seq_no - 1
138
worksheet_above.seq_no = worksheet_above.seq_no + 1
140
return {'result': 'ok'}
142
@named_operation('edit_worksheets')
143
def move_down(self, req, worksheetid):
144
"""Takes a list of worksheet-seq_no pairs and updates their
145
corresponding Worksheet objects to match."""
147
worksheet_above = req.store.find(Worksheet,
148
Worksheet.offering_id == self.context.id,
149
Worksheet.identifier == unicode(worksheetid)).one()
150
if worksheet_above is None:
151
raise NotFound('worksheet_below')
152
worksheet_below = req.store.find(Worksheet,
153
Worksheet.offering_id == self.context.id,
154
Worksheet.seq_no == (worksheet_above.seq_no + 1)).one()
155
if worksheet_below is None:
156
raise NotFound('worksheet_above')
158
worksheet_below.seq_no = worksheet_below.seq_no - 1
159
worksheet_above.seq_no = worksheet_above.seq_no + 1
161
return {'result': 'ok'}