~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-04 05:15:49 UTC
  • Revision ID: matt.giuca@gmail.com-20100204051549-zr8b4hnxykjowwob
Offering page: Added a link to manage exercises from all offerings.

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):
229
227
        """Find a user in a store by login name."""
230
228
        return store.find(cls, cls.login == unicode(login)).one()
231
229
 
232
 
    def get_permissions(self, user, config):
 
230
    def get_permissions(self, user):
233
231
        """Determine privileges held by a user over this object.
234
232
 
235
233
        If the user requesting privileges is this user or an admin,
259
257
    def __repr__(self):
260
258
        return "<%s '%s'>" % (type(self).__name__, self.short_name)
261
259
 
262
 
    def get_permissions(self, user, config):
 
260
    def get_permissions(self, user):
263
261
        """Determine privileges held by a user over this object.
264
262
 
265
263
        If the user requesting privileges is an admin, they may edit.
373
371
                               Enrolment.offering_id == self.id).one()
374
372
        Store.of(enrolment).remove(enrolment)
375
373
 
376
 
    def get_permissions(self, user, config):
 
374
    def get_permissions(self, user):
377
375
        perms = set()
378
376
        if user is not None:
379
377
            enrolment = self.get_enrolment(user)
380
378
            if enrolment or user.admin:
381
379
                perms.add('view')
382
 
            if enrolment and enrolment.role == u'tutor':
383
 
                perms.add('view_project_submissions')
384
 
                # Site-specific policy on the role of tutors
385
 
                if config['policy']['tutors_can_enrol_students']:
386
 
                    perms.add('enrol')
387
 
                    perms.add('enrol_student')
388
 
                if config['policy']['tutors_can_edit_worksheets']:
389
 
                    perms.add('edit_worksheets')
390
 
                if config['policy']['tutors_can_admin_groups']:
391
 
                    perms.add('admin_groups')
392
 
            if (enrolment and enrolment.role in (u'lecturer')) or user.admin:
393
 
                perms.add('view_project_submissions')
394
 
                perms.add('admin_groups')
395
 
                perms.add('edit_worksheets')
396
 
                perms.add('edit')           # Can edit projects & details
 
380
            if (enrolment and enrolment.role in (u'tutor', u'lecturer')) \
 
381
               or user.admin:
 
382
                perms.add('edit')
 
383
                # XXX Bug #493945 -- should tutors have these permissions?
 
384
                # Potentially move into the next category (lecturer & admin)
397
385
                perms.add('enrol')          # Can see enrolment screen at all
398
386
                perms.add('enrol_student')  # Can enrol students
 
387
            if (enrolment and enrolment.role in (u'lecturer')) or user.admin:
399
388
                perms.add('enrol_tutor')    # Can enrol tutors
400
389
            if user.admin:
401
390
                perms.add('enrol_lecturer') # Can enrol lecturers
426
415
        # XXX: Respect extensions.
427
416
        return self.projects.find(Project.deadline > datetime.datetime.now())
428
417
 
429
 
    def clone_worksheets(self, source):
430
 
        """Clone all worksheets from the specified source to this offering."""
431
 
        import ivle.worksheet.utils
432
 
        for worksheet in source.worksheets:
433
 
            newws = Worksheet()
434
 
            newws.seq_no = worksheet.seq_no
435
 
            newws.identifier = worksheet.identifier
436
 
            newws.name = worksheet.name
437
 
            newws.assessable = worksheet.assessable
438
 
            newws.data = worksheet.data
439
 
            newws.format = worksheet.format
440
 
            newws.offering = self
441
 
            Store.of(self).add(newws)
442
 
            ivle.worksheet.utils.update_exerciselist(newws)
443
 
 
444
 
 
445
418
class Enrolment(Storm):
446
419
    """An enrolment of a user in an offering.
447
420
 
473
446
        return "<%s %r in %r>" % (type(self).__name__, self.user,
474
447
                                  self.offering)
475
448
 
476
 
    def get_permissions(self, user, config):
477
 
        # A user can edit any enrolment that they could have created.
478
 
        perms = set()
479
 
        if ('enrol_' + str(self.role)) in self.offering.get_permissions(
480
 
            user, config):
481
 
            perms.add('edit')
482
 
        return perms
483
 
 
484
 
    def delete(self):
485
 
        """Delete this enrolment."""
486
 
        Store.of(self).remove(self)
487
 
 
488
 
 
489
449
# PROJECTS #
490
450
 
491
451
class ProjectSet(Storm):
511
471
        return "<%s %d in %r>" % (type(self).__name__, self.id,
512
472
                                  self.offering)
513
473
 
514
 
    def get_permissions(self, user, config):
515
 
        return self.offering.get_permissions(user, config)
 
474
    def get_permissions(self, user):
 
475
        return self.offering.get_permissions(user)
516
476
 
517
477
    def get_groups_for_user(self, user):
518
478
        """List all groups in this offering of which the user is a member."""
555
515
        else:
556
516
            return self.offering.students
557
517
 
558
 
class DeadlinePassed(Exception):
559
 
    """An exception indicating that a project cannot be submitted because the
560
 
    deadline has passed."""
561
 
    def __init__(self):
562
 
        pass
563
 
    def __str__(self):
564
 
        return "The project deadline has passed"
565
 
 
566
518
class Project(Storm):
567
519
    """A student project for which submissions can be made."""
568
520
 
589
541
        return "<%s '%s' in %r>" % (type(self).__name__, self.short_name,
590
542
                                  self.project_set.offering)
591
543
 
592
 
    def can_submit(self, principal, user):
 
544
    def can_submit(self, principal):
593
545
        return (self in principal.get_projects() and
594
 
                not self.has_deadline_passed(user))
 
546
                self.deadline > datetime.datetime.now())
595
547
 
596
548
    def submit(self, principal, path, revision, who):
597
549
        """Submit a Subversion path and revision to a project.
603
555
        @param who: The user who is actually making the submission.
604
556
        """
605
557
 
606
 
        if not self.can_submit(principal, who):
607
 
            raise DeadlinePassed()
 
558
        if not self.can_submit(principal):
 
559
            raise Exception('cannot submit')
608
560
 
609
561
        a = Assessed.get(Store.of(self), principal, self)
610
562
        ps = ProjectSubmission()
611
 
        # Raise SubmissionError if the path is illegal
612
 
        ps.path = ProjectSubmission.test_and_normalise_path(path)
 
563
        ps.path = path
613
564
        ps.revision = revision
614
565
        ps.date_submitted = datetime.datetime.now()
615
566
        ps.assessed = a
617
568
 
618
569
        return ps
619
570
 
620
 
    def get_permissions(self, user, config):
621
 
        return self.project_set.offering.get_permissions(user, config)
 
571
    def get_permissions(self, user):
 
572
        return self.project_set.offering.get_permissions(user)
622
573
 
623
574
    @property
624
575
    def latest_submissions(self):
702
653
            (not active_only) or (Semester.state == u'current'))
703
654
 
704
655
 
705
 
    def get_permissions(self, user, config):
 
656
    def get_permissions(self, user):
706
657
        if user.admin or user in self.members:
707
658
            return set(['submit_project'])
708
659
        else:
760
711
    def principal(self):
761
712
        return self.project_group or self.user
762
713
 
763
 
    @property
764
 
    def checkout_location(self):
765
 
        """Returns the location of the Subversion workspace for this piece of
766
 
        assessment, relative to each group member's home directory."""
767
 
        subjectname = self.project.project_set.offering.subject.short_name
768
 
        if self.is_group:
769
 
            checkout_dir_name = self.principal.short_name
770
 
        else:
771
 
            checkout_dir_name = "mywork"
772
 
        return subjectname + "/" + checkout_dir_name
773
 
 
774
714
    @classmethod
775
715
    def get(cls, store, principal, project):
776
716
        """Find or create an Assessed for the given user or group and project.
815
755
    approver = Reference(approver_id, User.id)
816
756
    notes = Unicode()
817
757
 
818
 
class SubmissionError(Exception):
819
 
    """Denotes a validation error during submission."""
820
 
    pass
821
 
 
822
758
class ProjectSubmission(Storm):
823
759
    """A submission from a user or group repository to a particular project.
824
760
 
840
776
    submitter = Reference(submitter_id, User.id)
841
777
    date_submitted = DateTime()
842
778
 
843
 
    def get_verify_url(self, user):
844
 
        """Get the URL for verifying this submission, within the account of
845
 
        the given user."""
846
 
        # If this is a solo project, then self.path will be prefixed with the
847
 
        # subject name. Remove the first path segment.
848
 
        submitpath = self.path[1:] if self.path[:1] == '/' else self.path
849
 
        if not self.assessed.is_group:
850
 
            if '/' in submitpath:
851
 
                submitpath = submitpath.split('/', 1)[1]
852
 
            else:
853
 
                submitpath = ''
854
 
        return "/files/%s/%s/%s?r=%d" % (user.login,
855
 
            self.assessed.checkout_location, submitpath, self.revision)
856
 
 
857
 
    @staticmethod
858
 
    def test_and_normalise_path(path):
859
 
        """Test that path is valid, and normalise it. This prevents possible
860
 
        injections using malicious paths.
861
 
        Returns the updated path, if successful.
862
 
        Raises SubmissionError if invalid.
863
 
        """
864
 
        # Ensure the path is absolute to prevent being tacked onto working
865
 
        # directories.
866
 
        # Prevent '\n' because it will break all sorts of things.
867
 
        # Prevent '[' and ']' because they can be used to inject into the
868
 
        # svn.conf.
869
 
        # Normalise to avoid resulting in ".." path segments.
870
 
        if not os.path.isabs(path):
871
 
            raise SubmissionError("Path is not absolute")
872
 
        if any(c in path for c in "\n[]"):
873
 
            raise SubmissionError("Path must not contain '\\n', '[' or ']'")
874
 
        return os.path.normpath(path)
875
779
 
876
780
# WORKSHEETS AND EXERCISES #
877
781
 
908
812
    def __repr__(self):
909
813
        return "<%s %s>" % (type(self).__name__, self.name)
910
814
 
911
 
    def get_permissions(self, user, config):
912
 
        return self.global_permissions(user, config)
913
 
 
914
 
    @staticmethod
915
 
    def global_permissions(user, config):
916
 
        """Gets the set of permissions this user has over *all* exercises.
917
 
        This is used to determine who may view the exercises list, and create
918
 
        new exercises."""
 
815
    def get_permissions(self, user):
919
816
        perms = set()
920
817
        roles = set()
921
818
        if user is not None:
925
822
            elif u'lecturer' in set((e.role for e in user.active_enrolments)):
926
823
                perms.add('edit')
927
824
                perms.add('view')
928
 
            elif (config['policy']['tutors_can_edit_worksheets']
929
 
            and u'tutor' in set((e.role for e in user.active_enrolments))):
930
 
                # Site-specific policy on the role of tutors
 
825
            elif u'tutor' in set((e.role for e in user.active_enrolments)):
931
826
                perms.add('edit')
932
827
                perms.add('view')
933
828
 
994
889
        store.find(WorksheetExercise,
995
890
            WorksheetExercise.worksheet == self).remove()
996
891
 
997
 
    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:
1003
 
            perms.add('edit')
1004
 
        else:
1005
 
            perms.discard('edit')
1006
 
        return perms
 
892
    def get_permissions(self, user):
 
893
        return self.offering.get_permissions(user)
1007
894
 
1008
895
    def get_xml(self):
1009
896
        """Returns the xml of this worksheet, converts from rst if required."""
1054
941
        return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
1055
942
                                  self.worksheet.identifier)
1056
943
 
1057
 
    def get_permissions(self, user, config):
1058
 
        return self.worksheet.get_permissions(user, config)
 
944
    def get_permissions(self, user):
 
945
        return self.worksheet.get_permissions(user)
1059
946
 
1060
947
 
1061
948
class ExerciseSave(Storm):
1108
995
    complete = Bool()
1109
996
    active = Bool()
1110
997
 
1111
 
    def get_permissions(self, user, config):
 
998
    def get_permissions(self, user):
1112
999
        return set(['view']) if user is self.user else set()
1113
1000
 
1114
1001
class TestSuite(Storm):