~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:
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
36
 
 
37
 
__all__ = ['get_store',
38
 
            'User',
39
 
            'Subject', 'Semester', 'Offering', 'Enrolment',
40
 
            'ProjectSet', 'Project', 'ProjectGroup', 'ProjectGroupMembership',
41
 
            'Assessed', 'ProjectSubmission', 'ProjectExtension',
42
 
            'Exercise', 'Worksheet', 'WorksheetExercise',
43
 
            'ExerciseSave', 'ExerciseAttempt',
44
 
            'TestCase', 'TestSuite', 'TestSuiteVar'
45
 
        ]
 
34
import ivle.caps
46
35
 
47
36
def _kwarg_init(self, **kwargs):
48
37
    for k,v in kwargs.items():
49
 
        if k.startswith('_') or not hasattr(self.__class__, k):
 
38
        if k.startswith('_') or not hasattr(self, k):
50
39
            raise TypeError("%s got an unexpected keyword argument '%s'"
51
 
                % (self.__class__.__name__, k))
 
40
                % self.__class__.__name__, k)
52
41
        setattr(self, k, v)
53
42
 
54
43
def get_conn_string():
55
44
    """
56
45
    Returns the Storm connection string, generated from the conf file.
57
46
    """
58
 
 
59
 
    clusterstr = ''
60
 
    if ivle.conf.db_user:
61
 
        clusterstr += ivle.conf.db_user
62
 
        if ivle.conf.db_password:
63
 
            clusterstr += ':' + ivle.conf.db_password
64
 
        clusterstr += '@'
65
 
 
66
 
    host = ivle.conf.db_host or 'localhost'
67
 
    port = ivle.conf.db_port or 5432
68
 
 
69
 
    clusterstr += '%s:%d' % (host, port)
70
 
 
71
 
    return "postgres://%s/%s" % (clusterstr, ivle.conf.db_dbname)
 
47
    return "postgres://%s:%s@%s:%d/%s" % (ivle.conf.db_user,
 
48
        ivle.conf.db_password, ivle.conf.db_host, ivle.conf.db_port,
 
49
        ivle.conf.db_dbname)
72
50
 
73
51
def get_store():
74
52
    """
77
55
    """
78
56
    return Store(create_database(get_conn_string()))
79
57
 
80
 
# USERS #
81
 
 
82
58
class User(Storm):
83
59
    """
84
60
    Represents an IVLE user.
89
65
    login = Unicode()
90
66
    passhash = Unicode()
91
67
    state = Unicode()
92
 
    admin = Bool()
 
68
    rolenm = Unicode()
93
69
    unixid = Int()
94
70
    nick = Unicode()
95
71
    pass_exp = DateTime()
101
77
    studentid = Unicode()
102
78
    settings = Unicode()
103
79
 
 
80
    def _get_role(self):
 
81
        if self.rolenm is None:
 
82
            return None
 
83
        return ivle.caps.Role(self.rolenm)
 
84
    def _set_role(self, value):
 
85
        if not isinstance(value, ivle.caps.Role):
 
86
            raise TypeError("role must be an ivle.caps.Role")
 
87
        self.rolenm = unicode(value)
 
88
    role = property(_get_role, _set_role)
 
89
 
104
90
    __init__ = _kwarg_init
105
91
 
106
92
    def __repr__(self):
117
103
            return None
118
104
        return self.hash_password(password) == self.passhash
119
105
 
120
 
    @property
121
 
    def display_name(self):
122
 
        return self.fullname
 
106
    def hasCap(self, capability):
 
107
        """Given a capability (which is a Role object), returns True if this
 
108
        User has that capability, False otherwise.
 
109
        """
 
110
        return self.role.hasCap(capability)
123
111
 
124
112
    @property
125
113
    def password_expired(self):
131
119
        fieldval = self.acct_exp
132
120
        return fieldval is not None and datetime.datetime.now() > fieldval
133
121
 
134
 
    @property
135
 
    def valid(self):
136
 
        return self.state == 'enabled' and not self.account_expired
137
 
 
138
122
    def _get_enrolments(self, justactive):
139
123
        return Store.of(self).find(Enrolment,
140
124
            Enrolment.user_id == self.id,
147
131
                Desc(Subject.code)
148
132
            )
149
133
 
150
 
    def _set_password(self, password):
151
 
        if password is None:
152
 
            self.passhash = None
153
 
        else:
154
 
            self.passhash = unicode(User.hash_password(password))
155
 
    password = property(fset=_set_password)
156
 
 
157
 
    @property
158
 
    def subjects(self):
159
 
        return Store.of(self).find(Subject,
160
 
            Enrolment.user_id == self.id,
161
 
            Enrolment.active == True,
162
 
            Offering.id == Enrolment.offering_id,
163
 
            Subject.id == Offering.subject_id).config(distinct=True)
164
 
 
165
 
    # TODO: Invitations should be listed too?
166
 
    def get_groups(self, offering=None):
167
 
        preds = [
168
 
            ProjectGroupMembership.user_id == self.id,
169
 
            ProjectGroup.id == ProjectGroupMembership.project_group_id,
170
 
        ]
171
 
        if offering:
172
 
            preds.extend([
173
 
                ProjectSet.offering_id == offering.id,
174
 
                ProjectGroup.project_set_id == ProjectSet.id,
175
 
            ])
176
 
        return Store.of(self).find(ProjectGroup, *preds)
177
 
 
178
 
    @property
179
 
    def groups(self):
180
 
        return self.get_groups()
181
 
 
182
134
    @property
183
135
    def active_enrolments(self):
184
136
        '''A sanely ordered list of the user's active enrolments.'''
189
141
        '''A sanely ordered list of all of the user's enrolments.'''
190
142
        return self._get_enrolments(False) 
191
143
 
192
 
    def get_projects(self, offering=None, active_only=True):
193
 
        '''Return Projects that the user can submit.
194
 
 
195
 
        This will include projects for offerings in which the user is
196
 
        enrolled, as long as the project is not in a project set which has
197
 
        groups (ie. if maximum number of group members is 0).
198
 
 
199
 
        Unless active_only is False, only projects for active offerings will
200
 
        be returned.
201
 
 
202
 
        If an offering is specified, returned projects will be limited to
203
 
        those for that offering.
204
 
        '''
205
 
        return Store.of(self).find(Project,
206
 
            Project.project_set_id == ProjectSet.id,
207
 
            ProjectSet.max_students_per_group == None,
208
 
            ProjectSet.offering_id == Offering.id,
209
 
            (offering is None) or (Offering.id == offering.id),
210
 
            Semester.id == Offering.semester_id,
211
 
            (not active_only) or (Semester.state == u'current'),
212
 
            Enrolment.offering_id == Offering.id,
213
 
            Enrolment.user_id == self.id)
214
 
 
215
144
    @staticmethod
216
145
    def hash_password(password):
217
146
        return md5.md5(password).hexdigest()
224
153
        """
225
154
        return store.find(cls, cls.login == unicode(login)).one()
226
155
 
227
 
    def get_permissions(self, user):
228
 
        if user and user.admin or user is self:
229
 
            return set(['view', 'edit', 'submit_project'])
230
 
        else:
231
 
            return set()
232
 
 
233
 
# SUBJECTS AND ENROLMENTS #
234
 
 
235
156
class Subject(Storm):
236
157
    __storm_table__ = "subject"
237
158
 
248
169
    def __repr__(self):
249
170
        return "<%s '%s'>" % (type(self).__name__, self.short_name)
250
171
 
251
 
    def get_permissions(self, user):
252
 
        perms = set()
253
 
        if user is not None:
254
 
            perms.add('view')
255
 
            if user.admin:
256
 
                perms.add('edit')
257
 
        return perms
258
 
 
259
172
class Semester(Storm):
260
173
    __storm_table__ = "semester"
261
174
 
262
175
    id = Int(primary=True, name="semesterid")
263
176
    year = Unicode()
264
177
    semester = Unicode()
265
 
    state = Unicode()
 
178
    active = Bool()
266
179
 
267
180
    offerings = ReferenceSet(id, 'Offering.semester_id')
268
 
    enrolments = ReferenceSet(id,
269
 
                              'Offering.semester_id',
270
 
                              'Offering.id',
271
 
                              'Enrolment.offering_id')
272
181
 
273
182
    __init__ = _kwarg_init
274
183
 
286
195
    groups_student_permissions = Unicode()
287
196
 
288
197
    enrolments = ReferenceSet(id, 'Enrolment.offering_id')
289
 
    members = ReferenceSet(id,
290
 
                           'Enrolment.offering_id',
291
 
                           'Enrolment.user_id',
292
 
                           'User.id')
293
 
    project_sets = ReferenceSet(id, 'ProjectSet.offering_id')
294
 
 
295
 
    worksheets = ReferenceSet(id, 
296
 
        'Worksheet.offering_id', 
297
 
        order_by="seq_no"
298
 
    )
299
198
 
300
199
    __init__ = _kwarg_init
301
200
 
303
202
        return "<%s %r in %r>" % (type(self).__name__, self.subject,
304
203
                                  self.semester)
305
204
 
306
 
    def enrol(self, user, role=u'student'):
307
 
        '''Enrol a user in this offering.'''
308
 
        enrolment = Store.of(self).find(Enrolment,
309
 
                               Enrolment.user_id == user.id,
310
 
                               Enrolment.offering_id == self.id).one()
311
 
 
312
 
        if enrolment is None:
313
 
            enrolment = Enrolment(user=user, offering=self)
314
 
            self.enrolments.add(enrolment)
315
 
 
316
 
        enrolment.active = True
317
 
        enrolment.role = role
318
 
 
319
 
    def unenrol(self, user):
320
 
        '''Unenrol a user from this offering.'''
321
 
        enrolment = Store.of(self).find(Enrolment,
322
 
                               Enrolment.user_id == user.id,
323
 
                               Enrolment.offering_id == self.id).one()
324
 
        Store.of(enrolment).remove(enrolment)
325
 
 
326
 
    def get_permissions(self, user):
327
 
        perms = set()
328
 
        if user is not None:
329
 
            enrolment = self.get_enrolment(user)
330
 
            if enrolment or user.admin:
331
 
                perms.add('view')
332
 
            if (enrolment and enrolment.role in (u'tutor', u'lecturer')) \
333
 
               or user.admin:
334
 
                perms.add('edit')
335
 
        return perms
336
 
 
337
 
    def get_enrolment(self, user):
338
 
        try:
339
 
            enrolment = self.enrolments.find(user=user).one()
340
 
        except NotOneError:
341
 
            enrolment = None
342
 
 
343
 
        return enrolment
344
 
 
345
205
class Enrolment(Storm):
346
206
    __storm_table__ = "enrolment"
347
207
    __storm_primary__ = "user_id", "offering_id"
350
210
    user = Reference(user_id, User.id)
351
211
    offering_id = Int(name="offeringid")
352
212
    offering = Reference(offering_id, Offering.id)
353
 
    role = Unicode()
354
 
    notes = Unicode()
355
 
    active = Bool()
356
 
 
357
 
    @property
358
 
    def groups(self):
359
 
        return Store.of(self).find(ProjectGroup,
360
 
                ProjectSet.offering_id == self.offering.id,
361
 
                ProjectGroup.project_set_id == ProjectSet.id,
362
 
                ProjectGroupMembership.project_group_id == ProjectGroup.id,
363
 
                ProjectGroupMembership.user_id == self.user.id)
364
 
 
365
 
    __init__ = _kwarg_init
366
 
 
367
 
    def __repr__(self):
368
 
        return "<%s %r in %r>" % (type(self).__name__, self.user,
369
 
                                  self.offering)
370
 
 
371
 
# PROJECTS #
372
 
 
373
 
class ProjectSet(Storm):
374
 
    __storm_table__ = "project_set"
375
 
 
376
 
    id = Int(name="projectsetid", primary=True)
377
 
    offering_id = Int(name="offeringid")
378
 
    offering = Reference(offering_id, Offering.id)
379
 
    max_students_per_group = Int()
380
 
 
381
 
    projects = ReferenceSet(id, 'Project.project_set_id')
382
 
    project_groups = ReferenceSet(id, 'ProjectGroup.project_set_id')
383
 
 
384
 
    __init__ = _kwarg_init
385
 
 
386
 
    def __repr__(self):
387
 
        return "<%s %d in %r>" % (type(self).__name__, self.id,
388
 
                                  self.offering)
389
 
 
390
 
class Project(Storm):
391
 
    __storm_table__ = "project"
392
 
 
393
 
    id = Int(name="projectid", primary=True)
394
 
    name = Unicode()
395
 
    short_name = Unicode()
396
 
    synopsis = Unicode()
397
 
    url = Unicode()
398
 
    project_set_id = Int(name="projectsetid")
399
 
    project_set = Reference(project_set_id, ProjectSet.id)
400
 
    deadline = DateTime()
401
 
 
402
 
    assesseds = ReferenceSet(id, 'Assessed.project_id')
403
 
    submissions = ReferenceSet(id,
404
 
                               'Assessed.project_id',
405
 
                               'Assessed.id',
406
 
                               'ProjectSubmission.assessed_id')
407
 
 
408
 
    __init__ = _kwarg_init
409
 
 
410
 
    def __repr__(self):
411
 
        return "<%s '%s' in %r>" % (type(self).__name__, self.short_name,
412
 
                                  self.project_set.offering)
413
 
 
414
 
    def can_submit(self, principal):
415
 
        return (self in principal.get_projects() and
416
 
                self.deadline > datetime.datetime.now())
417
 
 
418
 
    def submit(self, principal, path, revision, who):
419
 
        """Submit a Subversion path and revision to a project.
420
 
 
421
 
        'principal' is the owner of the Subversion repository, and the
422
 
        entity on behalf of whom the submission is being made. 'path' is
423
 
        a path within that repository, and 'revision' specifies which
424
 
        revision of that path. 'who' is the person making the submission.
425
 
        """
426
 
 
427
 
        if not self.can_submit(principal):
428
 
            raise Exception('cannot submit')
429
 
 
430
 
        a = Assessed.get(Store.of(self), principal, self)
431
 
        ps = ProjectSubmission()
432
 
        ps.path = path
433
 
        ps.revision = revision
434
 
        ps.date_submitted = datetime.datetime.now()
435
 
        ps.assessed = a
436
 
        ps.submitter = who
437
 
 
438
 
        return ps
439
 
 
440
 
 
441
 
class ProjectGroup(Storm):
442
 
    __storm_table__ = "project_group"
443
 
 
444
 
    id = Int(name="groupid", primary=True)
445
 
    name = Unicode(name="groupnm")
446
 
    project_set_id = Int(name="projectsetid")
447
 
    project_set = Reference(project_set_id, ProjectSet.id)
448
 
    nick = Unicode()
449
 
    created_by_id = Int(name="createdby")
450
 
    created_by = Reference(created_by_id, User.id)
451
 
    epoch = DateTime()
452
 
 
453
 
    members = ReferenceSet(id,
454
 
                           "ProjectGroupMembership.project_group_id",
455
 
                           "ProjectGroupMembership.user_id",
456
 
                           "User.id")
457
 
 
458
 
    __init__ = _kwarg_init
459
 
 
460
 
    def __repr__(self):
461
 
        return "<%s %s in %r>" % (type(self).__name__, self.name,
462
 
                                  self.project_set.offering)
463
 
 
464
 
    @property
465
 
    def display_name(self):
466
 
        return '%s (%s)' % (self.nick, self.name)
467
 
 
468
 
    def get_projects(self, offering=None, active_only=True):
469
 
        '''Return Projects that the group can submit.
470
 
 
471
 
        This will include projects in the project set which owns this group,
472
 
        unless the project set disallows groups (in which case none will be
473
 
        returned).
474
 
 
475
 
        Unless active_only is False, projects will only be returned if the
476
 
        group's offering is active.
477
 
 
478
 
        If an offering is specified, projects will only be returned if it
479
 
        matches the group's.
480
 
        '''
481
 
        return Store.of(self).find(Project,
482
 
            Project.project_set_id == ProjectSet.id,
483
 
            ProjectSet.id == self.project_set.id,
484
 
            ProjectSet.max_students_per_group != None,
485
 
            ProjectSet.offering_id == Offering.id,
486
 
            (offering is None) or (Offering.id == offering.id),
487
 
            Semester.id == Offering.semester_id,
488
 
            (not active_only) or (Semester.state == u'current'))
489
 
 
490
 
 
491
 
    def get_permissions(self, user):
492
 
        if user.admin or user in self.members:
493
 
            return set(['submit_project'])
494
 
        else:
495
 
            return set()
496
 
 
497
 
class ProjectGroupMembership(Storm):
498
 
    __storm_table__ = "group_member"
499
 
    __storm_primary__ = "user_id", "project_group_id"
500
 
 
501
 
    user_id = Int(name="loginid")
502
 
    user = Reference(user_id, User.id)
503
 
    project_group_id = Int(name="groupid")
504
 
    project_group = Reference(project_group_id, ProjectGroup.id)
505
 
 
506
 
    __init__ = _kwarg_init
507
 
 
508
 
    def __repr__(self):
509
 
        return "<%s %r in %r>" % (type(self).__name__, self.user,
510
 
                                  self.project_group)
511
 
 
512
 
class Assessed(Storm):
513
 
    __storm_table__ = "assessed"
514
 
 
515
 
    id = Int(name="assessedid", primary=True)
516
 
    user_id = Int(name="loginid")
517
 
    user = Reference(user_id, User.id)
518
 
    project_group_id = Int(name="groupid")
519
 
    project_group = Reference(project_group_id, ProjectGroup.id)
520
 
 
521
 
    project_id = Int(name="projectid")
522
 
    project = Reference(project_id, Project.id)
523
 
 
524
 
    extensions = ReferenceSet(id, 'ProjectExtension.assessed_id')
525
 
    submissions = ReferenceSet(id, 'ProjectSubmission.assessed_id')
526
 
 
527
 
    def __repr__(self):
528
 
        return "<%s %r in %r>" % (type(self).__name__,
529
 
            self.user or self.project_group, self.project)
530
 
 
531
 
    @classmethod
532
 
    def get(cls, store, principal, project):
533
 
        t = type(principal)
534
 
        if t not in (User, ProjectGroup):
535
 
            raise AssertionError('principal must be User or ProjectGroup')
536
 
 
537
 
        a = store.find(cls,
538
 
            (t is User) or (cls.project_group_id == principal.id),
539
 
            (t is ProjectGroup) or (cls.user_id == principal.id),
540
 
            Project.id == project.id).one()
541
 
 
542
 
        if a is None:
543
 
            a = cls()
544
 
            if t is User:
545
 
                a.user = principal
546
 
            else:
547
 
                a.project_group = principal
548
 
            a.project = project
549
 
            store.add(a)
550
 
 
551
 
        return a
552
 
 
553
 
 
554
 
class ProjectExtension(Storm):
555
 
    __storm_table__ = "project_extension"
556
 
 
557
 
    id = Int(name="extensionid", primary=True)
558
 
    assessed_id = Int(name="assessedid")
559
 
    assessed = Reference(assessed_id, Assessed.id)
560
 
    deadline = DateTime()
561
 
    approver_id = Int(name="approver")
562
 
    approver = Reference(approver_id, User.id)
563
 
    notes = Unicode()
564
 
 
565
 
class ProjectSubmission(Storm):
566
 
    __storm_table__ = "project_submission"
567
 
 
568
 
    id = Int(name="submissionid", primary=True)
569
 
    assessed_id = Int(name="assessedid")
570
 
    assessed = Reference(assessed_id, Assessed.id)
571
 
    path = Unicode()
572
 
    revision = Int()
573
 
    submitter_id = Int(name="submitter")
574
 
    submitter = Reference(submitter_id, User.id)
575
 
    date_submitted = DateTime()
576
 
 
577
 
 
578
 
# WORKSHEETS AND EXERCISES #
579
 
 
580
 
class Exercise(Storm):
581
 
    __storm_table__ = "exercise"
582
 
    id = Unicode(primary=True, name="identifier")
583
 
    name = Unicode()
584
 
    description = Unicode()
585
 
    partial = Unicode()
586
 
    solution = Unicode()
587
 
    include = Unicode()
588
 
    num_rows = Int()
589
 
 
590
 
    worksheet_exercises =  ReferenceSet(id,
591
 
        'WorksheetExercise.exercise_id')
592
 
 
593
 
    worksheets = ReferenceSet(id,
594
 
        'WorksheetExercise.exercise_id',
595
 
        'WorksheetExercise.worksheet_id',
596
 
        'Worksheet.id'
597
 
    )
598
 
    
599
 
    test_suites = ReferenceSet(id, 
600
 
        'TestSuite.exercise_id',
601
 
        order_by='seq_no')
602
 
 
603
 
    __init__ = _kwarg_init
604
 
 
605
 
    def __repr__(self):
606
 
        return "<%s %s>" % (type(self).__name__, self.name)
607
 
 
608
 
    def get_permissions(self, user):
609
 
        perms = set()
610
 
        roles = set()
611
 
        if user is not None:
612
 
            if user.admin:
613
 
                perms.add('edit')
614
 
                perms.add('view')
615
 
            elif 'lecturer' in set((e.role for e in user.active_enrolments)):
616
 
                perms.add('edit')
617
 
                perms.add('view')
618
 
            
619
 
        return perms
620
 
    
621
 
    def get_description(self):
622
 
        return rst(self.description)
623
 
 
624
 
    def delete(self):
625
 
        """Deletes the exercise, providing it has no associated worksheets."""
626
 
        if (self.worksheet_exercises.count() > 0):
627
 
            raise IntegrityError()
628
 
        for suite in self.test_suites:
629
 
            suite.delete()
630
 
        Store.of(self).remove(self)
631
 
 
632
 
class Worksheet(Storm):
633
 
    __storm_table__ = "worksheet"
634
 
 
635
 
    id = Int(primary=True, name="worksheetid")
636
 
    offering_id = Int(name="offeringid")
637
 
    identifier = Unicode()
638
 
    name = Unicode()
639
 
    assessable = Bool()
640
 
    data = Unicode()
641
 
    seq_no = Int()
642
 
    format = Unicode()
643
 
 
644
 
    attempts = ReferenceSet(id, "ExerciseAttempt.worksheetid")
645
 
    offering = Reference(offering_id, 'Offering.id')
646
 
 
647
 
    all_worksheet_exercises = ReferenceSet(id,
648
 
        'WorksheetExercise.worksheet_id')
649
 
 
650
 
    # Use worksheet_exercises to get access to the *active* WorksheetExercise
651
 
    # objects binding worksheets to exercises. This is required to access the
652
 
    # "optional" field.
653
 
 
654
 
    @property
655
 
    def worksheet_exercises(self):
656
 
        return self.all_worksheet_exercises.find(active=True)
657
 
 
658
 
    __init__ = _kwarg_init
659
 
 
660
 
    def __repr__(self):
661
 
        return "<%s %s>" % (type(self).__name__, self.name)
662
 
 
663
 
    # XXX Refactor this - make it an instance method of Subject rather than a
664
 
    # class method of Worksheet. Can't do that now because Subject isn't
665
 
    # linked referentially to the Worksheet.
666
 
    @classmethod
667
 
    def get_by_name(cls, store, subjectname, worksheetname):
668
 
        """
669
 
        Get the Worksheet from the db associated with a given store, subject
670
 
        name and worksheet name.
671
 
        """
672
 
        return store.find(cls, cls.subject == unicode(subjectname),
673
 
            cls.name == unicode(worksheetname)).one()
674
 
 
675
 
    def remove_all_exercises(self):
676
 
        """
677
 
        Remove all exercises from this worksheet.
678
 
        This does not delete the exercises themselves. It just removes them
679
 
        from the worksheet.
680
 
        """
681
 
        store = Store.of(self)
682
 
        for ws_ex in self.all_worksheet_exercises:
683
 
            if ws_ex.saves.count() > 0 or ws_ex.attempts.count() > 0:
684
 
                raise IntegrityError()
685
 
        store.find(WorksheetExercise,
686
 
            WorksheetExercise.worksheet == self).remove()
687
 
            
688
 
    def get_permissions(self, user):
689
 
        return self.offering.get_permissions(user)
690
 
    
691
 
    def get_xml(self):
692
 
        """Returns the xml of this worksheet, converts from rst if required."""
693
 
        if self.format == u'rst':
694
 
            ws_xml = rst(self.data)
695
 
            return ws_xml
696
 
        else:
697
 
            return self.data
698
 
    
699
 
    def delete(self):
700
 
        """Deletes the worksheet, provided it has no attempts on any exercises.
701
 
        
702
 
        Returns True if delete succeeded, or False if this worksheet has
703
 
        attempts attached."""
704
 
        for ws_ex in self.all_worksheet_exercises:
705
 
            if ws_ex.saves.count() > 0 or ws_ex.attempts.count() > 0:
706
 
                raise IntegrityError()
707
 
        
708
 
        self.remove_all_exercises()
709
 
        Store.of(self).remove(self)
710
 
        
711
 
class WorksheetExercise(Storm):
712
 
    __storm_table__ = "worksheet_exercise"
713
 
    
714
 
    id = Int(primary=True, name="ws_ex_id")
715
 
 
716
 
    worksheet_id = Int(name="worksheetid")
717
 
    worksheet = Reference(worksheet_id, Worksheet.id)
718
 
    exercise_id = Unicode(name="exerciseid")
719
 
    exercise = Reference(exercise_id, Exercise.id)
720
 
    optional = Bool()
721
 
    active = Bool()
722
 
    seq_no = Int()
723
 
    
724
 
    saves = ReferenceSet(id, "ExerciseSave.ws_ex_id")
725
 
    attempts = ReferenceSet(id, "ExerciseAttempt.ws_ex_id")
726
 
 
727
 
    __init__ = _kwarg_init
728
 
 
729
 
    def __repr__(self):
730
 
        return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
731
 
                                  self.worksheet.identifier)
732
 
 
733
 
    def get_permissions(self, user):
734
 
        return self.worksheet.get_permissions(user)
735
 
    
736
 
 
737
 
class ExerciseSave(Storm):
738
 
    """
739
 
    Represents a potential solution to an exercise that a user has submitted
740
 
    to the server for storage.
741
 
    A basic ExerciseSave is just the current saved text for this exercise for
742
 
    this user (doesn't count towards their attempts).
743
 
    ExerciseSave may be extended with additional semantics (such as
744
 
    ExerciseAttempt).
745
 
    """
746
 
    __storm_table__ = "exercise_save"
747
 
    __storm_primary__ = "ws_ex_id", "user_id"
748
 
 
749
 
    ws_ex_id = Int(name="ws_ex_id")
750
 
    worksheet_exercise = Reference(ws_ex_id, "WorksheetExercise.id")
751
 
 
752
 
    user_id = Int(name="loginid")
753
 
    user = Reference(user_id, User.id)
754
 
    date = DateTime()
755
 
    text = Unicode()
756
 
 
757
 
    __init__ = _kwarg_init
758
 
 
759
 
    def __repr__(self):
760
 
        return "<%s %s by %s at %s>" % (type(self).__name__,
761
 
            self.exercise.name, self.user.login, self.date.strftime("%c"))
762
 
 
763
 
class ExerciseAttempt(ExerciseSave):
764
 
    """
765
 
    An ExerciseAttempt is a special case of an ExerciseSave. Like an
766
 
    ExerciseSave, it constitutes exercise solution data that the user has
767
 
    submitted to the server for storage.
768
 
    In addition, it contains additional information about the submission.
769
 
    complete - True if this submission was successful, rendering this exercise
770
 
        complete for this user.
771
 
    active - True if this submission is "active" (usually true). Submissions
772
 
        may be de-activated by privileged users for special reasons, and then
773
 
        they won't count (either as a penalty or success), but will still be
774
 
        stored.
775
 
    """
776
 
    __storm_table__ = "exercise_attempt"
777
 
    __storm_primary__ = "ws_ex_id", "user_id", "date"
778
 
 
779
 
    # The "text" field is the same but has a different name in the DB table
780
 
    # for some reason.
781
 
    text = Unicode(name="attempt")
782
 
    complete = Bool()
783
 
    active = Bool()
784
 
    
785
 
    def get_permissions(self, user):
786
 
        return set(['view']) if user is self.user else set()
787
 
  
788
 
class TestSuite(Storm):
789
 
    """A Testsuite acts as a container for the test cases of an exercise."""
790
 
    __storm_table__ = "test_suite"
791
 
    __storm_primary__ = "exercise_id", "suiteid"
792
 
    
793
 
    suiteid = Int()
794
 
    exercise_id = Unicode(name="exerciseid")
795
 
    description = Unicode()
796
 
    seq_no = Int()
797
 
    function = Unicode()
798
 
    stdin = Unicode()
799
 
    exercise = Reference(exercise_id, Exercise.id)
800
 
    test_cases = ReferenceSet(suiteid, 'TestCase.suiteid', order_by="seq_no")
801
 
    variables = ReferenceSet(suiteid, 'TestSuiteVar.suiteid', order_by='arg_no')
802
 
    
803
 
    def delete(self):
804
 
        """Delete this suite, without asking questions."""
805
 
        for vaariable in self.variables:
806
 
            variable.delete()
807
 
        for test_case in self.test_cases:
808
 
            test_case.delete()
809
 
        Store.of(self).remove(self)
810
 
 
811
 
class TestCase(Storm):
812
 
    """A TestCase is a member of a TestSuite.
813
 
    
814
 
    It contains the data necessary to check if an exercise is correct"""
815
 
    __storm_table__ = "test_case"
816
 
    __storm_primary__ = "testid", "suiteid"
817
 
    
818
 
    testid = Int()
819
 
    suiteid = Int()
820
 
    suite = Reference(suiteid, "TestSuite.suiteid")
821
 
    passmsg = Unicode()
822
 
    failmsg = Unicode()
823
 
    test_default = Unicode()
824
 
    seq_no = Int()
825
 
    
826
 
    parts = ReferenceSet(testid, "TestCasePart.testid")
827
 
    
828
 
    __init__ = _kwarg_init
829
 
    
830
 
    def delete(self):
831
 
        for part in self.parts:
832
 
            part.delete()
833
 
        Store.of(self).remove(self)
834
 
 
835
 
class TestSuiteVar(Storm):
836
 
    """A container for the arguments of a Test Suite"""
837
 
    __storm_table__ = "suite_variable"
838
 
    __storm_primary__ = "varid"
839
 
    
840
 
    varid = Int()
841
 
    suiteid = Int()
842
 
    var_name = Unicode()
843
 
    var_value = Unicode()
844
 
    var_type = Unicode()
845
 
    arg_no = Int()
846
 
    
847
 
    suite = Reference(suiteid, "TestSuite.suiteid")
848
 
    
849
 
    __init__ = _kwarg_init
850
 
    
851
 
    def delete(self):
852
 
        Store.of(self).remove(self)
853
 
    
854
 
class TestCasePart(Storm):
855
 
    """A container for the test elements of a Test Case"""
856
 
    __storm_table__ = "test_case_part"
857
 
    __storm_primary__ = "partid"
858
 
    
859
 
    partid = Int()
860
 
    testid = Int()
861
 
    
862
 
    part_type = Unicode()
863
 
    test_type = Unicode()
864
 
    data = Unicode()
865
 
    filename = Unicode()
866
 
    
867
 
    test = Reference(testid, "TestCase.testid")
868
 
    
869
 
    __init__ = _kwarg_init
870
 
    
871
 
    def delete(self):
872
 
        Store.of(self).remove(self)
 
213
    notes = Unicode()
 
214
    active = Bool()
 
215
 
 
216
    __init__ = _kwarg_init
 
217
 
 
218
    def __repr__(self):
 
219
        return "<%s %r in %r>" % (type(self).__name__, self.user,
 
220
                                  self.offering)