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

« back to all changes in this revision

Viewing changes to ivle/database.py

  • Committer: Matt Giuca
  • Date: 2010-07-21 04:21:50 UTC
  • Revision ID: matt.giuca@gmail.com-20100721042150-qovg2sth81sgbdq7
Project page: Replaced the confusing title 'Assigned submitters' with 'Expected submitters', and the heading 'assigned' with 'name'.

Show diffs side-by-side

added added

removed removed

Lines of Context:
26
26
import hashlib
27
27
import datetime
28
28
import os
 
29
import urlparse
 
30
import urllib
29
31
 
30
32
from storm.locals import create_database, Store, Int, Unicode, DateTime, \
31
33
                         Reference, ReferenceSet, Bool, Storm, Desc
229
231
        """Find a user in a store by login name."""
230
232
        return store.find(cls, cls.login == unicode(login)).one()
231
233
 
 
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
 
232
240
    def get_permissions(self, user, config):
233
241
        """Determine privileges held by a user over this object.
234
242
 
325
333
    semester = Reference(semester_id, Semester.id)
326
334
    description = Unicode()
327
335
    url = Unicode()
 
336
    show_worksheet_marks = Bool()
 
337
    worksheet_cutoff = DateTime()
328
338
    groups_student_permissions = Unicode()
329
339
 
330
340
    enrolments = ReferenceSet(id, 'Enrolment.offering_id')
393
403
                perms.add('view_project_submissions')
394
404
                perms.add('admin_groups')
395
405
                perms.add('edit_worksheets')
 
406
                perms.add('view_worksheet_marks')
396
407
                perms.add('edit')           # Can edit projects & details
397
408
                perms.add('enrol')          # Can see enrolment screen at all
398
409
                perms.add('enrol_student')  # Can enrol students
426
437
        # XXX: Respect extensions.
427
438
        return self.projects.find(Project.deadline > datetime.datetime.now())
428
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
 
429
449
    def clone_worksheets(self, source):
430
450
        """Clone all worksheets from the specified source to this offering."""
431
451
        import ivle.worksheet.utils
435
455
            newws.identifier = worksheet.identifier
436
456
            newws.name = worksheet.name
437
457
            newws.assessable = worksheet.assessable
 
458
            newws.published = worksheet.published
438
459
            newws.data = worksheet.data
439
460
            newws.format = worksheet.format
440
461
            newws.offering = self
645
666
            return
646
667
        return assessed.submissions
647
668
 
 
669
    @property
 
670
    def can_delete(self):
 
671
        """Can only delete if there are no submissions."""
 
672
        return self.submissions.count() == 0
648
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)
649
681
 
650
682
class ProjectGroup(Storm):
651
683
    """A group of students working together on a project."""
701
733
            Semester.id == Offering.semester_id,
702
734
            (not active_only) or (Semester.state == u'current'))
703
735
 
 
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)
704
746
 
705
747
    def get_permissions(self, user, config):
706
748
        if user.admin or user in self.members:
798
840
 
799
841
        return a
800
842
 
 
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)
801
851
 
802
852
class ProjectExtension(Storm):
803
853
    """An extension granted to a user or group on a particular project.
815
865
    approver = Reference(approver_id, User.id)
816
866
    notes = Unicode()
817
867
 
 
868
    def delete(self):
 
869
        """Delete the extension."""
 
870
        Store.of(self).remove(self)
 
871
 
818
872
class SubmissionError(Exception):
819
873
    """Denotes a validation error during submission."""
820
874
    pass
854
908
        return "/files/%s/%s/%s?r=%d" % (user.login,
855
909
            self.assessed.checkout_location, submitpath, self.revision)
856
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
 
857
930
    @staticmethod
858
931
    def test_and_normalise_path(path):
859
932
        """Test that path is valid, and normalise it. This prevents possible
885
958
    id = Unicode(primary=True, name="identifier")
886
959
    name = Unicode()
887
960
    description = Unicode()
 
961
    _description_xhtml_cache = Unicode(name='description_xhtml_cache')
888
962
    partial = Unicode()
889
963
    solution = Unicode()
890
964
    include = Unicode()
933
1007
 
934
1008
        return perms
935
1009
 
936
 
    def get_description(self):
937
 
        """Return the description interpreted as reStructuredText."""
938
 
        return rst(self.description)
 
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)
939
1029
 
940
1030
    def delete(self):
941
1031
        """Deletes the exercise, providing it has no associated worksheets."""
958
1048
    identifier = Unicode()
959
1049
    name = Unicode()
960
1050
    assessable = Bool()
 
1051
    published = Bool()
961
1052
    data = Unicode()
 
1053
    _data_xhtml_cache = Unicode(name='data_xhtml_cache')
962
1054
    seq_no = Int()
963
1055
    format = Unicode()
964
1056
 
995
1087
            WorksheetExercise.worksheet == self).remove()
996
1088
 
997
1089
    def get_permissions(self, user, config):
998
 
        # Almost the same permissions as for the offering itself
999
 
        perms = self.offering.get_permissions(user, config)
1000
 
        # However, "edit" permission is derived from the "edit_worksheets"
1001
 
        # permission of the offering
1002
 
        if 'edit_worksheets' in perms:
 
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')
1003
1102
            perms.add('edit')
1004
 
        else:
1005
 
            perms.discard('edit')
 
1103
 
1006
1104
        return perms
1007
1105
 
1008
 
    def get_xml(self):
1009
 
        """Returns the xml of this worksheet, converts from rst if required."""
1010
 
        if self.format == u'rst':
1011
 
            ws_xml = rst(self.data)
1012
 
            return ws_xml
 
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
1013
1124
        else:
1014
1125
            return self.data
1015
1126
 
 
1127
    def set_data(self, data):
 
1128
        self.data = data
 
1129
        self._cache_data_xhtml(invalidate=True)
 
1130
 
1016
1131
    def delete(self):
1017
1132
        """Deletes the worksheet, provided it has no attempts on any exercises.
1018
1133
 
1080
1195
 
1081
1196
    def __repr__(self):
1082
1197
        return "<%s %s by %s at %s>" % (type(self).__name__,
1083
 
            self.exercise.name, self.user.login, self.date.strftime("%c"))
 
1198
            self.worksheet_exercise.exercise.name, self.user.login,
 
1199
            self.date.strftime("%c"))
1084
1200
 
1085
1201
class ExerciseAttempt(ExerciseSave):
1086
1202
    """An attempt at solving an exercise.