29
29
from storm.locals import create_database, Store, Int, Unicode, DateTime, \
30
30
Reference, ReferenceSet, Bool, Storm, Desc
31
from storm.expr import Select, Max
32
31
from storm.exceptions import NotOneError, IntegrityError
34
33
from ivle.worksheet.rst import rst
119
118
def display_name(self):
120
"""Returns the "nice name" of the user or group."""
121
119
return self.fullname
124
def short_name(self):
125
"""Returns the database "identifier" name of the user or group."""
129
122
def password_expired(self):
130
123
fieldval = self.pass_exp
131
124
return fieldval is not None and datetime.datetime.now() > fieldval
215
208
Semester.id == Offering.semester_id,
216
209
(not active_only) or (Semester.state == u'current'),
217
210
Enrolment.offering_id == Offering.id,
218
Enrolment.user_id == self.id,
219
Enrolment.active == True)
211
Enrolment.user_id == self.id)
222
214
def hash_password(password):
228
220
"""Find a user in a store by login name."""
229
221
return store.find(cls, cls.login == unicode(login)).one()
231
def get_permissions(self, user, config):
223
def get_permissions(self, user):
232
224
"""Determine privileges held by a user over this object.
234
226
If the user requesting privileges is this user or an admin,
235
227
they may do everything. Otherwise they may do nothing.
237
229
if user and user.admin or user is self:
238
return set(['view_public', 'view', 'edit', 'submit_project'])
230
return set(['view', 'edit', 'submit_project'])
240
return set(['view_public'])
242
234
# SUBJECTS AND ENROLMENTS #
250
242
code = Unicode(name="subj_code")
251
243
name = Unicode(name="subj_name")
252
244
short_name = Unicode(name="subj_short_name")
254
247
offerings = ReferenceSet(id, 'Offering.subject_id')
258
251
def __repr__(self):
259
252
return "<%s '%s'>" % (type(self).__name__, self.short_name)
261
def get_permissions(self, user, config):
254
def get_permissions(self, user):
262
255
"""Determine privileges held by a user over this object.
264
257
If the user requesting privileges is an admin, they may edit.
322
315
subject = Reference(subject_id, Subject.id)
323
316
semester_id = Int(name="semesterid")
324
317
semester = Reference(semester_id, Semester.id)
325
description = Unicode()
327
318
groups_student_permissions = Unicode()
329
320
enrolments = ReferenceSet(id, 'Enrolment.offering_id')
332
323
'Enrolment.user_id',
334
325
project_sets = ReferenceSet(id, 'ProjectSet.offering_id')
335
projects = ReferenceSet(id,
336
'ProjectSet.offering_id',
338
'Project.project_set_id')
340
327
worksheets = ReferenceSet(id,
341
328
'Worksheet.offering_id',
372
359
Enrolment.offering_id == self.id).one()
373
360
Store.of(enrolment).remove(enrolment)
375
def get_permissions(self, user, config):
362
def get_permissions(self, user):
377
364
if user is not None:
378
365
enrolment = self.get_enrolment(user)
379
366
if enrolment or user.admin:
380
367
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
368
if (enrolment and enrolment.role in (u'tutor', u'lecturer')) \
403
373
def get_enrolment(self, user):
412
def get_members_by_role(self, role):
413
return Store.of(self).find(User,
414
Enrolment.user_id == User.id,
415
Enrolment.offering_id == self.id,
416
Enrolment.role == role
417
).order_by(User.login)
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)
444
382
class Enrolment(Storm):
445
383
"""An enrolment of a user in an offering.
472
410
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
415
class ProjectSet(Storm):
510
435
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
547
"""Get the entities (groups or users) assigned to submit this project.
549
This will be a Storm ResultSet.
551
#If its a solo project, return everyone in offering
553
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"
438
def get_permissions(self, user):
439
return self.offering.get_permissions(user)
565
441
class Project(Storm):
566
442
"""A student project for which submissions can be made."""
588
464
return "<%s '%s' in %r>" % (type(self).__name__, self.short_name,
589
465
self.project_set.offering)
591
def can_submit(self, principal, user):
467
def can_submit(self, principal):
592
468
return (self in principal.get_projects() and
593
not self.has_deadline_passed(user))
469
self.deadline > datetime.datetime.now())
595
471
def submit(self, principal, path, revision, who):
596
472
"""Submit a Subversion path and revision to a project.
602
478
@param who: The user who is actually making the submission.
605
if not self.can_submit(principal, who):
606
raise DeadlinePassed()
481
if not self.can_submit(principal):
482
raise Exception('cannot submit')
608
484
a = Assessed.get(Store.of(self), principal, self)
609
485
ps = ProjectSubmission()
618
def get_permissions(self, user, config):
619
return self.project_set.offering.get_permissions(user, config)
622
def latest_submissions(self):
623
"""Return the latest submission for each Assessed."""
624
return Store.of(self).find(ProjectSubmission,
625
Assessed.project_id == self.id,
626
ProjectSubmission.assessed_id == Assessed.id,
627
ProjectSubmission.date_submitted == Select(
628
Max(ProjectSubmission.date_submitted),
629
ProjectSubmission.assessed_id == Assessed.id,
630
tables=ProjectSubmission
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
494
def get_permissions(self, user):
495
return self.project_set.offering.get_permissions(user)
648
498
class ProjectGroup(Storm):
674
524
def display_name(self):
675
"""Returns the "nice name" of the user or group."""
679
def short_name(self):
680
"""Returns the database "identifier" name of the user or group."""
525
return '%s (%s)' % (self.nick, self.name)
683
527
def get_projects(self, offering=None, active_only=True):
684
528
'''Find projects that the group can submit.
700
544
(not active_only) or (Semester.state == u'current'))
703
def get_permissions(self, user, config):
547
def get_permissions(self, user):
704
548
if user.admin or user in self.members:
705
549
return set(['submit_project'])
742
586
project = Reference(project_id, Project.id)
744
588
extensions = ReferenceSet(id, 'ProjectExtension.assessed_id')
745
submissions = ReferenceSet(
746
id, 'ProjectSubmission.assessed_id', order_by='date_submitted')
589
submissions = ReferenceSet(id, 'ProjectSubmission.assessed_id')
748
591
def __repr__(self):
749
592
return "<%s %r in %r>" % (type(self).__name__,
750
593
self.user or self.project_group, self.project)
754
"""True if the Assessed is a group, False if it is a user."""
755
return self.project_group is not None
758
596
def principal(self):
759
597
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
600
def get(cls, store, principal, project):
774
601
"""Find or create an Assessed for the given user or group and project.
783
610
a = store.find(cls,
784
611
(t is User) or (cls.project_group_id == principal.id),
785
612
(t is ProjectGroup) or (cls.user_id == principal.id),
786
cls.project_id == project.id).one()
613
Project.id == project.id).one()
834
661
submitter = Reference(submitter_id, User.id)
835
662
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
665
# WORKSHEETS AND EXERCISES #
883
697
def __repr__(self):
884
698
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
700
def get_permissions(self, user):
896
703
if user is not None:
900
707
elif u'lecturer' in set((e.role for e in user.active_enrolments)):
901
708
perms.add('edit')
902
709
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
710
elif u'tutor' in set((e.role for e in user.active_enrolments)):
906
711
perms.add('edit')
907
712
perms.add('view')
969
774
store.find(WorksheetExercise,
970
775
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')
777
def get_permissions(self, user):
778
return self.offering.get_permissions(user)
983
780
def get_xml(self):
984
781
"""Returns the xml of this worksheet, converts from rst if required."""
1029
826
return "<%s %s in %s>" % (type(self).__name__, self.exercise.name,
1030
827
self.worksheet.identifier)
1032
def get_permissions(self, user, config):
1033
return self.worksheet.get_permissions(user, config)
829
def get_permissions(self, user):
830
return self.worksheet.get_permissions(user)
1036
833
class ExerciseSave(Storm):
1083
880
complete = Bool()
1086
def get_permissions(self, user, config):
883
def get_permissions(self, user):
1087
884
return set(['view']) if user is self.user else set()
1089
886
class TestSuite(Storm):
1109
906
def delete(self):
1110
907
"""Delete this suite, without asking questions."""
1111
for variable in self.variables:
908
for vaariable in self.variables:
1112
909
variable.delete()
1113
910
for test_case in self.test_cases:
1114
911
test_case.delete()