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,
219
Enrolment.active == True)
218
Enrolment.user_id == self.id)
222
221
def hash_password(password):
228
227
"""Find a user in a store by login name."""
229
228
return store.find(cls, cls.login == unicode(login)).one()
231
def get_permissions(self, user, config):
230
def get_permissions(self, user):
232
231
"""Determine privileges held by a user over this object.
234
233
If the user requesting privileges is this user or an admin,
235
234
they may do everything. Otherwise they may do nothing.
237
236
if user and user.admin or user is self:
238
return set(['view_public', 'view', 'edit', 'submit_project'])
237
return set(['view', 'edit', 'submit_project'])
240
return set(['view_public'])
242
241
# SUBJECTS AND ENROLMENTS #
250
249
code = Unicode(name="subj_code")
251
250
name = Unicode(name="subj_name")
252
251
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, config):
261
def get_permissions(self, user):
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()
327
325
groups_student_permissions = Unicode()
329
327
enrolments = ReferenceSet(id, 'Enrolment.offering_id')
332
330
'Enrolment.user_id',
334
332
project_sets = ReferenceSet(id, 'ProjectSet.offering_id')
335
projects = ReferenceSet(id,
336
'ProjectSet.offering_id',
338
'Project.project_set_id')
340
334
worksheets = ReferenceSet(id,
341
335
'Worksheet.offering_id',
372
366
Enrolment.offering_id == self.id).one()
373
367
Store.of(enrolment).remove(enrolment)
375
def get_permissions(self, user, config):
369
def get_permissions(self, user):
377
371
if user is not None:
378
372
enrolment = self.get_enrolment(user)
379
373
if enrolment or user.admin:
380
374
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']:
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
375
if (enrolment and enrolment.role in (u'tutor', u'lecturer')) \
403
380
def get_enrolment(self, user):
414
391
Enrolment.user_id == User.id,
415
392
Enrolment.offering_id == self.id,
416
393
Enrolment.role == role
417
).order_by(User.login)
420
397
def students(self):
421
398
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)
444
400
class Enrolment(Storm):
445
401
"""An enrolment of a user in an offering.
472
428
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)
490
433
class ProjectSet(Storm):
510
453
return "<%s %d in %r>" % (type(self).__name__, self.id,
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
456
def get_permissions(self, user):
457
return self.offering.get_permissions(user)
546
460
def assigned(self):
549
463
This will be a Storm ResultSet.
551
465
#If its a solo project, return everyone in offering
466
if self.max_students_per_group is None:
467
return self.offering.students
553
469
return self.project_groups
555
return self.offering.students
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"
565
471
class Project(Storm):
566
472
"""A student project for which submissions can be made."""
588
494
return "<%s '%s' in %r>" % (type(self).__name__, self.short_name,
589
495
self.project_set.offering)
591
def can_submit(self, principal, user):
497
def can_submit(self, principal):
592
498
return (self in principal.get_projects() and
593
not self.has_deadline_passed(user))
499
self.deadline > datetime.datetime.now())
595
501
def submit(self, principal, path, revision, who):
596
502
"""Submit a Subversion path and revision to a project.
602
508
@param who: The user who is actually making the submission.
605
if not self.can_submit(principal, who):
606
raise DeadlinePassed()
511
if not self.can_submit(principal):
512
raise Exception('cannot submit')
608
514
a = Assessed.get(Store.of(self), principal, self)
609
515
ps = ProjectSubmission()
618
def get_permissions(self, user, config):
619
return self.project_set.offering.get_permissions(user, config)
524
def get_permissions(self, user):
525
return self.project_set.offering.get_permissions(user)
622
528
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
648
541
class ProjectGroup(Storm):
649
542
"""A group of students working together on a project."""
700
593
(not active_only) or (Semester.state == u'current'))
703
def get_permissions(self, user, config):
596
def get_permissions(self, user):
704
597
if user.admin or user in self.members:
705
598
return set(['submit_project'])
742
635
project = Reference(project_id, Project.id)
744
637
extensions = ReferenceSet(id, 'ProjectExtension.assessed_id')
745
submissions = ReferenceSet(
746
id, 'ProjectSubmission.assessed_id', order_by='date_submitted')
638
submissions = ReferenceSet(id, 'ProjectSubmission.assessed_id')
748
640
def __repr__(self):
749
641
return "<%s %r in %r>" % (type(self).__name__,
758
650
def principal(self):
759
651
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
773
654
def get(cls, store, principal, project):
774
655
"""Find or create an Assessed for the given user or group and project.
783
664
a = store.find(cls,
784
665
(t is User) or (cls.project_group_id == principal.id),
785
666
(t is ProjectGroup) or (cls.user_id == principal.id),
786
cls.project_id == project.id).one()
667
Project.id == project.id).one()
834
715
submitter = Reference(submitter_id, User.id)
835
716
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)
851
719
# WORKSHEETS AND EXERCISES #
883
751
def __repr__(self):
884
752
return "<%s %s>" % (type(self).__name__, self.name)
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
754
def get_permissions(self, user):
896
757
if user is not None:
900
761
elif u'lecturer' in set((e.role for e in user.active_enrolments)):
901
762
perms.add('edit')
902
763
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
764
elif u'tutor' in set((e.role for e in user.active_enrolments)):
906
765
perms.add('edit')
907
766
perms.add('view')
969
828
store.find(WorksheetExercise,
970
829
WorksheetExercise.worksheet == self).remove()
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')
831
def get_permissions(self, user):
832
return self.offering.get_permissions(user)
983
834
def get_xml(self):
984
835
"""Returns the xml of this worksheet, converts from rst if required."""
1029
880
return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
1030
881
self.worksheet.identifier)
1032
def get_permissions(self, user, config):
1033
return self.worksheet.get_permissions(user, config)
883
def get_permissions(self, user):
884
return self.worksheet.get_permissions(user)
1036
887
class ExerciseSave(Storm):
1083
934
complete = Bool()
1086
def get_permissions(self, user, config):
937
def get_permissions(self, user):
1087
938
return set(['view']) if user is self.user else set()
1089
940
class TestSuite(Storm):
1109
960
def delete(self):
1110
961
"""Delete this suite, without asking questions."""
1111
for variable in self.variables:
962
for vaariable in self.variables:
1112
963
variable.delete()
1113
964
for test_case in self.test_cases:
1114
965
test_case.delete()