~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to ivle/webapp/admin/subject.py

  • Committer: William Grant
  • Date: 2010-02-15 05:37:50 UTC
  • Revision ID: grantw@unimelb.edu.au-20100215053750-hihmegnp8e7dshc2
Ignore test coverage files.

Show diffs side-by-side

added added

removed removed

Lines of Context:
31
31
from storm.locals import Desc, Store
32
32
import genshi
33
33
from genshi.filters import HTMLFormFiller
34
 
from genshi.template import Context
 
34
from genshi.template import Context, TemplateLoader
35
35
import formencode
36
36
import formencode.validators
37
37
 
38
 
from ivle.webapp.base.forms import (BaseFormView, URLNameValidator,
39
 
                                    DateTimeValidator)
 
38
from ivle.webapp.base.forms import BaseFormView
40
39
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
41
40
from ivle.webapp.base.xhtml import XHTMLView
42
 
from ivle.webapp.errors import BadRequest
43
41
from ivle.webapp import ApplicationRoot
44
42
 
45
43
from ivle.database import Subject, Semester, Offering, Enrolment, User,\
47
45
from ivle import util
48
46
import ivle.date
49
47
 
50
 
from ivle.webapp.admin.publishing import (root_to_subject, root_to_semester,
 
48
from ivle.webapp.admin.projectservice import ProjectSetRESTView
 
49
from ivle.webapp.admin.offeringservice import OfferingRESTView
 
50
from ivle.webapp.admin.publishing import (root_to_subject,
51
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)
 
52
            subject_url, offering_url, projectset_url, project_url)
54
53
from ivle.webapp.admin.breadcrumbs import (SubjectBreadcrumb,
55
 
            OfferingBreadcrumb, UserBreadcrumb, ProjectBreadcrumb,
56
 
            ProjectsBreadcrumb, EnrolmentBreadcrumb)
 
54
            OfferingBreadcrumb, UserBreadcrumb, ProjectBreadcrumb)
57
55
from ivle.webapp.core import Plugin as CorePlugin
58
56
from ivle.webapp.groups import GroupsView
59
57
from ivle.webapp.media import media_url
63
61
    '''The view of the list of subjects.'''
64
62
    template = 'templates/subjects.html'
65
63
    tab = 'subjects'
66
 
    breadcrumb_text = "Subjects"
67
64
 
68
65
    def authorize(self, req):
69
66
        return req.user is not None
72
69
        ctx['req'] = req
73
70
        ctx['user'] = req.user
74
71
        ctx['semesters'] = []
 
72
        ctx['mediapath'] = media_url(req, CorePlugin, 'images/')
 
73
        ctx['SubjectEdit'] = SubjectEdit
75
74
 
76
75
        for semester in req.store.find(Semester).order_by(Desc(Semester.year),
77
76
                                                     Desc(Semester.semester)):
84
83
            if len(offerings):
85
84
                ctx['semesters'].append((semester, offerings))
86
85
 
87
 
 
88
 
class SubjectsManage(XHTMLView):
89
 
    '''Subject management view.'''
90
 
    template = 'templates/subjects-manage.html'
91
 
    tab = 'subjects'
92
 
 
93
 
    def authorize(self, req):
94
 
        return req.user is not None and req.user.admin
95
 
 
96
 
    def populate(self, req, ctx):
97
 
        ctx['req'] = req
98
 
        ctx['mediapath'] = media_url(req, CorePlugin, 'images/')
99
 
        ctx['SubjectView'] = SubjectView
100
 
        ctx['SubjectEdit'] = SubjectEdit
101
 
        ctx['SemesterEdit'] = SemesterEdit
102
 
 
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)
 
86
        # Admins get a separate list of subjects so they can add/edit.
 
87
        if req.user.admin:
 
88
            ctx['subjects'] = req.store.find(Subject).order_by(Subject.name)
106
89
 
107
90
 
108
91
class SubjectShortNameUniquenessValidator(formencode.FancyValidator):
126
109
class SubjectSchema(formencode.Schema):
127
110
    short_name = formencode.All(
128
111
        SubjectShortNameUniquenessValidator(),
129
 
        URLNameValidator(not_empty=True))
 
112
        formencode.validators.UnicodeString(not_empty=True))
130
113
    name = formencode.validators.UnicodeString(not_empty=True)
131
114
    code = formencode.validators.UnicodeString(not_empty=True)
132
115
 
145
128
    def validator(self):
146
129
        return SubjectSchema()
147
130
 
 
131
    def get_return_url(self, obj):
 
132
        return '/subjects'
 
133
 
148
134
 
149
135
class SubjectNew(SubjectFormView):
150
136
    """A form to create a subject."""
193
179
    def _to_python(self, value, state):
194
180
        if (state.store.find(
195
181
                Semester, year=value['year'], semester=value['semester']
196
 
                ).one() not in (None, state.existing_semester)):
 
182
                ).count() > 0):
197
183
            raise formencode.Invalid(
198
184
                'Semester already exists', value, state)
199
185
        return value
200
186
 
201
187
 
202
188
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())
 
189
    year = formencode.validators.UnicodeString()
 
190
    semester = formencode.validators.UnicodeString()
208
191
    chained_validators = [SemesterUniquenessValidator()]
209
192
 
210
193
 
211
 
class SemesterFormView(BaseFormView):
212
 
    tab = 'subjects'
213
 
 
214
 
    def authorize(self, req):
215
 
        return req.user is not None and req.user.admin
216
 
 
217
 
    @property
218
 
    def validator(self):
219
 
        return SemesterSchema()
220
 
 
221
 
    def get_return_url(self, obj):
222
 
        return '/subjects/+manage'
223
 
 
224
 
 
225
 
class SemesterNew(SemesterFormView):
 
194
class SemesterNew(BaseFormView):
226
195
    """A form to create a semester."""
227
196
    template = 'templates/semester-new.html'
228
197
    tab = 'subjects'
229
198
 
230
 
    def populate_state(self, state):
231
 
        state.existing_semester = None
 
199
    def authorize(self, req):
 
200
        return req.user is not None and req.user.admin
 
201
 
 
202
    @property
 
203
    def validator(self):
 
204
        return SemesterSchema()
232
205
 
233
206
    def get_default_data(self, req):
234
207
        return {}
237
210
        new_semester = Semester()
238
211
        new_semester.year = data['year']
239
212
        new_semester.semester = data['semester']
240
 
        new_semester.state = data['state']
241
213
 
242
214
        req.store.add(new_semester)
243
215
        return new_semester
244
216
 
245
 
 
246
 
class SemesterEdit(SemesterFormView):
247
 
    """A form to edit a semester."""
248
 
    template = 'templates/semester-edit.html'
249
 
 
250
 
    def populate_state(self, state):
251
 
        state.existing_semester = self.context
252
 
 
253
 
    def get_default_data(self, req):
254
 
        return {
255
 
            'year': self.context.year,
256
 
            'semester': self.context.semester,
257
 
            'state': self.context.state,
258
 
            }
259
 
 
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']
264
 
 
265
 
        return self.context
266
 
 
267
 
class SubjectView(XHTMLView):
268
 
    '''The view of the list of offerings in a given subject.'''
269
 
    template = 'templates/subject.html'
270
 
    tab = 'subjects'
271
 
 
272
 
    def authorize(self, req):
273
 
        return req.user is not None
274
 
 
275
 
    def populate(self, req, ctx):
276
 
        ctx['context'] = self.context
277
 
        ctx['req'] = req
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
 
217
    def get_return_url(self, obj):
 
218
        return '/subjects'
283
219
 
284
220
 
285
221
class OfferingView(XHTMLView):
298
234
        ctx['format_datetime'] = ivle.date.make_date_nice
299
235
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
300
236
        ctx['OfferingEdit'] = OfferingEdit
301
 
        ctx['OfferingCloneWorksheets'] = OfferingCloneWorksheets
302
237
        ctx['GroupsView'] = GroupsView
303
 
        ctx['EnrolmentsView'] = EnrolmentsView
304
 
        ctx['Project'] = ivle.database.Project
305
238
 
306
239
        # As we go, calculate the total score for this subject
307
240
        # (Assessable worksheets only, mandatory problems only)
308
241
 
309
242
        ctx['worksheets'], problems_total, problems_done = (
310
243
            ivle.worksheet.utils.create_list_of_fake_worksheets_and_stats(
311
 
                req.config, req.store, req.user, self.context))
 
244
                req.store, req.user, self.context))
312
245
 
313
246
        ctx['exercises_total'] = problems_total
314
247
        ctx['exercises_done'] = problems_done
385
318
    description = formencode.validators.UnicodeString(
386
319
        if_missing=None, not_empty=False)
387
320
    url = formencode.validators.URL(if_missing=None, not_empty=False)
388
 
    show_worksheet_marks = formencode.validators.StringBoolean(
389
 
        if_missing=False)
390
321
 
391
322
 
392
323
class OfferingAdminSchema(OfferingSchema):
412
343
 
413
344
    def populate(self, req, ctx):
414
345
        super(OfferingEdit, self).populate(req, ctx)
415
 
        ctx['subjects'] = req.store.find(Subject).order_by(Subject.name)
416
 
        ctx['semesters'] = req.store.find(Semester).order_by(
417
 
            Semester.year, Semester.semester)
418
 
        ctx['force_subject'] = None
 
346
        ctx['subjects'] = req.store.find(Subject)
 
347
        ctx['semesters'] = req.store.find(Semester)
419
348
 
420
349
    def populate_state(self, state):
421
350
        state.existing_offering = self.context
427
356
                        self.context.semester.semester,
428
357
            'url': self.context.url,
429
358
            'description': self.context.description,
430
 
            'show_worksheet_marks': self.context.show_worksheet_marks,
431
359
            }
432
360
 
433
361
    def save_object(self, req, data):
436
364
            self.context.semester = data['semester']
437
365
        self.context.description = data['description']
438
366
        self.context.url = unicode(data['url']) if data['url'] else None
439
 
        self.context.show_worksheet_marks = data['show_worksheet_marks']
440
367
        return self.context
441
368
 
442
369
 
454
381
 
455
382
    def populate(self, req, ctx):
456
383
        super(OfferingNew, self).populate(req, ctx)
457
 
        ctx['subjects'] = req.store.find(Subject).order_by(Subject.name)
458
 
        ctx['semesters'] = req.store.find(Semester).order_by(
459
 
            Semester.year, Semester.semester)
460
 
        ctx['force_subject'] = None
 
384
        ctx['subjects'] = req.store.find(Subject)
 
385
        ctx['semesters'] = req.store.find(Semester)
461
386
 
462
387
    def populate_state(self, state):
463
388
        state.existing_offering = None
471
396
        new_offering.semester = data['semester']
472
397
        new_offering.description = data['description']
473
398
        new_offering.url = unicode(data['url']) if data['url'] else None
474
 
        new_offering.show_worksheet_marks = data['show_worksheet_marks']
475
399
 
476
400
        req.store.add(new_offering)
477
401
        return new_offering
478
402
 
479
 
class SubjectOfferingNew(OfferingNew):
480
 
    """A form to create an offering for a given subject."""
481
 
    # Identical to OfferingNew, except it forces the subject to be the subject
482
 
    # in context
483
 
    def populate(self, req, ctx):
484
 
        super(SubjectOfferingNew, self).populate(req, ctx)
485
 
        ctx['force_subject'] = self.context
486
 
 
487
 
class OfferingCloneWorksheetsSchema(formencode.Schema):
488
 
    subject = formencode.All(
489
 
        SubjectValidator(), formencode.validators.UnicodeString())
490
 
    semester = formencode.All(
491
 
        SemesterValidator(), formencode.validators.UnicodeString())
492
 
 
493
 
 
494
 
class OfferingCloneWorksheets(BaseFormView):
495
 
    """A form to clone worksheets from one offering to another."""
496
 
    template = 'templates/offering-clone-worksheets.html'
497
 
    tab = 'subjects'
498
 
 
499
 
    def authorize(self, req):
500
 
        return req.user is not None and req.user.admin
501
 
 
502
 
    @property
503
 
    def validator(self):
504
 
        return OfferingCloneWorksheetsSchema()
505
 
 
506
 
    def populate(self, req, ctx):
507
 
        super(OfferingCloneWorksheets, self).populate(req, ctx)
508
 
        ctx['subjects'] = req.store.find(Subject).order_by(Subject.name)
509
 
        ctx['semesters'] = req.store.find(Semester).order_by(
510
 
            Semester.year, Semester.semester)
511
 
 
512
 
    def get_default_data(self, req):
513
 
        return {}
514
 
 
515
 
    def save_object(self, req, data):
516
 
        if self.context.worksheets.count() > 0:
517
 
            raise BadRequest(
518
 
                "Cannot clone to target with existing worksheets.")
519
 
        offering = req.store.find(
520
 
            Offering, subject=data['subject'], semester=data['semester']).one()
521
 
        if offering is None:
522
 
            raise BadRequest("No such offering.")
523
 
        if offering.worksheets.count() == 0:
524
 
            raise BadRequest("Source offering has no worksheets.")
525
 
 
526
 
        self.context.clone_worksheets(offering)
527
 
        return self.context
528
 
 
529
403
 
530
404
class UserValidator(formencode.FancyValidator):
531
405
    """A FormEncode validator that turns a username into a user.
578
452
    template = 'templates/enrolments.html'
579
453
    tab = 'subjects'
580
454
    permission = 'edit'
581
 
    breadcrumb_text = 'Enrolments'
582
455
 
583
456
    def populate(self, req, ctx):
584
 
        ctx['req'] = req
585
457
        ctx['offering'] = self.context
586
 
        ctx['mediapath'] = media_url(req, CorePlugin, 'images/')
587
 
        ctx['offering_perms'] = self.context.get_permissions(
588
 
            req.user, req.config)
589
 
        ctx['EnrolView'] = EnrolView
590
 
        ctx['EnrolmentEdit'] = EnrolmentEdit
591
 
        ctx['EnrolmentDelete'] = EnrolmentDelete
592
 
 
593
458
 
594
459
class EnrolView(XHTMLView):
595
460
    """A form to enrol a user in an offering."""
620
485
        ctx['offering'] = self.context
621
486
        ctx['roles_auth'] = self.context.get_permissions(req.user, req.config)
622
487
        ctx['errors'] = errors
623
 
        # If all of the fields validated, set the global form error.
624
 
        if isinstance(errors, basestring):
625
 
            ctx['error_value'] = errors
626
 
 
627
 
 
628
 
class EnrolmentEditSchema(formencode.Schema):
629
 
    role = formencode.All(formencode.validators.OneOf(
630
 
                                ["lecturer", "tutor", "student"]),
631
 
                          RoleEnrolmentValidator(),
632
 
                          formencode.validators.UnicodeString())
633
 
 
634
 
 
635
 
class EnrolmentEdit(BaseFormView):
636
 
    """A form to alter an enrolment's role."""
637
 
    template = 'templates/enrolment-edit.html'
638
 
    tab = 'subjects'
639
 
    permission = 'edit'
640
 
 
641
 
    def populate_state(self, state):
642
 
        state.offering = self.context.offering
643
 
 
644
 
    def get_default_data(self, req):
645
 
        return {'role': self.context.role}
646
 
 
647
 
    @property
648
 
    def validator(self):
649
 
        return EnrolmentEditSchema()
650
 
 
651
 
    def save_object(self, req, data):
652
 
        self.context.role = data['role']
653
 
 
654
 
    def get_return_url(self, obj):
655
 
        return self.req.publisher.generate(
656
 
            self.context.offering, EnrolmentsView)
657
 
 
658
 
    def populate(self, req, ctx):
659
 
        super(EnrolmentEdit, self).populate(req, ctx)
660
 
        ctx['offering_perms'] = self.context.offering.get_permissions(
661
 
            req.user, req.config)
662
 
 
663
 
 
664
 
class EnrolmentDelete(XHTMLView):
665
 
    """A form to alter an enrolment's role."""
666
 
    template = 'templates/enrolment-delete.html'
667
 
    tab = 'subjects'
668
 
    permission = 'edit'
669
 
 
670
 
    def populate(self, req, ctx):
671
 
        # If POSTing, delete delete delete.
672
 
        if req.method == 'POST':
673
 
            self.context.delete()
674
 
            req.store.commit()
675
 
            req.throw_redirect(req.publisher.generate(
676
 
                self.context.offering, EnrolmentsView))
677
 
 
678
 
        ctx['enrolment'] = self.context
679
 
 
680
488
 
681
489
class OfferingProjectsView(XHTMLView):
682
490
    """View the projects for an offering."""
683
491
    template = 'templates/offering_projects.html'
684
492
    permission = 'edit'
685
493
    tab = 'subjects'
686
 
    breadcrumb_text = 'Projects'
687
494
 
688
495
    def populate(self, req, ctx):
689
496
        self.plugin_styles[Plugin] = ["project.css"]
 
497
        self.plugin_scripts[Plugin] = ["project.js"]
690
498
        ctx['req'] = req
691
499
        ctx['offering'] = self.context
692
500
        ctx['projectsets'] = []
 
501
        ctx['OfferingRESTView'] = OfferingRESTView
693
502
 
694
503
        #Open the projectset Fragment, and render it for inclusion
695
504
        #into the ProjectSets page
 
505
        #XXX: This could be a lot cleaner
 
506
        loader = genshi.template.TemplateLoader(".", auto_reload=True)
 
507
 
696
508
        set_fragment = os.path.join(os.path.dirname(__file__),
697
509
                "templates/projectset_fragment.html")
698
510
        project_fragment = os.path.join(os.path.dirname(__file__),
699
511
                "templates/project_fragment.html")
700
512
 
701
 
        for projectset in \
702
 
            self.context.project_sets.order_by(ivle.database.ProjectSet.id):
703
 
            settmpl = self._loader.load(set_fragment)
 
513
        for projectset in self.context.project_sets:
 
514
            settmpl = loader.load(set_fragment)
704
515
            setCtx = Context()
705
516
            setCtx['req'] = req
706
517
            setCtx['projectset'] = projectset
707
518
            setCtx['projects'] = []
708
519
            setCtx['GroupsView'] = GroupsView
709
 
            setCtx['ProjectSetEdit'] = ProjectSetEdit
710
 
            setCtx['ProjectNew'] = ProjectNew
 
520
            setCtx['ProjectSetRESTView'] = ProjectSetRESTView
711
521
 
712
 
            for project in \
713
 
                projectset.projects.order_by(ivle.database.Project.deadline):
714
 
                projecttmpl = self._loader.load(project_fragment)
 
522
            for project in projectset.projects:
 
523
                projecttmpl = loader.load(project_fragment)
715
524
                projectCtx = Context()
716
525
                projectCtx['req'] = req
717
526
                projectCtx['project'] = project
718
 
                projectCtx['ProjectEdit'] = ProjectEdit
719
 
                projectCtx['ProjectDelete'] = ProjectDelete
720
527
 
721
528
                setCtx['projects'].append(
722
529
                        projecttmpl.generate(projectCtx))
752
559
        self.plugin_styles[Plugin] = ["project.css"]
753
560
 
754
561
        ctx['req'] = req
755
 
        ctx['permissions'] = self.context.get_permissions(req.user,req.config)
756
562
        ctx['GroupsView'] = GroupsView
757
563
        ctx['EnrolView'] = EnrolView
758
 
        ctx['format_datetime'] = ivle.date.make_date_nice
759
564
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
760
565
        ctx['build_subversion_url'] = self.build_subversion_url
761
566
        ctx['svn_addr'] = req.config['urls']['svn_addr']
762
567
        ctx['project'] = self.context
763
568
        ctx['user'] = req.user
764
 
        ctx['ProjectEdit'] = ProjectEdit
765
 
        ctx['ProjectDelete'] = ProjectDelete
766
 
 
767
 
class ProjectUniquenessValidator(formencode.FancyValidator):
768
 
    """A FormEncode validator that checks that a project short_name is unique
769
 
    in a given offering.
770
 
 
771
 
    The project referenced by state.existing_project is permitted to
772
 
    hold that short_name. If any other project holds it, the input is rejected.
773
 
    """
774
 
    def _to_python(self, value, state):
775
 
        if (state.store.find(
776
 
            Project,
777
 
            Project.short_name == unicode(value),
778
 
            Project.project_set_id == ProjectSet.id,
779
 
            ProjectSet.offering == state.offering).one() not in
780
 
            (None, state.existing_project)):
781
 
            raise formencode.Invalid(
782
 
                "A project with that URL name already exists in this offering."
783
 
                , value, state)
784
 
        return value
785
 
 
786
 
class ProjectSchema(formencode.Schema):
787
 
    name = formencode.validators.UnicodeString(not_empty=True)
788
 
    short_name = formencode.All(
789
 
        URLNameValidator(not_empty=True),
790
 
        ProjectUniquenessValidator())
791
 
    deadline = DateTimeValidator(not_empty=True)
792
 
    url = formencode.validators.URL(if_missing=None, not_empty=False)
793
 
    synopsis = formencode.validators.UnicodeString(not_empty=True)
794
 
 
795
 
class ProjectEdit(BaseFormView):
796
 
    """A form to edit a project."""
797
 
    template = 'templates/project-edit.html'
798
 
    tab = 'subjects'
799
 
    permission = 'edit'
800
 
 
801
 
    @property
802
 
    def validator(self):
803
 
        return ProjectSchema()
804
 
 
805
 
    def populate(self, req, ctx):
806
 
        super(ProjectEdit, self).populate(req, ctx)
807
 
        ctx['projectset'] = self.context.project_set
808
 
 
809
 
    def populate_state(self, state):
810
 
        state.offering = self.context.project_set.offering
811
 
        state.existing_project = self.context
812
 
 
813
 
    def get_default_data(self, req):
814
 
        return {
815
 
            'name':         self.context.name,
816
 
            'short_name':   self.context.short_name,
817
 
            'deadline':     self.context.deadline,
818
 
            'url':          self.context.url,
819
 
            'synopsis':     self.context.synopsis,
820
 
            }
821
 
 
822
 
    def save_object(self, req, data):
823
 
        self.context.name = data['name']
824
 
        self.context.short_name = data['short_name']
825
 
        self.context.deadline = data['deadline']
826
 
        self.context.url = unicode(data['url']) if data['url'] else None
827
 
        self.context.synopsis = data['synopsis']
828
 
        return self.context
829
 
 
830
 
class ProjectNew(BaseFormView):
831
 
    """A form to create a new project."""
832
 
    template = 'templates/project-new.html'
833
 
    tab = 'subjects'
834
 
    permission = 'edit'
835
 
 
836
 
    @property
837
 
    def validator(self):
838
 
        return ProjectSchema()
839
 
 
840
 
    def populate(self, req, ctx):
841
 
        super(ProjectNew, self).populate(req, ctx)
842
 
        ctx['projectset'] = self.context
843
 
 
844
 
    def populate_state(self, state):
845
 
        state.offering = self.context.offering
846
 
        state.existing_project = None
847
 
 
848
 
    def get_default_data(self, req):
849
 
        return {}
850
 
 
851
 
    def save_object(self, req, data):
852
 
        new_project = Project()
853
 
        new_project.project_set = self.context
854
 
        new_project.name = data['name']
855
 
        new_project.short_name = data['short_name']
856
 
        new_project.deadline = data['deadline']
857
 
        new_project.url = unicode(data['url']) if data['url'] else None
858
 
        new_project.synopsis = data['synopsis']
859
 
        req.store.add(new_project)
860
 
        return new_project
861
 
 
862
 
class ProjectDelete(XHTMLView):
863
 
    """A form to delete a project."""
864
 
    template = 'templates/project-delete.html'
865
 
    tab = 'subjects'
866
 
    permission = 'edit'
867
 
 
868
 
    def populate(self, req, ctx):
869
 
        # If post, delete the project, or display a message explaining that
870
 
        # the project cannot be deleted
871
 
        if self.context.can_delete:
872
 
            if req.method == 'POST':
873
 
                self.context.delete()
874
 
                self.template = 'templates/project-deleted.html'
875
 
        else:
876
 
            # Can't delete
877
 
            self.template = 'templates/project-undeletable.html'
878
 
 
879
 
        # If get and can delete, display a delete confirmation page
880
 
 
881
 
        # Variables for the template
882
 
        ctx['req'] = req
883
 
        ctx['project'] = self.context
884
 
        ctx['OfferingProjectsView'] = OfferingProjectsView
885
 
 
886
 
class ProjectSetSchema(formencode.Schema):
887
 
    group_size = formencode.validators.Int(if_missing=None, not_empty=False)
888
 
 
889
 
class ProjectSetEdit(BaseFormView):
890
 
    """A form to edit a project set."""
891
 
    template = 'templates/projectset-edit.html'
892
 
    tab = 'subjects'
893
 
    permission = 'edit'
894
 
 
895
 
    @property
896
 
    def validator(self):
897
 
        return ProjectSetSchema()
898
 
 
899
 
    def populate(self, req, ctx):
900
 
        super(ProjectSetEdit, self).populate(req, ctx)
901
 
 
902
 
    def get_default_data(self, req):
903
 
        return {
904
 
            'group_size': self.context.max_students_per_group,
905
 
            }
906
 
 
907
 
    def save_object(self, req, data):
908
 
        self.context.max_students_per_group = data['group_size']
909
 
        return self.context
910
 
 
911
 
class ProjectSetNew(BaseFormView):
912
 
    """A form to create a new project set."""
913
 
    template = 'templates/projectset-new.html'
914
 
    tab = 'subjects'
915
 
    permission = 'edit'
916
 
    breadcrumb_text = "Projects"
917
 
 
918
 
    @property
919
 
    def validator(self):
920
 
        return ProjectSetSchema()
921
 
 
922
 
    def populate(self, req, ctx):
923
 
        super(ProjectSetNew, self).populate(req, ctx)
924
 
 
925
 
    def get_default_data(self, req):
926
 
        return {}
927
 
 
928
 
    def save_object(self, req, data):
929
 
        new_set = ProjectSet()
930
 
        new_set.offering = self.context
931
 
        new_set.max_students_per_group = data['group_size']
932
 
        req.store.add(new_set)
933
 
        return new_set
934
569
 
935
570
class Plugin(ViewPlugin, MediaPlugin):
936
 
    forward_routes = (root_to_subject, root_to_semester, subject_to_offering,
937
 
                      offering_to_project, offering_to_projectset,
938
 
                      offering_to_enrolment)
939
 
    reverse_routes = (
940
 
        subject_url, semester_url, offering_url, projectset_url, project_url,
941
 
        enrolment_url)
 
571
    forward_routes = (root_to_subject, subject_to_offering,
 
572
                      offering_to_project, offering_to_projectset)
 
573
    reverse_routes = (subject_url, offering_url, projectset_url, project_url)
942
574
 
943
575
    views = [(ApplicationRoot, ('subjects', '+index'), SubjectsView),
944
 
             (ApplicationRoot, ('subjects', '+manage'), SubjectsManage),
945
576
             (ApplicationRoot, ('subjects', '+new'), SubjectNew),
946
577
             (ApplicationRoot, ('subjects', '+new-offering'), OfferingNew),
947
 
             (ApplicationRoot, ('+semesters', '+new'), SemesterNew),
948
 
             (Subject, '+index', SubjectView),
 
578
             (ApplicationRoot, ('subjects', '+new-semester'), SemesterNew),
949
579
             (Subject, '+edit', SubjectEdit),
950
 
             (Subject, '+new-offering', SubjectOfferingNew),
951
 
             (Semester, '+edit', SemesterEdit),
952
580
             (Offering, '+index', OfferingView),
953
581
             (Offering, '+edit', OfferingEdit),
954
 
             (Offering, '+clone-worksheets', OfferingCloneWorksheets),
955
582
             (Offering, ('+enrolments', '+index'), EnrolmentsView),
956
583
             (Offering, ('+enrolments', '+new'), EnrolView),
957
 
             (Enrolment, '+edit', EnrolmentEdit),
958
 
             (Enrolment, '+delete', EnrolmentDelete),
959
584
             (Offering, ('+projects', '+index'), OfferingProjectsView),
960
 
             (Offering, ('+projects', '+new-set'), ProjectSetNew),
961
 
             (ProjectSet, '+edit', ProjectSetEdit),
962
 
             (ProjectSet, '+new', ProjectNew),
963
585
             (Project, '+index', ProjectView),
964
 
             (Project, '+edit', ProjectEdit),
965
 
             (Project, '+delete', ProjectDelete),
 
586
 
 
587
             (Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
 
588
             (ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
966
589
             ]
967
590
 
968
591
    breadcrumbs = {Subject: SubjectBreadcrumb,
969
592
                   Offering: OfferingBreadcrumb,
970
593
                   User: UserBreadcrumb,
971
594
                   Project: ProjectBreadcrumb,
972
 
                   Enrolment: EnrolmentBreadcrumb,
973
595
                   }
974
596
 
975
597
    tabs = [