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

« back to all changes in this revision

Viewing changes to ivle/database.py

  • Committer: Matt Giuca
  • Date: 2010-07-20 09:42:45 UTC
  • Revision ID: matt.giuca@gmail.com-20100720094245-0poipwrxm9tde8et
TextView: Removed constructor (it just called its superclass ctor).

Show diffs side-by-side

added added

removed removed

Lines of Context:
25
25
 
26
26
import hashlib
27
27
import datetime
 
28
import os
 
29
import urlparse
 
30
import urllib
28
31
 
29
32
from storm.locals import create_database, Store, Int, Unicode, DateTime, \
30
33
                         Reference, ReferenceSet, Bool, Storm, Desc
215
218
            Semester.id == Offering.semester_id,
216
219
            (not active_only) or (Semester.state == u'current'),
217
220
            Enrolment.offering_id == Offering.id,
218
 
            Enrolment.user_id == self.id)
 
221
            Enrolment.user_id == self.id,
 
222
            Enrolment.active == True)
219
223
 
220
224
    @staticmethod
221
225
    def hash_password(password):
227
231
        """Find a user in a store by login name."""
228
232
        return store.find(cls, cls.login == unicode(login)).one()
229
233
 
230
 
    def get_permissions(self, user):
 
234
    def get_svn_url(self, config):
 
235
        """Get the subversion repository URL for this user or group."""
 
236
        url = config['urls']['svn_addr']
 
237
        path = 'users/%s' % self.login
 
238
        return urlparse.urljoin(url, path)
 
239
 
 
240
    def get_permissions(self, user, config):
231
241
        """Determine privileges held by a user over this object.
232
242
 
233
243
        If the user requesting privileges is this user or an admin,
234
244
        they may do everything. Otherwise they may do nothing.
235
245
        """
236
246
        if user and user.admin or user is self:
237
 
            return set(['view', 'edit', 'submit_project'])
 
247
            return set(['view_public', 'view', 'edit', 'submit_project'])
238
248
        else:
239
 
            return set()
 
249
            return set(['view_public'])
240
250
 
241
251
# SUBJECTS AND ENROLMENTS #
242
252
 
249
259
    code = Unicode(name="subj_code")
250
260
    name = Unicode(name="subj_name")
251
261
    short_name = Unicode(name="subj_short_name")
252
 
    url = Unicode()
253
262
 
254
263
    offerings = ReferenceSet(id, 'Offering.subject_id')
255
264
 
258
267
    def __repr__(self):
259
268
        return "<%s '%s'>" % (type(self).__name__, self.short_name)
260
269
 
261
 
    def get_permissions(self, user):
 
270
    def get_permissions(self, user, config):
262
271
        """Determine privileges held by a user over this object.
263
272
 
264
273
        If the user requesting privileges is an admin, they may edit.
322
331
    subject = Reference(subject_id, Subject.id)
323
332
    semester_id = Int(name="semesterid")
324
333
    semester = Reference(semester_id, Semester.id)
 
334
    description = Unicode()
 
335
    url = Unicode()
 
336
    show_worksheet_marks = Bool()
 
337
    worksheet_cutoff = DateTime()
325
338
    groups_student_permissions = Unicode()
326
339
 
327
340
    enrolments = ReferenceSet(id, 'Enrolment.offering_id')
330
343
                           'Enrolment.user_id',
331
344
                           'User.id')
332
345
    project_sets = ReferenceSet(id, 'ProjectSet.offering_id')
 
346
    projects = ReferenceSet(id,
 
347
                            'ProjectSet.offering_id',
 
348
                            'ProjectSet.id',
 
349
                            'Project.project_set_id')
333
350
 
334
351
    worksheets = ReferenceSet(id, 
335
352
        'Worksheet.offering_id', 
366
383
                               Enrolment.offering_id == self.id).one()
367
384
        Store.of(enrolment).remove(enrolment)
368
385
 
369
 
    def get_permissions(self, user):
 
386
    def get_permissions(self, user, config):
370
387
        perms = set()
371
388
        if user is not None:
372
389
            enrolment = self.get_enrolment(user)
373
390
            if enrolment or user.admin:
374
391
                perms.add('view')
375
 
            if (enrolment and enrolment.role in (u'tutor', u'lecturer')) \
376
 
               or user.admin:
377
 
                perms.add('edit')
 
392
            if enrolment and enrolment.role == u'tutor':
 
393
                perms.add('view_project_submissions')
 
394
                # Site-specific policy on the role of tutors
 
395
                if config['policy']['tutors_can_enrol_students']:
 
396
                    perms.add('enrol')
 
397
                    perms.add('enrol_student')
 
398
                if config['policy']['tutors_can_edit_worksheets']:
 
399
                    perms.add('edit_worksheets')
 
400
                if config['policy']['tutors_can_admin_groups']:
 
401
                    perms.add('admin_groups')
 
402
            if (enrolment and enrolment.role in (u'lecturer')) or user.admin:
 
403
                perms.add('view_project_submissions')
 
404
                perms.add('admin_groups')
 
405
                perms.add('edit_worksheets')
 
406
                perms.add('view_worksheet_marks')
 
407
                perms.add('edit')           # Can edit projects & details
 
408
                perms.add('enrol')          # Can see enrolment screen at all
 
409
                perms.add('enrol_student')  # Can enrol students
 
410
                perms.add('enrol_tutor')    # Can enrol tutors
 
411
            if user.admin:
 
412
                perms.add('enrol_lecturer') # Can enrol lecturers
378
413
        return perms
379
414
 
380
415
    def get_enrolment(self, user):
391
426
                Enrolment.user_id == User.id,
392
427
                Enrolment.offering_id == self.id,
393
428
                Enrolment.role == role
394
 
                )
 
429
                ).order_by(User.login)
395
430
 
396
431
    @property
397
432
    def students(self):
398
433
        return self.get_members_by_role(u'student')
399
434
 
 
435
    def get_open_projects_for_user(self, user):
 
436
        """Find all projects currently open to submissions by a user."""
 
437
        # XXX: Respect extensions.
 
438
        return self.projects.find(Project.deadline > datetime.datetime.now())
 
439
 
 
440
    def has_worksheet_cutoff_passed(self, user):
 
441
        """Check whether the worksheet cutoff has passed.
 
442
        A user is required, in case we support extensions.
 
443
        """
 
444
        if self.worksheet_cutoff is None:
 
445
            return False
 
446
        else:
 
447
            return self.worksheet_cutoff < datetime.datetime.now()
 
448
 
 
449
    def clone_worksheets(self, source):
 
450
        """Clone all worksheets from the specified source to this offering."""
 
451
        import ivle.worksheet.utils
 
452
        for worksheet in source.worksheets:
 
453
            newws = Worksheet()
 
454
            newws.seq_no = worksheet.seq_no
 
455
            newws.identifier = worksheet.identifier
 
456
            newws.name = worksheet.name
 
457
            newws.assessable = worksheet.assessable
 
458
            newws.published = worksheet.published
 
459
            newws.data = worksheet.data
 
460
            newws.format = worksheet.format
 
461
            newws.offering = self
 
462
            Store.of(self).add(newws)
 
463
            ivle.worksheet.utils.update_exerciselist(newws)
 
464
 
 
465
 
400
466
class Enrolment(Storm):
401
467
    """An enrolment of a user in an offering.
402
468
 
428
494
        return "<%s %r in %r>" % (type(self).__name__, self.user,
429
495
                                  self.offering)
430
496
 
 
497
    def get_permissions(self, user, config):
 
498
        # A user can edit any enrolment that they could have created.
 
499
        perms = set()
 
500
        if ('enrol_' + str(self.role)) in self.offering.get_permissions(
 
501
            user, config):
 
502
            perms.add('edit')
 
503
        return perms
 
504
 
 
505
    def delete(self):
 
506
        """Delete this enrolment."""
 
507
        Store.of(self).remove(self)
 
508
 
 
509
 
431
510
# PROJECTS #
432
511
 
433
512
class ProjectSet(Storm):
453
532
        return "<%s %d in %r>" % (type(self).__name__, self.id,
454
533
                                  self.offering)
455
534
 
456
 
    def get_permissions(self, user):
457
 
        return self.offering.get_permissions(user)
 
535
    def get_permissions(self, user, config):
 
536
        return self.offering.get_permissions(user, config)
 
537
 
 
538
    def get_groups_for_user(self, user):
 
539
        """List all groups in this offering of which the user is a member."""
 
540
        assert self.is_group
 
541
        return Store.of(self).find(
 
542
            ProjectGroup,
 
543
            ProjectGroupMembership.user_id == user.id,
 
544
            ProjectGroupMembership.project_group_id == ProjectGroup.id,
 
545
            ProjectGroup.project_set_id == self.id)
 
546
 
 
547
    def get_submission_principal(self, user):
 
548
        """Get the principal on behalf of which the user can submit.
 
549
 
 
550
        If this is a solo project set, the given user is returned. If
 
551
        the user is a member of exactly one group, all the group is
 
552
        returned. Otherwise, None is returned.
 
553
        """
 
554
        if self.is_group:
 
555
            groups = self.get_groups_for_user(user)
 
556
            if groups.count() == 1:
 
557
                return groups.one()
 
558
            else:
 
559
                return None
 
560
        else:
 
561
            return user
 
562
 
 
563
    @property
 
564
    def is_group(self):
 
565
        return self.max_students_per_group is not None
458
566
 
459
567
    @property
460
568
    def assigned(self):
463
571
        This will be a Storm ResultSet.
464
572
        """
465
573
        #If its a solo project, return everyone in offering
466
 
        if self.max_students_per_group is None:
 
574
        if self.is_group:
 
575
            return self.project_groups
 
576
        else:
467
577
            return self.offering.students
468
 
        else:
469
 
            return self.project_groups
 
578
 
 
579
class DeadlinePassed(Exception):
 
580
    """An exception indicating that a project cannot be submitted because the
 
581
    deadline has passed."""
 
582
    def __init__(self):
 
583
        pass
 
584
    def __str__(self):
 
585
        return "The project deadline has passed"
470
586
 
471
587
class Project(Storm):
472
588
    """A student project for which submissions can be made."""
494
610
        return "<%s '%s' in %r>" % (type(self).__name__, self.short_name,
495
611
                                  self.project_set.offering)
496
612
 
497
 
    def can_submit(self, principal):
 
613
    def can_submit(self, principal, user):
498
614
        return (self in principal.get_projects() and
499
 
                self.deadline > datetime.datetime.now())
 
615
                not self.has_deadline_passed(user))
500
616
 
501
617
    def submit(self, principal, path, revision, who):
502
618
        """Submit a Subversion path and revision to a project.
508
624
        @param who: The user who is actually making the submission.
509
625
        """
510
626
 
511
 
        if not self.can_submit(principal):
512
 
            raise Exception('cannot submit')
 
627
        if not self.can_submit(principal, who):
 
628
            raise DeadlinePassed()
513
629
 
514
630
        a = Assessed.get(Store.of(self), principal, self)
515
631
        ps = ProjectSubmission()
516
 
        ps.path = path
 
632
        # Raise SubmissionError if the path is illegal
 
633
        ps.path = ProjectSubmission.test_and_normalise_path(path)
517
634
        ps.revision = revision
518
635
        ps.date_submitted = datetime.datetime.now()
519
636
        ps.assessed = a
521
638
 
522
639
        return ps
523
640
 
524
 
    def get_permissions(self, user):
525
 
        return self.project_set.offering.get_permissions(user)
 
641
    def get_permissions(self, user, config):
 
642
        return self.project_set.offering.get_permissions(user, config)
526
643
 
527
644
    @property
528
645
    def latest_submissions(self):
537
654
            )
538
655
        )
539
656
 
 
657
    def has_deadline_passed(self, user):
 
658
        """Check whether the deadline has passed."""
 
659
        # XXX: Need to respect extensions.
 
660
        return self.deadline < datetime.datetime.now()
 
661
 
 
662
    def get_submissions_for_principal(self, principal):
 
663
        """Fetch a ResultSet of all submissions by a particular principal."""
 
664
        assessed = Assessed.get(Store.of(self), principal, self)
 
665
        if assessed is None:
 
666
            return
 
667
        return assessed.submissions
 
668
 
 
669
    @property
 
670
    def can_delete(self):
 
671
        """Can only delete if there are no submissions."""
 
672
        return self.submissions.count() == 0
 
673
 
 
674
    def delete(self):
 
675
        """Delete the project. Fails if can_delete is False."""
 
676
        if not self.can_delete:
 
677
            raise IntegrityError()
 
678
        for assessed in self.assesseds:
 
679
            assessed.delete()
 
680
        Store.of(self).remove(self)
540
681
 
541
682
class ProjectGroup(Storm):
542
683
    """A group of students working together on a project."""
592
733
            Semester.id == Offering.semester_id,
593
734
            (not active_only) or (Semester.state == u'current'))
594
735
 
 
736
    def get_svn_url(self, config):
 
737
        """Get the subversion repository URL for this user or group."""
 
738
        url = config['urls']['svn_addr']
 
739
        path = 'groups/%s_%s_%s_%s' % (
 
740
                self.project_set.offering.subject.short_name,
 
741
                self.project_set.offering.semester.year,
 
742
                self.project_set.offering.semester.semester,
 
743
                self.name
 
744
                )
 
745
        return urlparse.urljoin(url, path)
595
746
 
596
 
    def get_permissions(self, user):
 
747
    def get_permissions(self, user, config):
597
748
        if user.admin or user in self.members:
598
749
            return set(['submit_project'])
599
750
        else:
635
786
    project = Reference(project_id, Project.id)
636
787
 
637
788
    extensions = ReferenceSet(id, 'ProjectExtension.assessed_id')
638
 
    submissions = ReferenceSet(id, 'ProjectSubmission.assessed_id')
 
789
    submissions = ReferenceSet(
 
790
        id, 'ProjectSubmission.assessed_id', order_by='date_submitted')
639
791
 
640
792
    def __repr__(self):
641
793
        return "<%s %r in %r>" % (type(self).__name__,
650
802
    def principal(self):
651
803
        return self.project_group or self.user
652
804
 
 
805
    @property
 
806
    def checkout_location(self):
 
807
        """Returns the location of the Subversion workspace for this piece of
 
808
        assessment, relative to each group member's home directory."""
 
809
        subjectname = self.project.project_set.offering.subject.short_name
 
810
        if self.is_group:
 
811
            checkout_dir_name = self.principal.short_name
 
812
        else:
 
813
            checkout_dir_name = "mywork"
 
814
        return subjectname + "/" + checkout_dir_name
 
815
 
653
816
    @classmethod
654
817
    def get(cls, store, principal, project):
655
818
        """Find or create an Assessed for the given user or group and project.
664
827
        a = store.find(cls,
665
828
            (t is User) or (cls.project_group_id == principal.id),
666
829
            (t is ProjectGroup) or (cls.user_id == principal.id),
667
 
            Project.id == project.id).one()
 
830
            cls.project_id == project.id).one()
668
831
 
669
832
        if a is None:
670
833
            a = cls()
677
840
 
678
841
        return a
679
842
 
 
843
    def delete(self):
 
844
        """Delete the assessed. Fails if there are any submissions. Deletes
 
845
        extensions."""
 
846
        if self.submissions.count() > 0:
 
847
            raise IntegrityError()
 
848
        for extension in self.extensions:
 
849
            extension.delete()
 
850
        Store.of(self).remove(self)
680
851
 
681
852
class ProjectExtension(Storm):
682
853
    """An extension granted to a user or group on a particular project.
694
865
    approver = Reference(approver_id, User.id)
695
866
    notes = Unicode()
696
867
 
 
868
    def delete(self):
 
869
        """Delete the extension."""
 
870
        Store.of(self).remove(self)
 
871
 
 
872
class SubmissionError(Exception):
 
873
    """Denotes a validation error during submission."""
 
874
    pass
 
875
 
697
876
class ProjectSubmission(Storm):
698
877
    """A submission from a user or group repository to a particular project.
699
878
 
715
894
    submitter = Reference(submitter_id, User.id)
716
895
    date_submitted = DateTime()
717
896
 
 
897
    def get_verify_url(self, user):
 
898
        """Get the URL for verifying this submission, within the account of
 
899
        the given user."""
 
900
        # If this is a solo project, then self.path will be prefixed with the
 
901
        # subject name. Remove the first path segment.
 
902
        submitpath = self.path[1:] if self.path[:1] == '/' else self.path
 
903
        if not self.assessed.is_group:
 
904
            if '/' in submitpath:
 
905
                submitpath = submitpath.split('/', 1)[1]
 
906
            else:
 
907
                submitpath = ''
 
908
        return "/files/%s/%s/%s?r=%d" % (user.login,
 
909
            self.assessed.checkout_location, submitpath, self.revision)
 
910
 
 
911
    def get_svn_url(self, config):
 
912
        """Get subversion URL for this submission"""
 
913
        princ = self.assessed.principal
 
914
        base = princ.get_svn_url(config)
 
915
        if self.path.startswith(os.sep):
 
916
            return os.path.join(base,
 
917
                    urllib.quote(self.path[1:].encode('utf-8')))
 
918
        else:
 
919
            return os.path.join(base, urllib.quote(self.path.encode('utf-8')))
 
920
 
 
921
    def get_svn_export_command(self, req):
 
922
        """Returns a Unix shell command to export a submission"""
 
923
        svn_url = self.get_svn_url(req.config)
 
924
        username = (req.user.login if req.user.login.isalnum() else
 
925
                "'%s'"%req.user.login)
 
926
        export_dir = self.assessed.principal.short_name
 
927
        return "svn export --username %s -r%d '%s' %s"%(req.user.login,
 
928
                self.revision, svn_url, export_dir)
 
929
 
 
930
    @staticmethod
 
931
    def test_and_normalise_path(path):
 
932
        """Test that path is valid, and normalise it. This prevents possible
 
933
        injections using malicious paths.
 
934
        Returns the updated path, if successful.
 
935
        Raises SubmissionError if invalid.
 
936
        """
 
937
        # Ensure the path is absolute to prevent being tacked onto working
 
938
        # directories.
 
939
        # Prevent '\n' because it will break all sorts of things.
 
940
        # Prevent '[' and ']' because they can be used to inject into the
 
941
        # svn.conf.
 
942
        # Normalise to avoid resulting in ".." path segments.
 
943
        if not os.path.isabs(path):
 
944
            raise SubmissionError("Path is not absolute")
 
945
        if any(c in path for c in "\n[]"):
 
946
            raise SubmissionError("Path must not contain '\\n', '[' or ']'")
 
947
        return os.path.normpath(path)
718
948
 
719
949
# WORKSHEETS AND EXERCISES #
720
950
 
728
958
    id = Unicode(primary=True, name="identifier")
729
959
    name = Unicode()
730
960
    description = Unicode()
 
961
    _description_xhtml_cache = Unicode(name='description_xhtml_cache')
731
962
    partial = Unicode()
732
963
    solution = Unicode()
733
964
    include = Unicode()
751
982
    def __repr__(self):
752
983
        return "<%s %s>" % (type(self).__name__, self.name)
753
984
 
754
 
    def get_permissions(self, user):
 
985
    def get_permissions(self, user, config):
 
986
        return self.global_permissions(user, config)
 
987
 
 
988
    @staticmethod
 
989
    def global_permissions(user, config):
 
990
        """Gets the set of permissions this user has over *all* exercises.
 
991
        This is used to determine who may view the exercises list, and create
 
992
        new exercises."""
755
993
        perms = set()
756
994
        roles = set()
757
995
        if user is not None:
761
999
            elif u'lecturer' in set((e.role for e in user.active_enrolments)):
762
1000
                perms.add('edit')
763
1001
                perms.add('view')
764
 
            elif u'tutor' in set((e.role for e in user.active_enrolments)):
 
1002
            elif (config['policy']['tutors_can_edit_worksheets']
 
1003
            and u'tutor' in set((e.role for e in user.active_enrolments))):
 
1004
                # Site-specific policy on the role of tutors
765
1005
                perms.add('edit')
766
1006
                perms.add('view')
767
1007
 
768
1008
        return perms
769
1009
 
770
 
    def get_description(self):
771
 
        """Return the description interpreted as reStructuredText."""
772
 
        return rst(self.description)
 
1010
    def _cache_description_xhtml(self, invalidate=False):
 
1011
        # Don't regenerate an existing cache unless forced.
 
1012
        if self._description_xhtml_cache is not None and not invalidate:
 
1013
            return
 
1014
 
 
1015
        if self.description:
 
1016
            self._description_xhtml_cache = rst(self.description)
 
1017
        else:
 
1018
            self._description_xhtml_cache = None
 
1019
 
 
1020
    @property
 
1021
    def description_xhtml(self):
 
1022
        """The XHTML exercise description, converted from reStructuredText."""
 
1023
        self._cache_description_xhtml()
 
1024
        return self._description_xhtml_cache
 
1025
 
 
1026
    def set_description(self, description):
 
1027
        self.description = description
 
1028
        self._cache_description_xhtml(invalidate=True)
773
1029
 
774
1030
    def delete(self):
775
1031
        """Deletes the exercise, providing it has no associated worksheets."""
792
1048
    identifier = Unicode()
793
1049
    name = Unicode()
794
1050
    assessable = Bool()
 
1051
    published = Bool()
795
1052
    data = Unicode()
 
1053
    _data_xhtml_cache = Unicode(name='data_xhtml_cache')
796
1054
    seq_no = Int()
797
1055
    format = Unicode()
798
1056
 
828
1086
        store.find(WorksheetExercise,
829
1087
            WorksheetExercise.worksheet == self).remove()
830
1088
 
831
 
    def get_permissions(self, user):
832
 
        return self.offering.get_permissions(user)
833
 
 
834
 
    def get_xml(self):
835
 
        """Returns the xml of this worksheet, converts from rst if required."""
836
 
        if self.format == u'rst':
837
 
            ws_xml = rst(self.data)
838
 
            return ws_xml
 
1089
    def get_permissions(self, user, config):
 
1090
        offering_perms = self.offering.get_permissions(user, config)
 
1091
 
 
1092
        perms = set()
 
1093
 
 
1094
        # Anybody who can view an offering can view a published
 
1095
        # worksheet.
 
1096
        if 'view' in offering_perms and self.published:
 
1097
            perms.add('view')
 
1098
 
 
1099
        # Any worksheet editors can both view and edit.
 
1100
        if 'edit_worksheets' in offering_perms:
 
1101
            perms.add('view')
 
1102
            perms.add('edit')
 
1103
 
 
1104
        return perms
 
1105
 
 
1106
    def _cache_data_xhtml(self, invalidate=False):
 
1107
        # Don't regenerate an existing cache unless forced.
 
1108
        if self._data_xhtml_cache is not None and not invalidate:
 
1109
            return
 
1110
 
 
1111
        if self.format == u'rst':
 
1112
            self._data_xhtml_cache = rst(self.data)
 
1113
        else:
 
1114
            self._data_xhtml_cache = None
 
1115
 
 
1116
    @property
 
1117
    def data_xhtml(self):
 
1118
        """The XHTML of this worksheet, converted from rST if required."""
 
1119
        # Update the rST -> XHTML cache, if required.
 
1120
        self._cache_data_xhtml()
 
1121
 
 
1122
        if self.format == u'rst':
 
1123
            return self._data_xhtml_cache
839
1124
        else:
840
1125
            return self.data
841
1126
 
 
1127
    def set_data(self, data):
 
1128
        self.data = data
 
1129
        self._cache_data_xhtml(invalidate=True)
 
1130
 
842
1131
    def delete(self):
843
1132
        """Deletes the worksheet, provided it has no attempts on any exercises.
844
1133
 
880
1169
        return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
881
1170
                                  self.worksheet.identifier)
882
1171
 
883
 
    def get_permissions(self, user):
884
 
        return self.worksheet.get_permissions(user)
 
1172
    def get_permissions(self, user, config):
 
1173
        return self.worksheet.get_permissions(user, config)
885
1174
 
886
1175
 
887
1176
class ExerciseSave(Storm):
906
1195
 
907
1196
    def __repr__(self):
908
1197
        return "<%s %s by %s at %s>" % (type(self).__name__,
909
 
            self.exercise.name, self.user.login, self.date.strftime("%c"))
 
1198
            self.worksheet_exercise.exercise.name, self.user.login,
 
1199
            self.date.strftime("%c"))
910
1200
 
911
1201
class ExerciseAttempt(ExerciseSave):
912
1202
    """An attempt at solving an exercise.
934
1224
    complete = Bool()
935
1225
    active = Bool()
936
1226
 
937
 
    def get_permissions(self, user):
 
1227
    def get_permissions(self, user, config):
938
1228
        return set(['view']) if user is self.user else set()
939
1229
 
940
1230
class TestSuite(Storm):
959
1249
 
960
1250
    def delete(self):
961
1251
        """Delete this suite, without asking questions."""
962
 
        for vaariable in self.variables:
 
1252
        for variable in self.variables:
963
1253
            variable.delete()
964
1254
        for test_case in self.test_cases:
965
1255
            test_case.delete()
978
1268
    suite = Reference(suiteid, "TestSuite.suiteid")
979
1269
    passmsg = Unicode()
980
1270
    failmsg = Unicode()
981
 
    test_default = Unicode()
 
1271
    test_default = Unicode() # Currently unused - only used for file matching.
982
1272
    seq_no = Int()
983
1273
 
984
1274
    parts = ReferenceSet(testid, "TestCasePart.testid")