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
WorksheetExercise, Exercise
33
WorksheetExercise, Exercise, User
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_statistics',
38
'get_exercise_stored_text', 'get_exercise_attempts',
39
'get_exercise_attempt', 'test_exercise_submission',
42
class ExerciseNotFound(Exception):
37
45
def get_exercise_status(store, user, worksheet_exercise, as_of=None):
38
46
"""Given a storm.store, User and Exercise, returns information about
39
47
the user's performance on that problem.
76
84
return first_success is not None, num_attempts
86
def get_exercise_statistics(store, worksheet_exercise):
87
"""Return statistics about an exercise (with respect to a given
89
(number of students completed, number of students attempted)."""
90
# Count the set of Users whose ID matches an attempt in this worksheet
91
num_completed = store.find(User, User.id == ExerciseAttempt.user_id,
92
ExerciseAttempt.ws_ex_id == worksheet_exercise.id,
93
ExerciseAttempt.complete == True).config(distinct=True).count()
94
num_attempted = store.find(User, User.id == ExerciseAttempt.user_id,
95
ExerciseAttempt.ws_ex_id == worksheet_exercise.id,
96
).config(distinct=True).count()
97
return num_completed, num_attempted
78
99
def get_exercise_stored_text(store, user, worksheet_exercise):
79
100
"""Given a storm.store, User and WorksheetExercise, returns an
80
101
ivle.database.ExerciseSave object for the last saved/submitted attempt for
277
298
worksheet_exercise.seq_no = ex_num
278
299
worksheet_exercise.optional = optional
302
def test_exercise_submission(config, user, exercise, code):
303
"""Test the given code against an exercise.
305
The code is run in a console process as the provided user.
307
# Start a console to run the tests on
308
jail_path = os.path.join(config['paths']['jails']['mounts'],
310
working_dir = os.path.join("/home", user.login)
311
cons = ivle.console.Console(config, user.unixid, jail_path,
314
# Parse the file into a exercise object using the test suite
315
exercise_obj = ivle.webapp.tutorial.test.parse_exercise_file(
318
# Run the test cases. Get the result back as a JSONable object.
320
test_results = exercise_obj.run_tests(code)
328
class FakeWorksheetForMarks:
329
"""This class represents a worksheet and a particular students progress
332
Do not confuse this with a worksheet in the database. This worksheet
333
has extra information for use in the output, such as marks."""
334
def __init__(self, id, name, assessable, published):
337
self.assessable = assessable
338
self.published = published
339
self.complete_class = ''
340
self.optional_message = ''
344
return ("Worksheet(id=%s, name=%s, assessable=%s)"
345
% (repr(self.id), repr(self.name), repr(self.assessable)))
348
# XXX: This really shouldn't be needed.
349
def create_list_of_fake_worksheets_and_stats(config, store, user, offering):
350
"""Take an offering's real worksheets, converting them into stats.
352
The worksheet listing views expect special fake worksheet objects
353
that contain counts of exercises, whether they've been completed,
354
that sort of thing. A fake worksheet object is used to contain
355
these values, because nobody has managed to refactor the need out
362
# Offering.worksheets is ordered by the worksheets seq_no
363
worksheets = offering.worksheets
365
# Unless we can edit worksheets, hide unpublished ones.
366
if 'edit_worksheets' not in offering.get_permissions(user, config):
367
worksheets = worksheets.find(published=True)
369
for worksheet in worksheets:
370
new_worksheet = FakeWorksheetForMarks(
371
worksheet.identifier, worksheet.name, worksheet.assessable,
373
if new_worksheet.assessable:
374
# Calculate the user's score for this worksheet
375
mand_done, mand_total, opt_done, opt_total = (
376
ivle.worksheet.utils.calculate_score(store, user, worksheet))
378
optional_message = " (excluding optional exercises)"
380
optional_message = ""
381
if mand_done >= mand_total:
382
new_worksheet.complete_class = "complete"
384
new_worksheet.complete_class = "semicomplete"
386
new_worksheet.complete_class = "incomplete"
387
problems_done += mand_done
388
problems_total += mand_total
389
new_worksheet.mand_done = mand_done
390
new_worksheet.total = mand_total
391
new_worksheet.optional_message = optional_message
392
new_worksheets.append(new_worksheet)
394
return new_worksheets, problems_total, problems_done