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

« back to all changes in this revision

Viewing changes to ivle/database.py

  • Committer: me at id
  • Date: 2009-01-15 01:18:00 UTC
  • mto: This revision was merged to the branch mainline in revision 1090.
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:branches%2Fstorm:1145
Give ivle.database.User {password,account}_expired attributes, and get
ivle.dispatch.login to use them.

Show diffs side-by-side

added added

removed removed

Lines of Context:
28
28
import datetime
29
29
 
30
30
from storm.locals import create_database, Store, Int, Unicode, DateTime, \
31
 
                         Reference, ReferenceSet, Bool, Storm, Desc
 
31
                         Reference
32
32
 
33
33
import ivle.conf
34
34
import ivle.caps
35
35
 
36
 
__all__ = ['get_store',
37
 
            'User',
38
 
            'Subject', 'Semester', 'Offering', 'Enrolment',
39
 
            'ProjectSet', 'Project', 'ProjectGroup', 'ProjectGroupMembership',
40
 
            'Exercise', 'Worksheet', 'WorksheetExercise',
41
 
            'ExerciseSave', 'ExerciseAttempt',
42
 
            'AlreadyEnrolledError', 'TestCase', 'TestSuite'
43
 
        ]
44
 
 
45
 
def _kwarg_init(self, **kwargs):
46
 
    for k,v in kwargs.items():
47
 
        if k.startswith('_') or not hasattr(self.__class__, k):
48
 
            raise TypeError("%s got an unexpected keyword argument '%s'"
49
 
                % (self.__class__.__name__, k))
50
 
        setattr(self, k, v)
51
 
 
52
36
def get_conn_string():
53
37
    """
54
38
    Returns the Storm connection string, generated from the conf file.
64
48
    """
65
49
    return Store(create_database(get_conn_string()))
66
50
 
67
 
# USERS #
68
 
 
69
 
class User(Storm):
 
51
class User(object):
70
52
    """
71
53
    Represents an IVLE user.
72
54
    """
98
80
        self.rolenm = unicode(value)
99
81
    role = property(_get_role, _set_role)
100
82
 
101
 
    __init__ = _kwarg_init
 
83
    def __init__(self, **kwargs):
 
84
        """
 
85
        Create a new User object. Supply any columns as a keyword argument.
 
86
        """
 
87
        for k,v in kwargs.items():
 
88
            if k.startswith('_') or not hasattr(self, k):
 
89
                raise TypeError("User got an unexpected keyword argument '%s'"
 
90
                    % k)
 
91
            setattr(self, k, v)
102
92
 
103
93
    def __repr__(self):
104
94
        return "<%s '%s'>" % (type(self).__name__, self.login)
130
120
        fieldval = self.acct_exp
131
121
        return fieldval is not None and datetime.datetime.now() > fieldval
132
122
 
133
 
    @property
134
 
    def valid(self):
135
 
        return self.state == 'enabled' and not self.account_expired
136
 
 
137
 
    def _get_enrolments(self, justactive):
138
 
        return Store.of(self).find(Enrolment,
139
 
            Enrolment.user_id == self.id,
140
 
            (Enrolment.active == True) if justactive else True,
141
 
            Enrolment.offering_id == Offering.id,
142
 
            Offering.semester_id == Semester.id,
143
 
            Offering.subject_id == Subject.id).order_by(
144
 
                Desc(Semester.year),
145
 
                Desc(Semester.semester),
146
 
                Desc(Subject.code)
147
 
            )
148
 
 
149
 
    def _set_password(self, password):
150
 
        if password is None:
151
 
            self.passhash = None
152
 
        else:
153
 
            self.passhash = unicode(User.hash_password(password))
154
 
    password = property(fset=_set_password)
155
 
 
156
 
    @property
157
 
    def subjects(self):
158
 
        return Store.of(self).find(Subject,
159
 
            Enrolment.user_id == self.id,
160
 
            Enrolment.active == True,
161
 
            Offering.id == Enrolment.offering_id,
162
 
            Subject.id == Offering.subject_id).config(distinct=True)
163
 
 
164
 
    # TODO: Invitations should be listed too?
165
 
    def get_groups(self, offering=None):
166
 
        preds = [
167
 
            ProjectGroupMembership.user_id == self.id,
168
 
            ProjectGroup.id == ProjectGroupMembership.project_group_id,
169
 
        ]
170
 
        if offering:
171
 
            preds.extend([
172
 
                ProjectSet.offering_id == offering.id,
173
 
                ProjectGroup.project_set_id == ProjectSet.id,
174
 
            ])
175
 
        return Store.of(self).find(ProjectGroup, *preds)
176
 
 
177
 
    @property
178
 
    def groups(self):
179
 
        return self.get_groups()
180
 
 
181
 
    @property
182
 
    def active_enrolments(self):
183
 
        '''A sanely ordered list of the user's active enrolments.'''
184
 
        return self._get_enrolments(True)
185
 
 
186
 
    @property
187
 
    def enrolments(self):
188
 
        '''A sanely ordered list of all of the user's enrolments.'''
189
 
        return self._get_enrolments(False) 
190
 
 
191
123
    @staticmethod
192
124
    def hash_password(password):
193
125
        return md5.md5(password).hexdigest()
199
131
        login.
200
132
        """
201
133
        return store.find(cls, cls.login == unicode(login)).one()
202
 
 
203
 
    def get_permissions(self, user):
204
 
        if user and user.rolenm == 'admin' or user is self:
205
 
            return set(['view', 'edit'])
206
 
        else:
207
 
            return set()
208
 
 
209
 
# SUBJECTS AND ENROLMENTS #
210
 
 
211
 
class Subject(Storm):
212
 
    __storm_table__ = "subject"
213
 
 
214
 
    id = Int(primary=True, name="subjectid")
215
 
    code = Unicode(name="subj_code")
216
 
    name = Unicode(name="subj_name")
217
 
    short_name = Unicode(name="subj_short_name")
218
 
    url = Unicode()
219
 
 
220
 
    offerings = ReferenceSet(id, 'Offering.subject_id')
221
 
 
222
 
    __init__ = _kwarg_init
223
 
 
224
 
    def __repr__(self):
225
 
        return "<%s '%s'>" % (type(self).__name__, self.short_name)
226
 
 
227
 
    def get_permissions(self, user):
228
 
        perms = set()
229
 
        if user is not None:
230
 
            perms.add('view')
231
 
        if user.rolenm == 'admin':
232
 
            perms.add('edit')
233
 
        return perms
234
 
 
235
 
class Semester(Storm):
236
 
    __storm_table__ = "semester"
237
 
 
238
 
    id = Int(primary=True, name="semesterid")
239
 
    year = Unicode()
240
 
    semester = Unicode()
241
 
    active = Bool()
242
 
 
243
 
    offerings = ReferenceSet(id, 'Offering.semester_id')
244
 
 
245
 
    __init__ = _kwarg_init
246
 
 
247
 
    def __repr__(self):
248
 
        return "<%s %s/%s>" % (type(self).__name__, self.year, self.semester)
249
 
 
250
 
class Offering(Storm):
251
 
    __storm_table__ = "offering"
252
 
 
253
 
    id = Int(primary=True, name="offeringid")
254
 
    subject_id = Int(name="subject")
255
 
    subject = Reference(subject_id, Subject.id)
256
 
    semester_id = Int(name="semesterid")
257
 
    semester = Reference(semester_id, Semester.id)
258
 
    groups_student_permissions = Unicode()
259
 
 
260
 
    enrolments = ReferenceSet(id, 'Enrolment.offering_id')
261
 
    members = ReferenceSet(id,
262
 
                           'Enrolment.offering_id',
263
 
                           'Enrolment.user_id',
264
 
                           'User.id')
265
 
    project_sets = ReferenceSet(id, 'ProjectSet.offering_id')
266
 
 
267
 
    worksheets = ReferenceSet(id, 'Worksheet.offering_id')
268
 
 
269
 
    __init__ = _kwarg_init
270
 
 
271
 
    def __repr__(self):
272
 
        return "<%s %r in %r>" % (type(self).__name__, self.subject,
273
 
                                  self.semester)
274
 
 
275
 
    def enrol(self, user):
276
 
        '''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)
286
 
 
287
 
class Enrolment(Storm):
288
 
    __storm_table__ = "enrolment"
289
 
    __storm_primary__ = "user_id", "offering_id"
290
 
 
291
 
    user_id = Int(name="loginid")
292
 
    user = Reference(user_id, User.id)
293
 
    offering_id = Int(name="offeringid")
294
 
    offering = Reference(offering_id, Offering.id)
295
 
    notes = Unicode()
296
 
    active = Bool()
297
 
 
298
 
    @property
299
 
    def groups(self):
300
 
        return Store.of(self).find(ProjectGroup,
301
 
                ProjectSet.offering_id == self.offering.id,
302
 
                ProjectGroup.project_set_id == ProjectSet.id,
303
 
                ProjectGroupMembership.project_group_id == ProjectGroup.id,
304
 
                ProjectGroupMembership.user_id == self.user.id)
305
 
 
306
 
    __init__ = _kwarg_init
307
 
 
308
 
    def __repr__(self):
309
 
        return "<%s %r in %r>" % (type(self).__name__, self.user,
310
 
                                  self.offering)
311
 
 
312
 
class AlreadyEnrolledError(Exception):
313
 
    pass
314
 
 
315
 
# PROJECTS #
316
 
 
317
 
class ProjectSet(Storm):
318
 
    __storm_table__ = "project_set"
319
 
 
320
 
    id = Int(name="projectsetid", primary=True)
321
 
    offering_id = Int(name="offeringid")
322
 
    offering = Reference(offering_id, Offering.id)
323
 
    max_students_per_group = Int()
324
 
 
325
 
    projects = ReferenceSet(id, 'Project.project_set_id')
326
 
    project_groups = ReferenceSet(id, 'ProjectGroup.project_set_id')
327
 
 
328
 
    __init__ = _kwarg_init
329
 
 
330
 
    def __repr__(self):
331
 
        return "<%s %d in %r>" % (type(self).__name__, self.id,
332
 
                                  self.offering)
333
 
 
334
 
class Project(Storm):
335
 
    __storm_table__ = "project"
336
 
 
337
 
    id = Int(name="projectid", primary=True)
338
 
    synopsis = Unicode()
339
 
    url = Unicode()
340
 
    project_set_id = Int(name="projectsetid")
341
 
    project_set = Reference(project_set_id, ProjectSet.id)
342
 
    deadline = DateTime()
343
 
 
344
 
    __init__ = _kwarg_init
345
 
 
346
 
    def __repr__(self):
347
 
        return "<%s '%s' in %r>" % (type(self).__name__, self.synopsis,
348
 
                                  self.project_set.offering)
349
 
 
350
 
class ProjectGroup(Storm):
351
 
    __storm_table__ = "project_group"
352
 
 
353
 
    id = Int(name="groupid", primary=True)
354
 
    name = Unicode(name="groupnm")
355
 
    project_set_id = Int(name="projectsetid")
356
 
    project_set = Reference(project_set_id, ProjectSet.id)
357
 
    nick = Unicode()
358
 
    created_by_id = Int(name="createdby")
359
 
    created_by = Reference(created_by_id, User.id)
360
 
    epoch = DateTime()
361
 
 
362
 
    members = ReferenceSet(id,
363
 
                           "ProjectGroupMembership.project_group_id",
364
 
                           "ProjectGroupMembership.user_id",
365
 
                           "User.id")
366
 
 
367
 
    __init__ = _kwarg_init
368
 
 
369
 
    def __repr__(self):
370
 
        return "<%s %s in %r>" % (type(self).__name__, self.name,
371
 
                                  self.project_set.offering)
372
 
 
373
 
class ProjectGroupMembership(Storm):
374
 
    __storm_table__ = "group_member"
375
 
    __storm_primary__ = "user_id", "project_group_id"
376
 
 
377
 
    user_id = Int(name="loginid")
378
 
    user = Reference(user_id, User.id)
379
 
    project_group_id = Int(name="groupid")
380
 
    project_group = Reference(project_group_id, ProjectGroup.id)
381
 
 
382
 
    __init__ = _kwarg_init
383
 
 
384
 
    def __repr__(self):
385
 
        return "<%s %r in %r>" % (type(self).__name__, self.user,
386
 
                                  self.project_group)
387
 
 
388
 
# WORKSHEETS AND EXERCISES #
389
 
 
390
 
class Exercise(Storm):
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
395
 
    id = Unicode(primary=True, name="identifier")
396
 
    name = Unicode()
397
 
    description = Unicode()
398
 
    partial = Unicode()
399
 
    solution = Unicode()
400
 
    include = Unicode()
401
 
    num_rows = Int()
402
 
 
403
 
    worksheets = ReferenceSet(id,
404
 
        'WorksheetExercise.exercise_id',
405
 
        'WorksheetExercise.worksheet_id',
406
 
        'Worksheet.id'
407
 
    )
408
 
    
409
 
    test_suites = ReferenceSet(id, 'TestSuite.exercise_id')
410
 
 
411
 
    __init__ = _kwarg_init
412
 
 
413
 
    def __repr__(self):
414
 
        return "<%s %s>" % (type(self).__name__, self.name)
415
 
 
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
430
 
 
431
 
class Worksheet(Storm):
432
 
    __storm_table__ = "worksheet"
433
 
 
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()
438
 
    offering_id = Int(name="offeringid")
439
 
    name = Unicode(name="identifier")
440
 
    assessable = Bool()
441
 
    mtime = DateTime()
442
 
 
443
 
    offering = Reference(offering_id, 'Offering.id')
444
 
 
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,
453
 
        'WorksheetExercise.worksheet_id')
454
 
        
455
 
 
456
 
    __init__ = _kwarg_init
457
 
 
458
 
    def __repr__(self):
459
 
        return "<%s %s>" % (type(self).__name__, self.name)
460
 
 
461
 
    # XXX Refactor this - make it an instance method of Subject rather than a
462
 
    # class method of Worksheet. Can't do that now because Subject isn't
463
 
    # linked referentially to the Worksheet.
464
 
    @classmethod
465
 
    def get_by_name(cls, store, subjectname, worksheetname):
466
 
        """
467
 
        Get the Worksheet from the db associated with a given store, subject
468
 
        name and worksheet name.
469
 
        """
470
 
        return store.find(cls, cls.subject == unicode(subjectname),
471
 
            cls.name == unicode(worksheetname)).one()
472
 
 
473
 
    def remove_all_exercises(self, store):
474
 
        """
475
 
        Remove all exercises from this worksheet.
476
 
        This does not delete the exercises themselves. It just removes them
477
 
        from the worksheet.
478
 
        """
479
 
        store.find(WorksheetExercise,
480
 
            WorksheetExercise.worksheet == self).remove()
481
 
 
482
 
class WorksheetExercise(Storm):
483
 
    __storm_table__ = "worksheet_problem"
484
 
    __storm_primary__ = "worksheet_id", "exercise_id"
485
 
 
486
 
    worksheet_id = Int(name="worksheetid")
487
 
    worksheet = Reference(worksheet_id, Worksheet.id)
488
 
    exercise_id = Unicode(name="problemid")
489
 
    exercise = Reference(exercise_id, Exercise.id)
490
 
    optional = Bool()
491
 
 
492
 
    __init__ = _kwarg_init
493
 
 
494
 
    def __repr__(self):
495
 
        return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
496
 
                                  self.worksheet.name)
497
 
 
498
 
class ExerciseSave(Storm):
499
 
    """
500
 
    Represents a potential solution to an exercise that a user has submitted
501
 
    to the server for storage.
502
 
    A basic ExerciseSave is just the current saved text for this exercise for
503
 
    this user (doesn't count towards their attempts).
504
 
    ExerciseSave may be extended with additional semantics (such as
505
 
    ExerciseAttempt).
506
 
    """
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)
512
 
    user_id = Int(name="loginid")
513
 
    user = Reference(user_id, User.id)
514
 
    date = DateTime()
515
 
    text = Unicode()
516
 
    worksheetid = Int()
517
 
    worksheet = Reference(worksheetid, Worksheet.id)
518
 
 
519
 
    __init__ = _kwarg_init
520
 
 
521
 
    def __repr__(self):
522
 
        return "<%s %s by %s at %s>" % (type(self).__name__,
523
 
            self.exercise.name, self.user.login, self.date.strftime("%c"))
524
 
 
525
 
class ExerciseAttempt(ExerciseSave):
526
 
    """
527
 
    An ExerciseAttempt is a special case of an ExerciseSave. Like an
528
 
    ExerciseSave, it constitutes exercise solution data that the user has
529
 
    submitted to the server for storage.
530
 
    In addition, it contains additional information about the submission.
531
 
    complete - True if this submission was successful, rendering this exercise
532
 
        complete for this user.
533
 
    active - True if this submission is "active" (usually true). Submissions
534
 
        may be de-activated by privileged users for special reasons, and then
535
 
        they won't count (either as a penalty or success), but will still be
536
 
        stored.
537
 
    """
538
 
    __storm_table__ = "problem_attempt"
539
 
    __storm_primary__ = "exercise_id", "user_id", "date"
540
 
 
541
 
    # The "text" field is the same but has a different name in the DB table
542
 
    # for some reason.
543
 
    text = Unicode(name="attempt")
544
 
    complete = Bool()
545
 
    active = Bool()
546
 
    
547
 
    def get_permissions(self, user):
548
 
        return set(['view']) if user is self.user else set()
549
 
  
550
 
class TestSuite(Storm):
551
 
    """A Testsuite acts as a container for the test cases of an exercise."""
552
 
    __storm_table__ = "test_suite"
553
 
    __storm_primary__ = "exercise_id", "suiteid"
554
 
    
555
 
    suiteid = Int()
556
 
    exercise_id = Unicode(name="problemid")
557
 
    exercise = Reference(exercise_id, Exercise.id)
558
 
    test_cases = ReferenceSet(suiteid, 'TestCase.suiteid')
559
 
    description = Unicode()
560
 
    seq_no = Int()
561
 
 
562
 
class TestCase(Storm):
563
 
    """A TestCase is a member of a TestSuite.
564
 
    
565
 
    It contains the data necessary to check if an exercise is correct"""
566
 
    __storm_table__ = "test_case"
567
 
    __storm_primary__ = "testid", "suiteid"
568
 
    
569
 
    testid = Int()
570
 
    suiteid = Int()
571
 
    suite = Reference(suiteid, TestSuite.suiteid)
572
 
    passmsg = Unicode()
573
 
    failmsg = Unicode()
574
 
    init = Unicode()
575
 
    code_type = Unicode()
576
 
    code = Unicode()
577
 
    testtype = Unicode()
578
 
    seq_no = Int()
579
 
    
580
 
    __init__ = _kwarg_init