23
23
This module provides functions for tutorial and worksheet computations.
26
28
from storm.locals import And, Asc, Desc, Store
29
31
import ivle.database
30
32
from ivle.database import ExerciseAttempt, ExerciseSave, Worksheet, \
31
33
WorksheetExercise, Exercise
34
import ivle.webapp.tutorial.test
33
__all__ = ['get_exercise_status', 'get_exercise_stored_text',
34
'get_exercise_attempts', 'get_exercise_attempt',
36
__all__ = ['ExerciseNotFound', 'get_exercise_status',
37
'get_exercise_stored_text', 'get_exercise_attempts',
38
'get_exercise_attempt', 'test_exercise_submission',
37
def get_exercise_status(store, user, worksheet_exercise):
41
class ExerciseNotFound(Exception):
44
def get_exercise_status(store, user, worksheet_exercise, as_of=None):
38
45
"""Given a storm.store, User and Exercise, returns information about
39
46
the user's performance on that problem.
47
@param store: A storm.store
49
@param worksheet_exercise: An Exercise.
50
@param as_of: Optional datetime. If supplied, gets the status as of as_of.
40
51
Returns a tuple of:
41
52
- A boolean, whether they have successfully passed this exercise.
42
53
- An int, the number of attempts they have made up to and
48
59
is_relevant = ((ExerciseAttempt.user_id == user.id) &
49
60
(ExerciseAttempt.ws_ex_id == worksheet_exercise.id) &
50
61
(ExerciseAttempt.active == True))
63
is_relevant &= ExerciseAttempt.date <= as_of
52
65
# Get the first successful active attempt, or None if no success yet.
53
66
# (For this user, for this exercise).
162
175
saved.date = date
163
176
saved.text = text
165
def calculate_score(store, user, worksheet):
178
def calculate_score(store, user, worksheet, as_of=None):
167
180
Given a storm.store, User, Exercise and Worksheet, calculates a score for
168
181
the user on the given worksheet.
182
@param store: A storm.store
184
@param worksheet: A Worksheet.
185
@param as_of: Optional datetime. If supplied, gets the score as of as_of.
169
186
Returns a 4-tuple of ints, consisting of:
170
187
(No. mandatory exercises completed,
171
188
Total no. mandatory exercises,
183
200
worksheet = worksheet_exercise.worksheet
184
201
optional = worksheet_exercise.optional
186
done, _ = get_exercise_status(store, user, worksheet_exercise)
203
done, _ = get_exercise_status(store, user, worksheet_exercise, as_of)
187
204
# done is a bool, whether this student has completed that problem
195
212
return mand_done, mand_total, opt_done, opt_total
214
def calculate_mark(mand_done, mand_total):
215
"""Calculate a subject mark, given the result of all worksheets.
216
@param mand_done: The total number of mandatory exercises completed by
217
some student, across all worksheets.
218
@param mand_total: The total number of mandatory exercises across all
219
worksheets in the offering.
220
@return: (percent, mark, mark_total)
221
percent: The percentage of exercises the student has completed, as an
222
integer between 0 and 100 inclusive.
223
mark: The mark the student has received, based on the percentage.
224
mark_total: The total number of marks available (currently hard-coded
227
# We want to display a students mark out of 5. However, they are
228
# allowed to skip 1 in 5 questions and still get 'full marks'.
229
# Hence we divide by 16, essentially making 16 percent worth
230
# 1 star, and 80 or above worth 5.
232
percent_int = (100 * mand_done) // mand_total
234
# Avoid Div0, just give everyone 0 marks if there are none available
236
# percent / 16, rounded down, with a maximum mark of 5
238
mark = min(percent_int // 16, max_mark)
239
return (percent_int, mark, max_mark)
197
241
def update_exerciselist(worksheet):
198
242
"""Runs through the worksheetstream, generating the appropriate
199
243
WorksheetExercises, and de-activating the old ones."""
231
275
Exercise.id == exerciseid
233
277
if exercise is None:
278
raise ExerciseNotFound(exerciseid)
235
279
worksheet_exercise = WorksheetExercise()
236
280
worksheet_exercise.worksheet_id = worksheet.id
237
281
worksheet_exercise.exercise_id = exercise.id
240
284
worksheet_exercise.seq_no = ex_num
241
285
worksheet_exercise.optional = optional
288
def test_exercise_submission(config, user, exercise, code):
289
"""Test the given code against an exercise.
291
The code is run in a console process as the provided user.
293
# Start a console to run the tests on
294
jail_path = os.path.join(config['paths']['jails']['mounts'],
296
working_dir = os.path.join("/home", user.login)
297
cons = ivle.console.Console(config, user.unixid, jail_path,
300
# Parse the file into a exercise object using the test suite
301
exercise_obj = ivle.webapp.tutorial.test.parse_exercise_file(
304
# Run the test cases. Get the result back as a JSONable object.
306
test_results = exercise_obj.run_tests(code)