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())
400
428
class Enrolment(Storm):
401
429
"""An enrolment of a user in an offering.
453
481
return "<%s %d in %r>" % (type(self).__name__, self.id,
456
def get_permissions(self, user):
457
return self.offering.get_permissions(user)
484
def get_permissions(self, user, config):
485
return self.offering.get_permissions(user, config)
487
def get_groups_for_user(self, user):
488
"""List all groups in this offering of which the user is a member."""
490
return Store.of(self).find(
492
ProjectGroupMembership.user_id == user.id,
493
ProjectGroupMembership.project_group_id == ProjectGroup.id,
494
ProjectGroup.project_set_id == self.id)
496
def get_submission_principal(self, user):
497
"""Get the principal on behalf of which the user can submit.
499
If this is a solo project set, the given user is returned. If
500
the user is a member of exactly one group, all the group is
501
returned. Otherwise, None is returned.
504
groups = self.get_groups_for_user(user)
505
if groups.count() == 1:
514
return self.max_students_per_group is not None
460
517
def assigned(self):
463
520
This will be a Storm ResultSet.
465
522
#If its a solo project, return everyone in offering
466
if self.max_students_per_group is None:
524
return self.project_groups
467
526
return self.offering.students
469
return self.project_groups
528
class DeadlinePassed(Exception):
529
"""An exception indicating that a project cannot be submitted because the
530
deadline has passed."""
534
return "The project deadline has passed"
471
536
class Project(Storm):
472
537
"""A student project for which submissions can be made."""
494
559
return "<%s '%s' in %r>" % (type(self).__name__, self.short_name,
495
560
self.project_set.offering)
497
def can_submit(self, principal):
562
def can_submit(self, principal, user):
498
563
return (self in principal.get_projects() and
499
self.deadline > datetime.datetime.now())
564
not self.has_deadline_passed(user))
501
566
def submit(self, principal, path, revision, who):
502
567
"""Submit a Subversion path and revision to a project.
508
573
@param who: The user who is actually making the submission.
511
if not self.can_submit(principal):
512
raise Exception('cannot submit')
576
if not self.can_submit(principal, who):
577
raise DeadlinePassed()
514
579
a = Assessed.get(Store.of(self), principal, self)
515
580
ps = ProjectSubmission()
524
def get_permissions(self, user):
525
return self.project_set.offering.get_permissions(user)
589
def get_permissions(self, user, config):
590
return self.project_set.offering.get_permissions(user, config)
528
593
def latest_submissions(self):
605
def has_deadline_passed(self, user):
606
"""Check whether the deadline has passed."""
607
# XXX: Need to respect extensions.
608
return self.deadline < datetime.datetime.now()
610
def get_submissions_for_principal(self, principal):
611
"""Fetch a ResultSet of all submissions by a particular principal."""
612
assessed = Assessed.get(Store.of(self), principal, self)
615
return assessed.submissions
541
619
class ProjectGroup(Storm):
542
620
"""A group of students working together on a project."""
593
671
(not active_only) or (Semester.state == u'current'))
596
def get_permissions(self, user):
674
def get_permissions(self, user, config):
597
675
if user.admin or user in self.members:
598
676
return set(['submit_project'])
635
713
project = Reference(project_id, Project.id)
637
715
extensions = ReferenceSet(id, 'ProjectExtension.assessed_id')
638
submissions = ReferenceSet(id, 'ProjectSubmission.assessed_id')
716
submissions = ReferenceSet(
717
id, 'ProjectSubmission.assessed_id', order_by='date_submitted')
640
719
def __repr__(self):
641
720
return "<%s %r in %r>" % (type(self).__name__,
650
729
def principal(self):
651
730
return self.project_group or self.user
733
def checkout_location(self):
734
"""Returns the location of the Subversion workspace for this piece of
735
assessment, relative to each group member's home directory."""
736
subjectname = self.project.project_set.offering.subject.short_name
738
checkout_dir_name = self.principal.short_name
740
checkout_dir_name = "mywork"
741
return subjectname + "/" + checkout_dir_name
654
744
def get(cls, store, principal, project):
655
745
"""Find or create an Assessed for the given user or group and project.
664
754
a = store.find(cls,
665
755
(t is User) or (cls.project_group_id == principal.id),
666
756
(t is ProjectGroup) or (cls.user_id == principal.id),
667
Project.id == project.id).one()
757
cls.project_id == project.id).one()
715
805
submitter = Reference(submitter_id, User.id)
716
806
date_submitted = DateTime()
808
def get_verify_url(self, user):
809
"""Get the URL for verifying this submission, within the account of
811
# If this is a solo project, then self.path will be prefixed with the
812
# subject name. Remove the first path segment.
813
submitpath = self.path[1:] if self.path[:1] == '/' else self.path
814
if not self.assessed.is_group:
815
if '/' in submitpath:
816
submitpath = submitpath.split('/', 1)[1]
819
return "/files/%s/%s/%s?r=%d" % (user.login,
820
self.assessed.checkout_location, submitpath, self.revision)
719
822
# WORKSHEETS AND EXERCISES #
751
854
def __repr__(self):
752
855
return "<%s %s>" % (type(self).__name__, self.name)
754
def get_permissions(self, user):
857
def get_permissions(self, user, config):
858
return self.global_permissions(user, config)
861
def global_permissions(user, config):
862
"""Gets the set of permissions this user has over *all* exercises.
863
This is used to determine who may view the exercises list, and create
757
867
if user is not None:
761
871
elif u'lecturer' in set((e.role for e in user.active_enrolments)):
762
872
perms.add('edit')
763
873
perms.add('view')
764
elif u'tutor' in set((e.role for e in user.active_enrolments)):
874
elif (config['policy']['tutors_can_edit_worksheets']
875
and u'tutor' in set((e.role for e in user.active_enrolments))):
876
# Site-specific policy on the role of tutors
765
877
perms.add('edit')
766
878
perms.add('view')
828
940
store.find(WorksheetExercise,
829
941
WorksheetExercise.worksheet == self).remove()
831
def get_permissions(self, user):
832
return self.offering.get_permissions(user)
943
def get_permissions(self, user, config):
944
# Almost the same permissions as for the offering itself
945
perms = self.offering.get_permissions(user, config)
946
# However, "edit" permission is derived from the "edit_worksheets"
947
# permission of the offering
948
if 'edit_worksheets' in perms:
951
perms.discard('edit')
834
954
def get_xml(self):
835
955
"""Returns the xml of this worksheet, converts from rst if required."""
880
1000
return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
881
1001
self.worksheet.identifier)
883
def get_permissions(self, user):
884
return self.worksheet.get_permissions(user)
1003
def get_permissions(self, user, config):
1004
return self.worksheet.get_permissions(user, config)
887
1007
class ExerciseSave(Storm):
934
1054
complete = Bool()
937
def get_permissions(self, user):
1057
def get_permissions(self, user, config):
938
1058
return set(['view']) if user is self.user else set()
940
1060
class TestSuite(Storm):
960
1080
def delete(self):
961
1081
"""Delete this suite, without asking questions."""
962
for vaariable in self.variables:
1082
for variable in self.variables:
963
1083
variable.delete()
964
1084
for test_case in self.test_cases:
965
1085
test_case.delete()