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

« back to all changes in this revision

Viewing changes to ivle/database.py

  • Committer: Matt Giuca
  • Date: 2010-02-25 08:36:59 UTC
  • Revision ID: matt.giuca@gmail.com-20100225083659-2s8vdrfhx3bhvcmp
DateTimeValidator: Removed some debugging code. Oops.

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
28
29
 
29
30
from storm.locals import create_database, Store, Int, Unicode, DateTime, \
30
31
                         Reference, ReferenceSet, Bool, Storm, Desc
324
325
    semester = Reference(semester_id, Semester.id)
325
326
    description = Unicode()
326
327
    url = Unicode()
 
328
    show_worksheet_marks = Bool()
 
329
    worksheet_cutoff = DateTime()
327
330
    groups_student_permissions = Unicode()
328
331
 
329
332
    enrolments = ReferenceSet(id, 'Enrolment.offering_id')
392
395
                perms.add('view_project_submissions')
393
396
                perms.add('admin_groups')
394
397
                perms.add('edit_worksheets')
 
398
                perms.add('view_worksheet_marks')
395
399
                perms.add('edit')           # Can edit projects & details
396
400
                perms.add('enrol')          # Can see enrolment screen at all
397
401
                perms.add('enrol_student')  # Can enrol students
425
429
        # XXX: Respect extensions.
426
430
        return self.projects.find(Project.deadline > datetime.datetime.now())
427
431
 
 
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
 
428
449
class Enrolment(Storm):
429
450
    """An enrolment of a user in an offering.
430
451
 
456
477
        return "<%s %r in %r>" % (type(self).__name__, self.user,
457
478
                                  self.offering)
458
479
 
 
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
 
459
493
# PROJECTS #
460
494
 
461
495
class ProjectSet(Storm):
578
612
 
579
613
        a = Assessed.get(Store.of(self), principal, self)
580
614
        ps = ProjectSubmission()
581
 
        ps.path = path
 
615
        # Raise SubmissionError if the path is illegal
 
616
        ps.path = ProjectSubmission.test_and_normalise_path(path)
582
617
        ps.revision = revision
583
618
        ps.date_submitted = datetime.datetime.now()
584
619
        ps.assessed = a
614
649
            return
615
650
        return assessed.submissions
616
651
 
 
652
    @property
 
653
    def can_delete(self):
 
654
        """Can only delete if there are no submissions."""
 
655
        return self.submissions.count() == 0
617
656
 
 
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)
618
664
 
619
665
class ProjectGroup(Storm):
620
666
    """A group of students working together on a project."""
767
813
 
768
814
        return a
769
815
 
 
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)
770
824
 
771
825
class ProjectExtension(Storm):
772
826
    """An extension granted to a user or group on a particular project.
784
838
    approver = Reference(approver_id, User.id)
785
839
    notes = Unicode()
786
840
 
 
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
 
787
849
class ProjectSubmission(Storm):
788
850
    """A submission from a user or group repository to a particular project.
789
851
 
819
881
        return "/files/%s/%s/%s?r=%d" % (user.login,
820
882
            self.assessed.checkout_location, submitpath, self.revision)
821
883
 
 
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
 
822
903
# WORKSHEETS AND EXERCISES #
823
904
 
824
905
class Exercise(Storm):
831
912
    id = Unicode(primary=True, name="identifier")
832
913
    name = Unicode()
833
914
    description = Unicode()
 
915
    _description_xhtml_cache = Unicode(name='description_xhtml_cache')
834
916
    partial = Unicode()
835
917
    solution = Unicode()
836
918
    include = Unicode()
879
961
 
880
962
        return perms
881
963
 
882
 
    def get_description(self):
883
 
        """Return the description interpreted as reStructuredText."""
884
 
        return rst(self.description)
 
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)
885
983
 
886
984
    def delete(self):
887
985
        """Deletes the exercise, providing it has no associated worksheets."""
904
1002
    identifier = Unicode()
905
1003
    name = Unicode()
906
1004
    assessable = Bool()
 
1005
    published = Bool()
907
1006
    data = Unicode()
 
1007
    _data_xhtml_cache = Unicode(name='data_xhtml_cache')
908
1008
    seq_no = Int()
909
1009
    format = Unicode()
910
1010
 
941
1041
            WorksheetExercise.worksheet == self).remove()
942
1042
 
943
1043
    def get_permissions(self, user, config):
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:
 
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')
949
1056
            perms.add('edit')
950
 
        else:
951
 
            perms.discard('edit')
 
1057
 
952
1058
        return perms
953
1059
 
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
 
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
959
1078
        else:
960
1079
            return self.data
961
1080
 
 
1081
    def set_data(self, data):
 
1082
        self.data = data
 
1083
        self._cache_data_xhtml(invalidate=True)
 
1084
 
962
1085
    def delete(self):
963
1086
        """Deletes the worksheet, provided it has no attempts on any exercises.
964
1087
 
1026
1149
 
1027
1150
    def __repr__(self):
1028
1151
        return "<%s %s by %s at %s>" % (type(self).__name__,
1029
 
            self.exercise.name, self.user.login, self.date.strftime("%c"))
 
1152
            self.worksheet_exercise.exercise.name, self.user.login,
 
1153
            self.date.strftime("%c"))
1030
1154
 
1031
1155
class ExerciseAttempt(ExerciseSave):
1032
1156
    """An attempt at solving an exercise.