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

« back to all changes in this revision

Viewing changes to ivle/database.py

Drop www/ stuff from setup.

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
33
32
 
34
33
import ivle.conf
35
 
from ivle.worksheet.rst import rst
 
34
import ivle.caps
36
35
 
37
36
__all__ = ['get_store',
38
37
            'User',
40
39
            'ProjectSet', 'Project', 'ProjectGroup', 'ProjectGroupMembership',
41
40
            'Exercise', 'Worksheet', 'WorksheetExercise',
42
41
            'ExerciseSave', 'ExerciseAttempt',
43
 
            'TestCase', 'TestSuite', 'TestSuiteVar'
 
42
            'AlreadyEnrolledError', 'TestCase', 'TestSuite', 'TestSuiteVar'
44
43
        ]
45
44
 
46
45
def _kwarg_init(self, **kwargs):
54
53
    """
55
54
    Returns the Storm connection string, generated from the conf file.
56
55
    """
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)
 
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)
71
59
 
72
60
def get_store():
73
61
    """
88
76
    login = Unicode()
89
77
    passhash = Unicode()
90
78
    state = Unicode()
91
 
    admin = Bool()
 
79
    rolenm = Unicode()
92
80
    unixid = Int()
93
81
    nick = Unicode()
94
82
    pass_exp = DateTime()
100
88
    studentid = Unicode()
101
89
    settings = Unicode()
102
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
 
103
101
    __init__ = _kwarg_init
104
102
 
105
103
    def __repr__(self):
116
114
            return None
117
115
        return self.hash_password(password) == self.passhash
118
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
 
119
123
    @property
120
124
    def password_expired(self):
121
125
        fieldval = self.pass_exp
197
201
        return store.find(cls, cls.login == unicode(login)).one()
198
202
 
199
203
    def get_permissions(self, user):
200
 
        if user and user.admin or user is self:
 
204
        if user and user.rolenm == 'admin' or user is self:
201
205
            return set(['view', 'edit'])
202
206
        else:
203
207
            return set()
224
228
        perms = set()
225
229
        if user is not None:
226
230
            perms.add('view')
227
 
            if user.admin:
 
231
            if user.rolenm == 'admin':
228
232
                perms.add('edit')
229
233
        return perms
230
234
 
234
238
    id = Int(primary=True, name="semesterid")
235
239
    year = Unicode()
236
240
    semester = Unicode()
237
 
    state = Unicode()
 
241
    active = Bool()
238
242
 
239
243
    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, 
268
 
        'Worksheet.offering_id', 
269
 
        order_by="seq_no"
270
 
    )
 
267
    worksheets = ReferenceSet(id, 'Worksheet.offering_id')
271
268
 
272
269
    __init__ = _kwarg_init
273
270
 
275
272
        return "<%s %r in %r>" % (type(self).__name__, self.subject,
276
273
                                  self.semester)
277
274
 
278
 
    def enrol(self, user, role=u'student'):
 
275
    def enrol(self, user):
279
276
        '''Enrol a user in this offering.'''
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)
 
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)
297
286
 
298
287
    def get_permissions(self, user):
299
288
        perms = set()
300
289
        if user is not None:
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:
 
290
            perms.add('view')
 
291
            if user.rolenm == 'admin':
306
292
                perms.add('edit')
307
293
        return perms
308
294
 
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
 
 
317
295
class Enrolment(Storm):
318
296
    __storm_table__ = "enrolment"
319
297
    __storm_primary__ = "user_id", "offering_id"
322
300
    user = Reference(user_id, User.id)
323
301
    offering_id = Int(name="offeringid")
324
302
    offering = Reference(offering_id, Offering.id)
325
 
    role = Unicode()
326
303
    notes = Unicode()
327
304
    active = Bool()
328
305
 
340
317
        return "<%s %r in %r>" % (type(self).__name__, self.user,
341
318
                                  self.offering)
342
319
 
 
320
class AlreadyEnrolledError(Exception):
 
321
    pass
 
322
 
343
323
# PROJECTS #
344
324
 
345
325
class ProjectSet(Storm):
416
396
# WORKSHEETS AND EXERCISES #
417
397
 
418
398
class Exercise(Storm):
419
 
    __storm_table__ = "exercise"
 
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
420
403
    id = Unicode(primary=True, name="identifier")
421
404
    name = Unicode()
422
405
    description = Unicode()
425
408
    include = Unicode()
426
409
    num_rows = Int()
427
410
 
428
 
    worksheet_exercises =  ReferenceSet(id,
429
 
        'WorksheetExercise.exercise_id')
430
 
 
431
411
    worksheets = ReferenceSet(id,
432
412
        'WorksheetExercise.exercise_id',
433
413
        'WorksheetExercise.worksheet_id',
434
414
        'Worksheet.id'
435
415
    )
436
416
    
437
 
    test_suites = ReferenceSet(id, 
438
 
        'TestSuite.exercise_id',
439
 
        order_by='seq_no')
 
417
    test_suites = ReferenceSet(id, 'TestSuite.exercise_id')
440
418
 
441
419
    __init__ = _kwarg_init
442
420
 
443
421
    def __repr__(self):
444
422
        return "<%s %s>" % (type(self).__name__, self.name)
445
423
 
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)
469
424
 
470
425
class Worksheet(Storm):
471
426
    __storm_table__ = "worksheet"
472
427
 
473
428
    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.
474
431
    offering_id = Int(name="offeringid")
475
 
    identifier = Unicode()
476
 
    name = Unicode()
 
432
    name = Unicode(name="identifier")
477
433
    assessable = Bool()
478
 
    data = Unicode()
479
 
    seq_no = Int()
480
 
    format = Unicode()
 
434
    mtime = DateTime()
481
435
 
482
436
    attempts = ReferenceSet(id, "ExerciseAttempt.worksheetid")
483
437
    offering = Reference(offering_id, 'Offering.id')
484
438
 
485
 
    all_worksheet_exercises = ReferenceSet(id,
 
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
 
445
    # "optional" field.
 
446
    worksheet_exercises = ReferenceSet(id,
486
447
        '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
490
 
    # "optional" field.
491
 
 
492
 
    @property
493
 
    def worksheet_exercises(self):
494
 
        return self.all_worksheet_exercises.find(active=True)
 
448
        
495
449
 
496
450
    __init__ = _kwarg_init
497
451
 
510
464
        return store.find(cls, cls.subject == unicode(subjectname),
511
465
            cls.name == unicode(worksheetname)).one()
512
466
 
513
 
    def remove_all_exercises(self):
 
467
    def remove_all_exercises(self, store):
514
468
        """
515
469
        Remove all exercises from this worksheet.
516
470
        This does not delete the exercises themselves. It just removes them
517
471
        from the worksheet.
518
472
        """
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()
523
473
        store.find(WorksheetExercise,
524
474
            WorksheetExercise.worksheet == self).remove()
525
475
            
526
476
    def get_permissions(self, user):
527
477
        return self.offering.get_permissions(user)
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
 
        
 
478
 
549
479
class WorksheetExercise(Storm):
550
 
    __storm_table__ = "worksheet_exercise"
551
 
    
552
 
    id = Int(primary=True, name="ws_ex_id")
 
480
    __storm_table__ = "worksheet_problem"
 
481
    __storm_primary__ = "worksheet_id", "exercise_id"
553
482
 
554
483
    worksheet_id = Int(name="worksheetid")
555
484
    worksheet = Reference(worksheet_id, Worksheet.id)
556
 
    exercise_id = Unicode(name="exerciseid")
 
485
    exercise_id = Unicode(name="problemid")
557
486
    exercise = Reference(exercise_id, Exercise.id)
558
487
    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")
564
488
 
565
489
    __init__ = _kwarg_init
566
490
 
567
491
    def __repr__(self):
568
492
        return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
569
 
                                  self.worksheet.identifier)
570
 
 
571
 
    def get_permissions(self, user):
572
 
        return self.worksheet.get_permissions(user)
573
 
    
 
493
                                  self.worksheet.name)
574
494
 
575
495
class ExerciseSave(Storm):
576
496
    """
581
501
    ExerciseSave may be extended with additional semantics (such as
582
502
    ExerciseAttempt).
583
503
    """
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
 
 
 
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)
590
509
    user_id = Int(name="loginid")
591
510
    user = Reference(user_id, User.id)
592
511
    date = DateTime()
593
512
    text = Unicode()
 
513
    worksheetid = Int()
 
514
    worksheet = Reference(worksheetid, Worksheet.id)
594
515
 
595
516
    __init__ = _kwarg_init
596
517
 
611
532
        they won't count (either as a penalty or success), but will still be
612
533
        stored.
613
534
    """
614
 
    __storm_table__ = "exercise_attempt"
615
 
    __storm_primary__ = "ws_ex_id", "user_id", "date"
 
535
    __storm_table__ = "problem_attempt"
 
536
    __storm_primary__ = "exercise_id", "user_id", "date"
616
537
 
617
538
    # The "text" field is the same but has a different name in the DB table
618
539
    # for some reason.
629
550
    __storm_primary__ = "exercise_id", "suiteid"
630
551
    
631
552
    suiteid = Int()
632
 
    exercise_id = Unicode(name="exerciseid")
 
553
    exercise_id = Unicode(name="problemid")
633
554
    description = Unicode()
634
555
    seq_no = Int()
635
556
    function = Unicode()
636
557
    stdin = Unicode()
637
558
    exercise = Reference(exercise_id, Exercise.id)
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)
 
559
    test_cases = ReferenceSet(suiteid, 'TestCase.suiteid')
 
560
    variables = ReferenceSet(suiteid, 'TestSuiteVar.suiteid')
648
561
 
649
562
class TestCase(Storm):
650
563
    """A TestCase is a member of a TestSuite.
664
577
    parts = ReferenceSet(testid, "TestCasePart.testid")
665
578
    
666
579
    __init__ = _kwarg_init
667
 
    
668
 
    def delete(self):
669
 
        for part in self.parts:
670
 
            part.delete()
671
 
        Store.of(self).remove(self)
672
580
 
673
581
class TestSuiteVar(Storm):
674
582
    """A container for the arguments of a Test Suite"""
675
 
    __storm_table__ = "suite_variable"
 
583
    __storm_table__ = "suite_variables"
676
584
    __storm_primary__ = "varid"
677
585
    
678
586
    varid = Int()
686
594
    
687
595
    __init__ = _kwarg_init
688
596
    
689
 
    def delete(self):
690
 
        Store.of(self).remove(self)
691
 
    
692
597
class TestCasePart(Storm):
693
598
    """A container for the test elements of a Test Case"""
694
 
    __storm_table__ = "test_case_part"
 
599
    __storm_table__ = "test_case_parts"
695
600
    __storm_primary__ = "partid"
696
601
    
697
602
    partid = Int()
705
610
    test = Reference(testid, "TestCase.testid")
706
611
    
707
612
    __init__ = _kwarg_init
708
 
    
709
 
    def delete(self):
710
 
        Store.of(self).remove(self)