2
# Copyright (C) 2007-2008 The University of Melbourne
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22
# This is an IVLE application.
23
# A sample / testing application for IVLE.
31
from storm.locals import Desc, Store
33
from genshi.filters import HTMLFormFiller
34
from genshi.template import Context
36
import formencode.validators
38
from ivle.webapp.base.forms import (BaseFormView, URLNameValidator,
40
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
41
from ivle.webapp.base.xhtml import XHTMLView
42
from ivle.webapp.errors import BadRequest
43
from ivle.webapp import ApplicationRoot
45
from ivle.database import Subject, Semester, Offering, Enrolment, User,\
46
ProjectSet, Project, ProjectSubmission
50
from ivle.webapp.admin.publishing import (root_to_subject, root_to_semester,
51
subject_to_offering, offering_to_projectset, offering_to_project,
52
offering_to_enrolment, subject_url, semester_url, offering_url,
53
projectset_url, project_url, enrolment_url)
54
from ivle.webapp.admin.breadcrumbs import (SubjectBreadcrumb,
55
OfferingBreadcrumb, UserBreadcrumb, ProjectBreadcrumb,
56
ProjectsBreadcrumb, EnrolmentBreadcrumb)
57
from ivle.webapp.core import Plugin as CorePlugin
58
from ivle.webapp.groups import GroupsView
59
from ivle.webapp.media import media_url
60
from ivle.webapp.tutorial import Plugin as TutorialPlugin
62
class SubjectsView(XHTMLView):
63
'''The view of the list of subjects.'''
64
template = 'templates/subjects.html'
66
breadcrumb_text = "Subjects"
68
def authorize(self, req):
69
return req.user is not None
71
def populate(self, req, ctx):
73
ctx['user'] = req.user
76
for semester in req.store.find(Semester).order_by(Desc(Semester.year),
77
Desc(Semester.semester)):
79
# For admins, show all subjects in the system
80
offerings = list(semester.offerings.find())
82
offerings = [enrolment.offering for enrolment in
83
semester.enrolments.find(user=req.user)]
85
ctx['semesters'].append((semester, offerings))
88
class SubjectsManage(XHTMLView):
89
'''Subject management view.'''
90
template = 'templates/subjects-manage.html'
93
def authorize(self, req):
94
return req.user is not None and req.user.admin
96
def populate(self, req, ctx):
98
ctx['mediapath'] = media_url(req, CorePlugin, 'images/')
99
ctx['SubjectView'] = SubjectView
100
ctx['SubjectEdit'] = SubjectEdit
101
ctx['SemesterEdit'] = SemesterEdit
103
ctx['subjects'] = req.store.find(Subject).order_by(Subject.name)
104
ctx['semesters'] = req.store.find(Semester).order_by(
105
Semester.year, Semester.semester)
108
class SubjectShortNameUniquenessValidator(formencode.FancyValidator):
109
"""A FormEncode validator that checks that a subject name is unused.
111
The subject referenced by state.existing_subject is permitted
112
to hold that name. If any other object holds it, the input is rejected.
114
def __init__(self, matching=None):
115
self.matching = matching
117
def _to_python(self, value, state):
118
if (state.store.find(
119
Subject, short_name=value).one() not in
120
(None, state.existing_subject)):
121
raise formencode.Invalid(
122
'Short name already taken', value, state)
126
class SubjectSchema(formencode.Schema):
127
short_name = formencode.All(
128
SubjectShortNameUniquenessValidator(),
129
URLNameValidator(not_empty=True))
130
name = formencode.validators.UnicodeString(not_empty=True)
131
code = formencode.validators.UnicodeString(not_empty=True)
134
class SubjectFormView(BaseFormView):
135
"""An abstract form to add or edit a subject."""
138
def authorize(self, req):
139
return req.user is not None and req.user.admin
141
def populate_state(self, state):
142
state.existing_subject = None
146
return SubjectSchema()
149
class SubjectNew(SubjectFormView):
150
"""A form to create a subject."""
151
template = 'templates/subject-new.html'
153
def get_default_data(self, req):
156
def save_object(self, req, data):
157
new_subject = Subject()
158
new_subject.short_name = data['short_name']
159
new_subject.name = data['name']
160
new_subject.code = data['code']
162
req.store.add(new_subject)
166
class SubjectEdit(SubjectFormView):
167
"""A form to edit a subject."""
168
template = 'templates/subject-edit.html'
170
def populate_state(self, state):
171
state.existing_subject = self.context
173
def get_default_data(self, req):
175
'short_name': self.context.short_name,
176
'name': self.context.name,
177
'code': self.context.code,
180
def save_object(self, req, data):
181
self.context.short_name = data['short_name']
182
self.context.name = data['name']
183
self.context.code = data['code']
188
class SemesterUniquenessValidator(formencode.FancyValidator):
189
"""A FormEncode validator that checks that a semester is unique.
191
There cannot be more than one semester for the same year and semester.
193
def _to_python(self, value, state):
194
if (state.store.find(
195
Semester, year=value['year'], semester=value['semester']
196
).one() not in (None, state.existing_semester)):
197
raise formencode.Invalid(
198
'Semester already exists', value, state)
202
class SemesterSchema(formencode.Schema):
203
year = URLNameValidator()
204
semester = URLNameValidator()
205
state = formencode.All(
206
formencode.validators.OneOf(["past", "current", "future"]),
207
formencode.validators.UnicodeString())
208
chained_validators = [SemesterUniquenessValidator()]
211
class SemesterFormView(BaseFormView):
214
def authorize(self, req):
215
return req.user is not None and req.user.admin
219
return SemesterSchema()
221
def get_return_url(self, obj):
222
return '/subjects/+manage'
225
class SemesterNew(SemesterFormView):
226
"""A form to create a semester."""
227
template = 'templates/semester-new.html'
230
def populate_state(self, state):
231
state.existing_semester = None
233
def get_default_data(self, req):
236
def save_object(self, req, data):
237
new_semester = Semester()
238
new_semester.year = data['year']
239
new_semester.semester = data['semester']
240
new_semester.state = data['state']
242
req.store.add(new_semester)
246
class SemesterEdit(SemesterFormView):
247
"""A form to edit a semester."""
248
template = 'templates/semester-edit.html'
250
def populate_state(self, state):
251
state.existing_semester = self.context
253
def get_default_data(self, req):
255
'year': self.context.year,
256
'semester': self.context.semester,
257
'state': self.context.state,
260
def save_object(self, req, data):
261
self.context.year = data['year']
262
self.context.semester = data['semester']
263
self.context.state = data['state']
267
class SubjectView(XHTMLView):
268
'''The view of the list of offerings in a given subject.'''
269
template = 'templates/subject.html'
272
def authorize(self, req):
273
return req.user is not None
275
def populate(self, req, ctx):
276
ctx['context'] = self.context
278
ctx['user'] = req.user
279
ctx['offerings'] = list(self.context.offerings)
280
ctx['permissions'] = self.context.get_permissions(req.user,req.config)
281
ctx['SubjectEdit'] = SubjectEdit
282
ctx['SubjectOfferingNew'] = SubjectOfferingNew
285
class OfferingView(XHTMLView):
286
"""The home page of an offering."""
287
template = 'templates/offering.html'
291
def populate(self, req, ctx):
292
# Need the worksheet result styles.
293
self.plugin_styles[TutorialPlugin] = ['tutorial.css']
294
ctx['context'] = self.context
296
ctx['permissions'] = self.context.get_permissions(req.user,req.config)
297
ctx['format_submission_principal'] = util.format_submission_principal
298
ctx['format_datetime'] = ivle.date.make_date_nice
299
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
300
ctx['OfferingEdit'] = OfferingEdit
301
ctx['OfferingCloneWorksheets'] = OfferingCloneWorksheets
302
ctx['GroupsView'] = GroupsView
303
ctx['EnrolmentsView'] = EnrolmentsView
305
# As we go, calculate the total score for this subject
306
# (Assessable worksheets only, mandatory problems only)
308
ctx['worksheets'], problems_total, problems_done = (
309
ivle.worksheet.utils.create_list_of_fake_worksheets_and_stats(
310
req.config, req.store, req.user, self.context))
312
ctx['exercises_total'] = problems_total
313
ctx['exercises_done'] = problems_done
314
if problems_total > 0:
315
if problems_done >= problems_total:
316
ctx['worksheets_complete_class'] = "complete"
317
elif problems_done > 0:
318
ctx['worksheets_complete_class'] = "semicomplete"
320
ctx['worksheets_complete_class'] = "incomplete"
321
# Calculate the final percentage and mark for the subject
322
(ctx['exercises_pct'], ctx['worksheet_mark'],
323
ctx['worksheet_max_mark']) = (
324
ivle.worksheet.utils.calculate_mark(
325
problems_done, problems_total))
328
class SubjectValidator(formencode.FancyValidator):
329
"""A FormEncode validator that turns a subject name into a subject.
331
The state must have a 'store' attribute, which is the Storm store
334
def _to_python(self, value, state):
335
subject = state.store.find(Subject, short_name=value).one()
339
raise formencode.Invalid('Subject does not exist', value, state)
342
class SemesterValidator(formencode.FancyValidator):
343
"""A FormEncode validator that turns a string into a semester.
345
The string should be of the form 'year/semester', eg. '2009/1'.
347
The state must have a 'store' attribute, which is the Storm store
350
def _to_python(self, value, state):
352
year, semester = value.split('/')
354
year = semester = None
356
semester = state.store.find(
357
Semester, year=year, semester=semester).one()
361
raise formencode.Invalid('Semester does not exist', value, state)
364
class OfferingUniquenessValidator(formencode.FancyValidator):
365
"""A FormEncode validator that checks that an offering is unique.
367
There cannot be more than one offering in the same year and semester.
369
The offering referenced by state.existing_offering is permitted to
370
hold that year and semester tuple. If any other object holds it, the
373
def _to_python(self, value, state):
374
if (state.store.find(
375
Offering, subject=value['subject'],
376
semester=value['semester']).one() not in
377
(None, state.existing_offering)):
378
raise formencode.Invalid(
379
'Offering already exists', value, state)
383
class OfferingSchema(formencode.Schema):
384
description = formencode.validators.UnicodeString(
385
if_missing=None, not_empty=False)
386
url = formencode.validators.URL(if_missing=None, not_empty=False)
387
show_worksheet_marks = formencode.validators.StringBoolean(
391
class OfferingAdminSchema(OfferingSchema):
392
subject = formencode.All(
393
SubjectValidator(), formencode.validators.UnicodeString())
394
semester = formencode.All(
395
SemesterValidator(), formencode.validators.UnicodeString())
396
chained_validators = [OfferingUniquenessValidator()]
399
class OfferingEdit(BaseFormView):
400
"""A form to edit an offering's details."""
401
template = 'templates/offering-edit.html'
407
if self.req.user.admin:
408
return OfferingAdminSchema()
410
return OfferingSchema()
412
def populate(self, req, ctx):
413
super(OfferingEdit, self).populate(req, ctx)
414
ctx['subjects'] = req.store.find(Subject).order_by(Subject.name)
415
ctx['semesters'] = req.store.find(Semester).order_by(
416
Semester.year, Semester.semester)
417
ctx['force_subject'] = None
419
def populate_state(self, state):
420
state.existing_offering = self.context
422
def get_default_data(self, req):
424
'subject': self.context.subject.short_name,
425
'semester': self.context.semester.year + '/' +
426
self.context.semester.semester,
427
'url': self.context.url,
428
'description': self.context.description,
429
'show_worksheet_marks': self.context.show_worksheet_marks,
432
def save_object(self, req, data):
434
self.context.subject = data['subject']
435
self.context.semester = data['semester']
436
self.context.description = data['description']
437
self.context.url = unicode(data['url']) if data['url'] else None
438
self.context.show_worksheet_marks = data['show_worksheet_marks']
442
class OfferingNew(BaseFormView):
443
"""A form to create an offering."""
444
template = 'templates/offering-new.html'
447
def authorize(self, req):
448
return req.user is not None and req.user.admin
452
return OfferingAdminSchema()
454
def populate(self, req, ctx):
455
super(OfferingNew, self).populate(req, ctx)
456
ctx['subjects'] = req.store.find(Subject).order_by(Subject.name)
457
ctx['semesters'] = req.store.find(Semester).order_by(
458
Semester.year, Semester.semester)
459
ctx['force_subject'] = None
461
def populate_state(self, state):
462
state.existing_offering = None
464
def get_default_data(self, req):
467
def save_object(self, req, data):
468
new_offering = Offering()
469
new_offering.subject = data['subject']
470
new_offering.semester = data['semester']
471
new_offering.description = data['description']
472
new_offering.url = unicode(data['url']) if data['url'] else None
473
new_offering.show_worksheet_marks = data['show_worksheet_marks']
475
req.store.add(new_offering)
478
class SubjectOfferingNew(OfferingNew):
479
"""A form to create an offering for a given subject."""
480
# Identical to OfferingNew, except it forces the subject to be the subject
482
def populate(self, req, ctx):
483
super(SubjectOfferingNew, self).populate(req, ctx)
484
ctx['force_subject'] = self.context
486
class OfferingCloneWorksheetsSchema(formencode.Schema):
487
subject = formencode.All(
488
SubjectValidator(), formencode.validators.UnicodeString())
489
semester = formencode.All(
490
SemesterValidator(), formencode.validators.UnicodeString())
493
class OfferingCloneWorksheets(BaseFormView):
494
"""A form to clone worksheets from one offering to another."""
495
template = 'templates/offering-clone-worksheets.html'
498
def authorize(self, req):
499
return req.user is not None and req.user.admin
503
return OfferingCloneWorksheetsSchema()
505
def populate(self, req, ctx):
506
super(OfferingCloneWorksheets, self).populate(req, ctx)
507
ctx['subjects'] = req.store.find(Subject).order_by(Subject.name)
508
ctx['semesters'] = req.store.find(Semester).order_by(
509
Semester.year, Semester.semester)
511
def get_default_data(self, req):
514
def save_object(self, req, data):
515
if self.context.worksheets.count() > 0:
517
"Cannot clone to target with existing worksheets.")
518
offering = req.store.find(
519
Offering, subject=data['subject'], semester=data['semester']).one()
521
raise BadRequest("No such offering.")
522
if offering.worksheets.count() == 0:
523
raise BadRequest("Source offering has no worksheets.")
525
self.context.clone_worksheets(offering)
529
class UserValidator(formencode.FancyValidator):
530
"""A FormEncode validator that turns a username into a user.
532
The state must have a 'store' attribute, which is the Storm store
534
def _to_python(self, value, state):
535
user = User.get_by_login(state.store, value)
539
raise formencode.Invalid('User does not exist', value, state)
542
class NoEnrolmentValidator(formencode.FancyValidator):
543
"""A FormEncode validator that ensures absence of an enrolment.
545
The state must have an 'offering' attribute.
547
def _to_python(self, value, state):
548
if state.offering.get_enrolment(value):
549
raise formencode.Invalid('User already enrolled', value, state)
553
class RoleEnrolmentValidator(formencode.FancyValidator):
554
"""A FormEncode validator that checks permission to enrol users with a
557
The state must have an 'offering' attribute.
559
def _to_python(self, value, state):
560
if (("enrol_" + value) not in
561
state.offering.get_permissions(state.user, state.config)):
562
raise formencode.Invalid('Not allowed to assign users that role',
567
class EnrolSchema(formencode.Schema):
568
user = formencode.All(NoEnrolmentValidator(), UserValidator())
569
role = formencode.All(formencode.validators.OneOf(
570
["lecturer", "tutor", "student"]),
571
RoleEnrolmentValidator(),
572
formencode.validators.UnicodeString())
575
class EnrolmentsView(XHTMLView):
576
"""A page which displays all users enrolled in an offering."""
577
template = 'templates/enrolments.html'
580
breadcrumb_text = 'Enrolments'
582
def populate(self, req, ctx):
584
ctx['offering'] = self.context
585
ctx['mediapath'] = media_url(req, CorePlugin, 'images/')
586
ctx['offering_perms'] = self.context.get_permissions(
587
req.user, req.config)
588
ctx['EnrolView'] = EnrolView
589
ctx['EnrolmentEdit'] = EnrolmentEdit
590
ctx['EnrolmentDelete'] = EnrolmentDelete
593
class EnrolView(XHTMLView):
594
"""A form to enrol a user in an offering."""
595
template = 'templates/enrol.html'
599
def filter(self, stream, ctx):
600
return stream | HTMLFormFiller(data=ctx['data'])
602
def populate(self, req, ctx):
603
if req.method == 'POST':
604
data = dict(req.get_fieldstorage())
606
validator = EnrolSchema()
607
req.offering = self.context # XXX: Getting into state.
608
data = validator.to_python(data, state=req)
609
self.context.enrol(data['user'], data['role'])
611
req.throw_redirect(req.uri)
612
except formencode.Invalid, e:
613
errors = e.unpack_errors()
618
ctx['data'] = data or {}
619
ctx['offering'] = self.context
620
ctx['roles_auth'] = self.context.get_permissions(req.user, req.config)
621
ctx['errors'] = errors
622
# If all of the fields validated, set the global form error.
623
if isinstance(errors, basestring):
624
ctx['error_value'] = errors
627
class EnrolmentEditSchema(formencode.Schema):
628
role = formencode.All(formencode.validators.OneOf(
629
["lecturer", "tutor", "student"]),
630
RoleEnrolmentValidator(),
631
formencode.validators.UnicodeString())
634
class EnrolmentEdit(BaseFormView):
635
"""A form to alter an enrolment's role."""
636
template = 'templates/enrolment-edit.html'
640
def populate_state(self, state):
641
state.offering = self.context.offering
643
def get_default_data(self, req):
644
return {'role': self.context.role}
648
return EnrolmentEditSchema()
650
def save_object(self, req, data):
651
self.context.role = data['role']
653
def get_return_url(self, obj):
654
return self.req.publisher.generate(
655
self.context.offering, EnrolmentsView)
657
def populate(self, req, ctx):
658
super(EnrolmentEdit, self).populate(req, ctx)
659
ctx['offering_perms'] = self.context.offering.get_permissions(
660
req.user, req.config)
663
class EnrolmentDelete(XHTMLView):
664
"""A form to alter an enrolment's role."""
665
template = 'templates/enrolment-delete.html'
669
def populate(self, req, ctx):
670
# If POSTing, delete delete delete.
671
if req.method == 'POST':
672
self.context.delete()
674
req.throw_redirect(req.publisher.generate(
675
self.context.offering, EnrolmentsView))
677
ctx['enrolment'] = self.context
680
class OfferingProjectsView(XHTMLView):
681
"""View the projects for an offering."""
682
template = 'templates/offering_projects.html'
685
breadcrumb_text = 'Projects'
687
def populate(self, req, ctx):
688
self.plugin_styles[Plugin] = ["project.css"]
690
ctx['offering'] = self.context
691
ctx['projectsets'] = []
693
#Open the projectset Fragment, and render it for inclusion
694
#into the ProjectSets page
695
set_fragment = os.path.join(os.path.dirname(__file__),
696
"templates/projectset_fragment.html")
697
project_fragment = os.path.join(os.path.dirname(__file__),
698
"templates/project_fragment.html")
701
self.context.project_sets.order_by(ivle.database.ProjectSet.id):
702
settmpl = self._loader.load(set_fragment)
705
setCtx['projectset'] = projectset
706
setCtx['projects'] = []
707
setCtx['GroupsView'] = GroupsView
708
setCtx['ProjectSetEdit'] = ProjectSetEdit
709
setCtx['ProjectNew'] = ProjectNew
712
projectset.projects.order_by(ivle.database.Project.deadline):
713
projecttmpl = self._loader.load(project_fragment)
714
projectCtx = Context()
715
projectCtx['req'] = req
716
projectCtx['project'] = project
717
projectCtx['ProjectEdit'] = ProjectEdit
719
setCtx['projects'].append(
720
projecttmpl.generate(projectCtx))
722
ctx['projectsets'].append(settmpl.generate(setCtx))
725
class ProjectView(XHTMLView):
726
"""View the submissions for a ProjectSet"""
727
template = "templates/project.html"
728
permission = "view_project_submissions"
731
def build_subversion_url(self, svnroot, submission):
732
princ = submission.assessed.principal
734
if isinstance(princ, User):
735
path = 'users/%s' % princ.login
737
path = 'groups/%s_%s_%s_%s' % (
738
princ.project_set.offering.subject.short_name,
739
princ.project_set.offering.semester.year,
740
princ.project_set.offering.semester.semester,
743
return urlparse.urljoin(
745
os.path.join(path, submission.path[1:] if
746
submission.path.startswith(os.sep) else
749
def populate(self, req, ctx):
750
self.plugin_styles[Plugin] = ["project.css"]
753
ctx['permissions'] = self.context.get_permissions(req.user,req.config)
754
ctx['GroupsView'] = GroupsView
755
ctx['EnrolView'] = EnrolView
756
ctx['format_datetime'] = ivle.date.make_date_nice
757
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
758
ctx['build_subversion_url'] = self.build_subversion_url
759
ctx['svn_addr'] = req.config['urls']['svn_addr']
760
ctx['project'] = self.context
761
ctx['user'] = req.user
762
ctx['ProjectEdit'] = ProjectEdit
764
class ProjectUniquenessValidator(formencode.FancyValidator):
765
"""A FormEncode validator that checks that a project short_name is unique
768
The project referenced by state.existing_project is permitted to
769
hold that short_name. If any other project holds it, the input is rejected.
771
def _to_python(self, value, state):
772
if (state.store.find(
774
Project.short_name == unicode(value),
775
Project.project_set_id == ProjectSet.id,
776
ProjectSet.offering == state.offering).one() not in
777
(None, state.existing_project)):
778
raise formencode.Invalid(
779
"A project with that URL name already exists in this offering."
783
class ProjectSchema(formencode.Schema):
784
name = formencode.validators.UnicodeString(not_empty=True)
785
short_name = formencode.All(
786
URLNameValidator(not_empty=True),
787
ProjectUniquenessValidator())
788
deadline = DateTimeValidator(not_empty=True)
789
url = formencode.validators.URL(if_missing=None, not_empty=False)
790
synopsis = formencode.validators.UnicodeString(not_empty=True)
792
class ProjectEdit(BaseFormView):
793
"""A form to edit a project."""
794
template = 'templates/project-edit.html'
800
return ProjectSchema()
802
def populate(self, req, ctx):
803
super(ProjectEdit, self).populate(req, ctx)
804
ctx['projectset'] = self.context.project_set
806
def populate_state(self, state):
807
state.offering = self.context.project_set.offering
808
state.existing_project = self.context
810
def get_default_data(self, req):
812
'name': self.context.name,
813
'short_name': self.context.short_name,
814
'deadline': self.context.deadline,
815
'url': self.context.url,
816
'synopsis': self.context.synopsis,
819
def save_object(self, req, data):
820
self.context.name = data['name']
821
self.context.short_name = data['short_name']
822
self.context.deadline = data['deadline']
823
self.context.url = unicode(data['url']) if data['url'] else None
824
self.context.synopsis = data['synopsis']
827
class ProjectNew(BaseFormView):
828
"""A form to create a new project."""
829
template = 'templates/project-new.html'
835
return ProjectSchema()
837
def populate(self, req, ctx):
838
super(ProjectNew, self).populate(req, ctx)
839
ctx['projectset'] = self.context
841
def populate_state(self, state):
842
state.offering = self.context.offering
843
state.existing_project = None
845
def get_default_data(self, req):
848
def save_object(self, req, data):
849
new_project = Project()
850
new_project.project_set = self.context
851
new_project.name = data['name']
852
new_project.short_name = data['short_name']
853
new_project.deadline = data['deadline']
854
new_project.url = unicode(data['url']) if data['url'] else None
855
new_project.synopsis = data['synopsis']
856
req.store.add(new_project)
859
class ProjectSetSchema(formencode.Schema):
860
group_size = formencode.validators.Int(if_missing=None, not_empty=False)
862
class ProjectSetEdit(BaseFormView):
863
"""A form to edit a project set."""
864
template = 'templates/projectset-edit.html'
870
return ProjectSetSchema()
872
def populate(self, req, ctx):
873
super(ProjectSetEdit, self).populate(req, ctx)
875
def get_default_data(self, req):
877
'group_size': self.context.max_students_per_group,
880
def save_object(self, req, data):
881
self.context.max_students_per_group = data['group_size']
884
class ProjectSetNew(BaseFormView):
885
"""A form to create a new project set."""
886
template = 'templates/projectset-new.html'
889
breadcrumb_text = "Projects"
893
return ProjectSetSchema()
895
def populate(self, req, ctx):
896
super(ProjectSetNew, self).populate(req, ctx)
898
def get_default_data(self, req):
901
def save_object(self, req, data):
902
new_set = ProjectSet()
903
new_set.offering = self.context
904
new_set.max_students_per_group = data['group_size']
905
req.store.add(new_set)
908
class Plugin(ViewPlugin, MediaPlugin):
909
forward_routes = (root_to_subject, root_to_semester, subject_to_offering,
910
offering_to_project, offering_to_projectset,
911
offering_to_enrolment)
913
subject_url, semester_url, offering_url, projectset_url, project_url,
916
views = [(ApplicationRoot, ('subjects', '+index'), SubjectsView),
917
(ApplicationRoot, ('subjects', '+manage'), SubjectsManage),
918
(ApplicationRoot, ('subjects', '+new'), SubjectNew),
919
(ApplicationRoot, ('subjects', '+new-offering'), OfferingNew),
920
(ApplicationRoot, ('+semesters', '+new'), SemesterNew),
921
(Subject, '+index', SubjectView),
922
(Subject, '+edit', SubjectEdit),
923
(Subject, '+new-offering', SubjectOfferingNew),
924
(Semester, '+edit', SemesterEdit),
925
(Offering, '+index', OfferingView),
926
(Offering, '+edit', OfferingEdit),
927
(Offering, '+clone-worksheets', OfferingCloneWorksheets),
928
(Offering, ('+enrolments', '+index'), EnrolmentsView),
929
(Offering, ('+enrolments', '+new'), EnrolView),
930
(Enrolment, '+edit', EnrolmentEdit),
931
(Enrolment, '+delete', EnrolmentDelete),
932
(Offering, ('+projects', '+index'), OfferingProjectsView),
933
(Offering, ('+projects', '+new-set'), ProjectSetNew),
934
(ProjectSet, '+edit', ProjectSetEdit),
935
(ProjectSet, '+new', ProjectNew),
936
(Project, '+index', ProjectView),
937
(Project, '+edit', ProjectEdit),
940
breadcrumbs = {Subject: SubjectBreadcrumb,
941
Offering: OfferingBreadcrumb,
942
User: UserBreadcrumb,
943
Project: ProjectBreadcrumb,
944
Enrolment: EnrolmentBreadcrumb,
948
('subjects', 'Subjects',
949
'View subject content and complete worksheets',
950
'subjects.png', 'subjects', 5)
953
media = 'subject-media'