325
325
semester = Reference(semester_id, Semester.id)
326
326
description = Unicode()
328
show_worksheet_marks = Bool()
329
worksheet_cutoff = DateTime()
328
330
groups_student_permissions = Unicode()
330
332
enrolments = ReferenceSet(id, 'Enrolment.offering_id')
393
395
perms.add('view_project_submissions')
394
396
perms.add('admin_groups')
395
397
perms.add('edit_worksheets')
398
perms.add('view_worksheet_marks')
396
399
perms.add('edit') # Can edit projects & details
397
400
perms.add('enrol') # Can see enrolment screen at all
398
401
perms.add('enrol_student') # Can enrol students
426
429
# XXX: Respect extensions.
427
430
return self.projects.find(Project.deadline > datetime.datetime.now())
432
def has_worksheet_cutoff_passed(self, user):
433
"""Check whether the worksheet cutoff has passed.
434
A user is required, in case we support extensions.
436
if self.worksheet_cutoff is None:
439
return self.worksheet_cutoff < datetime.datetime.now()
429
441
def clone_worksheets(self, source):
430
442
"""Clone all worksheets from the specified source to this offering."""
431
443
import ivle.worksheet.utils
435
447
newws.identifier = worksheet.identifier
436
448
newws.name = worksheet.name
437
449
newws.assessable = worksheet.assessable
450
newws.published = worksheet.published
438
451
newws.data = worksheet.data
439
452
newws.format = worksheet.format
440
453
newws.offering = self
646
659
return assessed.submissions
662
def can_delete(self):
663
"""Can only delete if there are no submissions."""
664
return self.submissions.count() == 0
667
"""Delete the project. Fails if can_delete is False."""
668
if not self.can_delete:
669
raise IntegrityError()
670
for assessed in self.assesseds:
672
Store.of(self).remove(self)
650
674
class ProjectGroup(Storm):
651
675
"""A group of students working together on a project."""
826
"""Delete the assessed. Fails if there are any submissions. Deletes
828
if self.submissions.count() > 0:
829
raise IntegrityError()
830
for extension in self.extensions:
832
Store.of(self).remove(self)
802
834
class ProjectExtension(Storm):
803
835
"""An extension granted to a user or group on a particular project.
815
847
approver = Reference(approver_id, User.id)
816
848
notes = Unicode()
851
"""Delete the extension."""
852
Store.of(self).remove(self)
818
854
class SubmissionError(Exception):
819
855
"""Denotes a validation error during submission."""
936
def get_description(self):
937
"""Return the description interpreted as reStructuredText."""
938
return rst(self.description)
973
def _cache_description_xhtml(self, invalidate=False):
974
# Don't regenerate an existing cache unless forced.
975
if self._description_xhtml_cache is not None and not invalidate:
979
self._description_xhtml_cache = rst(self.description)
981
self._description_xhtml_cache = None
984
def description_xhtml(self):
985
"""The XHTML exercise description, converted from reStructuredText."""
986
self._cache_description_xhtml()
987
return self._description_xhtml_cache
989
def set_description(self, description):
990
self.description = description
991
self._cache_description_xhtml(invalidate=True)
940
993
def delete(self):
941
994
"""Deletes the exercise, providing it has no associated worksheets."""
995
1050
WorksheetExercise.worksheet == self).remove()
997
1052
def get_permissions(self, user, config):
998
# Almost the same permissions as for the offering itself
999
perms = self.offering.get_permissions(user, config)
1000
# However, "edit" permission is derived from the "edit_worksheets"
1001
# permission of the offering
1002
if 'edit_worksheets' in perms:
1053
offering_perms = self.offering.get_permissions(user, config)
1057
# Anybody who can view an offering can view a published
1059
if 'view' in offering_perms and self.published:
1062
# Any worksheet editors can both view and edit.
1063
if 'edit_worksheets' in offering_perms:
1003
1065
perms.add('edit')
1005
perms.discard('edit')
1009
"""Returns the xml of this worksheet, converts from rst if required."""
1010
if self.format == u'rst':
1011
ws_xml = rst(self.data)
1069
def _cache_data_xhtml(self, invalidate=False):
1070
# Don't regenerate an existing cache unless forced.
1071
if self._data_xhtml_cache is not None and not invalidate:
1074
if self.format == u'rst':
1075
self._data_xhtml_cache = rst(self.data)
1077
self._data_xhtml_cache = None
1080
def data_xhtml(self):
1081
"""The XHTML of this worksheet, converted from rST if required."""
1082
# Update the rST -> XHTML cache, if required.
1083
self._cache_data_xhtml()
1085
if self.format == u'rst':
1086
return self._data_xhtml_cache
1014
1088
return self.data
1090
def set_data(self, data):
1092
self._cache_data_xhtml(invalidate=True)
1016
1094
def delete(self):
1017
1095
"""Deletes the worksheet, provided it has no attempts on any exercises.
1081
1159
def __repr__(self):
1082
1160
return "<%s %s by %s at %s>" % (type(self).__name__,
1083
self.exercise.name, self.user.login, self.date.strftime("%c"))
1161
self.worksheet_exercise.exercise.name, self.user.login,
1162
self.date.strftime("%c"))
1085
1164
class ExerciseAttempt(ExerciseSave):
1086
1165
"""An attempt at solving an exercise.