~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-12 05:52:00 UTC
  • Revision ID: matt.giuca@gmail.com-20100212055200-lxsry6l34tc2m21q
doc/man/roles.rst: Update based on Will's feedback. Tutor access to submission repositories.

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
216
215
            Semester.id == Offering.semester_id,
217
216
            (not active_only) or (Semester.state == u'current'),
218
217
            Enrolment.offering_id == Offering.id,
219
 
            Enrolment.user_id == self.id,
220
 
            Enrolment.active == True)
 
218
            Enrolment.user_id == self.id)
221
219
 
222
220
    @staticmethod
223
221
    def hash_password(password):
325
323
    semester = Reference(semester_id, Semester.id)
326
324
    description = Unicode()
327
325
    url = Unicode()
328
 
    show_worksheet_marks = Bool()
329
 
    worksheet_cutoff = DateTime()
330
326
    groups_student_permissions = Unicode()
331
327
 
332
328
    enrolments = ReferenceSet(id, 'Enrolment.offering_id')
381
377
            enrolment = self.get_enrolment(user)
382
378
            if enrolment or user.admin:
383
379
                perms.add('view')
384
 
            if enrolment and enrolment.role == u'tutor':
385
 
                perms.add('view_project_submissions')
 
380
            if (enrolment and enrolment.role in (u'tutor', u'lecturer')) \
 
381
               or user.admin:
386
382
                # Site-specific policy on the role of tutors
387
383
                if config['policy']['tutors_can_enrol_students']:
388
384
                    perms.add('enrol')
389
385
                    perms.add('enrol_student')
390
386
                if config['policy']['tutors_can_edit_worksheets']:
391
387
                    perms.add('edit_worksheets')
392
 
                if config['policy']['tutors_can_admin_groups']:
393
 
                    perms.add('admin_groups')
394
388
            if (enrolment and enrolment.role in (u'lecturer')) or user.admin:
395
 
                perms.add('view_project_submissions')
396
 
                perms.add('admin_groups')
397
389
                perms.add('edit_worksheets')
398
 
                perms.add('view_worksheet_marks')
399
390
                perms.add('edit')           # Can edit projects & details
400
391
                perms.add('enrol')          # Can see enrolment screen at all
401
392
                perms.add('enrol_student')  # Can enrol students
429
420
        # XXX: Respect extensions.
430
421
        return self.projects.find(Project.deadline > datetime.datetime.now())
431
422
 
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
423
class Enrolment(Storm):
450
424
    """An enrolment of a user in an offering.
451
425
 
477
451
        return "<%s %r in %r>" % (type(self).__name__, self.user,
478
452
                                  self.offering)
479
453
 
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
454
# PROJECTS #
494
455
 
495
456
class ProjectSet(Storm):
612
573
 
613
574
        a = Assessed.get(Store.of(self), principal, self)
614
575
        ps = ProjectSubmission()
615
 
        # Raise SubmissionError if the path is illegal
616
 
        ps.path = ProjectSubmission.test_and_normalise_path(path)
 
576
        ps.path = path
617
577
        ps.revision = revision
618
578
        ps.date_submitted = datetime.datetime.now()
619
579
        ps.assessed = a
649
609
            return
650
610
        return assessed.submissions
651
611
 
652
 
    @property
653
 
    def can_delete(self):
654
 
        """Can only delete if there are no submissions."""
655
 
        return self.submissions.count() == 0
656
612
 
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
613
 
665
614
class ProjectGroup(Storm):
666
615
    """A group of students working together on a project."""
813
762
 
814
763
        return a
815
764
 
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
765
 
825
766
class ProjectExtension(Storm):
826
767
    """An extension granted to a user or group on a particular project.
838
779
    approver = Reference(approver_id, User.id)
839
780
    notes = Unicode()
840
781
 
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
782
class ProjectSubmission(Storm):
850
783
    """A submission from a user or group repository to a particular project.
851
784
 
881
814
        return "/files/%s/%s/%s?r=%d" % (user.login,
882
815
            self.assessed.checkout_location, submitpath, self.revision)
883
816
 
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
817
# WORKSHEETS AND EXERCISES #
904
818
 
905
819
class Exercise(Storm):
912
826
    id = Unicode(primary=True, name="identifier")
913
827
    name = Unicode()
914
828
    description = Unicode()
915
 
    _description_xhtml_cache = Unicode(name='description_xhtml_cache')
916
829
    partial = Unicode()
917
830
    solution = Unicode()
918
831
    include = Unicode()
961
874
 
962
875
        return perms
963
876
 
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)
 
877
    def get_description(self):
 
878
        """Return the description interpreted as reStructuredText."""
 
879
        return rst(self.description)
983
880
 
984
881
    def delete(self):
985
882
        """Deletes the exercise, providing it has no associated worksheets."""
1002
899
    identifier = Unicode()
1003
900
    name = Unicode()
1004
901
    assessable = Bool()
1005
 
    published = Bool()
1006
902
    data = Unicode()
1007
 
    _data_xhtml_cache = Unicode(name='data_xhtml_cache')
1008
903
    seq_no = Int()
1009
904
    format = Unicode()
1010
905
 
1041
936
            WorksheetExercise.worksheet == self).remove()
1042
937
 
1043
938
    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')
 
939
        # Almost the same permissions as for the offering itself
 
940
        perms = self.offering.get_permissions(user, config)
 
941
        # However, "edit" permission is derived from the "edit_worksheets"
 
942
        # permission of the offering
 
943
        if 'edit_worksheets' in perms:
1056
944
            perms.add('edit')
1057
 
 
 
945
        else:
 
946
            perms.discard('edit')
1058
947
        return perms
1059
948
 
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
 
949
    def get_xml(self):
 
950
        """Returns the xml of this worksheet, converts from rst if required."""
 
951
        if self.format == u'rst':
 
952
            ws_xml = rst(self.data)
 
953
            return ws_xml
1078
954
        else:
1079
955
            return self.data
1080
956
 
1081
 
    def set_data(self, data):
1082
 
        self.data = data
1083
 
        self._cache_data_xhtml(invalidate=True)
1084
 
 
1085
957
    def delete(self):
1086
958
        """Deletes the worksheet, provided it has no attempts on any exercises.
1087
959
 
1149
1021
 
1150
1022
    def __repr__(self):
1151
1023
        return "<%s %s by %s at %s>" % (type(self).__name__,
1152
 
            self.worksheet_exercise.exercise.name, self.user.login,
1153
 
            self.date.strftime("%c"))
 
1024
            self.exercise.name, self.user.login, self.date.strftime("%c"))
1154
1025
 
1155
1026
class ExerciseAttempt(ExerciseSave):
1156
1027
    """An attempt at solving an exercise.