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
36
36
__all__ = ['get_store',
39
39
'ProjectSet', 'Project', 'ProjectGroup', 'ProjectGroupMembership',
40
40
'Exercise', 'Worksheet', 'WorksheetExercise',
41
41
'ExerciseSave', 'ExerciseAttempt',
42
'AlreadyEnrolledError'
42
'TestCase', 'TestSuite', 'TestSuiteVar'
45
45
def _kwarg_init(self, **kwargs):
54
54
Returns the Storm connection string, generated from the conf file.
56
return "postgres://%s:%s@%s:%d/%s" % (ivle.conf.db_user,
57
ivle.conf.db_password, ivle.conf.db_host, ivle.conf.db_port,
59
clusterstr += ivle.conf.db_user
60
if ivle.conf.db_password:
61
clusterstr += ':' + ivle.conf.db_password
64
host = ivle.conf.db_host or 'localhost'
65
port = ivle.conf.db_port or 5432
67
clusterstr += '%s:%d' % (host, port)
69
return "postgres://%s/%s" % (clusterstr, ivle.conf.db_dbname)
88
99
studentid = Unicode()
89
100
settings = Unicode()
92
if self.rolenm is None:
94
return ivle.caps.Role(self.rolenm)
95
def _set_role(self, value):
96
if not isinstance(value, ivle.caps.Role):
97
raise TypeError("role must be an ivle.caps.Role")
98
self.rolenm = unicode(value)
99
role = property(_get_role, _set_role)
101
102
__init__ = _kwarg_init
103
104
def __repr__(self):
115
116
return self.hash_password(password) == self.passhash
117
def hasCap(self, capability):
118
"""Given a capability (which is a Role object), returns True if this
119
User has that capability, False otherwise.
121
return self.role.hasCap(capability)
124
119
def password_expired(self):
125
120
fieldval = self.pass_exp
214
219
def __repr__(self):
215
220
return "<%s '%s'>" % (type(self).__name__, self.short_name)
222
def get_permissions(self, user):
217
230
class Semester(Storm):
218
231
__storm_table__ = "semester"
220
233
id = Int(primary=True, name="semesterid")
222
235
semester = Unicode()
225
238
offerings = ReferenceSet(id, 'Offering.semester_id')
239
enrolments = ReferenceSet(id,
240
'Offering.semester_id',
242
'Enrolment.offering_id')
227
244
__init__ = _kwarg_init
240
257
groups_student_permissions = Unicode()
242
259
enrolments = ReferenceSet(id, 'Enrolment.offering_id')
260
members = ReferenceSet(id,
261
'Enrolment.offering_id',
264
project_sets = ReferenceSet(id, 'ProjectSet.offering_id')
266
worksheets = ReferenceSet(id,
267
'Worksheet.offering_id',
268
order_by="Worksheet.seq_no"
244
271
__init__ = _kwarg_init
247
274
return "<%s %r in %r>" % (type(self).__name__, self.subject,
250
def enrol(self, user):
277
def enrol(self, user, role=u'student'):
251
278
'''Enrol a user in this offering.'''
252
# We'll get a horrible database constraint violation error if we try
253
# to add a second enrolment.
254
if Store.of(self).find(Enrolment,
255
Enrolment.user_id == user.id,
256
Enrolment.offering_id == self.id).count() == 1:
257
raise AlreadyEnrolledError()
259
e = Enrolment(user=user, offering=self, active=True)
260
self.enrolments.add(e)
279
enrolment = Store.of(self).find(Enrolment,
280
Enrolment.user_id == user.id,
281
Enrolment.offering_id == self.id).one()
283
if enrolment is None:
284
enrolment = Enrolment(user=user, offering=self)
285
self.enrolments.add(enrolment)
287
enrolment.active = True
288
enrolment.role = role
290
def unenrol(self, user):
291
'''Unenrol a user from this offering.'''
292
enrolment = Store.of(self).find(Enrolment,
293
Enrolment.user_id == user.id,
294
Enrolment.offering_id == self.id).one()
295
Store.of(enrolment).remove(enrolment)
297
def get_permissions(self, user):
300
enrolment = self.get_enrolment(user)
301
if enrolment or user.admin:
303
if (enrolment and enrolment.role in (u'tutor', u'lecturer')) \
308
def get_enrolment(self, user):
310
enrolment = self.enrolments.find(user=user).one()
262
316
class Enrolment(Storm):
263
317
__storm_table__ = "enrolment"
267
321
user = Reference(user_id, User.id)
268
322
offering_id = Int(name="offeringid")
269
323
offering = Reference(offering_id, Offering.id)
270
325
notes = Unicode()
330
return Store.of(self).find(ProjectGroup,
331
ProjectSet.offering_id == self.offering.id,
332
ProjectGroup.project_set_id == ProjectSet.id,
333
ProjectGroupMembership.project_group_id == ProjectGroup.id,
334
ProjectGroupMembership.user_id == self.user.id)
273
336
__init__ = _kwarg_init
275
338
def __repr__(self):
276
339
return "<%s %r in %r>" % (type(self).__name__, self.user,
279
class AlreadyEnrolledError(Exception):
284
344
class ProjectSet(Storm):
323
386
created_by = Reference(created_by_id, User.id)
324
387
epoch = DateTime()
389
members = ReferenceSet(id,
390
"ProjectGroupMembership.project_group_id",
391
"ProjectGroupMembership.user_id",
326
394
__init__ = _kwarg_init
328
396
def __repr__(self):
329
397
return "<%s %s in %r>" % (type(self).__name__, self.name,
330
398
self.project_set.offering)
334
return Store.of(self).find(User,
335
ProjectGroupMembership.project_group_id == self.id,
336
User.id == ProjectGroupMembership.user_id)
338
400
class ProjectGroupMembership(Storm):
339
401
__storm_table__ = "group_member"
340
402
__storm_primary__ = "user_id", "project_group_id"
353
415
# WORKSHEETS AND EXERCISES #
355
417
class Exercise(Storm):
356
# Note: Table "problem" is called "Exercise" in the Object layer, since
357
# it's called that everywhere else.
358
__storm_table__ = "problem"
360
id = Int(primary=True, name="problemid")
361
name = Unicode(name="identifier")
418
__storm_table__ = "exercise"
419
id = Unicode(primary=True, name="identifier")
421
description = Unicode()
364
427
worksheets = ReferenceSet(id,
365
428
'WorksheetExercise.exercise_id',
366
429
'WorksheetExercise.worksheet_id',
433
test_suites = ReferenceSet(id, 'TestSuite.exercise_id')
370
435
__init__ = _kwarg_init
372
437
def __repr__(self):
373
438
return "<%s %s>" % (type(self).__name__, self.name)
376
def get_by_name(cls, store, name):
378
Get the Exercise from the db associated with a given store and name.
379
If the exercise is not in the database, creates it and inserts it
382
ex = store.find(cls, cls.name == unicode(name)).one()
385
ex = Exercise(name=unicode(name))
440
def get_permissions(self, user):
390
448
class Worksheet(Storm):
391
449
__storm_table__ = "worksheet"
393
451
id = Int(primary=True, name="worksheetid")
394
# XXX subject is not linked to a Subject object. This is a property of
395
# the database, and will be refactored.
397
name = Unicode(name="identifier")
452
offering_id = Int(name="offeringid")
453
identifier = Unicode()
398
455
assessable = Bool()
401
exercises = ReferenceSet(id,
402
'WorksheetExercise.worksheet_id',
403
'WorksheetExercise.exercise_id',
405
# Use worksheet_exercises to get access to the WorksheetExercise objects
406
# binding worksheets to exercises. This is required to access the
460
attempts = ReferenceSet(id, "ExerciseAttempt.worksheetid")
461
offering = Reference(offering_id, 'Offering.id')
463
all_worksheet_exercises = ReferenceSet(id,
464
'WorksheetExercise.worksheet_id')
466
# Use worksheet_exercises to get access to the *active* WorksheetExercise
467
# objects binding worksheets to exercises. This is required to access the
407
468
# "optional" field.
408
worksheet_exercises = ReferenceSet(id,
409
'WorksheetExercise.worksheet_id')
470
def worksheet_exercises(self):
471
return self.all_worksheet_exercises.find(active=True)
411
473
__init__ = _kwarg_init
434
496
store.find(WorksheetExercise,
435
497
WorksheetExercise.worksheet == self).remove()
499
def get_permissions(self, user):
500
return self.offering.get_permissions(user)
437
502
class WorksheetExercise(Storm):
438
__storm_table__ = "worksheet_problem"
439
__storm_primary__ = "worksheet_id", "exercise_id"
503
__storm_table__ = "worksheet_exercise"
505
id = Int(primary=True, name="ws_ex_id")
441
507
worksheet_id = Int(name="worksheetid")
442
508
worksheet = Reference(worksheet_id, Worksheet.id)
443
exercise_id = Int(name="problemid")
509
exercise_id = Unicode(name="exerciseid")
444
510
exercise = Reference(exercise_id, Exercise.id)
445
511
optional = Bool()
515
saves = ReferenceSet(id, "ExerciseSave.ws_ex_id")
516
attempts = ReferenceSet(id, "ExerciseAttempt.ws_ex_id")
447
518
__init__ = _kwarg_init
449
520
def __repr__(self):
450
521
return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
522
self.worksheet.identifier)
524
def get_permissions(self, user):
525
return self.worksheet.get_permissions(user)
453
527
class ExerciseSave(Storm):
459
533
ExerciseSave may be extended with additional semantics (such as
460
534
ExerciseAttempt).
462
__storm_table__ = "problem_save"
463
__storm_primary__ = "exercise_id", "user_id", "date"
465
exercise_id = Int(name="problemid")
466
exercise = Reference(exercise_id, Exercise.id)
536
__storm_table__ = "exercise_save"
537
__storm_primary__ = "ws_ex_id", "user_id"
539
ws_ex_id = Int(name="ws_ex_id")
540
worksheet_exercise = Reference(ws_ex_id, "WorksheetExercise.id")
467
542
user_id = Int(name="loginid")
468
543
user = Reference(user_id, User.id)
469
544
date = DateTime()
488
563
they won't count (either as a penalty or success), but will still be
491
__storm_table__ = "problem_attempt"
492
__storm_primary__ = "exercise_id", "user_id", "date"
566
__storm_table__ = "exercise_attempt"
567
__storm_primary__ = "ws_ex_id", "user_id", "date"
494
569
# The "text" field is the same but has a different name in the DB table
495
570
# for some reason.
496
571
text = Unicode(name="attempt")
497
572
complete = Bool()
575
def get_permissions(self, user):
576
return set(['view']) if user is self.user else set()
578
class TestSuite(Storm):
579
"""A Testsuite acts as a container for the test cases of an exercise."""
580
__storm_table__ = "test_suite"
581
__storm_primary__ = "exercise_id", "suiteid"
584
exercise_id = Unicode(name="exerciseid")
585
description = Unicode()
589
exercise = Reference(exercise_id, Exercise.id)
590
test_cases = ReferenceSet(suiteid, 'TestCase.suiteid')
591
variables = ReferenceSet(suiteid, 'TestSuiteVar.suiteid')
593
class TestCase(Storm):
594
"""A TestCase is a member of a TestSuite.
596
It contains the data necessary to check if an exercise is correct"""
597
__storm_table__ = "test_case"
598
__storm_primary__ = "testid", "suiteid"
602
suite = Reference(suiteid, "TestSuite.suiteid")
605
test_default = Unicode()
608
parts = ReferenceSet(testid, "TestCasePart.testid")
610
__init__ = _kwarg_init
612
class TestSuiteVar(Storm):
613
"""A container for the arguments of a Test Suite"""
614
__storm_table__ = "suite_variable"
615
__storm_primary__ = "varid"
620
var_value = Unicode()
624
suite = Reference(suiteid, "TestSuite.suiteid")
626
__init__ = _kwarg_init
628
class TestCasePart(Storm):
629
"""A container for the test elements of a Test Case"""
630
__storm_table__ = "test_case_part"
631
__storm_primary__ = "partid"
636
part_type = Unicode()
637
test_type = Unicode()
641
test = Reference(testid, "TestCase.testid")
643
__init__ = _kwarg_init