~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-18 03:31:47 UTC
  • Revision ID: grantw@unimelb.edu.au-20100218033147-z1es9tzrx7eg85gu
Ensure that we always close the DB connection at request termination, even in the case of an exception.

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 has_worksheet_cutoff_passed(self, user):
433
 
        """Check whether the worksheet cutoff has passed.
434
 
        A user is required, in case we support extensions.
435
 
        """
436
 
        if self.worksheet_cutoff is None:
437
 
            return False
438
 
        else:
439
 
            return self.worksheet_cutoff < datetime.datetime.now()
440
 
 
441
428
    def clone_worksheets(self, source):
442
429
        """Clone all worksheets from the specified source to this offering."""
443
430
        import ivle.worksheet.utils
447
434
            newws.identifier = worksheet.identifier
448
435
            newws.name = worksheet.name
449
436
            newws.assessable = worksheet.assessable
450
 
            newws.published = worksheet.published
451
437
            newws.data = worksheet.data
452
438
            newws.format = worksheet.format
453
439
            newws.offering = self
621
607
 
622
608
        a = Assessed.get(Store.of(self), principal, self)
623
609
        ps = ProjectSubmission()
624
 
        # Raise SubmissionError if the path is illegal
625
 
        ps.path = ProjectSubmission.test_and_normalise_path(path)
 
610
        ps.path = path
626
611
        ps.revision = revision
627
612
        ps.date_submitted = datetime.datetime.now()
628
613
        ps.assessed = a
658
643
            return
659
644
        return assessed.submissions
660
645
 
661
 
    @property
662
 
    def can_delete(self):
663
 
        """Can only delete if there are no submissions."""
664
 
        return self.submissions.count() == 0
665
646
 
666
 
    def delete(self):
667
 
        """Delete the project. Fails if can_delete is False."""
668
 
        if not self.can_delete:
669
 
            raise IntegrityError()
670
 
        for assessed in self.assesseds:
671
 
            assessed.delete()
672
 
        Store.of(self).remove(self)
673
647
 
674
648
class ProjectGroup(Storm):
675
649
    """A group of students working together on a project."""
822
796
 
823
797
        return a
824
798
 
825
 
    def delete(self):
826
 
        """Delete the assessed. Fails if there are any submissions. Deletes
827
 
        extensions."""
828
 
        if self.submissions.count() > 0:
829
 
            raise IntegrityError()
830
 
        for extension in self.extensions:
831
 
            extension.delete()
832
 
        Store.of(self).remove(self)
833
799
 
834
800
class ProjectExtension(Storm):
835
801
    """An extension granted to a user or group on a particular project.
847
813
    approver = Reference(approver_id, User.id)
848
814
    notes = Unicode()
849
815
 
850
 
    def delete(self):
851
 
        """Delete the extension."""
852
 
        Store.of(self).remove(self)
853
 
 
854
 
class SubmissionError(Exception):
855
 
    """Denotes a validation error during submission."""
856
 
    pass
857
 
 
858
816
class ProjectSubmission(Storm):
859
817
    """A submission from a user or group repository to a particular project.
860
818
 
890
848
        return "/files/%s/%s/%s?r=%d" % (user.login,
891
849
            self.assessed.checkout_location, submitpath, self.revision)
892
850
 
893
 
    @staticmethod
894
 
    def test_and_normalise_path(path):
895
 
        """Test that path is valid, and normalise it. This prevents possible
896
 
        injections using malicious paths.
897
 
        Returns the updated path, if successful.
898
 
        Raises SubmissionError if invalid.
899
 
        """
900
 
        # Ensure the path is absolute to prevent being tacked onto working
901
 
        # directories.
902
 
        # Prevent '\n' because it will break all sorts of things.
903
 
        # Prevent '[' and ']' because they can be used to inject into the
904
 
        # svn.conf.
905
 
        # Normalise to avoid resulting in ".." path segments.
906
 
        if not os.path.isabs(path):
907
 
            raise SubmissionError("Path is not absolute")
908
 
        if any(c in path for c in "\n[]"):
909
 
            raise SubmissionError("Path must not contain '\\n', '[' or ']'")
910
 
        return os.path.normpath(path)
911
 
 
912
851
# WORKSHEETS AND EXERCISES #
913
852
 
914
853
class Exercise(Storm):
921
860
    id = Unicode(primary=True, name="identifier")
922
861
    name = Unicode()
923
862
    description = Unicode()
924
 
    _description_xhtml_cache = Unicode(name='description_xhtml_cache')
925
863
    partial = Unicode()
926
864
    solution = Unicode()
927
865
    include = Unicode()
970
908
 
971
909
        return perms
972
910
 
973
 
    def _cache_description_xhtml(self, invalidate=False):
974
 
        # Don't regenerate an existing cache unless forced.
975
 
        if self._description_xhtml_cache is not None and not invalidate:
976
 
            return
977
 
 
978
 
        if self.description:
979
 
            self._description_xhtml_cache = rst(self.description)
980
 
        else:
981
 
            self._description_xhtml_cache = None
982
 
 
983
 
    @property
984
 
    def description_xhtml(self):
985
 
        """The XHTML exercise description, converted from reStructuredText."""
986
 
        self._cache_description_xhtml()
987
 
        return self._description_xhtml_cache
988
 
 
989
 
    def set_description(self, description):
990
 
        self.description = description
991
 
        self._cache_description_xhtml(invalidate=True)
 
911
    def get_description(self):
 
912
        """Return the description interpreted as reStructuredText."""
 
913
        return rst(self.description)
992
914
 
993
915
    def delete(self):
994
916
        """Deletes the exercise, providing it has no associated worksheets."""
1011
933
    identifier = Unicode()
1012
934
    name = Unicode()
1013
935
    assessable = Bool()
1014
 
    published = Bool()
1015
936
    data = Unicode()
1016
 
    _data_xhtml_cache = Unicode(name='data_xhtml_cache')
1017
937
    seq_no = Int()
1018
938
    format = Unicode()
1019
939
 
1050
970
            WorksheetExercise.worksheet == self).remove()
1051
971
 
1052
972
    def get_permissions(self, user, config):
1053
 
        offering_perms = self.offering.get_permissions(user, config)
1054
 
 
1055
 
        perms = set()
1056
 
 
1057
 
        # Anybody who can view an offering can view a published
1058
 
        # worksheet.
1059
 
        if 'view' in offering_perms and self.published:
1060
 
            perms.add('view')
1061
 
 
1062
 
        # Any worksheet editors can both view and edit.
1063
 
        if 'edit_worksheets' in offering_perms:
1064
 
            perms.add('view')
 
973
        # Almost the same permissions as for the offering itself
 
974
        perms = self.offering.get_permissions(user, config)
 
975
        # However, "edit" permission is derived from the "edit_worksheets"
 
976
        # permission of the offering
 
977
        if 'edit_worksheets' in perms:
1065
978
            perms.add('edit')
1066
 
 
 
979
        else:
 
980
            perms.discard('edit')
1067
981
        return perms
1068
982
 
1069
 
    def _cache_data_xhtml(self, invalidate=False):
1070
 
        # Don't regenerate an existing cache unless forced.
1071
 
        if self._data_xhtml_cache is not None and not invalidate:
1072
 
            return
1073
 
 
1074
 
        if self.format == u'rst':
1075
 
            self._data_xhtml_cache = rst(self.data)
1076
 
        else:
1077
 
            self._data_xhtml_cache = None
1078
 
 
1079
 
    @property
1080
 
    def data_xhtml(self):
1081
 
        """The XHTML of this worksheet, converted from rST if required."""
1082
 
        # Update the rST -> XHTML cache, if required.
1083
 
        self._cache_data_xhtml()
1084
 
 
1085
 
        if self.format == u'rst':
1086
 
            return self._data_xhtml_cache
 
983
    def get_xml(self):
 
984
        """Returns the xml of this worksheet, converts from rst if required."""
 
985
        if self.format == u'rst':
 
986
            ws_xml = rst(self.data)
 
987
            return ws_xml
1087
988
        else:
1088
989
            return self.data
1089
990
 
1090
 
    def set_data(self, data):
1091
 
        self.data = data
1092
 
        self._cache_data_xhtml(invalidate=True)
1093
 
 
1094
991
    def delete(self):
1095
992
        """Deletes the worksheet, provided it has no attempts on any exercises.
1096
993
 
1158
1055
 
1159
1056
    def __repr__(self):
1160
1057
        return "<%s %s by %s at %s>" % (type(self).__name__,
1161
 
            self.worksheet_exercise.exercise.name, self.user.login,
1162
 
            self.date.strftime("%c"))
 
1058
            self.exercise.name, self.user.login, self.date.strftime("%c"))
1163
1059
 
1164
1060
class ExerciseAttempt(ExerciseSave):
1165
1061
    """An attempt at solving an exercise.