~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-18 06:04:22 UTC
  • Revision ID: matt.giuca@gmail.com-20100218060422-38ugfm7aj4g053vy
Sample data: changed studenta's repository -- edited the chinese file so it linewraps appropriately.

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
31
32
from storm.exceptions import NotOneError, IntegrityError
32
33
 
33
34
from ivle.worksheet.rst import rst
116
117
 
117
118
    @property
118
119
    def display_name(self):
 
120
        """Returns the "nice name" of the user or group."""
119
121
        return self.fullname
120
122
 
121
123
    @property
 
124
    def short_name(self):
 
125
        """Returns the database "identifier" name of the user or group."""
 
126
        return self.login
 
127
 
 
128
    @property
122
129
    def password_expired(self):
123
130
        fieldval = self.pass_exp
124
131
        return fieldval is not None and datetime.datetime.now() > fieldval
208
215
            Semester.id == Offering.semester_id,
209
216
            (not active_only) or (Semester.state == u'current'),
210
217
            Enrolment.offering_id == Offering.id,
211
 
            Enrolment.user_id == self.id)
 
218
            Enrolment.user_id == self.id,
 
219
            Enrolment.active == True)
212
220
 
213
221
    @staticmethod
214
222
    def hash_password(password):
220
228
        """Find a user in a store by login name."""
221
229
        return store.find(cls, cls.login == unicode(login)).one()
222
230
 
223
 
    def get_permissions(self, user):
 
231
    def get_permissions(self, user, config):
224
232
        """Determine privileges held by a user over this object.
225
233
 
226
234
        If the user requesting privileges is this user or an admin,
227
235
        they may do everything. Otherwise they may do nothing.
228
236
        """
229
237
        if user and user.admin or user is self:
230
 
            return set(['view', 'edit', 'submit_project'])
 
238
            return set(['view_public', 'view', 'edit', 'submit_project'])
231
239
        else:
232
 
            return set()
 
240
            return set(['view_public'])
233
241
 
234
242
# SUBJECTS AND ENROLMENTS #
235
243
 
242
250
    code = Unicode(name="subj_code")
243
251
    name = Unicode(name="subj_name")
244
252
    short_name = Unicode(name="subj_short_name")
245
 
    url = Unicode()
246
253
 
247
254
    offerings = ReferenceSet(id, 'Offering.subject_id')
248
255
 
251
258
    def __repr__(self):
252
259
        return "<%s '%s'>" % (type(self).__name__, self.short_name)
253
260
 
254
 
    def get_permissions(self, user):
 
261
    def get_permissions(self, user, config):
255
262
        """Determine privileges held by a user over this object.
256
263
 
257
264
        If the user requesting privileges is an admin, they may edit.
315
322
    subject = Reference(subject_id, Subject.id)
316
323
    semester_id = Int(name="semesterid")
317
324
    semester = Reference(semester_id, Semester.id)
 
325
    description = Unicode()
 
326
    url = Unicode()
318
327
    groups_student_permissions = Unicode()
319
328
 
320
329
    enrolments = ReferenceSet(id, 'Enrolment.offering_id')
323
332
                           'Enrolment.user_id',
324
333
                           'User.id')
325
334
    project_sets = ReferenceSet(id, 'ProjectSet.offering_id')
 
335
    projects = ReferenceSet(id,
 
336
                            'ProjectSet.offering_id',
 
337
                            'ProjectSet.id',
 
338
                            'Project.project_set_id')
326
339
 
327
340
    worksheets = ReferenceSet(id, 
328
341
        'Worksheet.offering_id', 
359
372
                               Enrolment.offering_id == self.id).one()
360
373
        Store.of(enrolment).remove(enrolment)
361
374
 
362
 
    def get_permissions(self, user):
 
375
    def get_permissions(self, user, config):
363
376
        perms = set()
364
377
        if user is not None:
365
378
            enrolment = self.get_enrolment(user)
366
379
            if enrolment or user.admin:
367
380
                perms.add('view')
368
 
            if (enrolment and enrolment.role in (u'tutor', u'lecturer')) \
369
 
               or user.admin:
370
 
                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
371
401
        return perms
372
402
 
373
403
    def get_enrolment(self, user):
379
409
 
380
410
        return enrolment
381
411
 
 
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
 
382
444
class Enrolment(Storm):
383
445
    """An enrolment of a user in an offering.
384
446
 
410
472
        return "<%s %r in %r>" % (type(self).__name__, self.user,
411
473
                                  self.offering)
412
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
 
413
488
# PROJECTS #
414
489
 
415
490
class ProjectSet(Storm):
435
510
        return "<%s %d in %r>" % (type(self).__name__, self.id,
436
511
                                  self.offering)
437
512
 
438
 
    def get_permissions(self, user):
439
 
        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
 
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"
440
564
 
441
565
class Project(Storm):
442
566
    """A student project for which submissions can be made."""
464
588
        return "<%s '%s' in %r>" % (type(self).__name__, self.short_name,
465
589
                                  self.project_set.offering)
466
590
 
467
 
    def can_submit(self, principal):
 
591
    def can_submit(self, principal, user):
468
592
        return (self in principal.get_projects() and
469
 
                self.deadline > datetime.datetime.now())
 
593
                not self.has_deadline_passed(user))
470
594
 
471
595
    def submit(self, principal, path, revision, who):
472
596
        """Submit a Subversion path and revision to a project.
478
602
        @param who: The user who is actually making the submission.
479
603
        """
480
604
 
481
 
        if not self.can_submit(principal):
482
 
            raise Exception('cannot submit')
 
605
        if not self.can_submit(principal, who):
 
606
            raise DeadlinePassed()
483
607
 
484
608
        a = Assessed.get(Store.of(self), principal, self)
485
609
        ps = ProjectSubmission()
491
615
 
492
616
        return ps
493
617
 
494
 
    def get_permissions(self, user):
495
 
        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)
 
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
 
496
646
 
497
647
 
498
648
class ProjectGroup(Storm):
522
672
 
523
673
    @property
524
674
    def display_name(self):
525
 
        return '%s (%s)' % (self.nick, self.name)
 
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
526
682
 
527
683
    def get_projects(self, offering=None, active_only=True):
528
684
        '''Find projects that the group can submit.
544
700
            (not active_only) or (Semester.state == u'current'))
545
701
 
546
702
 
547
 
    def get_permissions(self, user):
 
703
    def get_permissions(self, user, config):
548
704
        if user.admin or user in self.members:
549
705
            return set(['submit_project'])
550
706
        else:
586
742
    project = Reference(project_id, Project.id)
587
743
 
588
744
    extensions = ReferenceSet(id, 'ProjectExtension.assessed_id')
589
 
    submissions = ReferenceSet(id, 'ProjectSubmission.assessed_id')
 
745
    submissions = ReferenceSet(
 
746
        id, 'ProjectSubmission.assessed_id', order_by='date_submitted')
590
747
 
591
748
    def __repr__(self):
592
749
        return "<%s %r in %r>" % (type(self).__name__,
593
750
            self.user or self.project_group, self.project)
594
751
 
 
752
    @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
    def principal(self):
 
759
        return self.project_group or self.user
 
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
 
595
772
    @classmethod
596
773
    def get(cls, store, principal, project):
597
774
        """Find or create an Assessed for the given user or group and project.
606
783
        a = store.find(cls,
607
784
            (t is User) or (cls.project_group_id == principal.id),
608
785
            (t is ProjectGroup) or (cls.user_id == principal.id),
609
 
            Project.id == project.id).one()
 
786
            cls.project_id == project.id).one()
610
787
 
611
788
        if a is None:
612
789
            a = cls()
657
834
    submitter = Reference(submitter_id, User.id)
658
835
    date_submitted = DateTime()
659
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)
660
850
 
661
851
# WORKSHEETS AND EXERCISES #
662
852
 
693
883
    def __repr__(self):
694
884
        return "<%s %s>" % (type(self).__name__, self.name)
695
885
 
696
 
    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."""
697
894
        perms = set()
698
895
        roles = set()
699
896
        if user is not None:
703
900
            elif u'lecturer' in set((e.role for e in user.active_enrolments)):
704
901
                perms.add('edit')
705
902
                perms.add('view')
706
 
            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
707
906
                perms.add('edit')
708
907
                perms.add('view')
709
908
 
770
969
        store.find(WorksheetExercise,
771
970
            WorksheetExercise.worksheet == self).remove()
772
971
 
773
 
    def get_permissions(self, user):
774
 
        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
775
982
 
776
983
    def get_xml(self):
777
984
        """Returns the xml of this worksheet, converts from rst if required."""
822
1029
        return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
823
1030
                                  self.worksheet.identifier)
824
1031
 
825
 
    def get_permissions(self, user):
826
 
        return self.worksheet.get_permissions(user)
 
1032
    def get_permissions(self, user, config):
 
1033
        return self.worksheet.get_permissions(user, config)
827
1034
 
828
1035
 
829
1036
class ExerciseSave(Storm):
876
1083
    complete = Bool()
877
1084
    active = Bool()
878
1085
 
879
 
    def get_permissions(self, user):
 
1086
    def get_permissions(self, user, config):
880
1087
        return set(['view']) if user is self.user else set()
881
1088
 
882
1089
class TestSuite(Storm):
901
1108
 
902
1109
    def delete(self):
903
1110
        """Delete this suite, without asking questions."""
904
 
        for vaariable in self.variables:
 
1111
        for variable in self.variables:
905
1112
            variable.delete()
906
1113
        for test_case in self.test_cases:
907
1114
            test_case.delete()
920
1127
    suite = Reference(suiteid, "TestSuite.suiteid")
921
1128
    passmsg = Unicode()
922
1129
    failmsg = Unicode()
923
 
    test_default = Unicode()
 
1130
    test_default = Unicode() # Currently unused - only used for file matching.
924
1131
    seq_no = Int()
925
1132
 
926
1133
    parts = ReferenceSet(testid, "TestCasePart.testid")