~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-26 06:33:35 UTC
  • Revision ID: grantw@unimelb.edu.au-20100226063335-25ka31up8cegwaiz
ivle.interpret.execute_raw now sets a clean environment, in particular with HOME set correctly.

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, TemplateLoader
 
34
from genshi.template import Context
35
35
import formencode
36
36
import formencode.validators
37
37
 
38
 
from ivle.webapp.base.forms import BaseFormView
 
38
from ivle.webapp.base.forms import (BaseFormView, URLNameValidator,
 
39
                                    DateTimeValidator)
39
40
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
40
41
from ivle.webapp.base.xhtml import XHTMLView
41
42
from ivle.webapp.errors import BadRequest
46
47
from ivle import util
47
48
import ivle.date
48
49
 
49
 
from ivle.webapp.admin.projectservice import ProjectSetRESTView
50
 
from ivle.webapp.admin.offeringservice import OfferingRESTView
51
50
from ivle.webapp.admin.publishing import (root_to_subject, root_to_semester,
52
51
            subject_to_offering, offering_to_projectset, offering_to_project,
53
52
            offering_to_enrolment, subject_url, semester_url, offering_url,
54
53
            projectset_url, project_url, enrolment_url)
55
54
from ivle.webapp.admin.breadcrumbs import (SubjectBreadcrumb,
56
55
            OfferingBreadcrumb, UserBreadcrumb, ProjectBreadcrumb,
57
 
            EnrolmentBreadcrumb)
 
56
            ProjectsBreadcrumb, EnrolmentBreadcrumb)
58
57
from ivle.webapp.core import Plugin as CorePlugin
59
58
from ivle.webapp.groups import GroupsView
60
59
from ivle.webapp.media import media_url
64
63
    '''The view of the list of subjects.'''
65
64
    template = 'templates/subjects.html'
66
65
    tab = 'subjects'
 
66
    breadcrumb_text = "Subjects"
67
67
 
68
68
    def authorize(self, req):
69
69
        return req.user is not None
96
96
    def populate(self, req, ctx):
97
97
        ctx['req'] = req
98
98
        ctx['mediapath'] = media_url(req, CorePlugin, 'images/')
 
99
        ctx['SubjectView'] = SubjectView
99
100
        ctx['SubjectEdit'] = SubjectEdit
100
101
        ctx['SemesterEdit'] = SemesterEdit
101
102
 
125
126
class SubjectSchema(formencode.Schema):
126
127
    short_name = formencode.All(
127
128
        SubjectShortNameUniquenessValidator(),
128
 
        formencode.validators.UnicodeString(not_empty=True))
 
129
        URLNameValidator(not_empty=True))
129
130
    name = formencode.validators.UnicodeString(not_empty=True)
130
131
    code = formencode.validators.UnicodeString(not_empty=True)
131
132
 
144
145
    def validator(self):
145
146
        return SubjectSchema()
146
147
 
147
 
    def get_return_url(self, obj):
148
 
        return '/subjects'
149
 
 
150
148
 
151
149
class SubjectNew(SubjectFormView):
152
150
    """A form to create a subject."""
202
200
 
203
201
 
204
202
class SemesterSchema(formencode.Schema):
205
 
    year = formencode.validators.UnicodeString()
206
 
    semester = formencode.validators.UnicodeString()
 
203
    year = URLNameValidator()
 
204
    semester = URLNameValidator()
207
205
    state = formencode.All(
208
206
        formencode.validators.OneOf(["past", "current", "future"]),
209
207
        formencode.validators.UnicodeString())
266
264
 
267
265
        return self.context
268
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
 
283
 
269
284
 
270
285
class OfferingView(XHTMLView):
271
286
    """The home page of an offering."""
286
301
        ctx['OfferingCloneWorksheets'] = OfferingCloneWorksheets
287
302
        ctx['GroupsView'] = GroupsView
288
303
        ctx['EnrolmentsView'] = EnrolmentsView
 
304
        ctx['Project'] = ivle.database.Project
289
305
 
290
306
        # As we go, calculate the total score for this subject
291
307
        # (Assessable worksheets only, mandatory problems only)
292
308
 
293
309
        ctx['worksheets'], problems_total, problems_done = (
294
310
            ivle.worksheet.utils.create_list_of_fake_worksheets_and_stats(
295
 
                req.store, req.user, self.context))
 
311
                req.config, req.store, req.user, self.context,
 
312
                as_of=self.context.worksheet_cutoff))
296
313
 
297
314
        ctx['exercises_total'] = problems_total
298
315
        ctx['exercises_done'] = problems_done
369
386
    description = formencode.validators.UnicodeString(
370
387
        if_missing=None, not_empty=False)
371
388
    url = formencode.validators.URL(if_missing=None, not_empty=False)
 
389
    worksheet_cutoff = DateTimeValidator(if_missing=None, not_empty=False)
 
390
    show_worksheet_marks = formencode.validators.StringBoolean(
 
391
        if_missing=False)
372
392
 
373
393
 
374
394
class OfferingAdminSchema(OfferingSchema):
397
417
        ctx['subjects'] = req.store.find(Subject).order_by(Subject.name)
398
418
        ctx['semesters'] = req.store.find(Semester).order_by(
399
419
            Semester.year, Semester.semester)
 
420
        ctx['force_subject'] = None
400
421
 
401
422
    def populate_state(self, state):
402
423
        state.existing_offering = self.context
408
429
                        self.context.semester.semester,
409
430
            'url': self.context.url,
410
431
            'description': self.context.description,
 
432
            'worksheet_cutoff': self.context.worksheet_cutoff,
 
433
            'show_worksheet_marks': self.context.show_worksheet_marks,
411
434
            }
412
435
 
413
436
    def save_object(self, req, data):
416
439
            self.context.semester = data['semester']
417
440
        self.context.description = data['description']
418
441
        self.context.url = unicode(data['url']) if data['url'] else None
 
442
        self.context.worksheet_cutoff = data['worksheet_cutoff']
 
443
        self.context.show_worksheet_marks = data['show_worksheet_marks']
419
444
        return self.context
420
445
 
421
446
 
436
461
        ctx['subjects'] = req.store.find(Subject).order_by(Subject.name)
437
462
        ctx['semesters'] = req.store.find(Semester).order_by(
438
463
            Semester.year, Semester.semester)
 
464
        ctx['force_subject'] = None
439
465
 
440
466
    def populate_state(self, state):
441
467
        state.existing_offering = None
449
475
        new_offering.semester = data['semester']
450
476
        new_offering.description = data['description']
451
477
        new_offering.url = unicode(data['url']) if data['url'] else None
 
478
        new_offering.worksheet_cutoff = data['worksheet_cutoff']
 
479
        new_offering.show_worksheet_marks = data['show_worksheet_marks']
452
480
 
453
481
        req.store.add(new_offering)
454
482
        return new_offering
455
483
 
 
484
class SubjectOfferingNew(OfferingNew):
 
485
    """A form to create an offering for a given subject."""
 
486
    # Identical to OfferingNew, except it forces the subject to be the subject
 
487
    # in context
 
488
    def populate(self, req, ctx):
 
489
        super(SubjectOfferingNew, self).populate(req, ctx)
 
490
        ctx['force_subject'] = self.context
456
491
 
457
492
class OfferingCloneWorksheetsSchema(formencode.Schema):
458
493
    subject = formencode.All(
590
625
        ctx['offering'] = self.context
591
626
        ctx['roles_auth'] = self.context.get_permissions(req.user, req.config)
592
627
        ctx['errors'] = errors
 
628
        # If all of the fields validated, set the global form error.
 
629
        if isinstance(errors, basestring):
 
630
            ctx['error_value'] = errors
593
631
 
594
632
 
595
633
class EnrolmentEditSchema(formencode.Schema):
654
692
 
655
693
    def populate(self, req, ctx):
656
694
        self.plugin_styles[Plugin] = ["project.css"]
657
 
        self.plugin_scripts[Plugin] = ["project.js"]
658
695
        ctx['req'] = req
659
696
        ctx['offering'] = self.context
660
697
        ctx['projectsets'] = []
661
 
        ctx['OfferingRESTView'] = OfferingRESTView
662
698
 
663
699
        #Open the projectset Fragment, and render it for inclusion
664
700
        #into the ProjectSets page
665
 
        #XXX: This could be a lot cleaner
666
 
        loader = genshi.template.TemplateLoader(".", auto_reload=True)
667
 
 
668
701
        set_fragment = os.path.join(os.path.dirname(__file__),
669
702
                "templates/projectset_fragment.html")
670
703
        project_fragment = os.path.join(os.path.dirname(__file__),
671
704
                "templates/project_fragment.html")
672
705
 
673
 
        for projectset in self.context.project_sets:
674
 
            settmpl = loader.load(set_fragment)
 
706
        for projectset in \
 
707
            self.context.project_sets.order_by(ivle.database.ProjectSet.id):
 
708
            settmpl = self._loader.load(set_fragment)
675
709
            setCtx = Context()
676
710
            setCtx['req'] = req
677
711
            setCtx['projectset'] = projectset
678
712
            setCtx['projects'] = []
679
713
            setCtx['GroupsView'] = GroupsView
680
 
            setCtx['ProjectSetRESTView'] = ProjectSetRESTView
 
714
            setCtx['ProjectSetEdit'] = ProjectSetEdit
 
715
            setCtx['ProjectNew'] = ProjectNew
681
716
 
682
 
            for project in projectset.projects:
683
 
                projecttmpl = loader.load(project_fragment)
 
717
            for project in \
 
718
                projectset.projects.order_by(ivle.database.Project.deadline):
 
719
                projecttmpl = self._loader.load(project_fragment)
684
720
                projectCtx = Context()
685
721
                projectCtx['req'] = req
686
722
                projectCtx['project'] = project
 
723
                projectCtx['ProjectEdit'] = ProjectEdit
 
724
                projectCtx['ProjectDelete'] = ProjectDelete
687
725
 
688
726
                setCtx['projects'].append(
689
727
                        projecttmpl.generate(projectCtx))
719
757
        self.plugin_styles[Plugin] = ["project.css"]
720
758
 
721
759
        ctx['req'] = req
 
760
        ctx['permissions'] = self.context.get_permissions(req.user,req.config)
722
761
        ctx['GroupsView'] = GroupsView
723
762
        ctx['EnrolView'] = EnrolView
 
763
        ctx['format_datetime'] = ivle.date.make_date_nice
724
764
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
725
765
        ctx['build_subversion_url'] = self.build_subversion_url
726
766
        ctx['svn_addr'] = req.config['urls']['svn_addr']
727
767
        ctx['project'] = self.context
728
768
        ctx['user'] = req.user
 
769
        ctx['ProjectEdit'] = ProjectEdit
 
770
        ctx['ProjectDelete'] = ProjectDelete
 
771
 
 
772
class ProjectUniquenessValidator(formencode.FancyValidator):
 
773
    """A FormEncode validator that checks that a project short_name is unique
 
774
    in a given offering.
 
775
 
 
776
    The project referenced by state.existing_project is permitted to
 
777
    hold that short_name. If any other project holds it, the input is rejected.
 
778
    """
 
779
    def _to_python(self, value, state):
 
780
        if (state.store.find(
 
781
            Project,
 
782
            Project.short_name == unicode(value),
 
783
            Project.project_set_id == ProjectSet.id,
 
784
            ProjectSet.offering == state.offering).one() not in
 
785
            (None, state.existing_project)):
 
786
            raise formencode.Invalid(
 
787
                "A project with that URL name already exists in this offering."
 
788
                , value, state)
 
789
        return value
 
790
 
 
791
class ProjectSchema(formencode.Schema):
 
792
    name = formencode.validators.UnicodeString(not_empty=True)
 
793
    short_name = formencode.All(
 
794
        URLNameValidator(not_empty=True),
 
795
        ProjectUniquenessValidator())
 
796
    deadline = DateTimeValidator(not_empty=True)
 
797
    url = formencode.validators.URL(if_missing=None, not_empty=False)
 
798
    synopsis = formencode.validators.UnicodeString(not_empty=True)
 
799
 
 
800
class ProjectEdit(BaseFormView):
 
801
    """A form to edit a project."""
 
802
    template = 'templates/project-edit.html'
 
803
    tab = 'subjects'
 
804
    permission = 'edit'
 
805
 
 
806
    @property
 
807
    def validator(self):
 
808
        return ProjectSchema()
 
809
 
 
810
    def populate(self, req, ctx):
 
811
        super(ProjectEdit, self).populate(req, ctx)
 
812
        ctx['projectset'] = self.context.project_set
 
813
 
 
814
    def populate_state(self, state):
 
815
        state.offering = self.context.project_set.offering
 
816
        state.existing_project = self.context
 
817
 
 
818
    def get_default_data(self, req):
 
819
        return {
 
820
            'name':         self.context.name,
 
821
            'short_name':   self.context.short_name,
 
822
            'deadline':     self.context.deadline,
 
823
            'url':          self.context.url,
 
824
            'synopsis':     self.context.synopsis,
 
825
            }
 
826
 
 
827
    def save_object(self, req, data):
 
828
        self.context.name = data['name']
 
829
        self.context.short_name = data['short_name']
 
830
        self.context.deadline = data['deadline']
 
831
        self.context.url = unicode(data['url']) if data['url'] else None
 
832
        self.context.synopsis = data['synopsis']
 
833
        return self.context
 
834
 
 
835
class ProjectNew(BaseFormView):
 
836
    """A form to create a new project."""
 
837
    template = 'templates/project-new.html'
 
838
    tab = 'subjects'
 
839
    permission = 'edit'
 
840
 
 
841
    @property
 
842
    def validator(self):
 
843
        return ProjectSchema()
 
844
 
 
845
    def populate(self, req, ctx):
 
846
        super(ProjectNew, self).populate(req, ctx)
 
847
        ctx['projectset'] = self.context
 
848
 
 
849
    def populate_state(self, state):
 
850
        state.offering = self.context.offering
 
851
        state.existing_project = None
 
852
 
 
853
    def get_default_data(self, req):
 
854
        return {}
 
855
 
 
856
    def save_object(self, req, data):
 
857
        new_project = Project()
 
858
        new_project.project_set = self.context
 
859
        new_project.name = data['name']
 
860
        new_project.short_name = data['short_name']
 
861
        new_project.deadline = data['deadline']
 
862
        new_project.url = unicode(data['url']) if data['url'] else None
 
863
        new_project.synopsis = data['synopsis']
 
864
        req.store.add(new_project)
 
865
        return new_project
 
866
 
 
867
class ProjectDelete(XHTMLView):
 
868
    """A form to delete a project."""
 
869
    template = 'templates/project-delete.html'
 
870
    tab = 'subjects'
 
871
    permission = 'edit'
 
872
 
 
873
    def populate(self, req, ctx):
 
874
        # If post, delete the project, or display a message explaining that
 
875
        # the project cannot be deleted
 
876
        if self.context.can_delete:
 
877
            if req.method == 'POST':
 
878
                self.context.delete()
 
879
                self.template = 'templates/project-deleted.html'
 
880
        else:
 
881
            # Can't delete
 
882
            self.template = 'templates/project-undeletable.html'
 
883
 
 
884
        # If get and can delete, display a delete confirmation page
 
885
 
 
886
        # Variables for the template
 
887
        ctx['req'] = req
 
888
        ctx['project'] = self.context
 
889
        ctx['OfferingProjectsView'] = OfferingProjectsView
 
890
 
 
891
class ProjectSetSchema(formencode.Schema):
 
892
    group_size = formencode.validators.Int(if_missing=None, not_empty=False)
 
893
 
 
894
class ProjectSetEdit(BaseFormView):
 
895
    """A form to edit a project set."""
 
896
    template = 'templates/projectset-edit.html'
 
897
    tab = 'subjects'
 
898
    permission = 'edit'
 
899
 
 
900
    @property
 
901
    def validator(self):
 
902
        return ProjectSetSchema()
 
903
 
 
904
    def populate(self, req, ctx):
 
905
        super(ProjectSetEdit, self).populate(req, ctx)
 
906
 
 
907
    def get_default_data(self, req):
 
908
        return {
 
909
            'group_size': self.context.max_students_per_group,
 
910
            }
 
911
 
 
912
    def save_object(self, req, data):
 
913
        self.context.max_students_per_group = data['group_size']
 
914
        return self.context
 
915
 
 
916
class ProjectSetNew(BaseFormView):
 
917
    """A form to create a new project set."""
 
918
    template = 'templates/projectset-new.html'
 
919
    tab = 'subjects'
 
920
    permission = 'edit'
 
921
    breadcrumb_text = "Projects"
 
922
 
 
923
    @property
 
924
    def validator(self):
 
925
        return ProjectSetSchema()
 
926
 
 
927
    def populate(self, req, ctx):
 
928
        super(ProjectSetNew, self).populate(req, ctx)
 
929
 
 
930
    def get_default_data(self, req):
 
931
        return {}
 
932
 
 
933
    def save_object(self, req, data):
 
934
        new_set = ProjectSet()
 
935
        new_set.offering = self.context
 
936
        new_set.max_students_per_group = data['group_size']
 
937
        req.store.add(new_set)
 
938
        return new_set
729
939
 
730
940
class Plugin(ViewPlugin, MediaPlugin):
731
941
    forward_routes = (root_to_subject, root_to_semester, subject_to_offering,
740
950
             (ApplicationRoot, ('subjects', '+new'), SubjectNew),
741
951
             (ApplicationRoot, ('subjects', '+new-offering'), OfferingNew),
742
952
             (ApplicationRoot, ('+semesters', '+new'), SemesterNew),
 
953
             (Subject, '+index', SubjectView),
743
954
             (Subject, '+edit', SubjectEdit),
 
955
             (Subject, '+new-offering', SubjectOfferingNew),
744
956
             (Semester, '+edit', SemesterEdit),
745
957
             (Offering, '+index', OfferingView),
746
958
             (Offering, '+edit', OfferingEdit),
750
962
             (Enrolment, '+edit', EnrolmentEdit),
751
963
             (Enrolment, '+delete', EnrolmentDelete),
752
964
             (Offering, ('+projects', '+index'), OfferingProjectsView),
 
965
             (Offering, ('+projects', '+new-set'), ProjectSetNew),
 
966
             (ProjectSet, '+edit', ProjectSetEdit),
 
967
             (ProjectSet, '+new', ProjectNew),
753
968
             (Project, '+index', ProjectView),
754
 
 
755
 
             (Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
756
 
             (ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
 
969
             (Project, '+edit', ProjectEdit),
 
970
             (Project, '+delete', ProjectDelete),
757
971
             ]
758
972
 
759
973
    breadcrumbs = {Subject: SubjectBreadcrumb,