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

« back to all changes in this revision

Viewing changes to ivle/database.py

  • Committer: William Grant
  • Date: 2009-02-17 00:01:10 UTC
  • mto: (1099.1.143 new-dispatch)
  • mto: This revision was merged to the branch mainline in revision 1100.
  • Revision ID: grantw@unimelb.edu.au-20090217000110-mysp1vl5n29uwm2v
Change serveservice's interface - it now uses a JSON-based non-CGI one.
This breaks serving things.

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'
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:
228
 
                perms.add('edit')
 
231
        if user.rolenm == 'admin':
 
232
            perms.add('edit')
229
233
        return perms
230
234
 
231
235
class Semester(Storm):
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)
297
 
 
298
 
    def get_permissions(self, user):
299
 
        perms = set()
300
 
        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:
306
 
                perms.add('edit')
307
 
        return perms
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
 
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)
316
286
 
317
287
class Enrolment(Storm):
318
288
    __storm_table__ = "enrolment"
322
292
    user = Reference(user_id, User.id)
323
293
    offering_id = Int(name="offeringid")
324
294
    offering = Reference(offering_id, Offering.id)
325
 
    role = Unicode()
326
295
    notes = Unicode()
327
296
    active = Bool()
328
297
 
340
309
        return "<%s %r in %r>" % (type(self).__name__, self.user,
341
310
                                  self.offering)
342
311
 
 
312
class AlreadyEnrolledError(Exception):
 
313
    pass
 
314
 
343
315
# PROJECTS #
344
316
 
345
317
class ProjectSet(Storm):
416
388
# WORKSHEETS AND EXERCISES #
417
389
 
418
390
class Exercise(Storm):
419
 
    __storm_table__ = "exercise"
 
391
    # Note: Table "problem" is called "Exercise" in the Object layer, since
 
392
    # it's called that everywhere else.
 
393
    __storm_table__ = "problem"
 
394
#TODO: Add in a field for the user-friendly identifier
420
395
    id = Unicode(primary=True, name="identifier")
421
396
    name = Unicode()
422
397
    description = Unicode()
425
400
    include = Unicode()
426
401
    num_rows = Int()
427
402
 
428
 
    worksheet_exercises =  ReferenceSet(id,
429
 
        'WorksheetExercise.exercise_id')
430
 
 
431
403
    worksheets = ReferenceSet(id,
432
404
        'WorksheetExercise.exercise_id',
433
405
        'WorksheetExercise.worksheet_id',
434
406
        'Worksheet.id'
435
407
    )
436
408
    
437
 
    test_suites = ReferenceSet(id, 
438
 
        'TestSuite.exercise_id',
439
 
        order_by='seq_no')
 
409
    test_suites = ReferenceSet(id, 'TestSuite.exercise_id')
440
410
 
441
411
    __init__ = _kwarg_init
442
412
 
443
413
    def __repr__(self):
444
414
        return "<%s %s>" % (type(self).__name__, self.name)
445
415
 
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)
 
416
    @classmethod
 
417
    def get_by_name(cls, store, name):
 
418
        """
 
419
        Get the Exercise from the db associated with a given store and name.
 
420
        If the exercise is not in the database, creates it and inserts it
 
421
        automatically.
 
422
        """
 
423
        ex = store.find(cls, cls.name == unicode(name)).one()
 
424
        if ex is not None:
 
425
            return ex
 
426
        ex = Exercise(name=unicode(name))
 
427
        store.add(ex)
 
428
        store.commit()
 
429
        return ex
469
430
 
470
431
class Worksheet(Storm):
471
432
    __storm_table__ = "worksheet"
472
433
 
473
434
    id = Int(primary=True, name="worksheetid")
 
435
    # XXX subject is not linked to a Subject object. This is a property of
 
436
    # the database, and will be refactored.
 
437
    subject = Unicode()
474
438
    offering_id = Int(name="offeringid")
475
 
    identifier = Unicode()
476
 
    name = Unicode()
 
439
    name = Unicode(name="identifier")
477
440
    assessable = Bool()
478
 
    data = Unicode()
479
 
    seq_no = Int()
480
 
    format = Unicode()
 
441
    mtime = DateTime()
481
442
 
482
 
    attempts = ReferenceSet(id, "ExerciseAttempt.worksheetid")
483
443
    offering = Reference(offering_id, 'Offering.id')
484
444
 
485
 
    all_worksheet_exercises = ReferenceSet(id,
 
445
    exercises = ReferenceSet(id,
 
446
        'WorksheetExercise.worksheet_id',
 
447
        'WorksheetExercise.exercise_id',
 
448
        Exercise.id)
 
449
    # Use worksheet_exercises to get access to the WorksheetExercise objects
 
450
    # binding worksheets to exercises. This is required to access the
 
451
    # "optional" field.
 
452
    worksheet_exercises = ReferenceSet(id,
486
453
        '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)
 
454
        
495
455
 
496
456
    __init__ = _kwarg_init
497
457
 
510
470
        return store.find(cls, cls.subject == unicode(subjectname),
511
471
            cls.name == unicode(worksheetname)).one()
512
472
 
513
 
    def remove_all_exercises(self):
 
473
    def remove_all_exercises(self, store):
514
474
        """
515
475
        Remove all exercises from this worksheet.
516
476
        This does not delete the exercises themselves. It just removes them
517
477
        from the worksheet.
518
478
        """
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
479
        store.find(WorksheetExercise,
524
480
            WorksheetExercise.worksheet == self).remove()
525
 
            
526
 
    def get_permissions(self, user):
527
 
        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
 
        
 
481
 
549
482
class WorksheetExercise(Storm):
550
 
    __storm_table__ = "worksheet_exercise"
551
 
    
552
 
    id = Int(primary=True, name="ws_ex_id")
 
483
    __storm_table__ = "worksheet_problem"
 
484
    __storm_primary__ = "worksheet_id", "exercise_id"
553
485
 
554
486
    worksheet_id = Int(name="worksheetid")
555
487
    worksheet = Reference(worksheet_id, Worksheet.id)
556
 
    exercise_id = Unicode(name="exerciseid")
 
488
    exercise_id = Unicode(name="problemid")
557
489
    exercise = Reference(exercise_id, Exercise.id)
558
490
    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
491
 
565
492
    __init__ = _kwarg_init
566
493
 
567
494
    def __repr__(self):
568
495
        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
 
    
 
496
                                  self.worksheet.name)
574
497
 
575
498
class ExerciseSave(Storm):
576
499
    """
581
504
    ExerciseSave may be extended with additional semantics (such as
582
505
    ExerciseAttempt).
583
506
    """
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
 
 
 
507
    __storm_table__ = "problem_save"
 
508
    __storm_primary__ = "exercise_id", "user_id", "date"
 
509
 
 
510
    exercise_id = Unicode(name="problemid")
 
511
    exercise = Reference(exercise_id, Exercise.id)
590
512
    user_id = Int(name="loginid")
591
513
    user = Reference(user_id, User.id)
592
514
    date = DateTime()
593
515
    text = Unicode()
 
516
    worksheetid = Int()
 
517
    worksheet = Reference(worksheetid, Worksheet.id)
594
518
 
595
519
    __init__ = _kwarg_init
596
520
 
611
535
        they won't count (either as a penalty or success), but will still be
612
536
        stored.
613
537
    """
614
 
    __storm_table__ = "exercise_attempt"
615
 
    __storm_primary__ = "ws_ex_id", "user_id", "date"
 
538
    __storm_table__ = "problem_attempt"
 
539
    __storm_primary__ = "exercise_id", "user_id", "date"
616
540
 
617
541
    # The "text" field is the same but has a different name in the DB table
618
542
    # for some reason.
629
553
    __storm_primary__ = "exercise_id", "suiteid"
630
554
    
631
555
    suiteid = Int()
632
 
    exercise_id = Unicode(name="exerciseid")
 
556
    exercise_id = Unicode(name="problemid")
 
557
    exercise = Reference(exercise_id, Exercise.id)
 
558
    test_cases = ReferenceSet(suiteid, 'TestCase.suiteid')
633
559
    description = Unicode()
634
560
    seq_no = Int()
635
 
    function = Unicode()
636
 
    stdin = Unicode()
637
 
    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)
648
561
 
649
562
class TestCase(Storm):
650
563
    """A TestCase is a member of a TestSuite.
655
568
    
656
569
    testid = Int()
657
570
    suiteid = Int()
658
 
    suite = Reference(suiteid, "TestSuite.suiteid")
 
571
    suite = Reference(suiteid, TestSuite.suiteid)
659
572
    passmsg = Unicode()
660
573
    failmsg = Unicode()
661
 
    test_default = Unicode()
 
574
    init = Unicode()
 
575
    code_type = Unicode()
 
576
    code = Unicode()
 
577
    testtype = Unicode()
662
578
    seq_no = Int()
663
579
    
664
 
    parts = ReferenceSet(testid, "TestCasePart.testid")
665
 
    
666
 
    __init__ = _kwarg_init
667
 
    
668
 
    def delete(self):
669
 
        for part in self.parts:
670
 
            part.delete()
671
 
        Store.of(self).remove(self)
672
 
 
673
 
class TestSuiteVar(Storm):
674
 
    """A container for the arguments of a Test Suite"""
675
 
    __storm_table__ = "suite_variable"
676
 
    __storm_primary__ = "varid"
677
 
    
678
 
    varid = Int()
679
 
    suiteid = Int()
680
 
    var_name = Unicode()
681
 
    var_value = Unicode()
682
 
    var_type = Unicode()
683
 
    arg_no = Int()
684
 
    
685
 
    suite = Reference(suiteid, "TestSuite.suiteid")
686
 
    
687
 
    __init__ = _kwarg_init
688
 
    
689
 
    def delete(self):
690
 
        Store.of(self).remove(self)
691
 
    
692
 
class TestCasePart(Storm):
693
 
    """A container for the test elements of a Test Case"""
694
 
    __storm_table__ = "test_case_part"
695
 
    __storm_primary__ = "partid"
696
 
    
697
 
    partid = Int()
698
 
    testid = Int()
699
 
    
700
 
    part_type = Unicode()
701
 
    test_type = Unicode()
702
 
    data = Unicode()
703
 
    filename = Unicode()
704
 
    
705
 
    test = Reference(testid, "TestCase.testid")
706
 
    
707
 
    __init__ = _kwarg_init
708
 
    
709
 
    def delete(self):
710
 
        Store.of(self).remove(self)
 
580
    __init__ = _kwarg_init