~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 05:53:45 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:1161
bin/ivle-showenrolment: Switch to ivle.database.User.enrolments from
    ivle.db.get_enrolment, removing the dependency on ivle.db.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
36
def _kwarg_init(self, **kwargs):
46
37
    for k,v in kwargs.items():
47
 
        if k.startswith('_') or not hasattr(self.__class__, k):
 
38
        if k.startswith('_') or not hasattr(self, k):
48
39
            raise TypeError("%s got an unexpected keyword argument '%s'"
49
 
                % (self.__class__.__name__, k))
 
40
                % self.__class__.__name__, k)
50
41
        setattr(self, k, v)
51
42
 
52
43
def get_conn_string():
64
55
    """
65
56
    return Store(create_database(get_conn_string()))
66
57
 
67
 
# USERS #
68
 
 
69
58
class User(Storm):
70
59
    """
71
60
    Represents an IVLE user.
130
119
        fieldval = self.acct_exp
131
120
        return fieldval is not None and datetime.datetime.now() > fieldval
132
121
 
133
 
    @property
134
 
    def valid(self):
135
 
        return self.state == 'enabled' and not self.account_expired
136
 
 
137
122
    def _get_enrolments(self, justactive):
138
123
        return Store.of(self).find(Enrolment,
139
124
            Enrolment.user_id == self.id,
146
131
                Desc(Subject.code)
147
132
            )
148
133
 
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
134
    @property
182
135
    def active_enrolments(self):
183
136
        '''A sanely ordered list of the user's active enrolments.'''
200
153
        """
201
154
        return store.find(cls, cls.login == unicode(login)).one()
202
155
 
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
156
class Subject(Storm):
212
157
    __storm_table__ = "subject"
213
158
 
224
169
    def __repr__(self):
225
170
        return "<%s '%s'>" % (type(self).__name__, self.short_name)
226
171
 
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
172
class Semester(Storm):
236
173
    __storm_table__ = "semester"
237
174
 
258
195
    groups_student_permissions = Unicode()
259
196
 
260
197
    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
198
 
269
199
    __init__ = _kwarg_init
270
200
 
272
202
        return "<%s %r in %r>" % (type(self).__name__, self.subject,
273
203
                                  self.semester)
274
204
 
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
205
class Enrolment(Storm):
288
206
    __storm_table__ = "enrolment"
289
207
    __storm_primary__ = "user_id", "offering_id"
295
213
    notes = Unicode()
296
214
    active = Bool()
297
215
 
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
 
216
    __init__ = _kwarg_init
 
217
 
 
218
    def __repr__(self):
 
219
        return "<%s %r in %r>" % (type(self).__name__, self.user,
 
220
                                  self.offering)