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)
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()
230
def get_permissions(self, user):
231
def get_permissions(self, user, config):
231
232
"""Determine privileges held by a user over this object.
233
234
If the user requesting privileges is this user or an admin,
249
250
code = Unicode(name="subj_code")
250
251
name = Unicode(name="subj_name")
251
252
short_name = Unicode(name="subj_short_name")
254
254
offerings = ReferenceSet(id, 'Offering.subject_id')
258
258
def __repr__(self):
259
259
return "<%s '%s'>" % (type(self).__name__, self.short_name)
261
def get_permissions(self, user):
261
def get_permissions(self, user, config):
262
262
"""Determine privileges held by a user over this object.
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()
325
327
groups_student_permissions = Unicode()
327
329
enrolments = ReferenceSet(id, 'Enrolment.offering_id')
330
332
'Enrolment.user_id',
332
334
project_sets = ReferenceSet(id, 'ProjectSet.offering_id')
335
projects = ReferenceSet(id,
336
'ProjectSet.offering_id',
338
'Project.project_set_id')
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)
369
def get_permissions(self, user):
375
def get_permissions(self, user, config):
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')) \
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']:
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
400
perms.add('enrol_lecturer') # Can enrol lecturers
380
403
def get_enrolment(self, user):
397
420
def students(self):
398
421
return self.get_members_by_role(u'student')
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())
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:
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)
400
444
class Enrolment(Storm):
401
445
"""An enrolment of a user in an offering.
428
472
return "<%s %r in %r>" % (type(self).__name__, self.user,
475
def get_permissions(self, user, config):
476
# A user can edit any enrolment that they could have created.
478
if ('enrol_' + str(self.role)) in self.offering.get_permissions(
484
"""Delete this enrolment."""
485
Store.of(self).remove(self)
433
490
class ProjectSet(Storm):
453
510
return "<%s %d in %r>" % (type(self).__name__, self.id,
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)
516
def get_groups_for_user(self, user):
517
"""List all groups in this offering of which the user is a member."""
519
return Store.of(self).find(
521
ProjectGroupMembership.user_id == user.id,
522
ProjectGroupMembership.project_group_id == ProjectGroup.id,
523
ProjectGroup.project_set_id == self.id)
525
def get_submission_principal(self, user):
526
"""Get the principal on behalf of which the user can submit.
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.
533
groups = self.get_groups_for_user(user)
534
if groups.count() == 1:
543
return self.max_students_per_group is not None
460
546
def assigned(self):
463
549
This will be a Storm ResultSet.
465
551
#If its a solo project, return everyone in offering
466
if self.max_students_per_group is None:
553
return self.project_groups
467
555
return self.offering.students
469
return self.project_groups
557
class DeadlinePassed(Exception):
558
"""An exception indicating that a project cannot be submitted because the
559
deadline has passed."""
563
return "The project deadline has passed"
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)
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))
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.
511
if not self.can_submit(principal):
512
raise Exception('cannot submit')
605
if not self.can_submit(principal, who):
606
raise DeadlinePassed()
514
608
a = Assessed.get(Store.of(self), principal, self)
515
609
ps = ProjectSubmission()
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)
528
622
def latest_submissions(self):
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()
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)
644
return assessed.submissions
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'))
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'])
635
742
project = Reference(project_id, Project.id)
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')
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
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
767
checkout_dir_name = self.principal.short_name
769
checkout_dir_name = "mywork"
770
return subjectname + "/" + checkout_dir_name
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()
715
834
submitter = Reference(submitter_id, User.id)
716
835
date_submitted = DateTime()
837
def get_verify_url(self, user):
838
"""Get the URL for verifying this submission, within the account of
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]
848
return "/files/%s/%s/%s?r=%d" % (user.login,
849
self.assessed.checkout_location, submitpath, self.revision)
719
851
# WORKSHEETS AND EXERCISES #
751
883
def __repr__(self):
752
884
return "<%s %s>" % (type(self).__name__, self.name)
754
def get_permissions(self, user):
886
def get_permissions(self, user, config):
887
return self.global_permissions(user, config)
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
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')
828
969
store.find(WorksheetExercise,
829
970
WorksheetExercise.worksheet == self).remove()
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:
980
perms.discard('edit')
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)
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)
887
1036
class ExerciseSave(Storm):
934
1083
complete = Bool()
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()
940
1089
class TestSuite(Storm):
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()