~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 00:19:48 UTC
  • Revision ID: matt.giuca@gmail.com-20100218001948-8079xhwqvrf2ul15
ivle.webapp.tutorial: Fixed unqualified reference to exception ExerciseNotFound.

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))
735
697
    permission = "view_project_submissions"
736
698
    tab = 'subjects'
737
699
 
 
700
    def build_subversion_url(self, svnroot, submission):
 
701
        princ = submission.assessed.principal
 
702
 
 
703
        if isinstance(princ, User):
 
704
            path = 'users/%s' % princ.login
 
705
        else:
 
706
            path = 'groups/%s_%s_%s_%s' % (
 
707
                    princ.project_set.offering.subject.short_name,
 
708
                    princ.project_set.offering.semester.year,
 
709
                    princ.project_set.offering.semester.semester,
 
710
                    princ.name
 
711
                    )
 
712
        return urlparse.urljoin(
 
713
                    svnroot,
 
714
                    os.path.join(path, submission.path[1:] if
 
715
                                       submission.path.startswith(os.sep) else
 
716
                                       submission.path))
 
717
 
738
718
    def populate(self, req, ctx):
739
719
        self.plugin_styles[Plugin] = ["project.css"]
740
720
 
741
721
        ctx['req'] = req
742
 
        ctx['permissions'] = self.context.get_permissions(req.user,req.config)
743
722
        ctx['GroupsView'] = GroupsView
744
723
        ctx['EnrolView'] = EnrolView
745
 
        ctx['format_datetime'] = ivle.date.make_date_nice
746
724
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
 
725
        ctx['build_subversion_url'] = self.build_subversion_url
 
726
        ctx['svn_addr'] = req.config['urls']['svn_addr']
747
727
        ctx['project'] = self.context
748
728
        ctx['user'] = req.user
749
 
        ctx['ProjectEdit'] = ProjectEdit
750
 
        ctx['ProjectDelete'] = ProjectDelete
751
 
 
752
 
class ProjectUniquenessValidator(formencode.FancyValidator):
753
 
    """A FormEncode validator that checks that a project short_name is unique
754
 
    in a given offering.
755
 
 
756
 
    The project referenced by state.existing_project is permitted to
757
 
    hold that short_name. If any other project holds it, the input is rejected.
758
 
    """
759
 
    def _to_python(self, value, state):
760
 
        if (state.store.find(
761
 
            Project,
762
 
            Project.short_name == unicode(value),
763
 
            Project.project_set_id == ProjectSet.id,
764
 
            ProjectSet.offering == state.offering).one() not in
765
 
            (None, state.existing_project)):
766
 
            raise formencode.Invalid(
767
 
                "A project with that URL name already exists in this offering."
768
 
                , value, state)
769
 
        return value
770
 
 
771
 
class ProjectSchema(formencode.Schema):
772
 
    name = formencode.validators.UnicodeString(not_empty=True)
773
 
    short_name = formencode.All(
774
 
        URLNameValidator(not_empty=True),
775
 
        ProjectUniquenessValidator())
776
 
    deadline = DateTimeValidator(not_empty=True)
777
 
    url = formencode.validators.URL(if_missing=None, not_empty=False)
778
 
    synopsis = formencode.validators.UnicodeString(not_empty=True)
779
 
 
780
 
class ProjectEdit(BaseFormView):
781
 
    """A form to edit a project."""
782
 
    template = 'templates/project-edit.html'
783
 
    tab = 'subjects'
784
 
    permission = 'edit'
785
 
 
786
 
    @property
787
 
    def validator(self):
788
 
        return ProjectSchema()
789
 
 
790
 
    def populate(self, req, ctx):
791
 
        super(ProjectEdit, self).populate(req, ctx)
792
 
        ctx['projectset'] = self.context.project_set
793
 
 
794
 
    def populate_state(self, state):
795
 
        state.offering = self.context.project_set.offering
796
 
        state.existing_project = self.context
797
 
 
798
 
    def get_default_data(self, req):
799
 
        return {
800
 
            'name':         self.context.name,
801
 
            'short_name':   self.context.short_name,
802
 
            'deadline':     self.context.deadline,
803
 
            'url':          self.context.url,
804
 
            'synopsis':     self.context.synopsis,
805
 
            }
806
 
 
807
 
    def save_object(self, req, data):
808
 
        self.context.name = data['name']
809
 
        self.context.short_name = data['short_name']
810
 
        self.context.deadline = data['deadline']
811
 
        self.context.url = unicode(data['url']) if data['url'] else None
812
 
        self.context.synopsis = data['synopsis']
813
 
        return self.context
814
 
 
815
 
class ProjectNew(BaseFormView):
816
 
    """A form to create a new project."""
817
 
    template = 'templates/project-new.html'
818
 
    tab = 'subjects'
819
 
    permission = 'edit'
820
 
 
821
 
    @property
822
 
    def validator(self):
823
 
        return ProjectSchema()
824
 
 
825
 
    def populate(self, req, ctx):
826
 
        super(ProjectNew, self).populate(req, ctx)
827
 
        ctx['projectset'] = self.context
828
 
 
829
 
    def populate_state(self, state):
830
 
        state.offering = self.context.offering
831
 
        state.existing_project = None
832
 
 
833
 
    def get_default_data(self, req):
834
 
        return {}
835
 
 
836
 
    def save_object(self, req, data):
837
 
        new_project = Project()
838
 
        new_project.project_set = self.context
839
 
        new_project.name = data['name']
840
 
        new_project.short_name = data['short_name']
841
 
        new_project.deadline = data['deadline']
842
 
        new_project.url = unicode(data['url']) if data['url'] else None
843
 
        new_project.synopsis = data['synopsis']
844
 
        req.store.add(new_project)
845
 
        return new_project
846
 
 
847
 
class ProjectDelete(XHTMLView):
848
 
    """A form to delete a project."""
849
 
    template = 'templates/project-delete.html'
850
 
    tab = 'subjects'
851
 
    permission = 'edit'
852
 
 
853
 
    def populate(self, req, ctx):
854
 
        # If post, delete the project, or display a message explaining that
855
 
        # the project cannot be deleted
856
 
        if self.context.can_delete:
857
 
            if req.method == 'POST':
858
 
                self.context.delete()
859
 
                self.template = 'templates/project-deleted.html'
860
 
        else:
861
 
            # Can't delete
862
 
            self.template = 'templates/project-undeletable.html'
863
 
 
864
 
        # If get and can delete, display a delete confirmation page
865
 
 
866
 
        # Variables for the template
867
 
        ctx['req'] = req
868
 
        ctx['project'] = self.context
869
 
        ctx['OfferingProjectsView'] = OfferingProjectsView
870
 
 
871
 
class ProjectSetSchema(formencode.Schema):
872
 
    group_size = formencode.validators.Int(if_missing=None, not_empty=False)
873
 
 
874
 
class ProjectSetEdit(BaseFormView):
875
 
    """A form to edit a project set."""
876
 
    template = 'templates/projectset-edit.html'
877
 
    tab = 'subjects'
878
 
    permission = 'edit'
879
 
 
880
 
    @property
881
 
    def validator(self):
882
 
        return ProjectSetSchema()
883
 
 
884
 
    def populate(self, req, ctx):
885
 
        super(ProjectSetEdit, self).populate(req, ctx)
886
 
 
887
 
    def get_default_data(self, req):
888
 
        return {
889
 
            'group_size': self.context.max_students_per_group,
890
 
            }
891
 
 
892
 
    def save_object(self, req, data):
893
 
        self.context.max_students_per_group = data['group_size']
894
 
        return self.context
895
 
 
896
 
class ProjectSetNew(BaseFormView):
897
 
    """A form to create a new project set."""
898
 
    template = 'templates/projectset-new.html'
899
 
    tab = 'subjects'
900
 
    permission = 'edit'
901
 
    breadcrumb_text = "Projects"
902
 
 
903
 
    @property
904
 
    def validator(self):
905
 
        return ProjectSetSchema()
906
 
 
907
 
    def populate(self, req, ctx):
908
 
        super(ProjectSetNew, self).populate(req, ctx)
909
 
 
910
 
    def get_default_data(self, req):
911
 
        return {}
912
 
 
913
 
    def save_object(self, req, data):
914
 
        new_set = ProjectSet()
915
 
        new_set.offering = self.context
916
 
        new_set.max_students_per_group = data['group_size']
917
 
        req.store.add(new_set)
918
 
        return new_set
919
729
 
920
730
class Plugin(ViewPlugin, MediaPlugin):
921
731
    forward_routes = (root_to_subject, root_to_semester, subject_to_offering,
930
740
             (ApplicationRoot, ('subjects', '+new'), SubjectNew),
931
741
             (ApplicationRoot, ('subjects', '+new-offering'), OfferingNew),
932
742
             (ApplicationRoot, ('+semesters', '+new'), SemesterNew),
933
 
             (Subject, '+index', SubjectView),
934
743
             (Subject, '+edit', SubjectEdit),
935
 
             (Subject, '+new-offering', SubjectOfferingNew),
936
744
             (Semester, '+edit', SemesterEdit),
937
745
             (Offering, '+index', OfferingView),
938
746
             (Offering, '+edit', OfferingEdit),
942
750
             (Enrolment, '+edit', EnrolmentEdit),
943
751
             (Enrolment, '+delete', EnrolmentDelete),
944
752
             (Offering, ('+projects', '+index'), OfferingProjectsView),
945
 
             (Offering, ('+projects', '+new-set'), ProjectSetNew),
946
 
             (ProjectSet, '+edit', ProjectSetEdit),
947
 
             (ProjectSet, '+new', ProjectNew),
948
753
             (Project, '+index', ProjectView),
949
 
             (Project, '+edit', ProjectEdit),
950
 
             (Project, '+delete', ProjectDelete),
 
754
 
 
755
             (Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
 
756
             (ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
951
757
             ]
952
758
 
953
759
    breadcrumbs = {Subject: SubjectBreadcrumb,