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

« back to all changes in this revision

Viewing changes to ivle/database.py

Start the console overlay minimised in the XHTML - previously the JavaScript
minimised it onload, so it appeared maximised for quite some fraction of a
second.

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'
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
126
130
        fieldval = self.acct_exp
127
131
        return fieldval is not None and datetime.datetime.now() > fieldval
128
132
 
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
 
 
205
199
# SUBJECTS AND ENROLMENTS #
206
200
 
207
201
class Subject(Storm):
220
214
    def __repr__(self):
221
215
        return "<%s '%s'>" % (type(self).__name__, self.short_name)
222
216
 
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
 
 
231
217
class Semester(Storm):
232
218
    __storm_table__ = "semester"
233
219
 
234
220
    id = Int(primary=True, name="semesterid")
235
221
    year = Unicode()
236
222
    semester = Unicode()
237
 
    state = Unicode()
 
223
    active = Bool()
238
224
 
239
225
    offerings = ReferenceSet(id, 'Offering.semester_id')
240
 
    enrolments = ReferenceSet(id,
241
 
                              'Offering.semester_id',
242
 
                              'Offering.id',
243
 
                              'Enrolment.offering_id')
244
226
 
245
227
    __init__ = _kwarg_init
246
228
 
264
246
                           'User.id')
265
247
    project_sets = ReferenceSet(id, 'ProjectSet.offering_id')
266
248
 
267
 
    worksheets = ReferenceSet(id, 
268
 
        'Worksheet.offering_id', 
269
 
        order_by="seq_no"
270
 
    )
271
 
 
272
249
    __init__ = _kwarg_init
273
250
 
274
251
    def __repr__(self):
275
252
        return "<%s %r in %r>" % (type(self).__name__, self.subject,
276
253
                                  self.semester)
277
254
 
278
 
    def enrol(self, user, role=u'student'):
 
255
    def enrol(self, user):
279
256
        '''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
 
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)
316
266
 
317
267
class Enrolment(Storm):
318
268
    __storm_table__ = "enrolment"
322
272
    user = Reference(user_id, User.id)
323
273
    offering_id = Int(name="offeringid")
324
274
    offering = Reference(offering_id, Offering.id)
325
 
    role = Unicode()
326
275
    notes = Unicode()
327
276
    active = Bool()
328
277
 
340
289
        return "<%s %r in %r>" % (type(self).__name__, self.user,
341
290
                                  self.offering)
342
291
 
 
292
class AlreadyEnrolledError(Exception):
 
293
    pass
 
294
 
343
295
# PROJECTS #
344
296
 
345
297
class ProjectSet(Storm):
416
368
# WORKSHEETS AND EXERCISES #
417
369
 
418
370
class Exercise(Storm):
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()
 
371
    # Note: Table "problem" is called "Exercise" in the Object layer, since
 
372
    # it's called that everywhere else.
 
373
    __storm_table__ = "problem"
427
374
 
428
 
    worksheet_exercises =  ReferenceSet(id,
429
 
        'WorksheetExercise.exercise_id')
 
375
    id = Int(primary=True, name="problemid")
 
376
    name = Unicode(name="identifier")
 
377
    spec = Unicode()
430
378
 
431
379
    worksheets = ReferenceSet(id,
432
380
        'WorksheetExercise.exercise_id',
433
381
        'WorksheetExercise.worksheet_id',
434
382
        'Worksheet.id'
435
383
    )
436
 
    
437
 
    test_suites = ReferenceSet(id, 
438
 
        'TestSuite.exercise_id',
439
 
        order_by='seq_no')
440
384
 
441
385
    __init__ = _kwarg_init
442
386
 
443
387
    def __repr__(self):
444
388
        return "<%s %s>" % (type(self).__name__, self.name)
445
389
 
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)
 
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
469
404
 
470
405
class Worksheet(Storm):
471
406
    __storm_table__ = "worksheet"
472
407
 
473
408
    id = Int(primary=True, name="worksheetid")
474
 
    offering_id = Int(name="offeringid")
475
 
    identifier = Unicode()
476
 
    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")
477
413
    assessable = Bool()
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,
 
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,
486
424
        'WorksheetExercise.worksheet_id')
487
425
 
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)
495
 
 
496
426
    __init__ = _kwarg_init
497
427
 
498
428
    def __repr__(self):
510
440
        return store.find(cls, cls.subject == unicode(subjectname),
511
441
            cls.name == unicode(worksheetname)).one()
512
442
 
513
 
    def remove_all_exercises(self):
 
443
    def remove_all_exercises(self, store):
514
444
        """
515
445
        Remove all exercises from this worksheet.
516
446
        This does not delete the exercises themselves. It just removes them
517
447
        from the worksheet.
518
448
        """
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
449
        store.find(WorksheetExercise,
524
450
            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
 
        
 
451
 
549
452
class WorksheetExercise(Storm):
550
 
    __storm_table__ = "worksheet_exercise"
551
 
    
552
 
    id = Int(primary=True, name="ws_ex_id")
 
453
    __storm_table__ = "worksheet_problem"
 
454
    __storm_primary__ = "worksheet_id", "exercise_id"
553
455
 
554
456
    worksheet_id = Int(name="worksheetid")
555
457
    worksheet = Reference(worksheet_id, Worksheet.id)
556
 
    exercise_id = Unicode(name="exerciseid")
 
458
    exercise_id = Int(name="problemid")
557
459
    exercise = Reference(exercise_id, Exercise.id)
558
460
    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
461
 
565
462
    __init__ = _kwarg_init
566
463
 
567
464
    def __repr__(self):
568
465
        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
 
    
 
466
                                  self.worksheet.name)
574
467
 
575
468
class ExerciseSave(Storm):
576
469
    """
581
474
    ExerciseSave may be extended with additional semantics (such as
582
475
    ExerciseAttempt).
583
476
    """
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
 
 
 
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)
590
482
    user_id = Int(name="loginid")
591
483
    user = Reference(user_id, User.id)
592
484
    date = DateTime()
611
503
        they won't count (either as a penalty or success), but will still be
612
504
        stored.
613
505
    """
614
 
    __storm_table__ = "exercise_attempt"
615
 
    __storm_primary__ = "ws_ex_id", "user_id", "date"
 
506
    __storm_table__ = "problem_attempt"
 
507
    __storm_primary__ = "exercise_id", "user_id", "date"
616
508
 
617
509
    # The "text" field is the same but has a different name in the DB table
618
510
    # for some reason.
619
511
    text = Unicode(name="attempt")
620
512
    complete = Bool()
621
513
    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)