30
30
from storm.locals import create_database, Store, Int, Unicode, DateTime, \
31
31
Reference, ReferenceSet, Bool, Storm, Desc
32
from storm.exceptions import NotOneError, IntegrityError
35
from ivle.worksheet.rst import rst
37
36
__all__ = ['get_store',
39
38
'Subject', 'Semester', 'Offering', 'Enrolment',
40
39
'ProjectSet', 'Project', 'ProjectGroup', 'ProjectGroupMembership',
41
'Assessed', 'ProjectSubmission', 'ProjectExtension',
42
40
'Exercise', 'Worksheet', 'WorksheetExercise',
43
41
'ExerciseSave', 'ExerciseAttempt',
44
'TestCase', 'TestSuite', 'TestSuiteVar'
42
'AlreadyEnrolledError'
47
45
def _kwarg_init(self, **kwargs):
266
247
project_sets = ReferenceSet(id, 'ProjectSet.offering_id')
268
worksheets = ReferenceSet(id,
269
'Worksheet.offering_id',
273
249
__init__ = _kwarg_init
275
251
def __repr__(self):
276
252
return "<%s %r in %r>" % (type(self).__name__, self.subject,
279
def enrol(self, user, role=u'student'):
255
def enrol(self, user):
280
256
'''Enrol a user in this offering.'''
281
enrolment = Store.of(self).find(Enrolment,
282
Enrolment.user_id == user.id,
283
Enrolment.offering_id == self.id).one()
285
if enrolment is None:
286
enrolment = Enrolment(user=user, offering=self)
287
self.enrolments.add(enrolment)
289
enrolment.active = True
290
enrolment.role = role
292
def unenrol(self, user):
293
'''Unenrol a user from this offering.'''
294
enrolment = Store.of(self).find(Enrolment,
295
Enrolment.user_id == user.id,
296
Enrolment.offering_id == self.id).one()
297
Store.of(enrolment).remove(enrolment)
299
def get_permissions(self, user):
302
enrolment = self.get_enrolment(user)
303
if enrolment or user.admin:
305
if (enrolment and enrolment.role in (u'tutor', u'lecturer')) \
310
def get_enrolment(self, user):
312
enrolment = self.enrolments.find(user=user).one()
257
# We'll get a horrible database constraint violation error if we try
258
# to add a second enrolment.
259
if Store.of(self).find(Enrolment,
260
Enrolment.user_id == user.id,
261
Enrolment.offering_id == self.id).count() == 1:
262
raise AlreadyEnrolledError()
264
e = Enrolment(user=user, offering=self, active=True)
265
self.enrolments.add(e)
318
267
class Enrolment(Storm):
319
268
__storm_table__ = "enrolment"
428
365
return "<%s %r in %r>" % (type(self).__name__, self.user,
429
366
self.project_group)
431
class Assessed(Storm):
432
__storm_table__ = "assessed"
434
id = Int(name="assessedid", primary=True)
435
user_id = Int(name="loginid")
436
user = Reference(user_id, User.id)
437
project_group_id = Int(name="groupid")
438
project_group = Reference(project_group_id, ProjectGroup.id)
440
project_id = Int(name="projectid")
441
project = Reference(project_id, Project.id)
443
extensions = ReferenceSet(id, 'ProjectExtension.assessed_id')
444
submissions = ReferenceSet(id, 'ProjectSubmission.assessed_id')
447
return "<%s %r in %r>" % (type(self).__name__,
448
self.user or self.project_group, self.project)
450
class ProjectExtension(Storm):
451
__storm_table__ = "project_extension"
453
id = Int(name="extensionid", primary=True)
454
assessed_id = Int(name="assessedid")
455
assessed = Reference(assessed_id, Assessed.id)
456
deadline = DateTime()
457
approver_id = Int(name="approver")
458
approver = Reference(approver_id, User.id)
461
class ProjectSubmission(Storm):
462
__storm_table__ = "project_submission"
464
id = Int(name="submissionid", primary=True)
465
assessed_id = Int(name="assessedid")
466
assessed = Reference(assessed_id, Assessed.id)
469
date_submitted = DateTime()
472
368
# WORKSHEETS AND EXERCISES #
474
370
class Exercise(Storm):
475
__storm_table__ = "exercise"
476
id = Unicode(primary=True, name="identifier")
478
description = Unicode()
371
# Note: Table "problem" is called "Exercise" in the Object layer, since
372
# it's called that everywhere else.
373
__storm_table__ = "problem"
484
worksheet_exercises = ReferenceSet(id,
485
'WorksheetExercise.exercise_id')
375
id = Int(primary=True, name="problemid")
376
name = Unicode(name="identifier")
487
379
worksheets = ReferenceSet(id,
488
380
'WorksheetExercise.exercise_id',
489
381
'WorksheetExercise.worksheet_id',
493
test_suites = ReferenceSet(id,
494
'TestSuite.exercise_id',
497
385
__init__ = _kwarg_init
499
387
def __repr__(self):
500
388
return "<%s %s>" % (type(self).__name__, self.name)
502
def get_permissions(self, user):
509
elif 'lecturer' in set((e.role for e in user.active_enrolments)):
515
def get_description(self):
516
return rst(self.description)
519
"""Deletes the exercise, providing it has no associated worksheets."""
520
if (self.worksheet_exercises.count() > 0):
521
raise IntegrityError()
522
for suite in self.test_suites:
524
Store.of(self).remove(self)
391
def get_by_name(cls, store, name):
393
Get the Exercise from the db associated with a given store and name.
394
If the exercise is not in the database, creates it and inserts it
397
ex = store.find(cls, cls.name == unicode(name)).one()
400
ex = Exercise(name=unicode(name))
526
405
class Worksheet(Storm):
527
406
__storm_table__ = "worksheet"
529
408
id = Int(primary=True, name="worksheetid")
530
offering_id = Int(name="offeringid")
531
identifier = Unicode()
409
# XXX subject is not linked to a Subject object. This is a property of
410
# the database, and will be refactored.
412
name = Unicode(name="identifier")
533
413
assessable = Bool()
538
attempts = ReferenceSet(id, "ExerciseAttempt.worksheetid")
539
offering = Reference(offering_id, 'Offering.id')
541
all_worksheet_exercises = ReferenceSet(id,
416
exercises = ReferenceSet(id,
417
'WorksheetExercise.worksheet_id',
418
'WorksheetExercise.exercise_id',
420
# Use worksheet_exercises to get access to the WorksheetExercise objects
421
# binding worksheets to exercises. This is required to access the
423
worksheet_exercises = ReferenceSet(id,
542
424
'WorksheetExercise.worksheet_id')
544
# Use worksheet_exercises to get access to the *active* WorksheetExercise
545
# objects binding worksheets to exercises. This is required to access the
549
def worksheet_exercises(self):
550
return self.all_worksheet_exercises.find(active=True)
552
426
__init__ = _kwarg_init
554
428
def __repr__(self):
566
440
return store.find(cls, cls.subject == unicode(subjectname),
567
441
cls.name == unicode(worksheetname)).one()
569
def remove_all_exercises(self):
443
def remove_all_exercises(self, store):
571
445
Remove all exercises from this worksheet.
572
446
This does not delete the exercises themselves. It just removes them
573
447
from the worksheet.
575
store = Store.of(self)
576
for ws_ex in self.all_worksheet_exercises:
577
if ws_ex.saves.count() > 0 or ws_ex.attempts.count() > 0:
578
raise IntegrityError()
579
449
store.find(WorksheetExercise,
580
450
WorksheetExercise.worksheet == self).remove()
582
def get_permissions(self, user):
583
return self.offering.get_permissions(user)
586
"""Returns the xml of this worksheet, converts from rst if required."""
587
if self.format == u'rst':
588
ws_xml = rst(self.data)
594
"""Deletes the worksheet, provided it has no attempts on any exercises.
596
Returns True if delete succeeded, or False if this worksheet has
597
attempts attached."""
598
for ws_ex in self.all_worksheet_exercises:
599
if ws_ex.saves.count() > 0 or ws_ex.attempts.count() > 0:
600
raise IntegrityError()
602
self.remove_all_exercises()
603
Store.of(self).remove(self)
605
452
class WorksheetExercise(Storm):
606
__storm_table__ = "worksheet_exercise"
608
id = Int(primary=True, name="ws_ex_id")
453
__storm_table__ = "worksheet_problem"
454
__storm_primary__ = "worksheet_id", "exercise_id"
610
456
worksheet_id = Int(name="worksheetid")
611
457
worksheet = Reference(worksheet_id, Worksheet.id)
612
exercise_id = Unicode(name="exerciseid")
458
exercise_id = Int(name="problemid")
613
459
exercise = Reference(exercise_id, Exercise.id)
614
460
optional = Bool()
618
saves = ReferenceSet(id, "ExerciseSave.ws_ex_id")
619
attempts = ReferenceSet(id, "ExerciseAttempt.ws_ex_id")
621
462
__init__ = _kwarg_init
623
464
def __repr__(self):
624
465
return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
625
self.worksheet.identifier)
627
def get_permissions(self, user):
628
return self.worksheet.get_permissions(user)
631
468
class ExerciseSave(Storm):
667
503
they won't count (either as a penalty or success), but will still be
670
__storm_table__ = "exercise_attempt"
671
__storm_primary__ = "ws_ex_id", "user_id", "date"
506
__storm_table__ = "problem_attempt"
507
__storm_primary__ = "exercise_id", "user_id", "date"
673
509
# The "text" field is the same but has a different name in the DB table
674
510
# for some reason.
675
511
text = Unicode(name="attempt")
676
512
complete = Bool()
679
def get_permissions(self, user):
680
return set(['view']) if user is self.user else set()
682
class TestSuite(Storm):
683
"""A Testsuite acts as a container for the test cases of an exercise."""
684
__storm_table__ = "test_suite"
685
__storm_primary__ = "exercise_id", "suiteid"
688
exercise_id = Unicode(name="exerciseid")
689
description = Unicode()
693
exercise = Reference(exercise_id, Exercise.id)
694
test_cases = ReferenceSet(suiteid, 'TestCase.suiteid', order_by="seq_no")
695
variables = ReferenceSet(suiteid, 'TestSuiteVar.suiteid', order_by='arg_no')
698
"""Delete this suite, without asking questions."""
699
for vaariable in self.variables:
701
for test_case in self.test_cases:
703
Store.of(self).remove(self)
705
class TestCase(Storm):
706
"""A TestCase is a member of a TestSuite.
708
It contains the data necessary to check if an exercise is correct"""
709
__storm_table__ = "test_case"
710
__storm_primary__ = "testid", "suiteid"
714
suite = Reference(suiteid, "TestSuite.suiteid")
717
test_default = Unicode()
720
parts = ReferenceSet(testid, "TestCasePart.testid")
722
__init__ = _kwarg_init
725
for part in self.parts:
727
Store.of(self).remove(self)
729
class TestSuiteVar(Storm):
730
"""A container for the arguments of a Test Suite"""
731
__storm_table__ = "suite_variable"
732
__storm_primary__ = "varid"
737
var_value = Unicode()
741
suite = Reference(suiteid, "TestSuite.suiteid")
743
__init__ = _kwarg_init
746
Store.of(self).remove(self)
748
class TestCasePart(Storm):
749
"""A container for the test elements of a Test Case"""
750
__storm_table__ = "test_case_part"
751
__storm_primary__ = "partid"
756
part_type = Unicode()
757
test_type = Unicode()
761
test = Reference(testid, "TestCase.testid")
763
__init__ = _kwarg_init
766
Store.of(self).remove(self)