23
23
This module provides functions for tutorial and worksheet computations.
28
26
from storm.locals import And, Asc, Desc, Store
31
29
import ivle.database
32
30
from ivle.database import ExerciseAttempt, ExerciseSave, Worksheet, \
33
31
WorksheetExercise, Exercise
34
import ivle.webapp.tutorial.test
36
__all__ = ['ExerciseNotFound', 'get_exercise_status',
37
'get_exercise_stored_text', 'get_exercise_attempts',
38
'get_exercise_attempt', 'test_exercise_submission',
33
__all__ = ['get_exercise_status', 'get_exercise_stored_text',
34
'get_exercise_attempts', 'get_exercise_attempt',
41
class ExerciseNotFound(Exception):
44
def get_exercise_status(store, user, worksheet_exercise, as_of=None):
37
def get_exercise_status(store, user, worksheet_exercise):
45
38
"""Given a storm.store, User and Exercise, returns information about
46
39
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.
51
40
Returns a tuple of:
52
41
- A boolean, whether they have successfully passed this exercise.
53
42
- An int, the number of attempts they have made up to and
59
48
is_relevant = ((ExerciseAttempt.user_id == user.id) &
60
49
(ExerciseAttempt.ws_ex_id == worksheet_exercise.id) &
61
50
(ExerciseAttempt.active == True))
63
is_relevant &= ExerciseAttempt.date <= as_of
65
52
# Get the first successful active attempt, or None if no success yet.
66
53
# (For this user, for this exercise).
175
162
saved.date = date
176
163
saved.text = text
178
def calculate_score(store, user, worksheet, as_of=None):
165
def calculate_score(store, user, worksheet):
180
167
Given a storm.store, User, Exercise and Worksheet, calculates a score for
181
168
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.
186
169
Returns a 4-tuple of ints, consisting of:
187
170
(No. mandatory exercises completed,
188
171
Total no. mandatory exercises,
200
183
worksheet = worksheet_exercise.worksheet
201
184
optional = worksheet_exercise.optional
203
done, _ = get_exercise_status(store, user, worksheet_exercise, as_of)
186
done, _ = get_exercise_status(store, user, worksheet_exercise)
204
187
# done is a bool, whether this student has completed that problem
212
195
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)
241
197
def update_exerciselist(worksheet):
242
198
"""Runs through the worksheetstream, generating the appropriate
243
199
WorksheetExercises, and de-activating the old ones."""
275
231
Exercise.id == exerciseid
277
233
if exercise is None:
278
raise ExerciseNotFound(exerciseid)
279
235
worksheet_exercise = WorksheetExercise()
280
236
worksheet_exercise.worksheet_id = worksheet.id
281
237
worksheet_exercise.exercise_id = exercise.id
284
240
worksheet_exercise.seq_no = ex_num
285
241
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)