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,
234
235
they may do everything. Otherwise they may do nothing.
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'])
240
return set(['view_public'])
241
242
# SUBJECTS AND ENROLMENTS #
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):
391
414
Enrolment.user_id == User.id,
392
415
Enrolment.offering_id == self.id,
393
416
Enrolment.role == role
417
).order_by(User.login)
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()