~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to ivle/database.py

Dispatch now generates an index for each plugin type, allowing plugins to
be written which are aware of other plugins, and other plugin types.

All view plugins now subclass from ivle.webapp.base.plugins.ViewPlugin,
as opposed to subclassing BasePlugin directly. This will allow us to
easily re-write console as an OverlayPlugin, and allow future new
plugins types to be created.

Show diffs side-by-side

added added

removed removed

Lines of Context:
31
31
                         Reference, ReferenceSet, Bool, Storm, Desc
32
32
 
33
33
import ivle.conf
 
34
import ivle.caps
34
35
 
35
36
__all__ = ['get_store',
36
37
            'User',
38
39
            'ProjectSet', 'Project', 'ProjectGroup', 'ProjectGroupMembership',
39
40
            'Exercise', 'Worksheet', 'WorksheetExercise',
40
41
            'ExerciseSave', 'ExerciseAttempt',
41
 
            'TestCase', 'TestSuite', 'TestSuiteVar'
 
42
            'AlreadyEnrolledError'
42
43
        ]
43
44
 
44
45
def _kwarg_init(self, **kwargs):
52
53
    """
53
54
    Returns the Storm connection string, generated from the conf file.
54
55
    """
55
 
 
56
 
    clusterstr = ''
57
 
    if ivle.conf.db_user:
58
 
        clusterstr += ivle.conf.db_user
59
 
        if ivle.conf.db_password:
60
 
            clusterstr += ':' + ivle.conf.db_password
61
 
        clusterstr += '@'
62
 
 
63
 
    host = ivle.conf.db_host or 'localhost'
64
 
    port = ivle.conf.db_port or 5432
65
 
 
66
 
    clusterstr += '%s:%d' % (host, port)
67
 
 
68
 
    return "postgres://%s/%s" % (clusterstr, ivle.conf.db_dbname)
 
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,
 
58
        ivle.conf.db_dbname)
69
59
 
70
60
def get_store():
71
61
    """
86
76
    login = Unicode()
87
77
    passhash = Unicode()
88
78
    state = Unicode()
89
 
    admin = Bool()
 
79
    rolenm = Unicode()
90
80
    unixid = Int()
91
81
    nick = Unicode()
92
82
    pass_exp = DateTime()
98
88
    studentid = Unicode()
99
89
    settings = Unicode()
100
90
 
 
91
    def _get_role(self):
 
92
        if self.rolenm is None:
 
93
            return 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)
 
100
 
101
101
    __init__ = _kwarg_init
102
102
 
103
103
    def __repr__(self):
114
114
            return None
115
115
        return self.hash_password(password) == self.passhash
116
116
 
 
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.
 
120
        """
 
121
        return self.role.hasCap(capability)
 
122
 
117
123
    @property
118
124
    def password_expired(self):
119
125
        fieldval = self.pass_exp
124
130
        fieldval = self.acct_exp
125
131
        return fieldval is not None and datetime.datetime.now() > fieldval
126
132
 
127
 
    @property
128
 
    def valid(self):
129
 
        return self.state == 'enabled' and not self.account_expired
130
 
 
131
133
    def _get_enrolments(self, justactive):
132
134
        return Store.of(self).find(Enrolment,
133
135
            Enrolment.user_id == self.id,
194
196
        """
195
197
        return store.find(cls, cls.login == unicode(login)).one()
196
198
 
197
 
    def get_permissions(self, user):
198
 
        if user and user.admin or user is self:
199
 
            return set(['view', 'edit'])
200
 
        else:
201
 
            return set()
202
 
 
203
199
# SUBJECTS AND ENROLMENTS #
204
200
 
205
201
class Subject(Storm):
218
214
    def __repr__(self):
219
215
        return "<%s '%s'>" % (type(self).__name__, self.short_name)
220
216
 
221
 
    def get_permissions(self, user):
222
 
        perms = set()
223
 
        if user is not None:
224
 
            perms.add('view')
225
 
            if user.admin:
226
 
                perms.add('edit')
227
 
        return perms
228
 
 
229
217
class Semester(Storm):
230
218
    __storm_table__ = "semester"
231
219
 
232
220
    id = Int(primary=True, name="semesterid")
233
221
    year = Unicode()
234
222
    semester = Unicode()
235
 
    state = Unicode()
 
223
    active = Bool()
236
224
 
237
225
    offerings = ReferenceSet(id, 'Offering.semester_id')
238
226
 
258
246
                           'User.id')
259
247
    project_sets = ReferenceSet(id, 'ProjectSet.offering_id')
260
248
 
261
 
    worksheets = ReferenceSet(id, 
262
 
        'Worksheet.offering_id', 
263
 
        order_by="Worksheet.seq_no"
264
 
    )
265
 
 
266
249
    __init__ = _kwarg_init
267
250
 
268
251
    def __repr__(self):
269
252
        return "<%s %r in %r>" % (type(self).__name__, self.subject,
270
253
                                  self.semester)
271
254
 
272
 
    def enrol(self, user, role=u'student'):
 
255
    def enrol(self, user):
273
256
        '''Enrol a user in this offering.'''
274
 
        enrolment = Store.of(self).find(Enrolment,
 
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,
275
260
                               Enrolment.user_id == user.id,
276
 
                               Enrolment.offering_id == self.id).one()
277
 
 
278
 
        if enrolment is None:
279
 
            enrolment = Enrolment(user=user, offering=self)
280
 
            self.enrolments.add(enrolment)
281
 
 
282
 
        enrolment.active = True
283
 
        enrolment.role = role
284
 
 
285
 
    def get_permissions(self, user):
286
 
        perms = set()
287
 
        if user is not None:
288
 
            perms.add('view')
289
 
            if user.admin:
290
 
                perms.add('edit')
291
 
        return perms
 
261
                               Enrolment.offering_id == self.id).count() == 1:
 
262
            raise AlreadyEnrolledError()
 
263
 
 
264
        e = Enrolment(user=user, offering=self, active=True)
 
265
        self.enrolments.add(e)
292
266
 
293
267
class Enrolment(Storm):
294
268
    __storm_table__ = "enrolment"
298
272
    user = Reference(user_id, User.id)
299
273
    offering_id = Int(name="offeringid")
300
274
    offering = Reference(offering_id, Offering.id)
301
 
    role = Unicode()
302
275
    notes = Unicode()
303
276
    active = Bool()
304
277
 
316
289
        return "<%s %r in %r>" % (type(self).__name__, self.user,
317
290
                                  self.offering)
318
291
 
 
292
class AlreadyEnrolledError(Exception):
 
293
    pass
 
294
 
319
295
# PROJECTS #
320
296
 
321
297
class ProjectSet(Storm):
392
368
# WORKSHEETS AND EXERCISES #
393
369
 
394
370
class Exercise(Storm):
395
 
    __storm_table__ = "exercise"
396
 
    id = Unicode(primary=True, name="identifier")
397
 
    name = Unicode()
398
 
    description = Unicode()
399
 
    partial = Unicode()
400
 
    solution = Unicode()
401
 
    include = Unicode()
402
 
    num_rows = Int()
 
371
    # Note: Table "problem" is called "Exercise" in the Object layer, since
 
372
    # it's called that everywhere else.
 
373
    __storm_table__ = "problem"
 
374
 
 
375
    id = Int(primary=True, name="problemid")
 
376
    name = Unicode(name="identifier")
 
377
    spec = Unicode()
403
378
 
404
379
    worksheets = ReferenceSet(id,
405
380
        'WorksheetExercise.exercise_id',
406
381
        'WorksheetExercise.worksheet_id',
407
382
        'Worksheet.id'
408
383
    )
409
 
    
410
 
    test_suites = ReferenceSet(id, 'TestSuite.exercise_id')
411
384
 
412
385
    __init__ = _kwarg_init
413
386
 
414
387
    def __repr__(self):
415
388
        return "<%s %s>" % (type(self).__name__, self.name)
416
389
 
417
 
    def get_permissions(self, user):
418
 
        perms = set()
419
 
        if user is not None:
420
 
            if user.admin:
421
 
                perms.add('edit')
422
 
                perms.add('view')
423
 
        return perms
 
390
    @classmethod
 
391
    def get_by_name(cls, store, name):
 
392
        """
 
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
 
395
        automatically.
 
396
        """
 
397
        ex = store.find(cls, cls.name == unicode(name)).one()
 
398
        if ex is not None:
 
399
            return ex
 
400
        ex = Exercise(name=unicode(name))
 
401
        store.add(ex)
 
402
        store.commit()
 
403
        return ex
424
404
 
425
405
class Worksheet(Storm):
426
406
    __storm_table__ = "worksheet"
427
407
 
428
408
    id = Int(primary=True, name="worksheetid")
429
 
    offering_id = Int(name="offeringid")
430
 
    identifier = Unicode()
431
 
    name = Unicode()
 
409
    # XXX subject is not linked to a Subject object. This is a property of
 
410
    # the database, and will be refactored.
 
411
    subject = Unicode()
 
412
    name = Unicode(name="identifier")
432
413
    assessable = Bool()
433
 
    data = Unicode()
434
 
    seq_no = Int()
435
 
    format = Unicode()
436
 
 
437
 
    attempts = ReferenceSet(id, "ExerciseAttempt.worksheetid")
438
 
    offering = Reference(offering_id, 'Offering.id')
439
 
 
440
 
    all_worksheet_exercises = ReferenceSet(id,
 
414
    mtime = DateTime()
 
415
 
 
416
    exercises = ReferenceSet(id,
 
417
        'WorksheetExercise.worksheet_id',
 
418
        'WorksheetExercise.exercise_id',
 
419
        Exercise.id)
 
420
    # Use worksheet_exercises to get access to the WorksheetExercise objects
 
421
    # binding worksheets to exercises. This is required to access the
 
422
    # "optional" field.
 
423
    worksheet_exercises = ReferenceSet(id,
441
424
        'WorksheetExercise.worksheet_id')
442
425
 
443
 
    # Use worksheet_exercises to get access to the *active* WorksheetExercise
444
 
    # objects binding worksheets to exercises. This is required to access the
445
 
    # "optional" field.
446
 
    @property
447
 
    def worksheet_exercises(self):
448
 
        return self.all_worksheet_exercises.find(active=True)
449
 
 
450
426
    __init__ = _kwarg_init
451
427
 
452
428
    def __repr__(self):
472
448
        """
473
449
        store.find(WorksheetExercise,
474
450
            WorksheetExercise.worksheet == self).remove()
475
 
            
476
 
    def get_permissions(self, user):
477
 
        return self.offering.get_permissions(user)
478
451
 
479
452
class WorksheetExercise(Storm):
480
 
    __storm_table__ = "worksheet_exercise"
481
 
    
482
 
    id = Int(primary=True, name="ws_ex_id")
 
453
    __storm_table__ = "worksheet_problem"
 
454
    __storm_primary__ = "worksheet_id", "exercise_id"
483
455
 
484
456
    worksheet_id = Int(name="worksheetid")
485
457
    worksheet = Reference(worksheet_id, Worksheet.id)
486
 
    exercise_id = Unicode(name="exerciseid")
 
458
    exercise_id = Int(name="problemid")
487
459
    exercise = Reference(exercise_id, Exercise.id)
488
460
    optional = Bool()
489
 
    active = Bool()
490
 
    seq_no = Int()
491
 
    
492
 
    saves = ReferenceSet(id, "ExerciseSave.ws_ex_id")
493
 
    attempts = ReferenceSet(id, "ExerciseAttempt.ws_ex_id")
494
461
 
495
462
    __init__ = _kwarg_init
496
463
 
497
464
    def __repr__(self):
498
465
        return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
499
 
                                  self.worksheet.identifier)
 
466
                                  self.worksheet.name)
500
467
 
501
468
class ExerciseSave(Storm):
502
469
    """
507
474
    ExerciseSave may be extended with additional semantics (such as
508
475
    ExerciseAttempt).
509
476
    """
510
 
    __storm_table__ = "exercise_save"
511
 
    __storm_primary__ = "ws_ex_id", "user_id"
512
 
 
513
 
    ws_ex_id = Int(name="ws_ex_id")
514
 
    worksheet_exercise = Reference(ws_ex_id, "WorksheetExercise.id")
515
 
 
 
477
    __storm_table__ = "problem_save"
 
478
    __storm_primary__ = "exercise_id", "user_id", "date"
 
479
 
 
480
    exercise_id = Int(name="problemid")
 
481
    exercise = Reference(exercise_id, Exercise.id)
516
482
    user_id = Int(name="loginid")
517
483
    user = Reference(user_id, User.id)
518
484
    date = DateTime()
537
503
        they won't count (either as a penalty or success), but will still be
538
504
        stored.
539
505
    """
540
 
    __storm_table__ = "exercise_attempt"
541
 
    __storm_primary__ = "ws_ex_id", "user_id", "date"
 
506
    __storm_table__ = "problem_attempt"
 
507
    __storm_primary__ = "exercise_id", "user_id", "date"
542
508
 
543
509
    # The "text" field is the same but has a different name in the DB table
544
510
    # for some reason.
545
511
    text = Unicode(name="attempt")
546
512
    complete = Bool()
547
513
    active = Bool()
548
 
    
549
 
    def get_permissions(self, user):
550
 
        return set(['view']) if user is self.user else set()
551
 
  
552
 
class TestSuite(Storm):
553
 
    """A Testsuite acts as a container for the test cases of an exercise."""
554
 
    __storm_table__ = "test_suite"
555
 
    __storm_primary__ = "exercise_id", "suiteid"
556
 
    
557
 
    suiteid = Int()
558
 
    exercise_id = Unicode(name="exerciseid")
559
 
    description = Unicode()
560
 
    seq_no = Int()
561
 
    function = Unicode()
562
 
    stdin = Unicode()
563
 
    exercise = Reference(exercise_id, Exercise.id)
564
 
    test_cases = ReferenceSet(suiteid, 'TestCase.suiteid')
565
 
    variables = ReferenceSet(suiteid, 'TestSuiteVar.suiteid')
566
 
 
567
 
class TestCase(Storm):
568
 
    """A TestCase is a member of a TestSuite.
569
 
    
570
 
    It contains the data necessary to check if an exercise is correct"""
571
 
    __storm_table__ = "test_case"
572
 
    __storm_primary__ = "testid", "suiteid"
573
 
    
574
 
    testid = Int()
575
 
    suiteid = Int()
576
 
    suite = Reference(suiteid, "TestSuite.suiteid")
577
 
    passmsg = Unicode()
578
 
    failmsg = Unicode()
579
 
    test_default = Unicode()
580
 
    seq_no = Int()
581
 
    
582
 
    parts = ReferenceSet(testid, "TestCasePart.testid")
583
 
    
584
 
    __init__ = _kwarg_init
585
 
 
586
 
class TestSuiteVar(Storm):
587
 
    """A container for the arguments of a Test Suite"""
588
 
    __storm_table__ = "suite_variable"
589
 
    __storm_primary__ = "varid"
590
 
    
591
 
    varid = Int()
592
 
    suiteid = Int()
593
 
    var_name = Unicode()
594
 
    var_value = Unicode()
595
 
    var_type = Unicode()
596
 
    arg_no = Int()
597
 
    
598
 
    suite = Reference(suiteid, "TestSuite.suiteid")
599
 
    
600
 
    __init__ = _kwarg_init
601
 
    
602
 
class TestCasePart(Storm):
603
 
    """A container for the test elements of a Test Case"""
604
 
    __storm_table__ = "test_case_part"
605
 
    __storm_primary__ = "partid"
606
 
    
607
 
    partid = Int()
608
 
    testid = Int()
609
 
    
610
 
    part_type = Unicode()
611
 
    test_type = Unicode()
612
 
    data = Unicode()
613
 
    filename = Unicode()
614
 
    
615
 
    test = Reference(testid, "TestCase.testid")
616
 
    
617
 
    __init__ = _kwarg_init