~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 05:33:30 UTC
  • Revision ID: matt.giuca@gmail.com-20100218053330-c3kuixsazmxtg49i
Correct locale setting for Subversion. Previously pysvn would throw a nasty
error on non-ASCII UTF-8 filenames, because its locale was not set to UTF-8.
Now locale.setlocale is called on all Python scripts which use pysvn
(ivle-fetchsubmissions, ivle.fileservice_lib, diffservice, svnlogservice).

bin/ivle-buildjail: Now runs locale-gen as root inside the jail when run with
-u. This is necessary to make en_US.UTF-8 a valid locale; otherwise ALL JAIL
CODE will now crash!

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
41
from ivle.webapp.errors import BadRequest
47
46
from ivle import util
48
47
import ivle.date
49
48
 
 
49
from ivle.webapp.admin.projectservice import ProjectSetRESTView
 
50
from ivle.webapp.admin.offeringservice import OfferingRESTView
50
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
53
            offering_to_enrolment, subject_url, semester_url, offering_url,
53
54
            projectset_url, project_url, enrolment_url)
54
55
from ivle.webapp.admin.breadcrumbs import (SubjectBreadcrumb,
55
56
            OfferingBreadcrumb, UserBreadcrumb, ProjectBreadcrumb,
56
 
            ProjectsBreadcrumb, EnrolmentBreadcrumb)
 
57
            EnrolmentBreadcrumb)
57
58
from ivle.webapp.core import Plugin as CorePlugin
58
59
from ivle.webapp.groups import GroupsView
59
60
from ivle.webapp.media import media_url
63
64
    '''The view of the list of subjects.'''
64
65
    template = 'templates/subjects.html'
65
66
    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
100
99
        ctx['SubjectEdit'] = SubjectEdit
101
100
        ctx['SemesterEdit'] = SemesterEdit
102
101
 
126
125
class SubjectSchema(formencode.Schema):
127
126
    short_name = formencode.All(
128
127
        SubjectShortNameUniquenessValidator(),
129
 
        URLNameValidator(not_empty=True))
 
128
        formencode.validators.UnicodeString(not_empty=True))
130
129
    name = formencode.validators.UnicodeString(not_empty=True)
131
130
    code = formencode.validators.UnicodeString(not_empty=True)
132
131
 
145
144
    def validator(self):
146
145
        return SubjectSchema()
147
146
 
 
147
    def get_return_url(self, obj):
 
148
        return '/subjects'
 
149
 
148
150
 
149
151
class SubjectNew(SubjectFormView):
150
152
    """A form to create a subject."""
200
202
 
201
203
 
202
204
class SemesterSchema(formencode.Schema):
203
 
    year = URLNameValidator()
204
 
    semester = URLNameValidator()
 
205
    year = formencode.validators.UnicodeString()
 
206
    semester = formencode.validators.UnicodeString()
205
207
    state = formencode.All(
206
208
        formencode.validators.OneOf(["past", "current", "future"]),
207
209
        formencode.validators.UnicodeString())
264
266
 
265
267
        return self.context
266
268
 
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
 
 
284
269
 
285
270
class OfferingView(XHTMLView):
286
271
    """The home page of an offering."""
301
286
        ctx['OfferingCloneWorksheets'] = OfferingCloneWorksheets
302
287
        ctx['GroupsView'] = GroupsView
303
288
        ctx['EnrolmentsView'] = EnrolmentsView
304
 
        ctx['Project'] = ivle.database.Project
305
289
 
306
290
        # As we go, calculate the total score for this subject
307
291
        # (Assessable worksheets only, mandatory problems only)
308
292
 
309
293
        ctx['worksheets'], problems_total, problems_done = (
310
294
            ivle.worksheet.utils.create_list_of_fake_worksheets_and_stats(
311
 
                req.config, req.store, req.user, self.context,
312
 
                as_of=self.context.worksheet_cutoff))
 
295
                req.store, req.user, self.context))
313
296
 
314
297
        ctx['exercises_total'] = problems_total
315
298
        ctx['exercises_done'] = problems_done
386
369
    description = formencode.validators.UnicodeString(
387
370
        if_missing=None, not_empty=False)
388
371
    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)
392
372
 
393
373
 
394
374
class OfferingAdminSchema(OfferingSchema):
417
397
        ctx['subjects'] = req.store.find(Subject).order_by(Subject.name)
418
398
        ctx['semesters'] = req.store.find(Semester).order_by(
419
399
            Semester.year, Semester.semester)
420
 
        ctx['force_subject'] = None
421
400
 
422
401
    def populate_state(self, state):
423
402
        state.existing_offering = self.context
429
408
                        self.context.semester.semester,
430
409
            'url': self.context.url,
431
410
            'description': self.context.description,
432
 
            'worksheet_cutoff': self.context.worksheet_cutoff,
433
 
            'show_worksheet_marks': self.context.show_worksheet_marks,
434
411
            }
435
412
 
436
413
    def save_object(self, req, data):
439
416
            self.context.semester = data['semester']
440
417
        self.context.description = data['description']
441
418
        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']
444
419
        return self.context
445
420
 
446
421
 
461
436
        ctx['subjects'] = req.store.find(Subject).order_by(Subject.name)
462
437
        ctx['semesters'] = req.store.find(Semester).order_by(
463
438
            Semester.year, Semester.semester)
464
 
        ctx['force_subject'] = None
465
439
 
466
440
    def populate_state(self, state):
467
441
        state.existing_offering = None
475
449
        new_offering.semester = data['semester']
476
450
        new_offering.description = data['description']
477
451
        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']
480
452
 
481
453
        req.store.add(new_offering)
482
454
        return new_offering
483
455
 
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
491
456
 
492
457
class OfferingCloneWorksheetsSchema(formencode.Schema):
493
458
    subject = formencode.All(
625
590
        ctx['offering'] = self.context
626
591
        ctx['roles_auth'] = self.context.get_permissions(req.user, req.config)
627
592
        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
631
593
 
632
594
 
633
595
class EnrolmentEditSchema(formencode.Schema):
692
654
 
693
655
    def populate(self, req, ctx):
694
656
        self.plugin_styles[Plugin] = ["project.css"]
 
657
        self.plugin_scripts[Plugin] = ["project.js"]
695
658
        ctx['req'] = req
696
659
        ctx['offering'] = self.context
697
660
        ctx['projectsets'] = []
 
661
        ctx['OfferingRESTView'] = OfferingRESTView
698
662
 
699
663
        #Open the projectset Fragment, and render it for inclusion
700
664
        #into the ProjectSets page
 
665
        #XXX: This could be a lot cleaner
 
666
        loader = genshi.template.TemplateLoader(".", auto_reload=True)
 
667
 
701
668
        set_fragment = os.path.join(os.path.dirname(__file__),
702
669
                "templates/projectset_fragment.html")
703
670
        project_fragment = os.path.join(os.path.dirname(__file__),
704
671
                "templates/project_fragment.html")
705
672
 
706
 
        for projectset in \
707
 
            self.context.project_sets.order_by(ivle.database.ProjectSet.id):
708
 
            settmpl = self._loader.load(set_fragment)
 
673
        for projectset in self.context.project_sets:
 
674
            settmpl = loader.load(set_fragment)
709
675
            setCtx = Context()
710
676
            setCtx['req'] = req
711
677
            setCtx['projectset'] = projectset
712
678
            setCtx['projects'] = []
713
679
            setCtx['GroupsView'] = GroupsView
714
 
            setCtx['ProjectSetEdit'] = ProjectSetEdit
715
 
            setCtx['ProjectNew'] = ProjectNew
 
680
            setCtx['ProjectSetRESTView'] = ProjectSetRESTView
716
681
 
717
 
            for project in \
718
 
                projectset.projects.order_by(ivle.database.Project.deadline):
719
 
                projecttmpl = self._loader.load(project_fragment)
 
682
            for project in projectset.projects:
 
683
                projecttmpl = loader.load(project_fragment)
720
684
                projectCtx = Context()
721
685
                projectCtx['req'] = req
722
686
                projectCtx['project'] = project
723
 
                projectCtx['ProjectEdit'] = ProjectEdit
724
 
                projectCtx['ProjectDelete'] = ProjectDelete
725
687
 
726
688
                setCtx['projects'].append(
727
689
                        projecttmpl.generate(projectCtx))
757
719
        self.plugin_styles[Plugin] = ["project.css"]
758
720
 
759
721
        ctx['req'] = req
760
 
        ctx['permissions'] = self.context.get_permissions(req.user,req.config)
761
722
        ctx['GroupsView'] = GroupsView
762
723
        ctx['EnrolView'] = EnrolView
763
 
        ctx['format_datetime'] = ivle.date.make_date_nice
764
724
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
765
725
        ctx['build_subversion_url'] = self.build_subversion_url
766
726
        ctx['svn_addr'] = req.config['urls']['svn_addr']
767
727
        ctx['project'] = self.context
768
728
        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
939
729
 
940
730
class Plugin(ViewPlugin, MediaPlugin):
941
731
    forward_routes = (root_to_subject, root_to_semester, subject_to_offering,
950
740
             (ApplicationRoot, ('subjects', '+new'), SubjectNew),
951
741
             (ApplicationRoot, ('subjects', '+new-offering'), OfferingNew),
952
742
             (ApplicationRoot, ('+semesters', '+new'), SemesterNew),
953
 
             (Subject, '+index', SubjectView),
954
743
             (Subject, '+edit', SubjectEdit),
955
 
             (Subject, '+new-offering', SubjectOfferingNew),
956
744
             (Semester, '+edit', SemesterEdit),
957
745
             (Offering, '+index', OfferingView),
958
746
             (Offering, '+edit', OfferingEdit),
962
750
             (Enrolment, '+edit', EnrolmentEdit),
963
751
             (Enrolment, '+delete', EnrolmentDelete),
964
752
             (Offering, ('+projects', '+index'), OfferingProjectsView),
965
 
             (Offering, ('+projects', '+new-set'), ProjectSetNew),
966
 
             (ProjectSet, '+edit', ProjectSetEdit),
967
 
             (ProjectSet, '+new', ProjectNew),
968
753
             (Project, '+index', ProjectView),
969
 
             (Project, '+edit', ProjectEdit),
970
 
             (Project, '+delete', ProjectDelete),
 
754
 
 
755
             (Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
 
756
             (ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
971
757
             ]
972
758
 
973
759
    breadcrumbs = {Subject: SubjectBreadcrumb,