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
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.context = self.user # XXX: Not quite right.
55
42
@require_permission('edit')
56
43
def GET(self, req):
57
44
"""Handles a GET Attempts action."""
58
exercise = req.store.find(Exercise, Exercise.id == self.exercise).one()
60
45
attempts = req.store.find(ExerciseAttempt,
61
ExerciseAttempt.exercise_id == exercise.id,
62
ExerciseAttempt.user_id == self.user.id)
46
ExerciseAttempt.ws_ex_id == self.context.worksheet_exercise.id,
47
ExerciseAttempt.user_id == self.context.user.id)
63
48
# attempts is a list of ExerciseAttempt objects. Convert to dictionaries
64
49
time_fmt = lambda dt: datetime.datetime.strftime(dt, TIMESTAMP_FORMAT)
65
50
attempts = [{'date': time_fmt(a.date), 'complete': a.complete}
71
56
@require_permission('edit')
72
57
def PUT(self, req, data):
73
58
""" Tests the given submission """
74
exercise = req.store.find(Exercise, Exercise.id == self.exercise).one()
78
# Start a console to run the tests on
79
jail_path = os.path.join(ivle.conf.jail_base, req.user.login)
80
working_dir = os.path.join("/home", req.user.login)
81
cons = ivle.console.Console(req.user.unixid, jail_path, working_dir)
83
# Parse the file into a exercise object using the test suite
84
exercise_obj = ivle.webapp.tutorial.test.parse_exercise_file(
87
# Run the test cases. Get the result back as a JSONable object.
89
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,
94
63
attempt = ivle.database.ExerciseAttempt(user=req.user,
96
date=datetime.datetime.now(),
97
complete=test_results['passed'],
99
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'])
101
70
req.store.add(attempt)
103
72
# Query the DB to get an updated score on whether or not this problem
104
73
# has EVER been completed (may be different from "passed", if it has
105
74
# been completed before), and the total number of attempts.
106
completed, attempts = ivle.worksheet.get_exercise_status(req.store,
75
completed, attempts = ivle.worksheet.utils.get_exercise_status(
76
req.store, req.user, self.context.worksheet_exercise)
108
77
test_results["completed"] = completed
109
78
test_results["attempts"] = attempts
114
83
class AttemptRESTView(JSONRESTView):
115
84
'''REST view of an exercise attempt.'''
117
def __init__(self, req, subject, worksheet, exercise, username, date):
118
# TODO: Find exercise within worksheet.
119
user = ivle.database.User.get_by_login(req.store, username)
124
date = datetime.datetime.strptime(date, TIMESTAMP_FORMAT)
128
exercise = ivle.database.Exercise.get_by_name(req.store, exercise)
129
attempt = ivle.worksheet.get_exercise_attempt(req.store, user,
130
exercise, as_of=date, allow_inactive=HISTORY_ALLOW_INACTIVE)
135
self.context = attempt
137
86
@require_permission('view')
138
87
def GET(self, req):
139
88
return {'code': self.context.text}
142
class ExerciseRESTView(JSONRESTView):
143
'''REST view of an exercise.'''
145
def get_permissions(self, user):
146
# 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()
152
@named_operation('save')
153
def save(self, req, text):
154
# Need to open JUST so we know this is a real exercise.
155
# (This avoids users submitting code for bogus exercises).
156
exercise = req.store.find(Exercise,
157
Exercise.id == self.exercise).one()
158
ivle.worksheet.save_exercise(req.store, req.user, exercise,
159
unicode(text), datetime.datetime.now())
160
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'}