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

« back to all changes in this revision

Viewing changes to ivle/database.py

Sentence-case the 'No Projects'.

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())
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
 
 
444
382
class Enrolment(Storm):
445
383
    """An enrolment of a user in an offering.
446
384
 
472
410
        return "<%s %r in %r>" % (type(self).__name__, self.user,
473
411
                                  self.offering)
474
412
 
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
 
 
488
413
# PROJECTS #
489
414
 
490
415
class ProjectSet(Storm):
510
435
        return "<%s %d in %r>" % (type(self).__name__, self.id,
511
436
                                  self.offering)
512
437
 
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
544
 
 
545
 
    @property
546
 
    def assigned(self):
547
 
        """Get the entities (groups or users) assigned to submit this project.
548
 
 
549
 
        This will be a Storm ResultSet.
550
 
        """
551
 
        #If its a solo project, return everyone in offering
552
 
        if self.is_group:
553
 
            return self.project_groups
554
 
        else:
555
 
            return self.offering.students
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"
 
438
    def get_permissions(self, user):
 
439
        return self.offering.get_permissions(user)
564
440
 
565
441
class Project(Storm):
566
442
    """A student project for which submissions can be made."""
588
464
        return "<%s '%s' in %r>" % (type(self).__name__, self.short_name,
589
465
                                  self.project_set.offering)
590
466
 
591
 
    def can_submit(self, principal, user):
 
467
    def can_submit(self, principal):
592
468
        return (self in principal.get_projects() and
593
 
                not self.has_deadline_passed(user))
 
469
                self.deadline > datetime.datetime.now())
594
470
 
595
471
    def submit(self, principal, path, revision, who):
596
472
        """Submit a Subversion path and revision to a project.
602
478
        @param who: The user who is actually making the submission.
603
479
        """
604
480
 
605
 
        if not self.can_submit(principal, who):
606
 
            raise DeadlinePassed()
 
481
        if not self.can_submit(principal):
 
482
            raise Exception('cannot submit')
607
483
 
608
484
        a = Assessed.get(Store.of(self), principal, self)
609
485
        ps = ProjectSubmission()
615
491
 
616
492
        return ps
617
493
 
618
 
    def get_permissions(self, user, config):
619
 
        return self.project_set.offering.get_permissions(user, config)
620
 
 
621
 
    @property
622
 
    def latest_submissions(self):
623
 
        """Return the latest submission for each Assessed."""
624
 
        return Store.of(self).find(ProjectSubmission,
625
 
            Assessed.project_id == self.id,
626
 
            ProjectSubmission.assessed_id == Assessed.id,
627
 
            ProjectSubmission.date_submitted == Select(
628
 
                    Max(ProjectSubmission.date_submitted),
629
 
                    ProjectSubmission.assessed_id == Assessed.id,
630
 
                    tables=ProjectSubmission
631
 
            )
632
 
        )
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
 
 
 
494
    def get_permissions(self, user):
 
495
        return self.project_set.offering.get_permissions(user)
646
496
 
647
497
 
648
498
class ProjectGroup(Storm):
672
522
 
673
523
    @property
674
524
    def display_name(self):
675
 
        """Returns the "nice name" of the user or group."""
676
 
        return self.nick
677
 
 
678
 
    @property
679
 
    def short_name(self):
680
 
        """Returns the database "identifier" name of the user or group."""
681
 
        return self.name
 
525
        return '%s (%s)' % (self.nick, self.name)
682
526
 
683
527
    def get_projects(self, offering=None, active_only=True):
684
528
        '''Find projects that the group can submit.
700
544
            (not active_only) or (Semester.state == u'current'))
701
545
 
702
546
 
703
 
    def get_permissions(self, user, config):
 
547
    def get_permissions(self, user):
704
548
        if user.admin or user in self.members:
705
549
            return set(['submit_project'])
706
550
        else:
742
586
    project = Reference(project_id, Project.id)
743
587
 
744
588
    extensions = ReferenceSet(id, 'ProjectExtension.assessed_id')
745
 
    submissions = ReferenceSet(
746
 
        id, 'ProjectSubmission.assessed_id', order_by='date_submitted')
 
589
    submissions = ReferenceSet(id, 'ProjectSubmission.assessed_id')
747
590
 
748
591
    def __repr__(self):
749
592
        return "<%s %r in %r>" % (type(self).__name__,
750
593
            self.user or self.project_group, self.project)
751
594
 
752
595
    @property
753
 
    def is_group(self):
754
 
        """True if the Assessed is a group, False if it is a user."""
755
 
        return self.project_group is not None
756
 
 
757
 
    @property
758
596
    def principal(self):
759
597
        return self.project_group or self.user
760
598
 
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
 
 
772
599
    @classmethod
773
600
    def get(cls, store, principal, project):
774
601
        """Find or create an Assessed for the given user or group and project.
783
610
        a = store.find(cls,
784
611
            (t is User) or (cls.project_group_id == principal.id),
785
612
            (t is ProjectGroup) or (cls.user_id == principal.id),
786
 
            cls.project_id == project.id).one()
 
613
            Project.id == project.id).one()
787
614
 
788
615
        if a is None:
789
616
            a = cls()
834
661
    submitter = Reference(submitter_id, User.id)
835
662
    date_submitted = DateTime()
836
663
 
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)
850
664
 
851
665
# WORKSHEETS AND EXERCISES #
852
666
 
883
697
    def __repr__(self):
884
698
        return "<%s %s>" % (type(self).__name__, self.name)
885
699
 
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."""
 
700
    def get_permissions(self, user):
894
701
        perms = set()
895
702
        roles = set()
896
703
        if user is not None:
900
707
            elif u'lecturer' in set((e.role for e in user.active_enrolments)):
901
708
                perms.add('edit')
902
709
                perms.add('view')
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
 
710
            elif u'tutor' in set((e.role for e in user.active_enrolments)):
906
711
                perms.add('edit')
907
712
                perms.add('view')
908
713
 
969
774
        store.find(WorksheetExercise,
970
775
            WorksheetExercise.worksheet == self).remove()
971
776
 
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
 
777
    def get_permissions(self, user):
 
778
        return self.offering.get_permissions(user)
982
779
 
983
780
    def get_xml(self):
984
781
        """Returns the xml of this worksheet, converts from rst if required."""
1029
826
        return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
1030
827
                                  self.worksheet.identifier)
1031
828
 
1032
 
    def get_permissions(self, user, config):
1033
 
        return self.worksheet.get_permissions(user, config)
 
829
    def get_permissions(self, user):
 
830
        return self.worksheet.get_permissions(user)
1034
831
 
1035
832
 
1036
833
class ExerciseSave(Storm):
1083
880
    complete = Bool()
1084
881
    active = Bool()
1085
882
 
1086
 
    def get_permissions(self, user, config):
 
883
    def get_permissions(self, user):
1087
884
        return set(['view']) if user is self.user else set()
1088
885
 
1089
886
class TestSuite(Storm):
1108
905
 
1109
906
    def delete(self):
1110
907
        """Delete this suite, without asking questions."""
1111
 
        for variable in self.variables:
 
908
        for vaariable in self.variables:
1112
909
            variable.delete()
1113
910
        for test_case in self.test_cases:
1114
911
            test_case.delete()
1127
924
    suite = Reference(suiteid, "TestSuite.suiteid")
1128
925
    passmsg = Unicode()
1129
926
    failmsg = Unicode()
1130
 
    test_default = Unicode() # Currently unused - only used for file matching.
 
927
    test_default = Unicode()
1131
928
    seq_no = Int()
1132
929
 
1133
930
    parts = ReferenceSet(testid, "TestCasePart.testid")