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

« back to all changes in this revision

Viewing changes to ivle/database.py

  • Committer: William Grant
  • Date: 2010-02-15 05:37:50 UTC
  • Revision ID: grantw@unimelb.edu.au-20100215053750-hihmegnp8e7dshc2
Ignore test coverage files.

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
28
 
30
29
from storm.locals import create_database, Store, Int, Unicode, DateTime, \
31
30
                         Reference, ReferenceSet, Bool, Storm, Desc
325
324
    semester = Reference(semester_id, Semester.id)
326
325
    description = Unicode()
327
326
    url = Unicode()
328
 
    show_worksheet_marks = Bool()
329
 
    worksheet_cutoff = DateTime()
330
327
    groups_student_permissions = Unicode()
331
328
 
332
329
    enrolments = ReferenceSet(id, 'Enrolment.offering_id')
395
392
                perms.add('view_project_submissions')
396
393
                perms.add('admin_groups')
397
394
                perms.add('edit_worksheets')
398
 
                perms.add('view_worksheet_marks')
399
395
                perms.add('edit')           # Can edit projects & details
400
396
                perms.add('enrol')          # Can see enrolment screen at all
401
397
                perms.add('enrol_student')  # Can enrol students
429
425
        # XXX: Respect extensions.
430
426
        return self.projects.find(Project.deadline > datetime.datetime.now())
431
427
 
432
 
    def clone_worksheets(self, source):
433
 
        """Clone all worksheets from the specified source to this offering."""
434
 
        import ivle.worksheet.utils
435
 
        for worksheet in source.worksheets:
436
 
            newws = Worksheet()
437
 
            newws.seq_no = worksheet.seq_no
438
 
            newws.identifier = worksheet.identifier
439
 
            newws.name = worksheet.name
440
 
            newws.assessable = worksheet.assessable
441
 
            newws.published = worksheet.published
442
 
            newws.data = worksheet.data
443
 
            newws.format = worksheet.format
444
 
            newws.offering = self
445
 
            Store.of(self).add(newws)
446
 
            ivle.worksheet.utils.update_exerciselist(newws)
447
 
 
448
 
 
449
428
class Enrolment(Storm):
450
429
    """An enrolment of a user in an offering.
451
430
 
477
456
        return "<%s %r in %r>" % (type(self).__name__, self.user,
478
457
                                  self.offering)
479
458
 
480
 
    def get_permissions(self, user, config):
481
 
        # A user can edit any enrolment that they could have created.
482
 
        perms = set()
483
 
        if ('enrol_' + str(self.role)) in self.offering.get_permissions(
484
 
            user, config):
485
 
            perms.add('edit')
486
 
        return perms
487
 
 
488
 
    def delete(self):
489
 
        """Delete this enrolment."""
490
 
        Store.of(self).remove(self)
491
 
 
492
 
 
493
459
# PROJECTS #
494
460
 
495
461
class ProjectSet(Storm):
612
578
 
613
579
        a = Assessed.get(Store.of(self), principal, self)
614
580
        ps = ProjectSubmission()
615
 
        # Raise SubmissionError if the path is illegal
616
 
        ps.path = ProjectSubmission.test_and_normalise_path(path)
 
581
        ps.path = path
617
582
        ps.revision = revision
618
583
        ps.date_submitted = datetime.datetime.now()
619
584
        ps.assessed = a
649
614
            return
650
615
        return assessed.submissions
651
616
 
652
 
    @property
653
 
    def can_delete(self):
654
 
        """Can only delete if there are no submissions."""
655
 
        return self.submissions.count() == 0
656
617
 
657
 
    def delete(self):
658
 
        """Delete the project. Fails if can_delete is False."""
659
 
        if not self.can_delete:
660
 
            raise IntegrityError()
661
 
        for assessed in self.assesseds:
662
 
            assessed.delete()
663
 
        Store.of(self).remove(self)
664
618
 
665
619
class ProjectGroup(Storm):
666
620
    """A group of students working together on a project."""
813
767
 
814
768
        return a
815
769
 
816
 
    def delete(self):
817
 
        """Delete the assessed. Fails if there are any submissions. Deletes
818
 
        extensions."""
819
 
        if self.submissions.count() > 0:
820
 
            raise IntegrityError()
821
 
        for extension in self.extensions:
822
 
            extension.delete()
823
 
        Store.of(self).remove(self)
824
770
 
825
771
class ProjectExtension(Storm):
826
772
    """An extension granted to a user or group on a particular project.
838
784
    approver = Reference(approver_id, User.id)
839
785
    notes = Unicode()
840
786
 
841
 
    def delete(self):
842
 
        """Delete the extension."""
843
 
        Store.of(self).remove(self)
844
 
 
845
 
class SubmissionError(Exception):
846
 
    """Denotes a validation error during submission."""
847
 
    pass
848
 
 
849
787
class ProjectSubmission(Storm):
850
788
    """A submission from a user or group repository to a particular project.
851
789
 
881
819
        return "/files/%s/%s/%s?r=%d" % (user.login,
882
820
            self.assessed.checkout_location, submitpath, self.revision)
883
821
 
884
 
    @staticmethod
885
 
    def test_and_normalise_path(path):
886
 
        """Test that path is valid, and normalise it. This prevents possible
887
 
        injections using malicious paths.
888
 
        Returns the updated path, if successful.
889
 
        Raises SubmissionError if invalid.
890
 
        """
891
 
        # Ensure the path is absolute to prevent being tacked onto working
892
 
        # directories.
893
 
        # Prevent '\n' because it will break all sorts of things.
894
 
        # Prevent '[' and ']' because they can be used to inject into the
895
 
        # svn.conf.
896
 
        # Normalise to avoid resulting in ".." path segments.
897
 
        if not os.path.isabs(path):
898
 
            raise SubmissionError("Path is not absolute")
899
 
        if any(c in path for c in "\n[]"):
900
 
            raise SubmissionError("Path must not contain '\\n', '[' or ']'")
901
 
        return os.path.normpath(path)
902
 
 
903
822
# WORKSHEETS AND EXERCISES #
904
823
 
905
824
class Exercise(Storm):
912
831
    id = Unicode(primary=True, name="identifier")
913
832
    name = Unicode()
914
833
    description = Unicode()
915
 
    _description_xhtml_cache = Unicode(name='description_xhtml_cache')
916
834
    partial = Unicode()
917
835
    solution = Unicode()
918
836
    include = Unicode()
961
879
 
962
880
        return perms
963
881
 
964
 
    def _cache_description_xhtml(self, invalidate=False):
965
 
        # Don't regenerate an existing cache unless forced.
966
 
        if self._description_xhtml_cache is not None and not invalidate:
967
 
            return
968
 
 
969
 
        if self.description:
970
 
            self._description_xhtml_cache = rst(self.description)
971
 
        else:
972
 
            self._description_xhtml_cache = None
973
 
 
974
 
    @property
975
 
    def description_xhtml(self):
976
 
        """The XHTML exercise description, converted from reStructuredText."""
977
 
        self._cache_description_xhtml()
978
 
        return self._description_xhtml_cache
979
 
 
980
 
    def set_description(self, description):
981
 
        self.description = description
982
 
        self._cache_description_xhtml(invalidate=True)
 
882
    def get_description(self):
 
883
        """Return the description interpreted as reStructuredText."""
 
884
        return rst(self.description)
983
885
 
984
886
    def delete(self):
985
887
        """Deletes the exercise, providing it has no associated worksheets."""
1002
904
    identifier = Unicode()
1003
905
    name = Unicode()
1004
906
    assessable = Bool()
1005
 
    published = Bool()
1006
907
    data = Unicode()
1007
 
    _data_xhtml_cache = Unicode(name='data_xhtml_cache')
1008
908
    seq_no = Int()
1009
909
    format = Unicode()
1010
910
 
1041
941
            WorksheetExercise.worksheet == self).remove()
1042
942
 
1043
943
    def get_permissions(self, user, config):
1044
 
        offering_perms = self.offering.get_permissions(user, config)
1045
 
 
1046
 
        perms = set()
1047
 
 
1048
 
        # Anybody who can view an offering can view a published
1049
 
        # worksheet.
1050
 
        if 'view' in offering_perms and self.published:
1051
 
            perms.add('view')
1052
 
 
1053
 
        # Any worksheet editors can both view and edit.
1054
 
        if 'edit_worksheets' in offering_perms:
1055
 
            perms.add('view')
 
944
        # Almost the same permissions as for the offering itself
 
945
        perms = self.offering.get_permissions(user, config)
 
946
        # However, "edit" permission is derived from the "edit_worksheets"
 
947
        # permission of the offering
 
948
        if 'edit_worksheets' in perms:
1056
949
            perms.add('edit')
1057
 
 
 
950
        else:
 
951
            perms.discard('edit')
1058
952
        return perms
1059
953
 
1060
 
    def _cache_data_xhtml(self, invalidate=False):
1061
 
        # Don't regenerate an existing cache unless forced.
1062
 
        if self._data_xhtml_cache is not None and not invalidate:
1063
 
            return
1064
 
 
1065
 
        if self.format == u'rst':
1066
 
            self._data_xhtml_cache = rst(self.data)
1067
 
        else:
1068
 
            self._data_xhtml_cache = None
1069
 
 
1070
 
    @property
1071
 
    def data_xhtml(self):
1072
 
        """The XHTML of this worksheet, converted from rST if required."""
1073
 
        # Update the rST -> XHTML cache, if required.
1074
 
        self._cache_data_xhtml()
1075
 
 
1076
 
        if self.format == u'rst':
1077
 
            return self._data_xhtml_cache
 
954
    def get_xml(self):
 
955
        """Returns the xml of this worksheet, converts from rst if required."""
 
956
        if self.format == u'rst':
 
957
            ws_xml = rst(self.data)
 
958
            return ws_xml
1078
959
        else:
1079
960
            return self.data
1080
961
 
1081
 
    def set_data(self, data):
1082
 
        self.data = data
1083
 
        self._cache_data_xhtml(invalidate=True)
1084
 
 
1085
962
    def delete(self):
1086
963
        """Deletes the worksheet, provided it has no attempts on any exercises.
1087
964
 
1149
1026
 
1150
1027
    def __repr__(self):
1151
1028
        return "<%s %s by %s at %s>" % (type(self).__name__,
1152
 
            self.worksheet_exercise.exercise.name, self.user.login,
1153
 
            self.date.strftime("%c"))
 
1029
            self.exercise.name, self.user.login, self.date.strftime("%c"))
1154
1030
 
1155
1031
class ExerciseAttempt(ExerciseSave):
1156
1032
    """An attempt at solving an exercise.