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

« back to all changes in this revision

Viewing changes to ivle/database.py

  • Committer: Matt Giuca
  • Date: 2009-03-24 03:14:44 UTC
  • mto: This revision was merged to the branch mainline in revision 1322.
  • Revision ID: matt.giuca@gmail.com-20090324031444-dbpn1bj6dr4zh4sx
Added auto-generated (and hand-modified) Sphinx files (conf, Makefile and
    index).
    Our IVLE documentation will be stored as Sphinx files.
    make html to generate the documentation.

Show diffs side-by-side

added added

removed removed

Lines of Context:
29
29
 
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
32
33
 
33
34
import ivle.conf
34
 
import ivle.caps
 
35
from ivle.worksheet.rst import rst
35
36
 
36
37
__all__ = ['get_store',
37
38
            'User',
39
40
            'ProjectSet', 'Project', 'ProjectGroup', 'ProjectGroupMembership',
40
41
            'Exercise', 'Worksheet', 'WorksheetExercise',
41
42
            'ExerciseSave', 'ExerciseAttempt',
42
 
            'AlreadyEnrolledError', 'TestCase', 'TestSuite', 'TestSuiteVar'
 
43
            'TestCase', 'TestSuite', 'TestSuiteVar'
43
44
        ]
44
45
 
45
46
def _kwarg_init(self, **kwargs):
53
54
    """
54
55
    Returns the Storm connection string, generated from the conf file.
55
56
    """
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)
 
57
 
 
58
    clusterstr = ''
 
59
    if ivle.conf.db_user:
 
60
        clusterstr += ivle.conf.db_user
 
61
        if ivle.conf.db_password:
 
62
            clusterstr += ':' + ivle.conf.db_password
 
63
        clusterstr += '@'
 
64
 
 
65
    host = ivle.conf.db_host or 'localhost'
 
66
    port = ivle.conf.db_port or 5432
 
67
 
 
68
    clusterstr += '%s:%d' % (host, port)
 
69
 
 
70
    return "postgres://%s/%s" % (clusterstr, ivle.conf.db_dbname)
59
71
 
60
72
def get_store():
61
73
    """
76
88
    login = Unicode()
77
89
    passhash = Unicode()
78
90
    state = Unicode()
79
 
    rolenm = Unicode()
 
91
    admin = Bool()
80
92
    unixid = Int()
81
93
    nick = Unicode()
82
94
    pass_exp = DateTime()
88
100
    studentid = Unicode()
89
101
    settings = Unicode()
90
102
 
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
103
    __init__ = _kwarg_init
102
104
 
103
105
    def __repr__(self):
114
116
            return None
115
117
        return self.hash_password(password) == self.passhash
116
118
 
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
 
 
123
119
    @property
124
120
    def password_expired(self):
125
121
        fieldval = self.pass_exp
201
197
        return store.find(cls, cls.login == unicode(login)).one()
202
198
 
203
199
    def get_permissions(self, user):
204
 
        if user and user.rolenm == 'admin' or user is self:
 
200
        if user and user.admin or user is self:
205
201
            return set(['view', 'edit'])
206
202
        else:
207
203
            return set()
228
224
        perms = set()
229
225
        if user is not None:
230
226
            perms.add('view')
231
 
            if user.rolenm == 'admin':
 
227
            if user.admin:
232
228
                perms.add('edit')
233
229
        return perms
234
230
 
238
234
    id = Int(primary=True, name="semesterid")
239
235
    year = Unicode()
240
236
    semester = Unicode()
241
 
    active = Bool()
 
237
    state = Unicode()
242
238
 
243
239
    offerings = ReferenceSet(id, 'Offering.semester_id')
 
240
    enrolments = ReferenceSet(id,
 
241
                              'Offering.semester_id',
 
242
                              'Offering.id',
 
243
                              'Enrolment.offering_id')
244
244
 
245
245
    __init__ = _kwarg_init
246
246
 
264
264
                           'User.id')
265
265
    project_sets = ReferenceSet(id, 'ProjectSet.offering_id')
266
266
 
267
 
    worksheets = ReferenceSet(id, 'Worksheet.offering_id')
 
267
    worksheets = ReferenceSet(id, 
 
268
        'Worksheet.offering_id', 
 
269
        order_by="seq_no"
 
270
    )
268
271
 
269
272
    __init__ = _kwarg_init
270
273
 
272
275
        return "<%s %r in %r>" % (type(self).__name__, self.subject,
273
276
                                  self.semester)
274
277
 
275
 
    def enrol(self, user):
 
278
    def enrol(self, user, role=u'student'):
276
279
        '''Enrol a user in this offering.'''
277
 
        # We'll get a horrible database constraint violation error if we try
278
 
        # to add a second enrolment.
279
 
        if Store.of(self).find(Enrolment,
280
 
                               Enrolment.user_id == user.id,
281
 
                               Enrolment.offering_id == self.id).count() == 1:
282
 
            raise AlreadyEnrolledError()
283
 
 
284
 
        e = Enrolment(user=user, offering=self, active=True)
285
 
        self.enrolments.add(e)
 
280
        enrolment = Store.of(self).find(Enrolment,
 
281
                               Enrolment.user_id == user.id,
 
282
                               Enrolment.offering_id == self.id).one()
 
283
 
 
284
        if enrolment is None:
 
285
            enrolment = Enrolment(user=user, offering=self)
 
286
            self.enrolments.add(enrolment)
 
287
 
 
288
        enrolment.active = True
 
289
        enrolment.role = role
 
290
 
 
291
    def unenrol(self, user):
 
292
        '''Unenrol a user from this offering.'''
 
293
        enrolment = Store.of(self).find(Enrolment,
 
294
                               Enrolment.user_id == user.id,
 
295
                               Enrolment.offering_id == self.id).one()
 
296
        Store.of(enrolment).remove(enrolment)
286
297
 
287
298
    def get_permissions(self, user):
288
299
        perms = set()
289
300
        if user is not None:
290
 
            perms.add('view')
291
 
            if user.rolenm == 'admin':
 
301
            enrolment = self.get_enrolment(user)
 
302
            if enrolment or user.admin:
 
303
                perms.add('view')
 
304
            if (enrolment and enrolment.role in (u'tutor', u'lecturer')) \
 
305
               or user.admin:
292
306
                perms.add('edit')
293
307
        return perms
294
308
 
 
309
    def get_enrolment(self, user):
 
310
        try:
 
311
            enrolment = self.enrolments.find(user=user).one()
 
312
        except NotOneError:
 
313
            enrolment = None
 
314
 
 
315
        return enrolment
 
316
 
295
317
class Enrolment(Storm):
296
318
    __storm_table__ = "enrolment"
297
319
    __storm_primary__ = "user_id", "offering_id"
300
322
    user = Reference(user_id, User.id)
301
323
    offering_id = Int(name="offeringid")
302
324
    offering = Reference(offering_id, Offering.id)
 
325
    role = Unicode()
303
326
    notes = Unicode()
304
327
    active = Bool()
305
328
 
317
340
        return "<%s %r in %r>" % (type(self).__name__, self.user,
318
341
                                  self.offering)
319
342
 
320
 
class AlreadyEnrolledError(Exception):
321
 
    pass
322
 
 
323
343
# PROJECTS #
324
344
 
325
345
class ProjectSet(Storm):
396
416
# WORKSHEETS AND EXERCISES #
397
417
 
398
418
class Exercise(Storm):
399
 
    # Note: Table "problem" is called "Exercise" in the Object layer, since
400
 
    # it's called that everywhere else.
401
 
    __storm_table__ = "problem"
402
 
#TODO: Add in a field for the user-friendly identifier
 
419
    __storm_table__ = "exercise"
403
420
    id = Unicode(primary=True, name="identifier")
404
421
    name = Unicode()
405
422
    description = Unicode()
408
425
    include = Unicode()
409
426
    num_rows = Int()
410
427
 
 
428
    worksheet_exercises =  ReferenceSet(id,
 
429
        'WorksheetExercise.exercise_id')
 
430
 
411
431
    worksheets = ReferenceSet(id,
412
432
        'WorksheetExercise.exercise_id',
413
433
        'WorksheetExercise.worksheet_id',
414
434
        'Worksheet.id'
415
435
    )
416
436
    
417
 
    test_suites = ReferenceSet(id, 'TestSuite.exercise_id')
 
437
    test_suites = ReferenceSet(id, 
 
438
        'TestSuite.exercise_id',
 
439
        order_by='seq_no')
418
440
 
419
441
    __init__ = _kwarg_init
420
442
 
421
443
    def __repr__(self):
422
444
        return "<%s %s>" % (type(self).__name__, self.name)
423
445
 
 
446
    def get_permissions(self, user):
 
447
        perms = set()
 
448
        roles = set()
 
449
        if user is not None:
 
450
            if user.admin:
 
451
                perms.add('edit')
 
452
                perms.add('view')
 
453
            elif 'lecturer' in set((e.role for e in user.active_enrolments)):
 
454
                perms.add('edit')
 
455
                perms.add('view')
 
456
            
 
457
        return perms
 
458
    
 
459
    def get_description(self):
 
460
        return rst(self.description)
 
461
 
 
462
    def delete(self):
 
463
        """Deletes the exercise, providing it has no associated worksheets."""
 
464
        if (self.worksheet_exercises.count() > 0):
 
465
            raise IntegrityError()
 
466
        for suite in self.test_suites:
 
467
            suite.delete()
 
468
        Store.of(self).remove(self)
424
469
 
425
470
class Worksheet(Storm):
426
471
    __storm_table__ = "worksheet"
427
472
 
428
473
    id = Int(primary=True, name="worksheetid")
429
 
    # XXX subject is not linked to a Subject object. This is a property of
430
 
    # the database, and will be refactored.
431
474
    offering_id = Int(name="offeringid")
432
 
    name = Unicode(name="identifier")
 
475
    identifier = Unicode()
 
476
    name = Unicode()
433
477
    assessable = Bool()
434
 
    mtime = DateTime()
 
478
    data = Unicode()
 
479
    seq_no = Int()
 
480
    format = Unicode()
435
481
 
436
482
    attempts = ReferenceSet(id, "ExerciseAttempt.worksheetid")
437
483
    offering = Reference(offering_id, 'Offering.id')
438
484
 
439
 
    exercises = ReferenceSet(id,
440
 
        'WorksheetExercise.worksheet_id',
441
 
        'WorksheetExercise.exercise_id',
442
 
        Exercise.id)
443
 
    # Use worksheet_exercises to get access to the WorksheetExercise objects
444
 
    # binding worksheets to exercises. This is required to access the
 
485
    all_worksheet_exercises = ReferenceSet(id,
 
486
        'WorksheetExercise.worksheet_id')
 
487
 
 
488
    # Use worksheet_exercises to get access to the *active* WorksheetExercise
 
489
    # objects binding worksheets to exercises. This is required to access the
445
490
    # "optional" field.
446
 
    worksheet_exercises = ReferenceSet(id,
447
 
        'WorksheetExercise.worksheet_id')
448
 
        
 
491
 
 
492
    @property
 
493
    def worksheet_exercises(self):
 
494
        return self.all_worksheet_exercises.find(active=True)
449
495
 
450
496
    __init__ = _kwarg_init
451
497
 
464
510
        return store.find(cls, cls.subject == unicode(subjectname),
465
511
            cls.name == unicode(worksheetname)).one()
466
512
 
467
 
    def remove_all_exercises(self, store):
 
513
    def remove_all_exercises(self):
468
514
        """
469
515
        Remove all exercises from this worksheet.
470
516
        This does not delete the exercises themselves. It just removes them
471
517
        from the worksheet.
472
518
        """
 
519
        store = Store.of(self)
 
520
        for ws_ex in self.all_worksheet_exercises:
 
521
            if ws_ex.saves.count() > 0 or ws_ex.attempts.count() > 0:
 
522
                raise IntegrityError()
473
523
        store.find(WorksheetExercise,
474
524
            WorksheetExercise.worksheet == self).remove()
475
525
            
476
526
    def get_permissions(self, user):
477
527
        return self.offering.get_permissions(user)
478
 
 
 
528
    
 
529
    def get_xml(self):
 
530
        """Returns the xml of this worksheet, converts from rst if required."""
 
531
        if self.format == u'rst':
 
532
            ws_xml = rst(self.data)
 
533
            return ws_xml
 
534
        else:
 
535
            return self.data
 
536
    
 
537
    def delete(self):
 
538
        """Deletes the worksheet, provided it has no attempts on any exercises.
 
539
        
 
540
        Returns True if delete succeeded, or False if this worksheet has
 
541
        attempts attached."""
 
542
        for ws_ex in self.all_worksheet_exercises:
 
543
            if ws_ex.saves.count() > 0 or ws_ex.attempts.count() > 0:
 
544
                raise IntegrityError()
 
545
        
 
546
        self.remove_all_exercises()
 
547
        Store.of(self).remove(self)
 
548
        
479
549
class WorksheetExercise(Storm):
480
 
    __storm_table__ = "worksheet_problem"
481
 
    __storm_primary__ = "worksheet_id", "exercise_id"
 
550
    __storm_table__ = "worksheet_exercise"
 
551
    
 
552
    id = Int(primary=True, name="ws_ex_id")
482
553
 
483
554
    worksheet_id = Int(name="worksheetid")
484
555
    worksheet = Reference(worksheet_id, Worksheet.id)
485
 
    exercise_id = Unicode(name="problemid")
 
556
    exercise_id = Unicode(name="exerciseid")
486
557
    exercise = Reference(exercise_id, Exercise.id)
487
558
    optional = Bool()
 
559
    active = Bool()
 
560
    seq_no = Int()
 
561
    
 
562
    saves = ReferenceSet(id, "ExerciseSave.ws_ex_id")
 
563
    attempts = ReferenceSet(id, "ExerciseAttempt.ws_ex_id")
488
564
 
489
565
    __init__ = _kwarg_init
490
566
 
491
567
    def __repr__(self):
492
568
        return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
493
 
                                  self.worksheet.name)
 
569
                                  self.worksheet.identifier)
 
570
 
 
571
    def get_permissions(self, user):
 
572
        return self.worksheet.get_permissions(user)
 
573
    
494
574
 
495
575
class ExerciseSave(Storm):
496
576
    """
501
581
    ExerciseSave may be extended with additional semantics (such as
502
582
    ExerciseAttempt).
503
583
    """
504
 
    __storm_table__ = "problem_save"
505
 
    __storm_primary__ = "exercise_id", "user_id", "date"
506
 
 
507
 
    exercise_id = Unicode(name="problemid")
508
 
    exercise = Reference(exercise_id, Exercise.id)
 
584
    __storm_table__ = "exercise_save"
 
585
    __storm_primary__ = "ws_ex_id", "user_id"
 
586
 
 
587
    ws_ex_id = Int(name="ws_ex_id")
 
588
    worksheet_exercise = Reference(ws_ex_id, "WorksheetExercise.id")
 
589
 
509
590
    user_id = Int(name="loginid")
510
591
    user = Reference(user_id, User.id)
511
592
    date = DateTime()
512
593
    text = Unicode()
513
 
    worksheetid = Int()
514
 
    worksheet = Reference(worksheetid, Worksheet.id)
515
594
 
516
595
    __init__ = _kwarg_init
517
596
 
532
611
        they won't count (either as a penalty or success), but will still be
533
612
        stored.
534
613
    """
535
 
    __storm_table__ = "problem_attempt"
536
 
    __storm_primary__ = "exercise_id", "user_id", "date"
 
614
    __storm_table__ = "exercise_attempt"
 
615
    __storm_primary__ = "ws_ex_id", "user_id", "date"
537
616
 
538
617
    # The "text" field is the same but has a different name in the DB table
539
618
    # for some reason.
550
629
    __storm_primary__ = "exercise_id", "suiteid"
551
630
    
552
631
    suiteid = Int()
553
 
    exercise_id = Unicode(name="problemid")
 
632
    exercise_id = Unicode(name="exerciseid")
554
633
    description = Unicode()
555
634
    seq_no = Int()
556
635
    function = Unicode()
557
636
    stdin = Unicode()
558
637
    exercise = Reference(exercise_id, Exercise.id)
559
 
    test_cases = ReferenceSet(suiteid, 'TestCase.suiteid')
560
 
    variables = ReferenceSet(suiteid, 'TestSuiteVar.suiteid')
 
638
    test_cases = ReferenceSet(suiteid, 'TestCase.suiteid', order_by="seq_no")
 
639
    variables = ReferenceSet(suiteid, 'TestSuiteVar.suiteid', order_by='arg_no')
 
640
    
 
641
    def delete(self):
 
642
        """Delete this suite, without asking questions."""
 
643
        for vaariable in self.variables:
 
644
            variable.delete()
 
645
        for test_case in self.test_cases:
 
646
            test_case.delete()
 
647
        Store.of(self).remove(self)
561
648
 
562
649
class TestCase(Storm):
563
650
    """A TestCase is a member of a TestSuite.
577
664
    parts = ReferenceSet(testid, "TestCasePart.testid")
578
665
    
579
666
    __init__ = _kwarg_init
 
667
    
 
668
    def delete(self):
 
669
        for part in self.parts:
 
670
            part.delete()
 
671
        Store.of(self).remove(self)
580
672
 
581
673
class TestSuiteVar(Storm):
582
674
    """A container for the arguments of a Test Suite"""
583
 
    __storm_table__ = "suite_variables"
 
675
    __storm_table__ = "suite_variable"
584
676
    __storm_primary__ = "varid"
585
677
    
586
678
    varid = Int()
594
686
    
595
687
    __init__ = _kwarg_init
596
688
    
 
689
    def delete(self):
 
690
        Store.of(self).remove(self)
 
691
    
597
692
class TestCasePart(Storm):
598
693
    """A container for the test elements of a Test Case"""
599
 
    __storm_table__ = "test_case_parts"
 
694
    __storm_table__ = "test_case_part"
600
695
    __storm_primary__ = "partid"
601
696
    
602
697
    partid = Int()
610
705
    test = Reference(testid, "TestCase.testid")
611
706
    
612
707
    __init__ = _kwarg_init
 
708
    
 
709
    def delete(self):
 
710
        Store.of(self).remove(self)