~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:22:13 UTC
  • Revision ID: matt.giuca@gmail.com-20100212052213-vq4x06ia07xk9d81
docs: Added new manual page, User roles, detailing the exact capabilities of each role.

Show diffs side-by-side

added added

removed removed

Lines of Context:
227
227
        """Find a user in a store by login name."""
228
228
        return store.find(cls, cls.login == unicode(login)).one()
229
229
 
230
 
    def get_permissions(self, user):
 
230
    def get_permissions(self, user, config):
231
231
        """Determine privileges held by a user over this object.
232
232
 
233
233
        If the user requesting privileges is this user or an admin,
234
234
        they may do everything. Otherwise they may do nothing.
235
235
        """
236
236
        if user and user.admin or user is self:
237
 
            return set(['view', 'edit', 'submit_project'])
 
237
            return set(['view_public', 'view', 'edit', 'submit_project'])
238
238
        else:
239
 
            return set()
 
239
            return set(['view_public'])
240
240
 
241
241
# SUBJECTS AND ENROLMENTS #
242
242
 
249
249
    code = Unicode(name="subj_code")
250
250
    name = Unicode(name="subj_name")
251
251
    short_name = Unicode(name="subj_short_name")
252
 
    url = Unicode()
253
252
 
254
253
    offerings = ReferenceSet(id, 'Offering.subject_id')
255
254
 
258
257
    def __repr__(self):
259
258
        return "<%s '%s'>" % (type(self).__name__, self.short_name)
260
259
 
261
 
    def get_permissions(self, user):
 
260
    def get_permissions(self, user, config):
262
261
        """Determine privileges held by a user over this object.
263
262
 
264
263
        If the user requesting privileges is an admin, they may edit.
322
321
    subject = Reference(subject_id, Subject.id)
323
322
    semester_id = Int(name="semesterid")
324
323
    semester = Reference(semester_id, Semester.id)
 
324
    description = Unicode()
 
325
    url = Unicode()
325
326
    groups_student_permissions = Unicode()
326
327
 
327
328
    enrolments = ReferenceSet(id, 'Enrolment.offering_id')
330
331
                           'Enrolment.user_id',
331
332
                           'User.id')
332
333
    project_sets = ReferenceSet(id, 'ProjectSet.offering_id')
 
334
    projects = ReferenceSet(id,
 
335
                            'ProjectSet.offering_id',
 
336
                            'ProjectSet.id',
 
337
                            'Project.project_set_id')
333
338
 
334
339
    worksheets = ReferenceSet(id, 
335
340
        'Worksheet.offering_id', 
366
371
                               Enrolment.offering_id == self.id).one()
367
372
        Store.of(enrolment).remove(enrolment)
368
373
 
369
 
    def get_permissions(self, user):
 
374
    def get_permissions(self, user, config):
370
375
        perms = set()
371
376
        if user is not None:
372
377
            enrolment = self.get_enrolment(user)
374
379
                perms.add('view')
375
380
            if (enrolment and enrolment.role in (u'tutor', u'lecturer')) \
376
381
               or user.admin:
377
 
                perms.add('edit')
 
382
                # Site-specific policy on the role of tutors
 
383
                if config['policy']['tutors_can_enrol_students']:
 
384
                    perms.add('enrol')
 
385
                    perms.add('enrol_student')
 
386
                if config['policy']['tutors_can_edit_worksheets']:
 
387
                    perms.add('edit_worksheets')
 
388
            if (enrolment and enrolment.role in (u'lecturer')) or user.admin:
 
389
                perms.add('edit_worksheets')
 
390
                perms.add('edit')           # Can edit projects & details
 
391
                perms.add('enrol')          # Can see enrolment screen at all
 
392
                perms.add('enrol_student')  # Can enrol students
 
393
                perms.add('enrol_tutor')    # Can enrol tutors
 
394
            if user.admin:
 
395
                perms.add('enrol_lecturer') # Can enrol lecturers
378
396
        return perms
379
397
 
380
398
    def get_enrolment(self, user):
391
409
                Enrolment.user_id == User.id,
392
410
                Enrolment.offering_id == self.id,
393
411
                Enrolment.role == role
394
 
                )
 
412
                ).order_by(User.login)
395
413
 
396
414
    @property
397
415
    def students(self):
398
416
        return self.get_members_by_role(u'student')
399
417
 
 
418
    def get_open_projects_for_user(self, user):
 
419
        """Find all projects currently open to submissions by a user."""
 
420
        # XXX: Respect extensions.
 
421
        return self.projects.find(Project.deadline > datetime.datetime.now())
 
422
 
400
423
class Enrolment(Storm):
401
424
    """An enrolment of a user in an offering.
402
425
 
453
476
        return "<%s %d in %r>" % (type(self).__name__, self.id,
454
477
                                  self.offering)
455
478
 
456
 
    def get_permissions(self, user):
457
 
        return self.offering.get_permissions(user)
 
479
    def get_permissions(self, user, config):
 
480
        return self.offering.get_permissions(user, config)
 
481
 
 
482
    def get_groups_for_user(self, user):
 
483
        """List all groups in this offering of which the user is a member."""
 
484
        assert self.is_group
 
485
        return Store.of(self).find(
 
486
            ProjectGroup,
 
487
            ProjectGroupMembership.user_id == user.id,
 
488
            ProjectGroupMembership.project_group_id == ProjectGroup.id,
 
489
            ProjectGroup.project_set_id == self.id)
 
490
 
 
491
    def get_submission_principal(self, user):
 
492
        """Get the principal on behalf of which the user can submit.
 
493
 
 
494
        If this is a solo project set, the given user is returned. If
 
495
        the user is a member of exactly one group, all the group is
 
496
        returned. Otherwise, None is returned.
 
497
        """
 
498
        if self.is_group:
 
499
            groups = self.get_groups_for_user(user)
 
500
            if groups.count() == 1:
 
501
                return groups.one()
 
502
            else:
 
503
                return None
 
504
        else:
 
505
            return user
 
506
 
 
507
    @property
 
508
    def is_group(self):
 
509
        return self.max_students_per_group is not None
458
510
 
459
511
    @property
460
512
    def assigned(self):
463
515
        This will be a Storm ResultSet.
464
516
        """
465
517
        #If its a solo project, return everyone in offering
466
 
        if self.max_students_per_group is None:
 
518
        if self.is_group:
 
519
            return self.project_groups
 
520
        else:
467
521
            return self.offering.students
468
 
        else:
469
 
            return self.project_groups
 
522
 
 
523
class DeadlinePassed(Exception):
 
524
    """An exception indicating that a project cannot be submitted because the
 
525
    deadline has passed."""
 
526
    def __init__(self):
 
527
        pass
 
528
    def __str__(self):
 
529
        return "The project deadline has passed"
470
530
 
471
531
class Project(Storm):
472
532
    """A student project for which submissions can be made."""
494
554
        return "<%s '%s' in %r>" % (type(self).__name__, self.short_name,
495
555
                                  self.project_set.offering)
496
556
 
497
 
    def can_submit(self, principal):
 
557
    def can_submit(self, principal, user):
498
558
        return (self in principal.get_projects() and
499
 
                self.deadline > datetime.datetime.now())
 
559
                not self.has_deadline_passed(user))
500
560
 
501
561
    def submit(self, principal, path, revision, who):
502
562
        """Submit a Subversion path and revision to a project.
508
568
        @param who: The user who is actually making the submission.
509
569
        """
510
570
 
511
 
        if not self.can_submit(principal):
512
 
            raise Exception('cannot submit')
 
571
        if not self.can_submit(principal, who):
 
572
            raise DeadlinePassed()
513
573
 
514
574
        a = Assessed.get(Store.of(self), principal, self)
515
575
        ps = ProjectSubmission()
521
581
 
522
582
        return ps
523
583
 
524
 
    def get_permissions(self, user):
525
 
        return self.project_set.offering.get_permissions(user)
 
584
    def get_permissions(self, user, config):
 
585
        return self.project_set.offering.get_permissions(user, config)
526
586
 
527
587
    @property
528
588
    def latest_submissions(self):
537
597
            )
538
598
        )
539
599
 
 
600
    def has_deadline_passed(self, user):
 
601
        """Check whether the deadline has passed."""
 
602
        # XXX: Need to respect extensions.
 
603
        return self.deadline < datetime.datetime.now()
 
604
 
 
605
    def get_submissions_for_principal(self, principal):
 
606
        """Fetch a ResultSet of all submissions by a particular principal."""
 
607
        assessed = Assessed.get(Store.of(self), principal, self)
 
608
        if assessed is None:
 
609
            return
 
610
        return assessed.submissions
 
611
 
 
612
 
540
613
 
541
614
class ProjectGroup(Storm):
542
615
    """A group of students working together on a project."""
593
666
            (not active_only) or (Semester.state == u'current'))
594
667
 
595
668
 
596
 
    def get_permissions(self, user):
 
669
    def get_permissions(self, user, config):
597
670
        if user.admin or user in self.members:
598
671
            return set(['submit_project'])
599
672
        else:
635
708
    project = Reference(project_id, Project.id)
636
709
 
637
710
    extensions = ReferenceSet(id, 'ProjectExtension.assessed_id')
638
 
    submissions = ReferenceSet(id, 'ProjectSubmission.assessed_id')
 
711
    submissions = ReferenceSet(
 
712
        id, 'ProjectSubmission.assessed_id', order_by='date_submitted')
639
713
 
640
714
    def __repr__(self):
641
715
        return "<%s %r in %r>" % (type(self).__name__,
650
724
    def principal(self):
651
725
        return self.project_group or self.user
652
726
 
 
727
    @property
 
728
    def checkout_location(self):
 
729
        """Returns the location of the Subversion workspace for this piece of
 
730
        assessment, relative to each group member's home directory."""
 
731
        subjectname = self.project.project_set.offering.subject.short_name
 
732
        if self.is_group:
 
733
            checkout_dir_name = self.principal.short_name
 
734
        else:
 
735
            checkout_dir_name = "mywork"
 
736
        return subjectname + "/" + checkout_dir_name
 
737
 
653
738
    @classmethod
654
739
    def get(cls, store, principal, project):
655
740
        """Find or create an Assessed for the given user or group and project.
664
749
        a = store.find(cls,
665
750
            (t is User) or (cls.project_group_id == principal.id),
666
751
            (t is ProjectGroup) or (cls.user_id == principal.id),
667
 
            Project.id == project.id).one()
 
752
            cls.project_id == project.id).one()
668
753
 
669
754
        if a is None:
670
755
            a = cls()
715
800
    submitter = Reference(submitter_id, User.id)
716
801
    date_submitted = DateTime()
717
802
 
 
803
    def get_verify_url(self, user):
 
804
        """Get the URL for verifying this submission, within the account of
 
805
        the given user."""
 
806
        # If this is a solo project, then self.path will be prefixed with the
 
807
        # subject name. Remove the first path segment.
 
808
        submitpath = self.path[1:] if self.path[:1] == '/' else self.path
 
809
        if not self.assessed.is_group:
 
810
            if '/' in submitpath:
 
811
                submitpath = submitpath.split('/', 1)[1]
 
812
            else:
 
813
                submitpath = ''
 
814
        return "/files/%s/%s/%s?r=%d" % (user.login,
 
815
            self.assessed.checkout_location, submitpath, self.revision)
718
816
 
719
817
# WORKSHEETS AND EXERCISES #
720
818
 
751
849
    def __repr__(self):
752
850
        return "<%s %s>" % (type(self).__name__, self.name)
753
851
 
754
 
    def get_permissions(self, user):
 
852
    def get_permissions(self, user, config):
 
853
        return self.global_permissions(user, config)
 
854
 
 
855
    @staticmethod
 
856
    def global_permissions(user, config):
 
857
        """Gets the set of permissions this user has over *all* exercises.
 
858
        This is used to determine who may view the exercises list, and create
 
859
        new exercises."""
755
860
        perms = set()
756
861
        roles = set()
757
862
        if user is not None:
761
866
            elif u'lecturer' in set((e.role for e in user.active_enrolments)):
762
867
                perms.add('edit')
763
868
                perms.add('view')
764
 
            elif u'tutor' in set((e.role for e in user.active_enrolments)):
 
869
            elif (config['policy']['tutors_can_edit_worksheets']
 
870
            and u'tutor' in set((e.role for e in user.active_enrolments))):
 
871
                # Site-specific policy on the role of tutors
765
872
                perms.add('edit')
766
873
                perms.add('view')
767
874
 
828
935
        store.find(WorksheetExercise,
829
936
            WorksheetExercise.worksheet == self).remove()
830
937
 
831
 
    def get_permissions(self, user):
832
 
        return self.offering.get_permissions(user)
 
938
    def get_permissions(self, user, config):
 
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:
 
944
            perms.add('edit')
 
945
        else:
 
946
            perms.discard('edit')
 
947
        return perms
833
948
 
834
949
    def get_xml(self):
835
950
        """Returns the xml of this worksheet, converts from rst if required."""
880
995
        return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
881
996
                                  self.worksheet.identifier)
882
997
 
883
 
    def get_permissions(self, user):
884
 
        return self.worksheet.get_permissions(user)
 
998
    def get_permissions(self, user, config):
 
999
        return self.worksheet.get_permissions(user, config)
885
1000
 
886
1001
 
887
1002
class ExerciseSave(Storm):
934
1049
    complete = Bool()
935
1050
    active = Bool()
936
1051
 
937
 
    def get_permissions(self, user):
 
1052
    def get_permissions(self, user, config):
938
1053
        return set(['view']) if user is self.user else set()
939
1054
 
940
1055
class TestSuite(Storm):
959
1074
 
960
1075
    def delete(self):
961
1076
        """Delete this suite, without asking questions."""
962
 
        for vaariable in self.variables:
 
1077
        for variable in self.variables:
963
1078
            variable.delete()
964
1079
        for test_case in self.test_cases:
965
1080
            test_case.delete()
978
1093
    suite = Reference(suiteid, "TestSuite.suiteid")
979
1094
    passmsg = Unicode()
980
1095
    failmsg = Unicode()
981
 
    test_default = Unicode()
 
1096
    test_default = Unicode() # Currently unused - only used for file matching.
982
1097
    seq_no = Int()
983
1098
 
984
1099
    parts = ReferenceSet(testid, "TestCasePart.testid")