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
33
from ivle.webapp.base.rest import (JSONRESTView, named_operation,
28
from ivle.database import Exercise, ExerciseAttempt, ExerciseSave, Worksheet, \
29
Offering, Subject, Semester, User, WorksheetExercise
30
import ivle.worksheet.utils
31
from ivle.webapp.base.rest import (JSONRESTView, write_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
# Trim off any trailing whitespace (can cause syntax errors in python)
60
# While technically this is a user error, it causes a lot of confusion
61
# for student since it's "invisible".
62
code = data['code'].rstrip()
64
test_results = ivle.worksheet.utils.test_exercise_submission(
65
req.config, req.user, self.context.worksheet_exercise.exercise,
103
68
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']))
69
worksheet_exercise = self.context.worksheet_exercise,
70
date = datetime.datetime.now(),
71
complete = test_results['passed'],
110
75
req.store.add(attempt)
112
77
# Query the DB to get an updated score on whether or not this problem
113
78
# has EVER been completed (may be different from "passed", if it has
114
79
# 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)
80
completed, attempts = ivle.worksheet.utils.get_exercise_status(
81
req.store, req.user, self.context.worksheet_exercise)
117
82
test_results["completed"] = completed
118
83
test_results["attempts"] = attempts
123
88
class AttemptRESTView(JSONRESTView):
124
89
'''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
91
@require_permission('view')
156
92
def GET(self, req):
157
93
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.
96
class WorksheetExerciseRESTView(JSONRESTView):
97
'''REST view of a worksheet exercise.'''
99
@write_operation('view')
100
def save(self, req, text):
101
# Find the appropriate WorksheetExercise to save to. If its not found,
102
# the user is submitting against a non-existant worksheet/exercise
104
old_save = req.store.find(ExerciseSave,
105
ExerciseSave.ws_ex_id == self.context.id,
106
ExerciseSave.user == req.user).one()
108
#Overwrite the old, or create a new if there isn't one
110
new_save = ExerciseSave()
111
req.store.add(new_save)
115
new_save.worksheet_exercise = self.context
116
new_save.user = req.user
117
new_save.text = unicode(text)
118
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
120
return {"result": "ok"}
123
class WorksheetsRESTView(JSONRESTView):
124
"""View used to update and create Worksheets."""
126
@write_operation('edit_worksheets')
127
def move_up(self, req, worksheetid):
128
"""Takes a list of worksheet-seq_no pairs and updates their
129
corresponding Worksheet objects to match."""
131
worksheet_below = req.store.find(Worksheet,
132
Worksheet.offering_id == self.context.id,
133
Worksheet.identifier == unicode(worksheetid)).one()
134
if worksheet_below is None:
135
raise NotFound('worksheet_below')
136
worksheet_above = req.store.find(Worksheet,
137
Worksheet.offering_id == self.context.id,
138
Worksheet.seq_no == (worksheet_below.seq_no - 1)).one()
139
if worksheet_above is None:
140
raise NotFound('worksheet_above')
142
worksheet_below.seq_no = worksheet_below.seq_no - 1
143
worksheet_above.seq_no = worksheet_above.seq_no + 1
145
return {'result': 'ok'}
147
@write_operation('edit_worksheets')
148
def move_down(self, req, worksheetid):
149
"""Takes a list of worksheet-seq_no pairs and updates their
150
corresponding Worksheet objects to match."""
152
worksheet_above = req.store.find(Worksheet,
153
Worksheet.offering_id == self.context.id,
154
Worksheet.identifier == unicode(worksheetid)).one()
155
if worksheet_above is None:
156
raise NotFound('worksheet_below')
157
worksheet_below = req.store.find(Worksheet,
158
Worksheet.offering_id == self.context.id,
159
Worksheet.seq_no == (worksheet_above.seq_no + 1)).one()
160
if worksheet_below is None:
161
raise NotFound('worksheet_above')
163
worksheet_below.seq_no = worksheet_below.seq_no - 1
164
worksheet_above.seq_no = worksheet_above.seq_no + 1
166
return {'result': 'ok'}