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

« back to all changes in this revision

Viewing changes to ivle/database.py

  • Committer: William Grant
  • Date: 2010-02-18 06:43:57 UTC
  • Revision ID: grantw@unimelb.edu.au-20100218064357-a43eajvau1gpzw29
Add ivle.tests, move ivle.test_chat into there, and be slightly more idiomatic.

Show diffs side-by-side

added added

removed removed

Lines of Context:
215
215
            Semester.id == Offering.semester_id,
216
216
            (not active_only) or (Semester.state == u'current'),
217
217
            Enrolment.offering_id == Offering.id,
218
 
            Enrolment.user_id == self.id)
 
218
            Enrolment.user_id == self.id,
 
219
            Enrolment.active == True)
219
220
 
220
221
    @staticmethod
221
222
    def hash_password(password):
227
228
        """Find a user in a store by login name."""
228
229
        return store.find(cls, cls.login == unicode(login)).one()
229
230
 
230
 
    def get_permissions(self, user):
 
231
    def get_permissions(self, user, config):
231
232
        """Determine privileges held by a user over this object.
232
233
 
233
234
        If the user requesting privileges is this user or an admin,
234
235
        they may do everything. Otherwise they may do nothing.
235
236
        """
236
237
        if user and user.admin or user is self:
237
 
            return set(['view', 'edit', 'submit_project'])
 
238
            return set(['view_public', 'view', 'edit', 'submit_project'])
238
239
        else:
239
 
            return set()
 
240
            return set(['view_public'])
240
241
 
241
242
# SUBJECTS AND ENROLMENTS #
242
243
 
249
250
    code = Unicode(name="subj_code")
250
251
    name = Unicode(name="subj_name")
251
252
    short_name = Unicode(name="subj_short_name")
252
 
    url = Unicode()
253
253
 
254
254
    offerings = ReferenceSet(id, 'Offering.subject_id')
255
255
 
258
258
    def __repr__(self):
259
259
        return "<%s '%s'>" % (type(self).__name__, self.short_name)
260
260
 
261
 
    def get_permissions(self, user):
 
261
    def get_permissions(self, user, config):
262
262
        """Determine privileges held by a user over this object.
263
263
 
264
264
        If the user requesting privileges is an admin, they may edit.
322
322
    subject = Reference(subject_id, Subject.id)
323
323
    semester_id = Int(name="semesterid")
324
324
    semester = Reference(semester_id, Semester.id)
 
325
    description = Unicode()
 
326
    url = Unicode()
325
327
    groups_student_permissions = Unicode()
326
328
 
327
329
    enrolments = ReferenceSet(id, 'Enrolment.offering_id')
330
332
                           'Enrolment.user_id',
331
333
                           'User.id')
332
334
    project_sets = ReferenceSet(id, 'ProjectSet.offering_id')
 
335
    projects = ReferenceSet(id,
 
336
                            'ProjectSet.offering_id',
 
337
                            'ProjectSet.id',
 
338
                            'Project.project_set_id')
333
339
 
334
340
    worksheets = ReferenceSet(id, 
335
341
        'Worksheet.offering_id', 
366
372
                               Enrolment.offering_id == self.id).one()
367
373
        Store.of(enrolment).remove(enrolment)
368
374
 
369
 
    def get_permissions(self, user):
 
375
    def get_permissions(self, user, config):
370
376
        perms = set()
371
377
        if user is not None:
372
378
            enrolment = self.get_enrolment(user)
373
379
            if enrolment or user.admin:
374
380
                perms.add('view')
375
 
            if (enrolment and enrolment.role in (u'tutor', u'lecturer')) \
376
 
               or user.admin:
377
 
                perms.add('edit')
 
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
378
401
        return perms
379
402
 
380
403
    def get_enrolment(self, user):
391
414
                Enrolment.user_id == User.id,
392
415
                Enrolment.offering_id == self.id,
393
416
                Enrolment.role == role
394
 
                )
 
417
                ).order_by(User.login)
395
418
 
396
419
    @property
397
420
    def students(self):
398
421
        return self.get_members_by_role(u'student')
399
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())
 
427
 
 
428
    def clone_worksheets(self, source):
 
429
        """Clone all worksheets from the specified source to this offering."""
 
430
        import ivle.worksheet.utils
 
431
        for worksheet in source.worksheets:
 
432
            newws = Worksheet()
 
433
            newws.seq_no = worksheet.seq_no
 
434
            newws.identifier = worksheet.identifier
 
435
            newws.name = worksheet.name
 
436
            newws.assessable = worksheet.assessable
 
437
            newws.data = worksheet.data
 
438
            newws.format = worksheet.format
 
439
            newws.offering = self
 
440
            Store.of(self).add(newws)
 
441
            ivle.worksheet.utils.update_exerciselist(newws)
 
442
 
 
443
 
400
444
class Enrolment(Storm):
401
445
    """An enrolment of a user in an offering.
402
446
 
428
472
        return "<%s %r in %r>" % (type(self).__name__, self.user,
429
473
                                  self.offering)
430
474
 
 
475
    def get_permissions(self, user, config):
 
476
        # A user can edit any enrolment that they could have created.
 
477
        perms = set()
 
478
        if ('enrol_' + str(self.role)) in self.offering.get_permissions(
 
479
            user, config):
 
480
            perms.add('edit')
 
481
        return perms
 
482
 
 
483
    def delete(self):
 
484
        """Delete this enrolment."""
 
485
        Store.of(self).remove(self)
 
486
 
 
487
 
431
488
# PROJECTS #
432
489
 
433
490
class ProjectSet(Storm):
453
510
        return "<%s %d in %r>" % (type(self).__name__, self.id,
454
511
                                  self.offering)
455
512
 
456
 
    def get_permissions(self, user):
457
 
        return self.offering.get_permissions(user)
 
513
    def get_permissions(self, user, config):
 
514
        return self.offering.get_permissions(user, config)
 
515
 
 
516
    def get_groups_for_user(self, user):
 
517
        """List all groups in this offering of which the user is a member."""
 
518
        assert self.is_group
 
519
        return Store.of(self).find(
 
520
            ProjectGroup,
 
521
            ProjectGroupMembership.user_id == user.id,
 
522
            ProjectGroupMembership.project_group_id == ProjectGroup.id,
 
523
            ProjectGroup.project_set_id == self.id)
 
524
 
 
525
    def get_submission_principal(self, user):
 
526
        """Get the principal on behalf of which the user can submit.
 
527
 
 
528
        If this is a solo project set, the given user is returned. If
 
529
        the user is a member of exactly one group, all the group is
 
530
        returned. Otherwise, None is returned.
 
531
        """
 
532
        if self.is_group:
 
533
            groups = self.get_groups_for_user(user)
 
534
            if groups.count() == 1:
 
535
                return groups.one()
 
536
            else:
 
537
                return None
 
538
        else:
 
539
            return user
 
540
 
 
541
    @property
 
542
    def is_group(self):
 
543
        return self.max_students_per_group is not None
458
544
 
459
545
    @property
460
546
    def assigned(self):
463
549
        This will be a Storm ResultSet.
464
550
        """
465
551
        #If its a solo project, return everyone in offering
466
 
        if self.max_students_per_group is None:
 
552
        if self.is_group:
 
553
            return self.project_groups
 
554
        else:
467
555
            return self.offering.students
468
 
        else:
469
 
            return self.project_groups
 
556
 
 
557
class DeadlinePassed(Exception):
 
558
    """An exception indicating that a project cannot be submitted because the
 
559
    deadline has passed."""
 
560
    def __init__(self):
 
561
        pass
 
562
    def __str__(self):
 
563
        return "The project deadline has passed"
470
564
 
471
565
class Project(Storm):
472
566
    """A student project for which submissions can be made."""
494
588
        return "<%s '%s' in %r>" % (type(self).__name__, self.short_name,
495
589
                                  self.project_set.offering)
496
590
 
497
 
    def can_submit(self, principal):
 
591
    def can_submit(self, principal, user):
498
592
        return (self in principal.get_projects() and
499
 
                self.deadline > datetime.datetime.now())
 
593
                not self.has_deadline_passed(user))
500
594
 
501
595
    def submit(self, principal, path, revision, who):
502
596
        """Submit a Subversion path and revision to a project.
508
602
        @param who: The user who is actually making the submission.
509
603
        """
510
604
 
511
 
        if not self.can_submit(principal):
512
 
            raise Exception('cannot submit')
 
605
        if not self.can_submit(principal, who):
 
606
            raise DeadlinePassed()
513
607
 
514
608
        a = Assessed.get(Store.of(self), principal, self)
515
609
        ps = ProjectSubmission()
521
615
 
522
616
        return ps
523
617
 
524
 
    def get_permissions(self, user):
525
 
        return self.project_set.offering.get_permissions(user)
 
618
    def get_permissions(self, user, config):
 
619
        return self.project_set.offering.get_permissions(user, config)
526
620
 
527
621
    @property
528
622
    def latest_submissions(self):
537
631
            )
538
632
        )
539
633
 
 
634
    def has_deadline_passed(self, user):
 
635
        """Check whether the deadline has passed."""
 
636
        # XXX: Need to respect extensions.
 
637
        return self.deadline < datetime.datetime.now()
 
638
 
 
639
    def get_submissions_for_principal(self, principal):
 
640
        """Fetch a ResultSet of all submissions by a particular principal."""
 
641
        assessed = Assessed.get(Store.of(self), principal, self)
 
642
        if assessed is None:
 
643
            return
 
644
        return assessed.submissions
 
645
 
 
646
 
540
647
 
541
648
class ProjectGroup(Storm):
542
649
    """A group of students working together on a project."""
593
700
            (not active_only) or (Semester.state == u'current'))
594
701
 
595
702
 
596
 
    def get_permissions(self, user):
 
703
    def get_permissions(self, user, config):
597
704
        if user.admin or user in self.members:
598
705
            return set(['submit_project'])
599
706
        else:
635
742
    project = Reference(project_id, Project.id)
636
743
 
637
744
    extensions = ReferenceSet(id, 'ProjectExtension.assessed_id')
638
 
    submissions = ReferenceSet(id, 'ProjectSubmission.assessed_id')
 
745
    submissions = ReferenceSet(
 
746
        id, 'ProjectSubmission.assessed_id', order_by='date_submitted')
639
747
 
640
748
    def __repr__(self):
641
749
        return "<%s %r in %r>" % (type(self).__name__,
650
758
    def principal(self):
651
759
        return self.project_group or self.user
652
760
 
 
761
    @property
 
762
    def checkout_location(self):
 
763
        """Returns the location of the Subversion workspace for this piece of
 
764
        assessment, relative to each group member's home directory."""
 
765
        subjectname = self.project.project_set.offering.subject.short_name
 
766
        if self.is_group:
 
767
            checkout_dir_name = self.principal.short_name
 
768
        else:
 
769
            checkout_dir_name = "mywork"
 
770
        return subjectname + "/" + checkout_dir_name
 
771
 
653
772
    @classmethod
654
773
    def get(cls, store, principal, project):
655
774
        """Find or create an Assessed for the given user or group and project.
664
783
        a = store.find(cls,
665
784
            (t is User) or (cls.project_group_id == principal.id),
666
785
            (t is ProjectGroup) or (cls.user_id == principal.id),
667
 
            Project.id == project.id).one()
 
786
            cls.project_id == project.id).one()
668
787
 
669
788
        if a is None:
670
789
            a = cls()
715
834
    submitter = Reference(submitter_id, User.id)
716
835
    date_submitted = DateTime()
717
836
 
 
837
    def get_verify_url(self, user):
 
838
        """Get the URL for verifying this submission, within the account of
 
839
        the given user."""
 
840
        # If this is a solo project, then self.path will be prefixed with the
 
841
        # subject name. Remove the first path segment.
 
842
        submitpath = self.path[1:] if self.path[:1] == '/' else self.path
 
843
        if not self.assessed.is_group:
 
844
            if '/' in submitpath:
 
845
                submitpath = submitpath.split('/', 1)[1]
 
846
            else:
 
847
                submitpath = ''
 
848
        return "/files/%s/%s/%s?r=%d" % (user.login,
 
849
            self.assessed.checkout_location, submitpath, self.revision)
718
850
 
719
851
# WORKSHEETS AND EXERCISES #
720
852
 
751
883
    def __repr__(self):
752
884
        return "<%s %s>" % (type(self).__name__, self.name)
753
885
 
754
 
    def get_permissions(self, user):
 
886
    def get_permissions(self, user, config):
 
887
        return self.global_permissions(user, config)
 
888
 
 
889
    @staticmethod
 
890
    def global_permissions(user, config):
 
891
        """Gets the set of permissions this user has over *all* exercises.
 
892
        This is used to determine who may view the exercises list, and create
 
893
        new exercises."""
755
894
        perms = set()
756
895
        roles = set()
757
896
        if user is not None:
761
900
            elif u'lecturer' in set((e.role for e in user.active_enrolments)):
762
901
                perms.add('edit')
763
902
                perms.add('view')
764
 
            elif u'tutor' in set((e.role for e in user.active_enrolments)):
 
903
            elif (config['policy']['tutors_can_edit_worksheets']
 
904
            and u'tutor' in set((e.role for e in user.active_enrolments))):
 
905
                # Site-specific policy on the role of tutors
765
906
                perms.add('edit')
766
907
                perms.add('view')
767
908
 
828
969
        store.find(WorksheetExercise,
829
970
            WorksheetExercise.worksheet == self).remove()
830
971
 
831
 
    def get_permissions(self, user):
832
 
        return self.offering.get_permissions(user)
 
972
    def get_permissions(self, user, config):
 
973
        # Almost the same permissions as for the offering itself
 
974
        perms = self.offering.get_permissions(user, config)
 
975
        # However, "edit" permission is derived from the "edit_worksheets"
 
976
        # permission of the offering
 
977
        if 'edit_worksheets' in perms:
 
978
            perms.add('edit')
 
979
        else:
 
980
            perms.discard('edit')
 
981
        return perms
833
982
 
834
983
    def get_xml(self):
835
984
        """Returns the xml of this worksheet, converts from rst if required."""
880
1029
        return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
881
1030
                                  self.worksheet.identifier)
882
1031
 
883
 
    def get_permissions(self, user):
884
 
        return self.worksheet.get_permissions(user)
 
1032
    def get_permissions(self, user, config):
 
1033
        return self.worksheet.get_permissions(user, config)
885
1034
 
886
1035
 
887
1036
class ExerciseSave(Storm):
934
1083
    complete = Bool()
935
1084
    active = Bool()
936
1085
 
937
 
    def get_permissions(self, user):
 
1086
    def get_permissions(self, user, config):
938
1087
        return set(['view']) if user is self.user else set()
939
1088
 
940
1089
class TestSuite(Storm):
959
1108
 
960
1109
    def delete(self):
961
1110
        """Delete this suite, without asking questions."""
962
 
        for vaariable in self.variables:
 
1111
        for variable in self.variables:
963
1112
            variable.delete()
964
1113
        for test_case in self.test_cases:
965
1114
            test_case.delete()
978
1127
    suite = Reference(suiteid, "TestSuite.suiteid")
979
1128
    passmsg = Unicode()
980
1129
    failmsg = Unicode()
981
 
    test_default = Unicode()
 
1130
    test_default = Unicode() # Currently unused - only used for file matching.
982
1131
    seq_no = Int()
983
1132
 
984
1133
    parts = ReferenceSet(testid, "TestCasePart.testid")