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

« back to all changes in this revision

Viewing changes to ivle/database.py

  • Committer: William Grant
  • Date: 2009-12-08 05:17:41 UTC
  • Revision ID: grantw@unimelb.edu.au-20091208051741-ge3tbktd1ebyt6p4
Use the publishing framework to generate URLs to projectsets.

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,
251
249
    code = Unicode(name="subj_code")
252
250
    name = Unicode(name="subj_name")
253
251
    short_name = Unicode(name="subj_short_name")
 
252
    url = Unicode()
254
253
 
255
254
    offerings = ReferenceSet(id, 'Offering.subject_id')
256
255
 
259
258
    def __repr__(self):
260
259
        return "<%s '%s'>" % (type(self).__name__, self.short_name)
261
260
 
262
 
    def get_permissions(self, user, config):
 
261
    def get_permissions(self, user):
263
262
        """Determine privileges held by a user over this object.
264
263
 
265
264
        If the user requesting privileges is an admin, they may edit.
323
322
    subject = Reference(subject_id, Subject.id)
324
323
    semester_id = Int(name="semesterid")
325
324
    semester = Reference(semester_id, Semester.id)
326
 
    description = Unicode()
327
 
    url = Unicode()
328
 
    show_worksheet_marks = Bool()
329
 
    worksheet_cutoff = DateTime()
330
325
    groups_student_permissions = Unicode()
331
326
 
332
327
    enrolments = ReferenceSet(id, 'Enrolment.offering_id')
335
330
                           'Enrolment.user_id',
336
331
                           'User.id')
337
332
    project_sets = ReferenceSet(id, 'ProjectSet.offering_id')
338
 
    projects = ReferenceSet(id,
339
 
                            'ProjectSet.offering_id',
340
 
                            'ProjectSet.id',
341
 
                            'Project.project_set_id')
342
333
 
343
334
    worksheets = ReferenceSet(id, 
344
335
        'Worksheet.offering_id', 
375
366
                               Enrolment.offering_id == self.id).one()
376
367
        Store.of(enrolment).remove(enrolment)
377
368
 
378
 
    def get_permissions(self, user, config):
 
369
    def get_permissions(self, user):
379
370
        perms = set()
380
371
        if user is not None:
381
372
            enrolment = self.get_enrolment(user)
382
373
            if enrolment or user.admin:
383
374
                perms.add('view')
384
 
            if enrolment and enrolment.role == u'tutor':
385
 
                perms.add('view_project_submissions')
386
 
                # Site-specific policy on the role of tutors
387
 
                if config['policy']['tutors_can_enrol_students']:
388
 
                    perms.add('enrol')
389
 
                    perms.add('enrol_student')
390
 
                if config['policy']['tutors_can_edit_worksheets']:
391
 
                    perms.add('edit_worksheets')
392
 
                if config['policy']['tutors_can_admin_groups']:
393
 
                    perms.add('admin_groups')
394
 
            if (enrolment and enrolment.role in (u'lecturer')) or user.admin:
395
 
                perms.add('view_project_submissions')
396
 
                perms.add('admin_groups')
397
 
                perms.add('edit_worksheets')
398
 
                perms.add('edit')           # Can edit projects & details
399
 
                perms.add('enrol')          # Can see enrolment screen at all
400
 
                perms.add('enrol_student')  # Can enrol students
401
 
                perms.add('enrol_tutor')    # Can enrol tutors
402
 
            if user.admin:
403
 
                perms.add('enrol_lecturer') # Can enrol lecturers
 
375
            if (enrolment and enrolment.role in (u'tutor', u'lecturer')) \
 
376
               or user.admin:
 
377
                perms.add('edit')
404
378
        return perms
405
379
 
406
380
    def get_enrolment(self, user):
417
391
                Enrolment.user_id == User.id,
418
392
                Enrolment.offering_id == self.id,
419
393
                Enrolment.role == role
420
 
                ).order_by(User.login)
 
394
                )
421
395
 
422
396
    @property
423
397
    def students(self):
424
398
        return self.get_members_by_role(u'student')
425
399
 
426
 
    def get_open_projects_for_user(self, user):
427
 
        """Find all projects currently open to submissions by a user."""
428
 
        # XXX: Respect extensions.
429
 
        return self.projects.find(Project.deadline > datetime.datetime.now())
430
 
 
431
 
    def clone_worksheets(self, source):
432
 
        """Clone all worksheets from the specified source to this offering."""
433
 
        import ivle.worksheet.utils
434
 
        for worksheet in source.worksheets:
435
 
            newws = Worksheet()
436
 
            newws.seq_no = worksheet.seq_no
437
 
            newws.identifier = worksheet.identifier
438
 
            newws.name = worksheet.name
439
 
            newws.assessable = worksheet.assessable
440
 
            newws.published = worksheet.published
441
 
            newws.data = worksheet.data
442
 
            newws.format = worksheet.format
443
 
            newws.offering = self
444
 
            Store.of(self).add(newws)
445
 
            ivle.worksheet.utils.update_exerciselist(newws)
446
 
 
447
 
 
448
400
class Enrolment(Storm):
449
401
    """An enrolment of a user in an offering.
450
402
 
476
428
        return "<%s %r in %r>" % (type(self).__name__, self.user,
477
429
                                  self.offering)
478
430
 
479
 
    def get_permissions(self, user, config):
480
 
        # A user can edit any enrolment that they could have created.
481
 
        perms = set()
482
 
        if ('enrol_' + str(self.role)) in self.offering.get_permissions(
483
 
            user, config):
484
 
            perms.add('edit')
485
 
        return perms
486
 
 
487
 
    def delete(self):
488
 
        """Delete this enrolment."""
489
 
        Store.of(self).remove(self)
490
 
 
491
 
 
492
431
# PROJECTS #
493
432
 
494
433
class ProjectSet(Storm):
514
453
        return "<%s %d in %r>" % (type(self).__name__, self.id,
515
454
                                  self.offering)
516
455
 
517
 
    def get_permissions(self, user, config):
518
 
        return self.offering.get_permissions(user, config)
519
 
 
520
 
    def get_groups_for_user(self, user):
521
 
        """List all groups in this offering of which the user is a member."""
522
 
        assert self.is_group
523
 
        return Store.of(self).find(
524
 
            ProjectGroup,
525
 
            ProjectGroupMembership.user_id == user.id,
526
 
            ProjectGroupMembership.project_group_id == ProjectGroup.id,
527
 
            ProjectGroup.project_set_id == self.id)
528
 
 
529
 
    def get_submission_principal(self, user):
530
 
        """Get the principal on behalf of which the user can submit.
531
 
 
532
 
        If this is a solo project set, the given user is returned. If
533
 
        the user is a member of exactly one group, all the group is
534
 
        returned. Otherwise, None is returned.
535
 
        """
536
 
        if self.is_group:
537
 
            groups = self.get_groups_for_user(user)
538
 
            if groups.count() == 1:
539
 
                return groups.one()
540
 
            else:
541
 
                return None
542
 
        else:
543
 
            return user
544
 
 
545
 
    @property
546
 
    def is_group(self):
547
 
        return self.max_students_per_group is not None
 
456
    def get_permissions(self, user):
 
457
        return self.offering.get_permissions(user)
548
458
 
549
459
    @property
550
460
    def assigned(self):
553
463
        This will be a Storm ResultSet.
554
464
        """
555
465
        #If its a solo project, return everyone in offering
556
 
        if self.is_group:
 
466
        if self.max_students_per_group is None:
 
467
            return self.offering.students
 
468
        else:
557
469
            return self.project_groups
558
 
        else:
559
 
            return self.offering.students
560
 
 
561
 
class DeadlinePassed(Exception):
562
 
    """An exception indicating that a project cannot be submitted because the
563
 
    deadline has passed."""
564
 
    def __init__(self):
565
 
        pass
566
 
    def __str__(self):
567
 
        return "The project deadline has passed"
568
470
 
569
471
class Project(Storm):
570
472
    """A student project for which submissions can be made."""
592
494
        return "<%s '%s' in %r>" % (type(self).__name__, self.short_name,
593
495
                                  self.project_set.offering)
594
496
 
595
 
    def can_submit(self, principal, user):
 
497
    def can_submit(self, principal):
596
498
        return (self in principal.get_projects() and
597
 
                not self.has_deadline_passed(user))
 
499
                self.deadline > datetime.datetime.now())
598
500
 
599
501
    def submit(self, principal, path, revision, who):
600
502
        """Submit a Subversion path and revision to a project.
606
508
        @param who: The user who is actually making the submission.
607
509
        """
608
510
 
609
 
        if not self.can_submit(principal, who):
610
 
            raise DeadlinePassed()
 
511
        if not self.can_submit(principal):
 
512
            raise Exception('cannot submit')
611
513
 
612
514
        a = Assessed.get(Store.of(self), principal, self)
613
515
        ps = ProjectSubmission()
614
 
        # Raise SubmissionError if the path is illegal
615
 
        ps.path = ProjectSubmission.test_and_normalise_path(path)
 
516
        ps.path = path
616
517
        ps.revision = revision
617
518
        ps.date_submitted = datetime.datetime.now()
618
519
        ps.assessed = a
620
521
 
621
522
        return ps
622
523
 
623
 
    def get_permissions(self, user, config):
624
 
        return self.project_set.offering.get_permissions(user, config)
 
524
    def get_permissions(self, user):
 
525
        return self.project_set.offering.get_permissions(user)
625
526
 
626
527
    @property
627
528
    def latest_submissions(self):
636
537
            )
637
538
        )
638
539
 
639
 
    def has_deadline_passed(self, user):
640
 
        """Check whether the deadline has passed."""
641
 
        # XXX: Need to respect extensions.
642
 
        return self.deadline < datetime.datetime.now()
643
 
 
644
 
    def get_submissions_for_principal(self, principal):
645
 
        """Fetch a ResultSet of all submissions by a particular principal."""
646
 
        assessed = Assessed.get(Store.of(self), principal, self)
647
 
        if assessed is None:
648
 
            return
649
 
        return assessed.submissions
650
 
 
651
 
 
652
540
 
653
541
class ProjectGroup(Storm):
654
542
    """A group of students working together on a project."""
705
593
            (not active_only) or (Semester.state == u'current'))
706
594
 
707
595
 
708
 
    def get_permissions(self, user, config):
 
596
    def get_permissions(self, user):
709
597
        if user.admin or user in self.members:
710
598
            return set(['submit_project'])
711
599
        else:
747
635
    project = Reference(project_id, Project.id)
748
636
 
749
637
    extensions = ReferenceSet(id, 'ProjectExtension.assessed_id')
750
 
    submissions = ReferenceSet(
751
 
        id, 'ProjectSubmission.assessed_id', order_by='date_submitted')
 
638
    submissions = ReferenceSet(id, 'ProjectSubmission.assessed_id')
752
639
 
753
640
    def __repr__(self):
754
641
        return "<%s %r in %r>" % (type(self).__name__,
763
650
    def principal(self):
764
651
        return self.project_group or self.user
765
652
 
766
 
    @property
767
 
    def checkout_location(self):
768
 
        """Returns the location of the Subversion workspace for this piece of
769
 
        assessment, relative to each group member's home directory."""
770
 
        subjectname = self.project.project_set.offering.subject.short_name
771
 
        if self.is_group:
772
 
            checkout_dir_name = self.principal.short_name
773
 
        else:
774
 
            checkout_dir_name = "mywork"
775
 
        return subjectname + "/" + checkout_dir_name
776
 
 
777
653
    @classmethod
778
654
    def get(cls, store, principal, project):
779
655
        """Find or create an Assessed for the given user or group and project.
788
664
        a = store.find(cls,
789
665
            (t is User) or (cls.project_group_id == principal.id),
790
666
            (t is ProjectGroup) or (cls.user_id == principal.id),
791
 
            cls.project_id == project.id).one()
 
667
            Project.id == project.id).one()
792
668
 
793
669
        if a is None:
794
670
            a = cls()
818
694
    approver = Reference(approver_id, User.id)
819
695
    notes = Unicode()
820
696
 
821
 
class SubmissionError(Exception):
822
 
    """Denotes a validation error during submission."""
823
 
    pass
824
 
 
825
697
class ProjectSubmission(Storm):
826
698
    """A submission from a user or group repository to a particular project.
827
699
 
843
715
    submitter = Reference(submitter_id, User.id)
844
716
    date_submitted = DateTime()
845
717
 
846
 
    def get_verify_url(self, user):
847
 
        """Get the URL for verifying this submission, within the account of
848
 
        the given user."""
849
 
        # If this is a solo project, then self.path will be prefixed with the
850
 
        # subject name. Remove the first path segment.
851
 
        submitpath = self.path[1:] if self.path[:1] == '/' else self.path
852
 
        if not self.assessed.is_group:
853
 
            if '/' in submitpath:
854
 
                submitpath = submitpath.split('/', 1)[1]
855
 
            else:
856
 
                submitpath = ''
857
 
        return "/files/%s/%s/%s?r=%d" % (user.login,
858
 
            self.assessed.checkout_location, submitpath, self.revision)
859
 
 
860
 
    @staticmethod
861
 
    def test_and_normalise_path(path):
862
 
        """Test that path is valid, and normalise it. This prevents possible
863
 
        injections using malicious paths.
864
 
        Returns the updated path, if successful.
865
 
        Raises SubmissionError if invalid.
866
 
        """
867
 
        # Ensure the path is absolute to prevent being tacked onto working
868
 
        # directories.
869
 
        # Prevent '\n' because it will break all sorts of things.
870
 
        # Prevent '[' and ']' because they can be used to inject into the
871
 
        # svn.conf.
872
 
        # Normalise to avoid resulting in ".." path segments.
873
 
        if not os.path.isabs(path):
874
 
            raise SubmissionError("Path is not absolute")
875
 
        if any(c in path for c in "\n[]"):
876
 
            raise SubmissionError("Path must not contain '\\n', '[' or ']'")
877
 
        return os.path.normpath(path)
878
718
 
879
719
# WORKSHEETS AND EXERCISES #
880
720
 
911
751
    def __repr__(self):
912
752
        return "<%s %s>" % (type(self).__name__, self.name)
913
753
 
914
 
    def get_permissions(self, user, config):
915
 
        return self.global_permissions(user, config)
916
 
 
917
 
    @staticmethod
918
 
    def global_permissions(user, config):
919
 
        """Gets the set of permissions this user has over *all* exercises.
920
 
        This is used to determine who may view the exercises list, and create
921
 
        new exercises."""
 
754
    def get_permissions(self, user):
922
755
        perms = set()
923
756
        roles = set()
924
757
        if user is not None:
928
761
            elif u'lecturer' in set((e.role for e in user.active_enrolments)):
929
762
                perms.add('edit')
930
763
                perms.add('view')
931
 
            elif (config['policy']['tutors_can_edit_worksheets']
932
 
            and u'tutor' in set((e.role for e in user.active_enrolments))):
933
 
                # Site-specific policy on the role of tutors
 
764
            elif u'tutor' in set((e.role for e in user.active_enrolments)):
934
765
                perms.add('edit')
935
766
                perms.add('view')
936
767
 
961
792
    identifier = Unicode()
962
793
    name = Unicode()
963
794
    assessable = Bool()
964
 
    published = Bool()
965
795
    data = Unicode()
966
796
    seq_no = Int()
967
797
    format = Unicode()
998
828
        store.find(WorksheetExercise,
999
829
            WorksheetExercise.worksheet == self).remove()
1000
830
 
1001
 
    def get_permissions(self, user, config):
1002
 
        # Almost the same permissions as for the offering itself
1003
 
        perms = self.offering.get_permissions(user, config)
1004
 
        # However, "edit" permission is derived from the "edit_worksheets"
1005
 
        # permission of the offering
1006
 
        if 'edit_worksheets' in perms:
1007
 
            perms.add('edit')
1008
 
        else:
1009
 
            perms.discard('edit')
1010
 
        return perms
 
831
    def get_permissions(self, user):
 
832
        return self.offering.get_permissions(user)
1011
833
 
1012
834
    def get_xml(self):
1013
835
        """Returns the xml of this worksheet, converts from rst if required."""
1058
880
        return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
1059
881
                                  self.worksheet.identifier)
1060
882
 
1061
 
    def get_permissions(self, user, config):
1062
 
        return self.worksheet.get_permissions(user, config)
 
883
    def get_permissions(self, user):
 
884
        return self.worksheet.get_permissions(user)
1063
885
 
1064
886
 
1065
887
class ExerciseSave(Storm):
1112
934
    complete = Bool()
1113
935
    active = Bool()
1114
936
 
1115
 
    def get_permissions(self, user, config):
 
937
    def get_permissions(self, user):
1116
938
        return set(['view']) if user is self.user else set()
1117
939
 
1118
940
class TestSuite(Storm):
1137
959
 
1138
960
    def delete(self):
1139
961
        """Delete this suite, without asking questions."""
1140
 
        for variable in self.variables:
 
962
        for vaariable in self.variables:
1141
963
            variable.delete()
1142
964
        for test_case in self.test_cases:
1143
965
            test_case.delete()
1156
978
    suite = Reference(suiteid, "TestSuite.suiteid")
1157
979
    passmsg = Unicode()
1158
980
    failmsg = Unicode()
1159
 
    test_default = Unicode() # Currently unused - only used for file matching.
 
981
    test_default = Unicode()
1160
982
    seq_no = Int()
1161
983
 
1162
984
    parts = ReferenceSet(testid, "TestCasePart.testid")