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

« back to all changes in this revision

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

  • Committer: Matt Giuca
  • Date: 2010-02-18 03:57:33 UTC
  • Revision ID: matt.giuca@gmail.com-20100218035733-d4v0iy4hq0wbjas6
ivle.fileservice_lib.listing: Previously assumed any SVN client error meant the directory was not versioned, and silently dropped SVN metadata. Now checks the error message, and for any unexpected errors, raises the exception rather than assuming unversioned. Fixes Launchpad Bug #523592.

Show diffs side-by-side

added added

removed removed

Lines of Context:
38
38
from ivle.webapp.base.forms import BaseFormView
39
39
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
40
40
from ivle.webapp.base.xhtml import XHTMLView
 
41
from ivle.webapp.errors import BadRequest
41
42
from ivle.webapp import ApplicationRoot
42
43
 
43
44
from ivle.database import Subject, Semester, Offering, Enrolment, User,\
47
48
 
48
49
from ivle.webapp.admin.projectservice import ProjectSetRESTView
49
50
from ivle.webapp.admin.offeringservice import OfferingRESTView
50
 
from ivle.webapp.admin.publishing import (root_to_subject,
 
51
from ivle.webapp.admin.publishing import (root_to_subject, root_to_semester,
51
52
            subject_to_offering, offering_to_projectset, offering_to_project,
52
 
            subject_url, offering_url, projectset_url, project_url)
 
53
            offering_to_enrolment, subject_url, semester_url, offering_url,
 
54
            projectset_url, project_url, enrolment_url)
53
55
from ivle.webapp.admin.breadcrumbs import (SubjectBreadcrumb,
54
 
            OfferingBreadcrumb, UserBreadcrumb, ProjectBreadcrumb)
 
56
            OfferingBreadcrumb, UserBreadcrumb, ProjectBreadcrumb,
 
57
            EnrolmentBreadcrumb)
55
58
from ivle.webapp.core import Plugin as CorePlugin
56
59
from ivle.webapp.groups import GroupsView
57
60
from ivle.webapp.media import media_url
69
72
        ctx['req'] = req
70
73
        ctx['user'] = req.user
71
74
        ctx['semesters'] = []
72
 
        ctx['mediapath'] = media_url(req, CorePlugin, 'images/')
73
 
        ctx['SubjectEdit'] = SubjectEdit
74
75
 
75
76
        for semester in req.store.find(Semester).order_by(Desc(Semester.year),
76
77
                                                     Desc(Semester.semester)):
83
84
            if len(offerings):
84
85
                ctx['semesters'].append((semester, offerings))
85
86
 
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)
 
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['SubjectEdit'] = SubjectEdit
 
100
        ctx['SemesterEdit'] = SemesterEdit
 
101
 
 
102
        ctx['subjects'] = req.store.find(Subject).order_by(Subject.name)
 
103
        ctx['semesters'] = req.store.find(Semester).order_by(
 
104
            Semester.year, Semester.semester)
89
105
 
90
106
 
91
107
class SubjectShortNameUniquenessValidator(formencode.FancyValidator):
179
195
    def _to_python(self, value, state):
180
196
        if (state.store.find(
181
197
                Semester, year=value['year'], semester=value['semester']
182
 
                ).count() > 0):
 
198
                ).one() not in (None, state.existing_semester)):
183
199
            raise formencode.Invalid(
184
200
                'Semester already exists', value, state)
185
201
        return value
188
204
class SemesterSchema(formencode.Schema):
189
205
    year = formencode.validators.UnicodeString()
190
206
    semester = formencode.validators.UnicodeString()
 
207
    state = formencode.All(
 
208
        formencode.validators.OneOf(["past", "current", "future"]),
 
209
        formencode.validators.UnicodeString())
191
210
    chained_validators = [SemesterUniquenessValidator()]
192
211
 
193
212
 
194
 
class SemesterNew(BaseFormView):
 
213
class SemesterFormView(BaseFormView):
 
214
    tab = 'subjects'
 
215
 
 
216
    def authorize(self, req):
 
217
        return req.user is not None and req.user.admin
 
218
 
 
219
    @property
 
220
    def validator(self):
 
221
        return SemesterSchema()
 
222
 
 
223
    def get_return_url(self, obj):
 
224
        return '/subjects/+manage'
 
225
 
 
226
 
 
227
class SemesterNew(SemesterFormView):
195
228
    """A form to create a semester."""
196
229
    template = 'templates/semester-new.html'
197
230
    tab = 'subjects'
198
231
 
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
    def populate_state(self, state):
 
233
        state.existing_semester = None
205
234
 
206
235
    def get_default_data(self, req):
207
236
        return {}
210
239
        new_semester = Semester()
211
240
        new_semester.year = data['year']
212
241
        new_semester.semester = data['semester']
 
242
        new_semester.state = data['state']
213
243
 
214
244
        req.store.add(new_semester)
215
245
        return new_semester
216
246
 
217
 
    def get_return_url(self, obj):
218
 
        return '/subjects'
 
247
 
 
248
class SemesterEdit(SemesterFormView):
 
249
    """A form to edit a semester."""
 
250
    template = 'templates/semester-edit.html'
 
251
 
 
252
    def populate_state(self, state):
 
253
        state.existing_semester = self.context
 
254
 
 
255
    def get_default_data(self, req):
 
256
        return {
 
257
            'year': self.context.year,
 
258
            'semester': self.context.semester,
 
259
            'state': self.context.state,
 
260
            }
 
261
 
 
262
    def save_object(self, req, data):
 
263
        self.context.year = data['year']
 
264
        self.context.semester = data['semester']
 
265
        self.context.state = data['state']
 
266
 
 
267
        return self.context
219
268
 
220
269
 
221
270
class OfferingView(XHTMLView):
234
283
        ctx['format_datetime'] = ivle.date.make_date_nice
235
284
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
236
285
        ctx['OfferingEdit'] = OfferingEdit
 
286
        ctx['OfferingCloneWorksheets'] = OfferingCloneWorksheets
237
287
        ctx['GroupsView'] = GroupsView
 
288
        ctx['EnrolmentsView'] = EnrolmentsView
238
289
 
239
290
        # As we go, calculate the total score for this subject
240
291
        # (Assessable worksheets only, mandatory problems only)
343
394
 
344
395
    def populate(self, req, ctx):
345
396
        super(OfferingEdit, self).populate(req, ctx)
346
 
        ctx['subjects'] = req.store.find(Subject)
347
 
        ctx['semesters'] = req.store.find(Semester)
 
397
        ctx['subjects'] = req.store.find(Subject).order_by(Subject.name)
 
398
        ctx['semesters'] = req.store.find(Semester).order_by(
 
399
            Semester.year, Semester.semester)
348
400
 
349
401
    def populate_state(self, state):
350
402
        state.existing_offering = self.context
381
433
 
382
434
    def populate(self, req, ctx):
383
435
        super(OfferingNew, self).populate(req, ctx)
384
 
        ctx['subjects'] = req.store.find(Subject)
385
 
        ctx['semesters'] = req.store.find(Semester)
 
436
        ctx['subjects'] = req.store.find(Subject).order_by(Subject.name)
 
437
        ctx['semesters'] = req.store.find(Semester).order_by(
 
438
            Semester.year, Semester.semester)
386
439
 
387
440
    def populate_state(self, state):
388
441
        state.existing_offering = None
401
454
        return new_offering
402
455
 
403
456
 
 
457
class OfferingCloneWorksheetsSchema(formencode.Schema):
 
458
    subject = formencode.All(
 
459
        SubjectValidator(), formencode.validators.UnicodeString())
 
460
    semester = formencode.All(
 
461
        SemesterValidator(), formencode.validators.UnicodeString())
 
462
 
 
463
 
 
464
class OfferingCloneWorksheets(BaseFormView):
 
465
    """A form to clone worksheets from one offering to another."""
 
466
    template = 'templates/offering-clone-worksheets.html'
 
467
    tab = 'subjects'
 
468
 
 
469
    def authorize(self, req):
 
470
        return req.user is not None and req.user.admin
 
471
 
 
472
    @property
 
473
    def validator(self):
 
474
        return OfferingCloneWorksheetsSchema()
 
475
 
 
476
    def populate(self, req, ctx):
 
477
        super(OfferingCloneWorksheets, self).populate(req, ctx)
 
478
        ctx['subjects'] = req.store.find(Subject).order_by(Subject.name)
 
479
        ctx['semesters'] = req.store.find(Semester).order_by(
 
480
            Semester.year, Semester.semester)
 
481
 
 
482
    def get_default_data(self, req):
 
483
        return {}
 
484
 
 
485
    def save_object(self, req, data):
 
486
        if self.context.worksheets.count() > 0:
 
487
            raise BadRequest(
 
488
                "Cannot clone to target with existing worksheets.")
 
489
        offering = req.store.find(
 
490
            Offering, subject=data['subject'], semester=data['semester']).one()
 
491
        if offering is None:
 
492
            raise BadRequest("No such offering.")
 
493
        if offering.worksheets.count() == 0:
 
494
            raise BadRequest("Source offering has no worksheets.")
 
495
 
 
496
        self.context.clone_worksheets(offering)
 
497
        return self.context
 
498
 
 
499
 
404
500
class UserValidator(formencode.FancyValidator):
405
501
    """A FormEncode validator that turns a username into a user.
406
502
 
452
548
    template = 'templates/enrolments.html'
453
549
    tab = 'subjects'
454
550
    permission = 'edit'
 
551
    breadcrumb_text = 'Enrolments'
455
552
 
456
553
    def populate(self, req, ctx):
 
554
        ctx['req'] = req
457
555
        ctx['offering'] = self.context
 
556
        ctx['mediapath'] = media_url(req, CorePlugin, 'images/')
 
557
        ctx['offering_perms'] = self.context.get_permissions(
 
558
            req.user, req.config)
 
559
        ctx['EnrolView'] = EnrolView
 
560
        ctx['EnrolmentEdit'] = EnrolmentEdit
 
561
        ctx['EnrolmentDelete'] = EnrolmentDelete
 
562
 
458
563
 
459
564
class EnrolView(XHTMLView):
460
565
    """A form to enrol a user in an offering."""
486
591
        ctx['roles_auth'] = self.context.get_permissions(req.user, req.config)
487
592
        ctx['errors'] = errors
488
593
 
 
594
 
 
595
class EnrolmentEditSchema(formencode.Schema):
 
596
    role = formencode.All(formencode.validators.OneOf(
 
597
                                ["lecturer", "tutor", "student"]),
 
598
                          RoleEnrolmentValidator(),
 
599
                          formencode.validators.UnicodeString())
 
600
 
 
601
 
 
602
class EnrolmentEdit(BaseFormView):
 
603
    """A form to alter an enrolment's role."""
 
604
    template = 'templates/enrolment-edit.html'
 
605
    tab = 'subjects'
 
606
    permission = 'edit'
 
607
 
 
608
    def populate_state(self, state):
 
609
        state.offering = self.context.offering
 
610
 
 
611
    def get_default_data(self, req):
 
612
        return {'role': self.context.role}
 
613
 
 
614
    @property
 
615
    def validator(self):
 
616
        return EnrolmentEditSchema()
 
617
 
 
618
    def save_object(self, req, data):
 
619
        self.context.role = data['role']
 
620
 
 
621
    def get_return_url(self, obj):
 
622
        return self.req.publisher.generate(
 
623
            self.context.offering, EnrolmentsView)
 
624
 
 
625
    def populate(self, req, ctx):
 
626
        super(EnrolmentEdit, self).populate(req, ctx)
 
627
        ctx['offering_perms'] = self.context.offering.get_permissions(
 
628
            req.user, req.config)
 
629
 
 
630
 
 
631
class EnrolmentDelete(XHTMLView):
 
632
    """A form to alter an enrolment's role."""
 
633
    template = 'templates/enrolment-delete.html'
 
634
    tab = 'subjects'
 
635
    permission = 'edit'
 
636
 
 
637
    def populate(self, req, ctx):
 
638
        # If POSTing, delete delete delete.
 
639
        if req.method == 'POST':
 
640
            self.context.delete()
 
641
            req.store.commit()
 
642
            req.throw_redirect(req.publisher.generate(
 
643
                self.context.offering, EnrolmentsView))
 
644
 
 
645
        ctx['enrolment'] = self.context
 
646
 
 
647
 
489
648
class OfferingProjectsView(XHTMLView):
490
649
    """View the projects for an offering."""
491
650
    template = 'templates/offering_projects.html'
492
651
    permission = 'edit'
493
652
    tab = 'subjects'
 
653
    breadcrumb_text = 'Projects'
494
654
 
495
655
    def populate(self, req, ctx):
496
656
        self.plugin_styles[Plugin] = ["project.css"]
568
728
        ctx['user'] = req.user
569
729
 
570
730
class Plugin(ViewPlugin, MediaPlugin):
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)
 
731
    forward_routes = (root_to_subject, root_to_semester, subject_to_offering,
 
732
                      offering_to_project, offering_to_projectset,
 
733
                      offering_to_enrolment)
 
734
    reverse_routes = (
 
735
        subject_url, semester_url, offering_url, projectset_url, project_url,
 
736
        enrolment_url)
574
737
 
575
738
    views = [(ApplicationRoot, ('subjects', '+index'), SubjectsView),
 
739
             (ApplicationRoot, ('subjects', '+manage'), SubjectsManage),
576
740
             (ApplicationRoot, ('subjects', '+new'), SubjectNew),
577
741
             (ApplicationRoot, ('subjects', '+new-offering'), OfferingNew),
578
 
             (ApplicationRoot, ('subjects', '+new-semester'), SemesterNew),
 
742
             (ApplicationRoot, ('+semesters', '+new'), SemesterNew),
579
743
             (Subject, '+edit', SubjectEdit),
 
744
             (Semester, '+edit', SemesterEdit),
580
745
             (Offering, '+index', OfferingView),
581
746
             (Offering, '+edit', OfferingEdit),
 
747
             (Offering, '+clone-worksheets', OfferingCloneWorksheets),
582
748
             (Offering, ('+enrolments', '+index'), EnrolmentsView),
583
749
             (Offering, ('+enrolments', '+new'), EnrolView),
 
750
             (Enrolment, '+edit', EnrolmentEdit),
 
751
             (Enrolment, '+delete', EnrolmentDelete),
584
752
             (Offering, ('+projects', '+index'), OfferingProjectsView),
585
753
             (Project, '+index', ProjectView),
586
754
 
592
760
                   Offering: OfferingBreadcrumb,
593
761
                   User: UserBreadcrumb,
594
762
                   Project: ProjectBreadcrumb,
 
763
                   Enrolment: EnrolmentBreadcrumb,
595
764
                   }
596
765
 
597
766
    tabs = [