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

« back to all changes in this revision

Viewing changes to ivle/database.py

  • Committer: William Grant
  • Date: 2009-05-11 04:28:07 UTC
  • mto: (1165.3.65 submissions-admin)
  • mto: This revision was merged to the branch mainline in revision 1247.
  • Revision ID: grantw@unimelb.edu.au-20090511042807-xk9seshzis29sfs6
Exclude group members from the SVN submission grants, to avoid revoking write access.

Show diffs side-by-side

added added

removed removed

Lines of Context:
28
28
 
29
29
from storm.locals import create_database, Store, Int, Unicode, DateTime, \
30
30
                         Reference, ReferenceSet, Bool, Storm, Desc
31
 
from storm.expr import Select, Max
32
31
from storm.exceptions import NotOneError, IntegrityError
33
32
 
34
33
from ivle.worksheet.rst import rst
117
116
 
118
117
    @property
119
118
    def display_name(self):
120
 
        """Returns the "nice name" of the user or group."""
121
119
        return self.fullname
122
120
 
123
121
    @property
124
 
    def short_name(self):
125
 
        """Returns the database "identifier" name of the user or group."""
126
 
        return self.login
127
 
 
128
 
    @property
129
122
    def password_expired(self):
130
123
        fieldval = self.pass_exp
131
124
        return fieldval is not None and datetime.datetime.now() > fieldval
215
208
            Semester.id == Offering.semester_id,
216
209
            (not active_only) or (Semester.state == u'current'),
217
210
            Enrolment.offering_id == Offering.id,
218
 
            Enrolment.user_id == self.id,
219
 
            Enrolment.active == True)
 
211
            Enrolment.user_id == self.id)
220
212
 
221
213
    @staticmethod
222
214
    def hash_password(password):
228
220
        """Find a user in a store by login name."""
229
221
        return store.find(cls, cls.login == unicode(login)).one()
230
222
 
231
 
    def get_permissions(self, user, config):
 
223
    def get_permissions(self, user):
232
224
        """Determine privileges held by a user over this object.
233
225
 
234
226
        If the user requesting privileges is this user or an admin,
235
227
        they may do everything. Otherwise they may do nothing.
236
228
        """
237
229
        if user and user.admin or user is self:
238
 
            return set(['view_public', 'view', 'edit', 'submit_project'])
 
230
            return set(['view', 'edit', 'submit_project'])
239
231
        else:
240
 
            return set(['view_public'])
 
232
            return set()
241
233
 
242
234
# SUBJECTS AND ENROLMENTS #
243
235
 
250
242
    code = Unicode(name="subj_code")
251
243
    name = Unicode(name="subj_name")
252
244
    short_name = Unicode(name="subj_short_name")
 
245
    url = Unicode()
253
246
 
254
247
    offerings = ReferenceSet(id, 'Offering.subject_id')
255
248
 
258
251
    def __repr__(self):
259
252
        return "<%s '%s'>" % (type(self).__name__, self.short_name)
260
253
 
261
 
    def get_permissions(self, user, config):
 
254
    def get_permissions(self, user):
262
255
        """Determine privileges held by a user over this object.
263
256
 
264
257
        If the user requesting privileges is an admin, they may edit.
322
315
    subject = Reference(subject_id, Subject.id)
323
316
    semester_id = Int(name="semesterid")
324
317
    semester = Reference(semester_id, Semester.id)
325
 
    description = Unicode()
326
 
    url = Unicode()
327
318
    groups_student_permissions = Unicode()
328
319
 
329
320
    enrolments = ReferenceSet(id, 'Enrolment.offering_id')
332
323
                           'Enrolment.user_id',
333
324
                           'User.id')
334
325
    project_sets = ReferenceSet(id, 'ProjectSet.offering_id')
335
 
    projects = ReferenceSet(id,
336
 
                            'ProjectSet.offering_id',
337
 
                            'ProjectSet.id',
338
 
                            'Project.project_set_id')
339
326
 
340
327
    worksheets = ReferenceSet(id, 
341
328
        'Worksheet.offering_id', 
372
359
                               Enrolment.offering_id == self.id).one()
373
360
        Store.of(enrolment).remove(enrolment)
374
361
 
375
 
    def get_permissions(self, user, config):
 
362
    def get_permissions(self, user):
376
363
        perms = set()
377
364
        if user is not None:
378
365
            enrolment = self.get_enrolment(user)
379
366
            if enrolment or user.admin:
380
367
                perms.add('view')
381
 
            if enrolment and enrolment.role == u'tutor':
382
 
                perms.add('view_project_submissions')
383
 
                # Site-specific policy on the role of tutors
384
 
                if config['policy']['tutors_can_enrol_students']:
385
 
                    perms.add('enrol')
386
 
                    perms.add('enrol_student')
387
 
                if config['policy']['tutors_can_edit_worksheets']:
388
 
                    perms.add('edit_worksheets')
389
 
                if config['policy']['tutors_can_admin_groups']:
390
 
                    perms.add('admin_groups')
391
 
            if (enrolment and enrolment.role in (u'lecturer')) or user.admin:
392
 
                perms.add('view_project_submissions')
393
 
                perms.add('admin_groups')
394
 
                perms.add('edit_worksheets')
395
 
                perms.add('edit')           # Can edit projects & details
396
 
                perms.add('enrol')          # Can see enrolment screen at all
397
 
                perms.add('enrol_student')  # Can enrol students
398
 
                perms.add('enrol_tutor')    # Can enrol tutors
399
 
            if user.admin:
400
 
                perms.add('enrol_lecturer') # Can enrol lecturers
 
368
            if (enrolment and enrolment.role in (u'tutor', u'lecturer')) \
 
369
               or user.admin:
 
370
                perms.add('edit')
401
371
        return perms
402
372
 
403
373
    def get_enrolment(self, user):
409
379
 
410
380
        return enrolment
411
381
 
412
 
    def get_members_by_role(self, role):
413
 
        return Store.of(self).find(User,
414
 
                Enrolment.user_id == User.id,
415
 
                Enrolment.offering_id == self.id,
416
 
                Enrolment.role == role
417
 
                ).order_by(User.login)
418
 
 
419
 
    @property
420
 
    def students(self):
421
 
        return self.get_members_by_role(u'student')
422
 
 
423
 
    def get_open_projects_for_user(self, user):
424
 
        """Find all projects currently open to submissions by a user."""
425
 
        # XXX: Respect extensions.
426
 
        return self.projects.find(Project.deadline > datetime.datetime.now())
 
382
    def get_students(self):
 
383
        enrolments = self.enrolments.find(role=u'student')
 
384
        return [enrolment.user for enrolment in enrolments]
427
385
 
428
386
class Enrolment(Storm):
429
387
    """An enrolment of a user in an offering.
481
439
        return "<%s %d in %r>" % (type(self).__name__, self.id,
482
440
                                  self.offering)
483
441
 
484
 
    def get_permissions(self, user, config):
485
 
        return self.offering.get_permissions(user, config)
486
 
 
487
 
    def get_groups_for_user(self, user):
488
 
        """List all groups in this offering of which the user is a member."""
489
 
        assert self.is_group
490
 
        return Store.of(self).find(
491
 
            ProjectGroup,
492
 
            ProjectGroupMembership.user_id == user.id,
493
 
            ProjectGroupMembership.project_group_id == ProjectGroup.id,
494
 
            ProjectGroup.project_set_id == self.id)
495
 
 
496
 
    def get_submission_principal(self, user):
497
 
        """Get the principal on behalf of which the user can submit.
498
 
 
499
 
        If this is a solo project set, the given user is returned. If
500
 
        the user is a member of exactly one group, all the group is
501
 
        returned. Otherwise, None is returned.
502
 
        """
503
 
        if self.is_group:
504
 
            groups = self.get_groups_for_user(user)
505
 
            if groups.count() == 1:
506
 
                return groups.one()
507
 
            else:
508
 
                return None
 
442
    def get_permissions(self, user):
 
443
        return self.offering.get_permissions(user)
 
444
 
 
445
    # Get the individuals (groups or users) Assigned to this project
 
446
    def get_assigned(self):
 
447
        #If its a Solo project, return everyone in offering
 
448
        if self.max_students_per_group is None:
 
449
            return self.offering.get_students()
509
450
        else:
510
 
            return user
511
 
 
512
 
    @property
513
 
    def is_group(self):
514
 
        return self.max_students_per_group is not None
515
 
 
516
 
    @property
517
 
    def assigned(self):
518
 
        """Get the entities (groups or users) assigned to submit this project.
519
 
 
520
 
        This will be a Storm ResultSet.
521
 
        """
522
 
        #If its a solo project, return everyone in offering
523
 
        if self.is_group:
524
451
            return self.project_groups
525
 
        else:
526
 
            return self.offering.students
527
 
 
528
 
class DeadlinePassed(Exception):
529
 
    """An exception indicating that a project cannot be submitted because the
530
 
    deadline has passed."""
531
 
    def __init__(self):
532
 
        pass
533
 
    def __str__(self):
534
 
        return "The project deadline has passed"
535
452
 
536
453
class Project(Storm):
537
454
    """A student project for which submissions can be made."""
559
476
        return "<%s '%s' in %r>" % (type(self).__name__, self.short_name,
560
477
                                  self.project_set.offering)
561
478
 
562
 
    def can_submit(self, principal, user):
 
479
    def can_submit(self, principal):
563
480
        return (self in principal.get_projects() and
564
 
                not self.has_deadline_passed(user))
 
481
                self.deadline > datetime.datetime.now())
565
482
 
566
483
    def submit(self, principal, path, revision, who):
567
484
        """Submit a Subversion path and revision to a project.
573
490
        @param who: The user who is actually making the submission.
574
491
        """
575
492
 
576
 
        if not self.can_submit(principal, who):
577
 
            raise DeadlinePassed()
 
493
        if not self.can_submit(principal):
 
494
            raise Exception('cannot submit')
578
495
 
579
496
        a = Assessed.get(Store.of(self), principal, self)
580
497
        ps = ProjectSubmission()
586
503
 
587
504
        return ps
588
505
 
589
 
    def get_permissions(self, user, config):
590
 
        return self.project_set.offering.get_permissions(user, config)
591
 
 
592
 
    @property
593
 
    def latest_submissions(self):
594
 
        """Return the latest submission for each Assessed."""
595
 
        return Store.of(self).find(ProjectSubmission,
596
 
            Assessed.project_id == self.id,
597
 
            ProjectSubmission.assessed_id == Assessed.id,
598
 
            ProjectSubmission.date_submitted == Select(
599
 
                    Max(ProjectSubmission.date_submitted),
600
 
                    ProjectSubmission.assessed_id == Assessed.id,
601
 
                    tables=ProjectSubmission
602
 
            )
603
 
        )
604
 
 
605
 
    def has_deadline_passed(self, user):
606
 
        """Check whether the deadline has passed."""
607
 
        # XXX: Need to respect extensions.
608
 
        return self.deadline < datetime.datetime.now()
609
 
 
610
 
    def get_submissions_for_principal(self, principal):
611
 
        """Fetch a ResultSet of all submissions by a particular principal."""
612
 
        assessed = Assessed.get(Store.of(self), principal, self)
613
 
        if assessed is None:
614
 
            return
615
 
        return assessed.submissions
616
 
 
 
506
    def get_permissions(self, user):
 
507
        return self.project_set.offering.get_permissions(user)
617
508
 
618
509
 
619
510
class ProjectGroup(Storm):
643
534
 
644
535
    @property
645
536
    def display_name(self):
646
 
        """Returns the "nice name" of the user or group."""
647
 
        return self.nick
648
 
 
649
 
    @property
650
 
    def short_name(self):
651
 
        """Returns the database "identifier" name of the user or group."""
652
 
        return self.name
 
537
        return '%s (%s)' % (self.nick, self.name)
653
538
 
654
539
    def get_projects(self, offering=None, active_only=True):
655
540
        '''Find projects that the group can submit.
671
556
            (not active_only) or (Semester.state == u'current'))
672
557
 
673
558
 
674
 
    def get_permissions(self, user, config):
 
559
    def get_permissions(self, user):
675
560
        if user.admin or user in self.members:
676
561
            return set(['submit_project'])
677
562
        else:
713
598
    project = Reference(project_id, Project.id)
714
599
 
715
600
    extensions = ReferenceSet(id, 'ProjectExtension.assessed_id')
716
 
    submissions = ReferenceSet(
717
 
        id, 'ProjectSubmission.assessed_id', order_by='date_submitted')
 
601
    submissions = ReferenceSet(id, 'ProjectSubmission.assessed_id')
718
602
 
719
603
    def __repr__(self):
720
604
        return "<%s %r in %r>" % (type(self).__name__,
721
605
            self.user or self.project_group, self.project)
722
606
 
723
607
    @property
724
 
    def is_group(self):
725
 
        """True if the Assessed is a group, False if it is a user."""
726
 
        return self.project_group is not None
727
 
 
728
 
    @property
729
608
    def principal(self):
730
609
        return self.project_group or self.user
731
610
 
732
 
    @property
733
 
    def checkout_location(self):
734
 
        """Returns the location of the Subversion workspace for this piece of
735
 
        assessment, relative to each group member's home directory."""
736
 
        subjectname = self.project.project_set.offering.subject.short_name
737
 
        if self.is_group:
738
 
            checkout_dir_name = self.principal.short_name
739
 
        else:
740
 
            checkout_dir_name = "mywork"
741
 
        return subjectname + "/" + checkout_dir_name
742
 
 
743
611
    @classmethod
744
612
    def get(cls, store, principal, project):
745
613
        """Find or create an Assessed for the given user or group and project.
754
622
        a = store.find(cls,
755
623
            (t is User) or (cls.project_group_id == principal.id),
756
624
            (t is ProjectGroup) or (cls.user_id == principal.id),
757
 
            cls.project_id == project.id).one()
 
625
            Project.id == project.id).one()
758
626
 
759
627
        if a is None:
760
628
            a = cls()
805
673
    submitter = Reference(submitter_id, User.id)
806
674
    date_submitted = DateTime()
807
675
 
808
 
    def get_verify_url(self, user):
809
 
        """Get the URL for verifying this submission, within the account of
810
 
        the given user."""
811
 
        # If this is a solo project, then self.path will be prefixed with the
812
 
        # subject name. Remove the first path segment.
813
 
        submitpath = self.path[1:] if self.path[:1] == '/' else self.path
814
 
        if not self.assessed.is_group:
815
 
            if '/' in submitpath:
816
 
                submitpath = submitpath.split('/', 1)[1]
817
 
            else:
818
 
                submitpath = ''
819
 
        return "/files/%s/%s/%s?r=%d" % (user.login,
820
 
            self.assessed.checkout_location, submitpath, self.revision)
821
676
 
822
677
# WORKSHEETS AND EXERCISES #
823
678
 
854
709
    def __repr__(self):
855
710
        return "<%s %s>" % (type(self).__name__, self.name)
856
711
 
857
 
    def get_permissions(self, user, config):
858
 
        return self.global_permissions(user, config)
859
 
 
860
 
    @staticmethod
861
 
    def global_permissions(user, config):
862
 
        """Gets the set of permissions this user has over *all* exercises.
863
 
        This is used to determine who may view the exercises list, and create
864
 
        new exercises."""
 
712
    def get_permissions(self, user):
865
713
        perms = set()
866
714
        roles = set()
867
715
        if user is not None:
871
719
            elif u'lecturer' in set((e.role for e in user.active_enrolments)):
872
720
                perms.add('edit')
873
721
                perms.add('view')
874
 
            elif (config['policy']['tutors_can_edit_worksheets']
875
 
            and u'tutor' in set((e.role for e in user.active_enrolments))):
876
 
                # Site-specific policy on the role of tutors
 
722
            elif u'tutor' in set((e.role for e in user.active_enrolments)):
877
723
                perms.add('edit')
878
724
                perms.add('view')
879
725
 
940
786
        store.find(WorksheetExercise,
941
787
            WorksheetExercise.worksheet == self).remove()
942
788
 
943
 
    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:
949
 
            perms.add('edit')
950
 
        else:
951
 
            perms.discard('edit')
952
 
        return perms
 
789
    def get_permissions(self, user):
 
790
        return self.offering.get_permissions(user)
953
791
 
954
792
    def get_xml(self):
955
793
        """Returns the xml of this worksheet, converts from rst if required."""
1000
838
        return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
1001
839
                                  self.worksheet.identifier)
1002
840
 
1003
 
    def get_permissions(self, user, config):
1004
 
        return self.worksheet.get_permissions(user, config)
 
841
    def get_permissions(self, user):
 
842
        return self.worksheet.get_permissions(user)
1005
843
 
1006
844
 
1007
845
class ExerciseSave(Storm):
1054
892
    complete = Bool()
1055
893
    active = Bool()
1056
894
 
1057
 
    def get_permissions(self, user, config):
 
895
    def get_permissions(self, user):
1058
896
        return set(['view']) if user is self.user else set()
1059
897
 
1060
898
class TestSuite(Storm):
1079
917
 
1080
918
    def delete(self):
1081
919
        """Delete this suite, without asking questions."""
1082
 
        for variable in self.variables:
 
920
        for vaariable in self.variables:
1083
921
            variable.delete()
1084
922
        for test_case in self.test_cases:
1085
923
            test_case.delete()
1098
936
    suite = Reference(suiteid, "TestSuite.suiteid")
1099
937
    passmsg = Unicode()
1100
938
    failmsg = Unicode()
1101
 
    test_default = Unicode() # Currently unused - only used for file matching.
 
939
    test_default = Unicode()
1102
940
    seq_no = Int()
1103
941
 
1104
942
    parts = ReferenceSet(testid, "TestCasePart.testid")