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

« back to all changes in this revision

Viewing changes to ivle/database.py

  • Committer: William Grant
  • Date: 2009-04-28 07:08:56 UTC
  • Revision ID: grantw@unimelb.edu.au-20090428070856-75yc00g6ea24qfqz
Drop ivle.conf.{subjects,exercises}_base - they were unused.

Show diffs side-by-side

added added

removed removed

Lines of Context:
24
24
It also provides miscellaneous utility functions for database interaction.
25
25
"""
26
26
 
27
 
import md5
 
27
import hashlib
28
28
import datetime
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
 
import ivle.conf
34
 
import ivle.caps
 
34
from ivle.worksheet.rst import rst
35
35
 
36
36
__all__ = ['get_store',
37
37
            'User',
38
38
            'Subject', 'Semester', 'Offering', 'Enrolment',
39
39
            'ProjectSet', 'Project', 'ProjectGroup', 'ProjectGroupMembership',
 
40
            'Assessed', 'ProjectSubmission', 'ProjectExtension',
40
41
            'Exercise', 'Worksheet', 'WorksheetExercise',
41
42
            'ExerciseSave', 'ExerciseAttempt',
42
 
            'AlreadyEnrolledError', 'TestCase', 'TestSuite', 'TestSuiteVar'
 
43
            'TestCase', 'TestSuite', 'TestSuiteVar'
43
44
        ]
44
45
 
45
46
def _kwarg_init(self, **kwargs):
49
50
                % (self.__class__.__name__, k))
50
51
        setattr(self, k, v)
51
52
 
52
 
def get_conn_string():
53
 
    """
54
 
    Returns the Storm connection string, generated from the conf file.
 
53
def get_conn_string(config):
 
54
    """Create a Storm connection string to the IVLE database
 
55
 
 
56
    @param config: The IVLE configuration.
55
57
    """
56
58
 
57
59
    clusterstr = ''
58
 
    if ivle.conf.db_user:
59
 
        clusterstr += ivle.conf.db_user
60
 
        if ivle.conf.db_password:
61
 
            clusterstr += ':' + ivle.conf.db_password
 
60
    if config['database']['username']:
 
61
        clusterstr += config['database']['username']
 
62
        if config['database']['password']:
 
63
            clusterstr += ':' + config['database']['password']
62
64
        clusterstr += '@'
63
65
 
64
 
    host = ivle.conf.db_host or 'localhost'
65
 
    port = ivle.conf.db_port or 5432
 
66
    host = config['database']['host'] or 'localhost'
 
67
    port = config['database']['port'] or 5432
66
68
 
67
69
    clusterstr += '%s:%d' % (host, port)
68
70
 
69
 
    return "postgres://%s/%s" % (clusterstr, ivle.conf.db_dbname)
70
 
 
71
 
def get_store():
72
 
    """
73
 
    Open a database connection and transaction. Return a storm.store.Store
74
 
    instance connected to the configured IVLE database.
75
 
    """
76
 
    return Store(create_database(get_conn_string()))
 
71
    return "postgres://%s/%s" % (clusterstr, config['database']['name'])
 
72
 
 
73
def get_store(config):
 
74
    """Create a Storm store connected to the IVLE database.
 
75
 
 
76
    @param config: The IVLE configuration.
 
77
    """
 
78
    return Store(create_database(get_conn_string(config)))
77
79
 
78
80
# USERS #
79
81
 
87
89
    login = Unicode()
88
90
    passhash = Unicode()
89
91
    state = Unicode()
90
 
    rolenm = Unicode()
 
92
    admin = Bool()
91
93
    unixid = Int()
92
94
    nick = Unicode()
93
95
    pass_exp = DateTime()
99
101
    studentid = Unicode()
100
102
    settings = Unicode()
101
103
 
102
 
    def _get_role(self):
103
 
        if self.rolenm is None:
104
 
            return None
105
 
        return ivle.caps.Role(self.rolenm)
106
 
    def _set_role(self, value):
107
 
        if not isinstance(value, ivle.caps.Role):
108
 
            raise TypeError("role must be an ivle.caps.Role")
109
 
        self.rolenm = unicode(value)
110
 
    role = property(_get_role, _set_role)
111
 
 
112
104
    __init__ = _kwarg_init
113
105
 
114
106
    def __repr__(self):
125
117
            return None
126
118
        return self.hash_password(password) == self.passhash
127
119
 
128
 
    def hasCap(self, capability):
129
 
        """Given a capability (which is a Role object), returns True if this
130
 
        User has that capability, False otherwise.
131
 
        """
132
 
        return self.role.hasCap(capability)
 
120
    @property
 
121
    def display_name(self):
 
122
        return self.fullname
133
123
 
134
124
    @property
135
125
    def password_expired(self):
199
189
        '''A sanely ordered list of all of the user's enrolments.'''
200
190
        return self._get_enrolments(False) 
201
191
 
 
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
 
202
215
    @staticmethod
203
216
    def hash_password(password):
204
 
        return md5.md5(password).hexdigest()
 
217
        return hashlib.md5(password).hexdigest()
205
218
 
206
219
    @classmethod
207
220
    def get_by_login(cls, store, login):
212
225
        return store.find(cls, cls.login == unicode(login)).one()
213
226
 
214
227
    def get_permissions(self, user):
215
 
        if user and user.rolenm == 'admin' or user is self:
216
 
            return set(['view', 'edit'])
 
228
        if user and user.admin or user is self:
 
229
            return set(['view', 'edit', 'submit_project'])
217
230
        else:
218
231
            return set()
219
232
 
239
252
        perms = set()
240
253
        if user is not None:
241
254
            perms.add('view')
242
 
            if user.rolenm == 'admin':
 
255
            if user.admin:
243
256
                perms.add('edit')
244
257
        return perms
245
258
 
 
259
    def active_offerings(self):
 
260
        """Return a sequence of currently active offerings for this subject
 
261
        (offerings whose semester.state is "current"). There should be 0 or 1
 
262
        elements in this sequence, but it's possible there are more.
 
263
        """
 
264
        return self.offerings.find(Offering.semester_id == Semester.id,
 
265
                                   Semester.state == u'current')
 
266
 
 
267
    def offering_for_semester(self, year, semester):
 
268
        """Get the offering for the given year/semester, or None."""
 
269
        return self.offerings.find(Offering.semester_id == Semester.id,
 
270
                               Semester.year == unicode(year),
 
271
                               Semester.semester == unicode(semester)).one()
 
272
 
246
273
class Semester(Storm):
247
274
    __storm_table__ = "semester"
248
275
 
249
276
    id = Int(primary=True, name="semesterid")
250
277
    year = Unicode()
251
278
    semester = Unicode()
252
 
    active = Bool()
 
279
    state = Unicode()
253
280
 
254
281
    offerings = ReferenceSet(id, 'Offering.semester_id')
 
282
    enrolments = ReferenceSet(id,
 
283
                              'Offering.semester_id',
 
284
                              'Offering.id',
 
285
                              'Enrolment.offering_id')
255
286
 
256
287
    __init__ = _kwarg_init
257
288
 
277
308
 
278
309
    worksheets = ReferenceSet(id, 
279
310
        'Worksheet.offering_id', 
280
 
        order_by="Worksheet.seq_no"
 
311
        order_by="seq_no"
281
312
    )
282
313
 
283
314
    __init__ = _kwarg_init
286
317
        return "<%s %r in %r>" % (type(self).__name__, self.subject,
287
318
                                  self.semester)
288
319
 
289
 
    def enrol(self, user):
 
320
    def enrol(self, user, role=u'student'):
290
321
        '''Enrol a user in this offering.'''
291
 
        # We'll get a horrible database constraint violation error if we try
292
 
        # to add a second enrolment.
293
 
        if Store.of(self).find(Enrolment,
294
 
                               Enrolment.user_id == user.id,
295
 
                               Enrolment.offering_id == self.id).count() == 1:
296
 
            raise AlreadyEnrolledError()
297
 
 
298
 
        e = Enrolment(user=user, offering=self, active=True)
299
 
        self.enrolments.add(e)
 
322
        enrolment = Store.of(self).find(Enrolment,
 
323
                               Enrolment.user_id == user.id,
 
324
                               Enrolment.offering_id == self.id).one()
 
325
 
 
326
        if enrolment is None:
 
327
            enrolment = Enrolment(user=user, offering=self)
 
328
            self.enrolments.add(enrolment)
 
329
 
 
330
        enrolment.active = True
 
331
        enrolment.role = role
 
332
 
 
333
    def unenrol(self, user):
 
334
        '''Unenrol a user from this offering.'''
 
335
        enrolment = Store.of(self).find(Enrolment,
 
336
                               Enrolment.user_id == user.id,
 
337
                               Enrolment.offering_id == self.id).one()
 
338
        Store.of(enrolment).remove(enrolment)
300
339
 
301
340
    def get_permissions(self, user):
302
341
        perms = set()
303
342
        if user is not None:
304
 
            perms.add('view')
305
 
            if user.rolenm == 'admin':
 
343
            enrolment = self.get_enrolment(user)
 
344
            if enrolment or user.admin:
 
345
                perms.add('view')
 
346
            if (enrolment and enrolment.role in (u'tutor', u'lecturer')) \
 
347
               or user.admin:
306
348
                perms.add('edit')
307
349
        return perms
308
350
 
 
351
    def get_enrolment(self, user):
 
352
        try:
 
353
            enrolment = self.enrolments.find(user=user).one()
 
354
        except NotOneError:
 
355
            enrolment = None
 
356
 
 
357
        return enrolment
 
358
 
309
359
class Enrolment(Storm):
310
360
    __storm_table__ = "enrolment"
311
361
    __storm_primary__ = "user_id", "offering_id"
314
364
    user = Reference(user_id, User.id)
315
365
    offering_id = Int(name="offeringid")
316
366
    offering = Reference(offering_id, Offering.id)
 
367
    role = Unicode()
317
368
    notes = Unicode()
318
369
    active = Bool()
319
370
 
331
382
        return "<%s %r in %r>" % (type(self).__name__, self.user,
332
383
                                  self.offering)
333
384
 
334
 
class AlreadyEnrolledError(Exception):
335
 
    pass
336
 
 
337
385
# PROJECTS #
338
386
 
339
387
class ProjectSet(Storm):
357
405
    __storm_table__ = "project"
358
406
 
359
407
    id = Int(name="projectid", primary=True)
 
408
    name = Unicode()
 
409
    short_name = Unicode()
360
410
    synopsis = Unicode()
361
411
    url = Unicode()
362
412
    project_set_id = Int(name="projectsetid")
363
413
    project_set = Reference(project_set_id, ProjectSet.id)
364
414
    deadline = DateTime()
365
415
 
 
416
    assesseds = ReferenceSet(id, 'Assessed.project_id')
 
417
    submissions = ReferenceSet(id,
 
418
                               'Assessed.project_id',
 
419
                               'Assessed.id',
 
420
                               'ProjectSubmission.assessed_id')
 
421
 
366
422
    __init__ = _kwarg_init
367
423
 
368
424
    def __repr__(self):
369
 
        return "<%s '%s' in %r>" % (type(self).__name__, self.synopsis,
 
425
        return "<%s '%s' in %r>" % (type(self).__name__, self.short_name,
370
426
                                  self.project_set.offering)
371
427
 
 
428
    def can_submit(self, principal):
 
429
        return (self in principal.get_projects() and
 
430
                self.deadline > datetime.datetime.now())
 
431
 
 
432
    def submit(self, principal, path, revision, who):
 
433
        """Submit a Subversion path and revision to a project.
 
434
 
 
435
        'principal' is the owner of the Subversion repository, and the
 
436
        entity on behalf of whom the submission is being made. 'path' is
 
437
        a path within that repository, and 'revision' specifies which
 
438
        revision of that path. 'who' is the person making the submission.
 
439
        """
 
440
 
 
441
        if not self.can_submit(principal):
 
442
            raise Exception('cannot submit')
 
443
 
 
444
        a = Assessed.get(Store.of(self), principal, self)
 
445
        ps = ProjectSubmission()
 
446
        ps.path = path
 
447
        ps.revision = revision
 
448
        ps.date_submitted = datetime.datetime.now()
 
449
        ps.assessed = a
 
450
        ps.submitter = who
 
451
 
 
452
        return ps
 
453
 
 
454
 
372
455
class ProjectGroup(Storm):
373
456
    __storm_table__ = "project_group"
374
457
 
392
475
        return "<%s %s in %r>" % (type(self).__name__, self.name,
393
476
                                  self.project_set.offering)
394
477
 
 
478
    @property
 
479
    def display_name(self):
 
480
        return '%s (%s)' % (self.nick, self.name)
 
481
 
 
482
    def get_projects(self, offering=None, active_only=True):
 
483
        '''Return Projects that the group can submit.
 
484
 
 
485
        This will include projects in the project set which owns this group,
 
486
        unless the project set disallows groups (in which case none will be
 
487
        returned).
 
488
 
 
489
        Unless active_only is False, projects will only be returned if the
 
490
        group's offering is active.
 
491
 
 
492
        If an offering is specified, projects will only be returned if it
 
493
        matches the group's.
 
494
        '''
 
495
        return Store.of(self).find(Project,
 
496
            Project.project_set_id == ProjectSet.id,
 
497
            ProjectSet.id == self.project_set.id,
 
498
            ProjectSet.max_students_per_group != None,
 
499
            ProjectSet.offering_id == Offering.id,
 
500
            (offering is None) or (Offering.id == offering.id),
 
501
            Semester.id == Offering.semester_id,
 
502
            (not active_only) or (Semester.state == u'current'))
 
503
 
 
504
 
 
505
    def get_permissions(self, user):
 
506
        if user.admin or user in self.members:
 
507
            return set(['submit_project'])
 
508
        else:
 
509
            return set()
 
510
 
395
511
class ProjectGroupMembership(Storm):
396
512
    __storm_table__ = "group_member"
397
513
    __storm_primary__ = "user_id", "project_group_id"
407
523
        return "<%s %r in %r>" % (type(self).__name__, self.user,
408
524
                                  self.project_group)
409
525
 
 
526
class Assessed(Storm):
 
527
    __storm_table__ = "assessed"
 
528
 
 
529
    id = Int(name="assessedid", primary=True)
 
530
    user_id = Int(name="loginid")
 
531
    user = Reference(user_id, User.id)
 
532
    project_group_id = Int(name="groupid")
 
533
    project_group = Reference(project_group_id, ProjectGroup.id)
 
534
 
 
535
    project_id = Int(name="projectid")
 
536
    project = Reference(project_id, Project.id)
 
537
 
 
538
    extensions = ReferenceSet(id, 'ProjectExtension.assessed_id')
 
539
    submissions = ReferenceSet(id, 'ProjectSubmission.assessed_id')
 
540
 
 
541
    def __repr__(self):
 
542
        return "<%s %r in %r>" % (type(self).__name__,
 
543
            self.user or self.project_group, self.project)
 
544
 
 
545
    @classmethod
 
546
    def get(cls, store, principal, project):
 
547
        t = type(principal)
 
548
        if t not in (User, ProjectGroup):
 
549
            raise AssertionError('principal must be User or ProjectGroup')
 
550
 
 
551
        a = store.find(cls,
 
552
            (t is User) or (cls.project_group_id == principal.id),
 
553
            (t is ProjectGroup) or (cls.user_id == principal.id),
 
554
            Project.id == project.id).one()
 
555
 
 
556
        if a is None:
 
557
            a = cls()
 
558
            if t is User:
 
559
                a.user = principal
 
560
            else:
 
561
                a.project_group = principal
 
562
            a.project = project
 
563
            store.add(a)
 
564
 
 
565
        return a
 
566
 
 
567
 
 
568
class ProjectExtension(Storm):
 
569
    __storm_table__ = "project_extension"
 
570
 
 
571
    id = Int(name="extensionid", primary=True)
 
572
    assessed_id = Int(name="assessedid")
 
573
    assessed = Reference(assessed_id, Assessed.id)
 
574
    deadline = DateTime()
 
575
    approver_id = Int(name="approver")
 
576
    approver = Reference(approver_id, User.id)
 
577
    notes = Unicode()
 
578
 
 
579
class ProjectSubmission(Storm):
 
580
    __storm_table__ = "project_submission"
 
581
 
 
582
    id = Int(name="submissionid", primary=True)
 
583
    assessed_id = Int(name="assessedid")
 
584
    assessed = Reference(assessed_id, Assessed.id)
 
585
    path = Unicode()
 
586
    revision = Int()
 
587
    submitter_id = Int(name="submitter")
 
588
    submitter = Reference(submitter_id, User.id)
 
589
    date_submitted = DateTime()
 
590
 
 
591
 
410
592
# WORKSHEETS AND EXERCISES #
411
593
 
412
594
class Exercise(Storm):
413
 
    # Note: Table "problem" is called "Exercise" in the Object layer, since
414
 
    # it's called that everywhere else.
415
 
    __storm_table__ = "problem"
 
595
    __storm_table__ = "exercise"
416
596
    id = Unicode(primary=True, name="identifier")
417
597
    name = Unicode()
418
598
    description = Unicode()
421
601
    include = Unicode()
422
602
    num_rows = Int()
423
603
 
 
604
    worksheet_exercises =  ReferenceSet(id,
 
605
        'WorksheetExercise.exercise_id')
 
606
 
424
607
    worksheets = ReferenceSet(id,
425
608
        'WorksheetExercise.exercise_id',
426
609
        'WorksheetExercise.worksheet_id',
427
610
        'Worksheet.id'
428
611
    )
429
612
    
430
 
    test_suites = ReferenceSet(id, 'TestSuite.exercise_id')
 
613
    test_suites = ReferenceSet(id, 
 
614
        'TestSuite.exercise_id',
 
615
        order_by='seq_no')
431
616
 
432
617
    __init__ = _kwarg_init
433
618
 
434
619
    def __repr__(self):
435
620
        return "<%s %s>" % (type(self).__name__, self.name)
436
621
 
 
622
    def get_permissions(self, user):
 
623
        perms = set()
 
624
        roles = set()
 
625
        if user is not None:
 
626
            if user.admin:
 
627
                perms.add('edit')
 
628
                perms.add('view')
 
629
            elif 'lecturer' in set((e.role for e in user.active_enrolments)):
 
630
                perms.add('edit')
 
631
                perms.add('view')
 
632
            
 
633
        return perms
 
634
    
 
635
    def get_description(self):
 
636
        return rst(self.description)
 
637
 
 
638
    def delete(self):
 
639
        """Deletes the exercise, providing it has no associated worksheets."""
 
640
        if (self.worksheet_exercises.count() > 0):
 
641
            raise IntegrityError()
 
642
        for suite in self.test_suites:
 
643
            suite.delete()
 
644
        Store.of(self).remove(self)
437
645
 
438
646
class Worksheet(Storm):
439
647
    __storm_table__ = "worksheet"
450
658
    attempts = ReferenceSet(id, "ExerciseAttempt.worksheetid")
451
659
    offering = Reference(offering_id, 'Offering.id')
452
660
 
453
 
    # Use worksheet_exercises to get access to the WorksheetExercise objects
454
 
    # binding worksheets to exercises. This is required to access the
 
661
    all_worksheet_exercises = ReferenceSet(id,
 
662
        'WorksheetExercise.worksheet_id')
 
663
 
 
664
    # Use worksheet_exercises to get access to the *active* WorksheetExercise
 
665
    # objects binding worksheets to exercises. This is required to access the
455
666
    # "optional" field.
456
 
    worksheet_exercises = ReferenceSet(id,
457
 
        'WorksheetExercise.worksheet_id')
458
 
        
 
667
 
 
668
    @property
 
669
    def worksheet_exercises(self):
 
670
        return self.all_worksheet_exercises.find(active=True)
459
671
 
460
672
    __init__ = _kwarg_init
461
673
 
462
674
    def __repr__(self):
463
675
        return "<%s %s>" % (type(self).__name__, self.name)
464
676
 
465
 
    # XXX Refactor this - make it an instance method of Subject rather than a
466
 
    # class method of Worksheet. Can't do that now because Subject isn't
467
 
    # linked referentially to the Worksheet.
468
 
    @classmethod
469
 
    def get_by_name(cls, store, subjectname, worksheetname):
470
 
        """
471
 
        Get the Worksheet from the db associated with a given store, subject
472
 
        name and worksheet name.
473
 
        """
474
 
        return store.find(cls, cls.subject == unicode(subjectname),
475
 
            cls.name == unicode(worksheetname)).one()
476
 
 
477
 
    def remove_all_exercises(self, store):
 
677
    def remove_all_exercises(self):
478
678
        """
479
679
        Remove all exercises from this worksheet.
480
680
        This does not delete the exercises themselves. It just removes them
481
681
        from the worksheet.
482
682
        """
 
683
        store = Store.of(self)
 
684
        for ws_ex in self.all_worksheet_exercises:
 
685
            if ws_ex.saves.count() > 0 or ws_ex.attempts.count() > 0:
 
686
                raise IntegrityError()
483
687
        store.find(WorksheetExercise,
484
688
            WorksheetExercise.worksheet == self).remove()
485
689
            
486
690
    def get_permissions(self, user):
487
691
        return self.offering.get_permissions(user)
488
 
 
 
692
    
 
693
    def get_xml(self):
 
694
        """Returns the xml of this worksheet, converts from rst if required."""
 
695
        if self.format == u'rst':
 
696
            ws_xml = rst(self.data)
 
697
            return ws_xml
 
698
        else:
 
699
            return self.data
 
700
    
 
701
    def delete(self):
 
702
        """Deletes the worksheet, provided it has no attempts on any exercises.
 
703
        
 
704
        Returns True if delete succeeded, or False if this worksheet has
 
705
        attempts attached."""
 
706
        for ws_ex in self.all_worksheet_exercises:
 
707
            if ws_ex.saves.count() > 0 or ws_ex.attempts.count() > 0:
 
708
                raise IntegrityError()
 
709
        
 
710
        self.remove_all_exercises()
 
711
        Store.of(self).remove(self)
 
712
        
489
713
class WorksheetExercise(Storm):
490
 
    __storm_table__ = "worksheet_problem"
 
714
    __storm_table__ = "worksheet_exercise"
491
715
    
492
 
    id = Int(primary=True, name="ws_prob_id")
 
716
    id = Int(primary=True, name="ws_ex_id")
493
717
 
494
718
    worksheet_id = Int(name="worksheetid")
495
719
    worksheet = Reference(worksheet_id, Worksheet.id)
496
 
    exercise_id = Unicode(name="problemid")
 
720
    exercise_id = Unicode(name="exerciseid")
497
721
    exercise = Reference(exercise_id, Exercise.id)
498
722
    optional = Bool()
499
723
    active = Bool()
508
732
        return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
509
733
                                  self.worksheet.identifier)
510
734
 
 
735
    def get_permissions(self, user):
 
736
        return self.worksheet.get_permissions(user)
 
737
    
 
738
 
511
739
class ExerciseSave(Storm):
512
740
    """
513
741
    Represents a potential solution to an exercise that a user has submitted
517
745
    ExerciseSave may be extended with additional semantics (such as
518
746
    ExerciseAttempt).
519
747
    """
520
 
    __storm_table__ = "problem_save"
 
748
    __storm_table__ = "exercise_save"
521
749
    __storm_primary__ = "ws_ex_id", "user_id"
522
750
 
523
 
    ws_ex_id = Int(name="ws_prob_id")
 
751
    ws_ex_id = Int(name="ws_ex_id")
524
752
    worksheet_exercise = Reference(ws_ex_id, "WorksheetExercise.id")
525
753
 
526
754
    user_id = Int(name="loginid")
547
775
        they won't count (either as a penalty or success), but will still be
548
776
        stored.
549
777
    """
550
 
    __storm_table__ = "problem_attempt"
 
778
    __storm_table__ = "exercise_attempt"
551
779
    __storm_primary__ = "ws_ex_id", "user_id", "date"
552
780
 
553
781
    # The "text" field is the same but has a different name in the DB table
565
793
    __storm_primary__ = "exercise_id", "suiteid"
566
794
    
567
795
    suiteid = Int()
568
 
    exercise_id = Unicode(name="problemid")
 
796
    exercise_id = Unicode(name="exerciseid")
569
797
    description = Unicode()
570
798
    seq_no = Int()
571
799
    function = Unicode()
572
800
    stdin = Unicode()
573
801
    exercise = Reference(exercise_id, Exercise.id)
574
 
    test_cases = ReferenceSet(suiteid, 'TestCase.suiteid')
575
 
    variables = ReferenceSet(suiteid, 'TestSuiteVar.suiteid')
 
802
    test_cases = ReferenceSet(suiteid, 'TestCase.suiteid', order_by="seq_no")
 
803
    variables = ReferenceSet(suiteid, 'TestSuiteVar.suiteid', order_by='arg_no')
 
804
    
 
805
    def delete(self):
 
806
        """Delete this suite, without asking questions."""
 
807
        for vaariable in self.variables:
 
808
            variable.delete()
 
809
        for test_case in self.test_cases:
 
810
            test_case.delete()
 
811
        Store.of(self).remove(self)
576
812
 
577
813
class TestCase(Storm):
578
814
    """A TestCase is a member of a TestSuite.
592
828
    parts = ReferenceSet(testid, "TestCasePart.testid")
593
829
    
594
830
    __init__ = _kwarg_init
 
831
    
 
832
    def delete(self):
 
833
        for part in self.parts:
 
834
            part.delete()
 
835
        Store.of(self).remove(self)
595
836
 
596
837
class TestSuiteVar(Storm):
597
838
    """A container for the arguments of a Test Suite"""
598
 
    __storm_table__ = "suite_variables"
 
839
    __storm_table__ = "suite_variable"
599
840
    __storm_primary__ = "varid"
600
841
    
601
842
    varid = Int()
609
850
    
610
851
    __init__ = _kwarg_init
611
852
    
 
853
    def delete(self):
 
854
        Store.of(self).remove(self)
 
855
    
612
856
class TestCasePart(Storm):
613
857
    """A container for the test elements of a Test Case"""
614
 
    __storm_table__ = "test_case_parts"
 
858
    __storm_table__ = "test_case_part"
615
859
    __storm_primary__ = "partid"
616
860
    
617
861
    partid = Int()
625
869
    test = Reference(testid, "TestCase.testid")
626
870
    
627
871
    __init__ = _kwarg_init
 
872
    
 
873
    def delete(self):
 
874
        Store.of(self).remove(self)