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

« back to all changes in this revision

Viewing changes to ivle/database.py

  • Committer: William Grant
  • Date: 2009-03-17 07:12:11 UTC
  • Revision ID: grantw@unimelb.edu.au-20090317071211-641qkzx7nofnkpgy
Stream files from serveservice, rather than reading the whole file in at once.

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'
 
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
130
126
        fieldval = self.acct_exp
131
127
        return fieldval is not None and datetime.datetime.now() > fieldval
132
128
 
 
129
    @property
 
130
    def valid(self):
 
131
        return self.state == 'enabled' and not self.account_expired
 
132
 
133
133
    def _get_enrolments(self, justactive):
134
134
        return Store.of(self).find(Enrolment,
135
135
            Enrolment.user_id == self.id,
196
196
        """
197
197
        return store.find(cls, cls.login == unicode(login)).one()
198
198
 
 
199
    def get_permissions(self, user):
 
200
        if user and user.admin or user is self:
 
201
            return set(['view', 'edit'])
 
202
        else:
 
203
            return set()
 
204
 
199
205
# SUBJECTS AND ENROLMENTS #
200
206
 
201
207
class Subject(Storm):
214
220
    def __repr__(self):
215
221
        return "<%s '%s'>" % (type(self).__name__, self.short_name)
216
222
 
 
223
    def get_permissions(self, user):
 
224
        perms = set()
 
225
        if user is not None:
 
226
            perms.add('view')
 
227
            if user.admin:
 
228
                perms.add('edit')
 
229
        return perms
 
230
 
217
231
class Semester(Storm):
218
232
    __storm_table__ = "semester"
219
233
 
220
234
    id = Int(primary=True, name="semesterid")
221
235
    year = Unicode()
222
236
    semester = Unicode()
223
 
    active = Bool()
 
237
    state = Unicode()
224
238
 
225
239
    offerings = ReferenceSet(id, 'Offering.semester_id')
 
240
    enrolments = ReferenceSet(id,
 
241
                              'Offering.semester_id',
 
242
                              'Offering.id',
 
243
                              'Enrolment.offering_id')
226
244
 
227
245
    __init__ = _kwarg_init
228
246
 
246
264
                           'User.id')
247
265
    project_sets = ReferenceSet(id, 'ProjectSet.offering_id')
248
266
 
 
267
    worksheets = ReferenceSet(id, 
 
268
        'Worksheet.offering_id', 
 
269
        order_by="seq_no"
 
270
    )
 
271
 
249
272
    __init__ = _kwarg_init
250
273
 
251
274
    def __repr__(self):
252
275
        return "<%s %r in %r>" % (type(self).__name__, self.subject,
253
276
                                  self.semester)
254
277
 
255
 
    def enrol(self, user):
 
278
    def enrol(self, user, role=u'student'):
256
279
        '''Enrol a user in this offering.'''
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,
260
 
                               Enrolment.user_id == user.id,
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)
 
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
266
316
 
267
317
class Enrolment(Storm):
268
318
    __storm_table__ = "enrolment"
272
322
    user = Reference(user_id, User.id)
273
323
    offering_id = Int(name="offeringid")
274
324
    offering = Reference(offering_id, Offering.id)
 
325
    role = Unicode()
275
326
    notes = Unicode()
276
327
    active = Bool()
277
328
 
289
340
        return "<%s %r in %r>" % (type(self).__name__, self.user,
290
341
                                  self.offering)
291
342
 
292
 
class AlreadyEnrolledError(Exception):
293
 
    pass
294
 
 
295
343
# PROJECTS #
296
344
 
297
345
class ProjectSet(Storm):
368
416
# WORKSHEETS AND EXERCISES #
369
417
 
370
418
class Exercise(Storm):
371
 
    # Note: Table "problem" is called "Exercise" in the Object layer, since
372
 
    # it's called that everywhere else.
373
 
    __storm_table__ = "problem"
 
419
    __storm_table__ = "exercise"
 
420
    id = Unicode(primary=True, name="identifier")
 
421
    name = Unicode()
 
422
    description = Unicode()
 
423
    partial = Unicode()
 
424
    solution = Unicode()
 
425
    include = Unicode()
 
426
    num_rows = Int()
374
427
 
375
 
    id = Int(primary=True, name="problemid")
376
 
    name = Unicode(name="identifier")
377
 
    spec = Unicode()
 
428
    worksheet_exercises =  ReferenceSet(id,
 
429
        'WorksheetExercise.exercise_id')
378
430
 
379
431
    worksheets = ReferenceSet(id,
380
432
        'WorksheetExercise.exercise_id',
381
433
        'WorksheetExercise.worksheet_id',
382
434
        'Worksheet.id'
383
435
    )
 
436
    
 
437
    test_suites = ReferenceSet(id, 
 
438
        'TestSuite.exercise_id',
 
439
        order_by='seq_no')
384
440
 
385
441
    __init__ = _kwarg_init
386
442
 
387
443
    def __repr__(self):
388
444
        return "<%s %s>" % (type(self).__name__, self.name)
389
445
 
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
 
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)
404
469
 
405
470
class Worksheet(Storm):
406
471
    __storm_table__ = "worksheet"
407
472
 
408
473
    id = Int(primary=True, name="worksheetid")
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")
 
474
    offering_id = Int(name="offeringid")
 
475
    identifier = Unicode()
 
476
    name = Unicode()
413
477
    assessable = Bool()
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
 
478
    data = Unicode()
 
479
    seq_no = Int()
 
480
    format = Unicode()
 
481
 
 
482
    attempts = ReferenceSet(id, "ExerciseAttempt.worksheetid")
 
483
    offering = Reference(offering_id, 'Offering.id')
 
484
 
 
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
422
490
    # "optional" field.
423
 
    worksheet_exercises = ReferenceSet(id,
424
 
        'WorksheetExercise.worksheet_id')
 
491
 
 
492
    @property
 
493
    def worksheet_exercises(self):
 
494
        return self.all_worksheet_exercises.find(active=True)
425
495
 
426
496
    __init__ = _kwarg_init
427
497
 
440
510
        return store.find(cls, cls.subject == unicode(subjectname),
441
511
            cls.name == unicode(worksheetname)).one()
442
512
 
443
 
    def remove_all_exercises(self, store):
 
513
    def remove_all_exercises(self):
444
514
        """
445
515
        Remove all exercises from this worksheet.
446
516
        This does not delete the exercises themselves. It just removes them
447
517
        from the worksheet.
448
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()
449
523
        store.find(WorksheetExercise,
450
524
            WorksheetExercise.worksheet == self).remove()
451
 
 
 
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
        
452
549
class WorksheetExercise(Storm):
453
 
    __storm_table__ = "worksheet_problem"
454
 
    __storm_primary__ = "worksheet_id", "exercise_id"
 
550
    __storm_table__ = "worksheet_exercise"
 
551
    
 
552
    id = Int(primary=True, name="ws_ex_id")
455
553
 
456
554
    worksheet_id = Int(name="worksheetid")
457
555
    worksheet = Reference(worksheet_id, Worksheet.id)
458
 
    exercise_id = Int(name="problemid")
 
556
    exercise_id = Unicode(name="exerciseid")
459
557
    exercise = Reference(exercise_id, Exercise.id)
460
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")
461
564
 
462
565
    __init__ = _kwarg_init
463
566
 
464
567
    def __repr__(self):
465
568
        return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
466
 
                                  self.worksheet.name)
 
569
                                  self.worksheet.identifier)
 
570
 
 
571
    def get_permissions(self, user):
 
572
        return self.worksheet.get_permissions(user)
 
573
    
467
574
 
468
575
class ExerciseSave(Storm):
469
576
    """
474
581
    ExerciseSave may be extended with additional semantics (such as
475
582
    ExerciseAttempt).
476
583
    """
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)
 
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
 
482
590
    user_id = Int(name="loginid")
483
591
    user = Reference(user_id, User.id)
484
592
    date = DateTime()
503
611
        they won't count (either as a penalty or success), but will still be
504
612
        stored.
505
613
    """
506
 
    __storm_table__ = "problem_attempt"
507
 
    __storm_primary__ = "exercise_id", "user_id", "date"
 
614
    __storm_table__ = "exercise_attempt"
 
615
    __storm_primary__ = "ws_ex_id", "user_id", "date"
508
616
 
509
617
    # The "text" field is the same but has a different name in the DB table
510
618
    # for some reason.
511
619
    text = Unicode(name="attempt")
512
620
    complete = Bool()
513
621
    active = Bool()
 
622
    
 
623
    def get_permissions(self, user):
 
624
        return set(['view']) if user is self.user else set()
 
625
  
 
626
class TestSuite(Storm):
 
627
    """A Testsuite acts as a container for the test cases of an exercise."""
 
628
    __storm_table__ = "test_suite"
 
629
    __storm_primary__ = "exercise_id", "suiteid"
 
630
    
 
631
    suiteid = Int()
 
632
    exercise_id = Unicode(name="exerciseid")
 
633
    description = Unicode()
 
634
    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
 
 
649
class TestCase(Storm):
 
650
    """A TestCase is a member of a TestSuite.
 
651
    
 
652
    It contains the data necessary to check if an exercise is correct"""
 
653
    __storm_table__ = "test_case"
 
654
    __storm_primary__ = "testid", "suiteid"
 
655
    
 
656
    testid = Int()
 
657
    suiteid = Int()
 
658
    suite = Reference(suiteid, "TestSuite.suiteid")
 
659
    passmsg = Unicode()
 
660
    failmsg = Unicode()
 
661
    test_default = Unicode()
 
662
    seq_no = Int()
 
663
    
 
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)