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.
453
497
return "<%s %d in %r>" % (type(self).__name__, self.id,
456
def get_permissions(self, user):
457
return self.offering.get_permissions(user)
500
def get_permissions(self, user, config):
501
return self.offering.get_permissions(user, config)
503
def get_groups_for_user(self, user):
504
"""List all groups in this offering of which the user is a member."""
506
return Store.of(self).find(
508
ProjectGroupMembership.user_id == user.id,
509
ProjectGroupMembership.project_group_id == ProjectGroup.id,
510
ProjectGroup.project_set_id == self.id)
512
def get_submission_principal(self, user):
513
"""Get the principal on behalf of which the user can submit.
515
If this is a solo project set, the given user is returned. If
516
the user is a member of exactly one group, all the group is
517
returned. Otherwise, None is returned.
520
groups = self.get_groups_for_user(user)
521
if groups.count() == 1:
530
return self.max_students_per_group is not None
460
533
def assigned(self):
463
536
This will be a Storm ResultSet.
465
538
#If its a solo project, return everyone in offering
466
if self.max_students_per_group is None:
540
return self.project_groups
467
542
return self.offering.students
469
return self.project_groups
544
class DeadlinePassed(Exception):
545
"""An exception indicating that a project cannot be submitted because the
546
deadline has passed."""
550
return "The project deadline has passed"
471
552
class Project(Storm):
472
553
"""A student project for which submissions can be made."""
494
575
return "<%s '%s' in %r>" % (type(self).__name__, self.short_name,
495
576
self.project_set.offering)
497
def can_submit(self, principal):
578
def can_submit(self, principal, user):
498
579
return (self in principal.get_projects() and
499
self.deadline > datetime.datetime.now())
580
not self.has_deadline_passed(user))
501
582
def submit(self, principal, path, revision, who):
502
583
"""Submit a Subversion path and revision to a project.
508
589
@param who: The user who is actually making the submission.
511
if not self.can_submit(principal):
512
raise Exception('cannot submit')
592
if not self.can_submit(principal, who):
593
raise DeadlinePassed()
514
595
a = Assessed.get(Store.of(self), principal, self)
515
596
ps = ProjectSubmission()
524
def get_permissions(self, user):
525
return self.project_set.offering.get_permissions(user)
605
def get_permissions(self, user, config):
606
return self.project_set.offering.get_permissions(user, config)
528
609
def latest_submissions(self):
621
def has_deadline_passed(self, user):
622
"""Check whether the deadline has passed."""
623
# XXX: Need to respect extensions.
624
return self.deadline < datetime.datetime.now()
626
def get_submissions_for_principal(self, principal):
627
"""Fetch a ResultSet of all submissions by a particular principal."""
628
assessed = Assessed.get(Store.of(self), principal, self)
631
return assessed.submissions
541
635
class ProjectGroup(Storm):
542
636
"""A group of students working together on a project."""
593
687
(not active_only) or (Semester.state == u'current'))
596
def get_permissions(self, user):
690
def get_permissions(self, user, config):
597
691
if user.admin or user in self.members:
598
692
return set(['submit_project'])
635
729
project = Reference(project_id, Project.id)
637
731
extensions = ReferenceSet(id, 'ProjectExtension.assessed_id')
638
submissions = ReferenceSet(id, 'ProjectSubmission.assessed_id')
732
submissions = ReferenceSet(
733
id, 'ProjectSubmission.assessed_id', order_by='date_submitted')
640
735
def __repr__(self):
641
736
return "<%s %r in %r>" % (type(self).__name__,
650
745
def principal(self):
651
746
return self.project_group or self.user
749
def checkout_location(self):
750
"""Returns the location of the Subversion workspace for this piece of
751
assessment, relative to each group member's home directory."""
752
subjectname = self.project.project_set.offering.subject.short_name
754
checkout_dir_name = self.principal.short_name
756
checkout_dir_name = "mywork"
757
return subjectname + "/" + checkout_dir_name
654
760
def get(cls, store, principal, project):
655
761
"""Find or create an Assessed for the given user or group and project.
664
770
a = store.find(cls,
665
771
(t is User) or (cls.project_group_id == principal.id),
666
772
(t is ProjectGroup) or (cls.user_id == principal.id),
667
Project.id == project.id).one()
773
cls.project_id == project.id).one()
715
821
submitter = Reference(submitter_id, User.id)
716
822
date_submitted = DateTime()
824
def get_verify_url(self, user):
825
"""Get the URL for verifying this submission, within the account of
827
# If this is a solo project, then self.path will be prefixed with the
828
# subject name. Remove the first path segment.
829
submitpath = self.path[1:] if self.path[:1] == '/' else self.path
830
if not self.assessed.is_group:
831
if '/' in submitpath:
832
submitpath = submitpath.split('/', 1)[1]
835
return "/files/%s/%s/%s?r=%d" % (user.login,
836
self.assessed.checkout_location, submitpath, self.revision)
719
838
# WORKSHEETS AND EXERCISES #
751
870
def __repr__(self):
752
871
return "<%s %s>" % (type(self).__name__, self.name)
754
def get_permissions(self, user):
873
def get_permissions(self, user, config):
874
return self.global_permissions(user, config)
877
def global_permissions(user, config):
878
"""Gets the set of permissions this user has over *all* exercises.
879
This is used to determine who may view the exercises list, and create
757
883
if user is not None:
761
887
elif u'lecturer' in set((e.role for e in user.active_enrolments)):
762
888
perms.add('edit')
763
889
perms.add('view')
764
elif u'tutor' in set((e.role for e in user.active_enrolments)):
890
elif (config['policy']['tutors_can_edit_worksheets']
891
and u'tutor' in set((e.role for e in user.active_enrolments))):
892
# Site-specific policy on the role of tutors
765
893
perms.add('edit')
766
894
perms.add('view')
828
956
store.find(WorksheetExercise,
829
957
WorksheetExercise.worksheet == self).remove()
831
def get_permissions(self, user):
832
return self.offering.get_permissions(user)
959
def get_permissions(self, user, config):
960
# Almost the same permissions as for the offering itself
961
perms = self.offering.get_permissions(user, config)
962
# However, "edit" permission is derived from the "edit_worksheets"
963
# permission of the offering
964
if 'edit_worksheets' in perms:
967
perms.discard('edit')
834
970
def get_xml(self):
835
971
"""Returns the xml of this worksheet, converts from rst if required."""
880
1016
return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
881
1017
self.worksheet.identifier)
883
def get_permissions(self, user):
884
return self.worksheet.get_permissions(user)
1019
def get_permissions(self, user, config):
1020
return self.worksheet.get_permissions(user, config)
887
1023
class ExerciseSave(Storm):
934
1070
complete = Bool()
937
def get_permissions(self, user):
1073
def get_permissions(self, user, config):
938
1074
return set(['view']) if user is self.user else set()
940
1076
class TestSuite(Storm):
960
1096
def delete(self):
961
1097
"""Delete this suite, without asking questions."""
962
for vaariable in self.variables:
1098
for variable in self.variables:
963
1099
variable.delete()
964
1100
for test_case in self.test_cases:
965
1101
test_case.delete()