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

« back to all changes in this revision

Viewing changes to ivle/database.py

  • Committer: William Grant
  • Date: 2009-06-29 01:55:55 UTC
  • mto: This revision was merged to the branch mainline in revision 1322.
  • Revision ID: grantw@unimelb.edu.au-20090629015555-49xxv0piiieunx8e
Add docs on configuring Apache.

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
31
28
 
32
29
from storm.locals import create_database, Store, Int, Unicode, DateTime, \
33
30
                         Reference, ReferenceSet, Bool, Storm, Desc
218
215
            Semester.id == Offering.semester_id,
219
216
            (not active_only) or (Semester.state == u'current'),
220
217
            Enrolment.offering_id == Offering.id,
221
 
            Enrolment.user_id == self.id,
222
 
            Enrolment.active == True)
 
218
            Enrolment.user_id == self.id)
223
219
 
224
220
    @staticmethod
225
221
    def hash_password(password):
231
227
        """Find a user in a store by login name."""
232
228
        return store.find(cls, cls.login == unicode(login)).one()
233
229
 
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):
 
230
    def get_permissions(self, user):
241
231
        """Determine privileges held by a user over this object.
242
232
 
243
233
        If the user requesting privileges is this user or an admin,
244
234
        they may do everything. Otherwise they may do nothing.
245
235
        """
246
236
        if user and user.admin or user is self:
247
 
            return set(['view_public', 'view', 'edit', 'submit_project'])
 
237
            return set(['view', 'edit', 'submit_project'])
248
238
        else:
249
 
            return set(['view_public'])
 
239
            return set()
250
240
 
251
241
# SUBJECTS AND ENROLMENTS #
252
242
 
259
249
    code = Unicode(name="subj_code")
260
250
    name = Unicode(name="subj_name")
261
251
    short_name = Unicode(name="subj_short_name")
 
252
    url = Unicode()
262
253
 
263
254
    offerings = ReferenceSet(id, 'Offering.subject_id')
264
255
 
267
258
    def __repr__(self):
268
259
        return "<%s '%s'>" % (type(self).__name__, self.short_name)
269
260
 
270
 
    def get_permissions(self, user, config):
 
261
    def get_permissions(self, user):
271
262
        """Determine privileges held by a user over this object.
272
263
 
273
264
        If the user requesting privileges is an admin, they may edit.
331
322
    subject = Reference(subject_id, Subject.id)
332
323
    semester_id = Int(name="semesterid")
333
324
    semester = Reference(semester_id, Semester.id)
334
 
    description = Unicode()
335
 
    url = Unicode()
336
 
    show_worksheet_marks = Bool()
337
 
    worksheet_cutoff = DateTime()
338
325
    groups_student_permissions = Unicode()
339
326
 
340
327
    enrolments = ReferenceSet(id, 'Enrolment.offering_id')
343
330
                           'Enrolment.user_id',
344
331
                           'User.id')
345
332
    project_sets = ReferenceSet(id, 'ProjectSet.offering_id')
346
 
    projects = ReferenceSet(id,
347
 
                            'ProjectSet.offering_id',
348
 
                            'ProjectSet.id',
349
 
                            'Project.project_set_id')
350
333
 
351
334
    worksheets = ReferenceSet(id, 
352
335
        'Worksheet.offering_id', 
383
366
                               Enrolment.offering_id == self.id).one()
384
367
        Store.of(enrolment).remove(enrolment)
385
368
 
386
 
    def get_permissions(self, user, config):
 
369
    def get_permissions(self, user):
387
370
        perms = set()
388
371
        if user is not None:
389
372
            enrolment = self.get_enrolment(user)
390
373
            if enrolment or user.admin:
391
374
                perms.add('view')
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
 
375
            if (enrolment and enrolment.role in (u'tutor', u'lecturer')) \
 
376
               or user.admin:
 
377
                perms.add('edit')
413
378
        return perms
414
379
 
415
380
    def get_enrolment(self, user):
426
391
                Enrolment.user_id == User.id,
427
392
                Enrolment.offering_id == self.id,
428
393
                Enrolment.role == role
429
 
                ).order_by(User.login)
 
394
                )
430
395
 
431
396
    @property
432
397
    def students(self):
433
398
        return self.get_members_by_role(u'student')
434
399
 
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
 
 
466
400
class Enrolment(Storm):
467
401
    """An enrolment of a user in an offering.
468
402
 
494
428
        return "<%s %r in %r>" % (type(self).__name__, self.user,
495
429
                                  self.offering)
496
430
 
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
 
 
510
431
# PROJECTS #
511
432
 
512
433
class ProjectSet(Storm):
532
453
        return "<%s %d in %r>" % (type(self).__name__, self.id,
533
454
                                  self.offering)
534
455
 
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
 
456
    def get_permissions(self, user):
 
457
        return self.offering.get_permissions(user)
566
458
 
567
459
    @property
568
460
    def assigned(self):
571
463
        This will be a Storm ResultSet.
572
464
        """
573
465
        #If its a solo project, return everyone in offering
574
 
        if self.is_group:
 
466
        if self.max_students_per_group is None:
 
467
            return self.offering.students
 
468
        else:
575
469
            return self.project_groups
576
 
        else:
577
 
            return self.offering.students
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"
586
470
 
587
471
class Project(Storm):
588
472
    """A student project for which submissions can be made."""
610
494
        return "<%s '%s' in %r>" % (type(self).__name__, self.short_name,
611
495
                                  self.project_set.offering)
612
496
 
613
 
    def can_submit(self, principal, user):
 
497
    def can_submit(self, principal):
614
498
        return (self in principal.get_projects() and
615
 
                not self.has_deadline_passed(user))
 
499
                self.deadline > datetime.datetime.now())
616
500
 
617
501
    def submit(self, principal, path, revision, who):
618
502
        """Submit a Subversion path and revision to a project.
624
508
        @param who: The user who is actually making the submission.
625
509
        """
626
510
 
627
 
        if not self.can_submit(principal, who):
628
 
            raise DeadlinePassed()
 
511
        if not self.can_submit(principal):
 
512
            raise Exception('cannot submit')
629
513
 
630
514
        a = Assessed.get(Store.of(self), principal, self)
631
515
        ps = ProjectSubmission()
632
 
        # Raise SubmissionError if the path is illegal
633
 
        ps.path = ProjectSubmission.test_and_normalise_path(path)
 
516
        ps.path = path
634
517
        ps.revision = revision
635
518
        ps.date_submitted = datetime.datetime.now()
636
519
        ps.assessed = a
638
521
 
639
522
        return ps
640
523
 
641
 
    def get_permissions(self, user, config):
642
 
        return self.project_set.offering.get_permissions(user, config)
 
524
    def get_permissions(self, user):
 
525
        return self.project_set.offering.get_permissions(user)
643
526
 
644
527
    @property
645
528
    def latest_submissions(self):
654
537
            )
655
538
        )
656
539
 
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)
681
540
 
682
541
class ProjectGroup(Storm):
683
542
    """A group of students working together on a project."""
733
592
            Semester.id == Offering.semester_id,
734
593
            (not active_only) or (Semester.state == u'current'))
735
594
 
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)
746
595
 
747
 
    def get_permissions(self, user, config):
 
596
    def get_permissions(self, user):
748
597
        if user.admin or user in self.members:
749
598
            return set(['submit_project'])
750
599
        else:
786
635
    project = Reference(project_id, Project.id)
787
636
 
788
637
    extensions = ReferenceSet(id, 'ProjectExtension.assessed_id')
789
 
    submissions = ReferenceSet(
790
 
        id, 'ProjectSubmission.assessed_id', order_by='date_submitted')
 
638
    submissions = ReferenceSet(id, 'ProjectSubmission.assessed_id')
791
639
 
792
640
    def __repr__(self):
793
641
        return "<%s %r in %r>" % (type(self).__name__,
802
650
    def principal(self):
803
651
        return self.project_group or self.user
804
652
 
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
 
 
816
653
    @classmethod
817
654
    def get(cls, store, principal, project):
818
655
        """Find or create an Assessed for the given user or group and project.
827
664
        a = store.find(cls,
828
665
            (t is User) or (cls.project_group_id == principal.id),
829
666
            (t is ProjectGroup) or (cls.user_id == principal.id),
830
 
            cls.project_id == project.id).one()
 
667
            Project.id == project.id).one()
831
668
 
832
669
        if a is None:
833
670
            a = cls()
840
677
 
841
678
        return a
842
679
 
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)
851
680
 
852
681
class ProjectExtension(Storm):
853
682
    """An extension granted to a user or group on a particular project.
865
694
    approver = Reference(approver_id, User.id)
866
695
    notes = Unicode()
867
696
 
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
 
 
876
697
class ProjectSubmission(Storm):
877
698
    """A submission from a user or group repository to a particular project.
878
699
 
894
715
    submitter = Reference(submitter_id, User.id)
895
716
    date_submitted = DateTime()
896
717
 
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)
948
718
 
949
719
# WORKSHEETS AND EXERCISES #
950
720
 
958
728
    id = Unicode(primary=True, name="identifier")
959
729
    name = Unicode()
960
730
    description = Unicode()
961
 
    _description_xhtml_cache = Unicode(name='description_xhtml_cache')
962
731
    partial = Unicode()
963
732
    solution = Unicode()
964
733
    include = Unicode()
982
751
    def __repr__(self):
983
752
        return "<%s %s>" % (type(self).__name__, self.name)
984
753
 
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."""
 
754
    def get_permissions(self, user):
993
755
        perms = set()
994
756
        roles = set()
995
757
        if user is not None:
999
761
            elif u'lecturer' in set((e.role for e in user.active_enrolments)):
1000
762
                perms.add('edit')
1001
763
                perms.add('view')
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
 
764
            elif u'tutor' in set((e.role for e in user.active_enrolments)):
1005
765
                perms.add('edit')
1006
766
                perms.add('view')
1007
767
 
1008
768
        return perms
1009
769
 
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)
 
770
    def get_description(self):
 
771
        """Return the description interpreted as reStructuredText."""
 
772
        return rst(self.description)
1029
773
 
1030
774
    def delete(self):
1031
775
        """Deletes the exercise, providing it has no associated worksheets."""
1048
792
    identifier = Unicode()
1049
793
    name = Unicode()
1050
794
    assessable = Bool()
1051
 
    published = Bool()
1052
795
    data = Unicode()
1053
 
    _data_xhtml_cache = Unicode(name='data_xhtml_cache')
1054
796
    seq_no = Int()
1055
797
    format = Unicode()
1056
798
 
1086
828
        store.find(WorksheetExercise,
1087
829
            WorksheetExercise.worksheet == self).remove()
1088
830
 
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
 
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
1124
839
        else:
1125
840
            return self.data
1126
841
 
1127
 
    def set_data(self, data):
1128
 
        self.data = data
1129
 
        self._cache_data_xhtml(invalidate=True)
1130
 
 
1131
842
    def delete(self):
1132
843
        """Deletes the worksheet, provided it has no attempts on any exercises.
1133
844
 
1169
880
        return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
1170
881
                                  self.worksheet.identifier)
1171
882
 
1172
 
    def get_permissions(self, user, config):
1173
 
        return self.worksheet.get_permissions(user, config)
 
883
    def get_permissions(self, user):
 
884
        return self.worksheet.get_permissions(user)
1174
885
 
1175
886
 
1176
887
class ExerciseSave(Storm):
1195
906
 
1196
907
    def __repr__(self):
1197
908
        return "<%s %s by %s at %s>" % (type(self).__name__,
1198
 
            self.worksheet_exercise.exercise.name, self.user.login,
1199
 
            self.date.strftime("%c"))
 
909
            self.exercise.name, self.user.login, self.date.strftime("%c"))
1200
910
 
1201
911
class ExerciseAttempt(ExerciseSave):
1202
912
    """An attempt at solving an exercise.
1224
934
    complete = Bool()
1225
935
    active = Bool()
1226
936
 
1227
 
    def get_permissions(self, user, config):
 
937
    def get_permissions(self, user):
1228
938
        return set(['view']) if user is self.user else set()
1229
939
 
1230
940
class TestSuite(Storm):
1249
959
 
1250
960
    def delete(self):
1251
961
        """Delete this suite, without asking questions."""
1252
 
        for variable in self.variables:
 
962
        for vaariable in self.variables:
1253
963
            variable.delete()
1254
964
        for test_case in self.test_cases:
1255
965
            test_case.delete()
1268
978
    suite = Reference(suiteid, "TestSuite.suiteid")
1269
979
    passmsg = Unicode()
1270
980
    failmsg = Unicode()
1271
 
    test_default = Unicode() # Currently unused - only used for file matching.
 
981
    test_default = Unicode()
1272
982
    seq_no = Int()
1273
983
 
1274
984
    parts = ReferenceSet(testid, "TestCasePart.testid")