~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-24 13:13:21 UTC
  • mfrom: (1689.1.19 worksheet-marks)
  • Revision ID: matt.giuca@gmail.com-20100224131321-eno7xbrqsyukz869
Added worksheet marks reporting UI for lecturers. This includes the ability to view worksheet marks table in the web application and download as a CSV, as well as some basic statistics on exercise completion. Removed the ivle-marks script, as now lecturers can get the same data themselves. Fixes Launchpad bug #520179.

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, URLNameValidator
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
301
302
        ctx['OfferingCloneWorksheets'] = OfferingCloneWorksheets
302
303
        ctx['GroupsView'] = GroupsView
303
304
        ctx['EnrolmentsView'] = EnrolmentsView
304
 
        ctx['Project'] = ivle.database.Project
305
305
 
306
306
        # As we go, calculate the total score for this subject
307
307
        # (Assessable worksheets only, mandatory problems only)
308
308
 
309
309
        ctx['worksheets'], problems_total, problems_done = (
310
310
            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))
 
311
                req.config, req.store, req.user, self.context))
313
312
 
314
313
        ctx['exercises_total'] = problems_total
315
314
        ctx['exercises_done'] = problems_done
386
385
    description = formencode.validators.UnicodeString(
387
386
        if_missing=None, not_empty=False)
388
387
    url = formencode.validators.URL(if_missing=None, not_empty=False)
389
 
    worksheet_cutoff = DateTimeValidator(if_missing=None, not_empty=False)
390
388
    show_worksheet_marks = formencode.validators.StringBoolean(
391
389
        if_missing=False)
392
390
 
429
427
                        self.context.semester.semester,
430
428
            'url': self.context.url,
431
429
            'description': self.context.description,
432
 
            'worksheet_cutoff': self.context.worksheet_cutoff,
433
430
            'show_worksheet_marks': self.context.show_worksheet_marks,
434
431
            }
435
432
 
439
436
            self.context.semester = data['semester']
440
437
        self.context.description = data['description']
441
438
        self.context.url = unicode(data['url']) if data['url'] else None
442
 
        self.context.worksheet_cutoff = data['worksheet_cutoff']
443
439
        self.context.show_worksheet_marks = data['show_worksheet_marks']
444
440
        return self.context
445
441
 
475
471
        new_offering.semester = data['semester']
476
472
        new_offering.description = data['description']
477
473
        new_offering.url = unicode(data['url']) if data['url'] else None
478
 
        new_offering.worksheet_cutoff = data['worksheet_cutoff']
479
474
        new_offering.show_worksheet_marks = data['show_worksheet_marks']
480
475
 
481
476
        req.store.add(new_offering)
692
687
 
693
688
    def populate(self, req, ctx):
694
689
        self.plugin_styles[Plugin] = ["project.css"]
 
690
        self.plugin_scripts[Plugin] = ["project.js"]
695
691
        ctx['req'] = req
696
692
        ctx['offering'] = self.context
697
693
        ctx['projectsets'] = []
 
694
        ctx['OfferingRESTView'] = OfferingRESTView
698
695
 
699
696
        #Open the projectset Fragment, and render it for inclusion
700
697
        #into the ProjectSets page
 
698
        #XXX: This could be a lot cleaner
 
699
        loader = genshi.template.TemplateLoader(".", auto_reload=True)
 
700
 
701
701
        set_fragment = os.path.join(os.path.dirname(__file__),
702
702
                "templates/projectset_fragment.html")
703
703
        project_fragment = os.path.join(os.path.dirname(__file__),
704
704
                "templates/project_fragment.html")
705
705
 
706
 
        for projectset in \
707
 
            self.context.project_sets.order_by(ivle.database.ProjectSet.id):
708
 
            settmpl = self._loader.load(set_fragment)
 
706
        for projectset in self.context.project_sets:
 
707
            settmpl = loader.load(set_fragment)
709
708
            setCtx = Context()
710
709
            setCtx['req'] = req
711
710
            setCtx['projectset'] = projectset
712
711
            setCtx['projects'] = []
713
712
            setCtx['GroupsView'] = GroupsView
714
 
            setCtx['ProjectSetEdit'] = ProjectSetEdit
715
 
            setCtx['ProjectNew'] = ProjectNew
 
713
            setCtx['ProjectSetRESTView'] = ProjectSetRESTView
716
714
 
717
 
            for project in \
718
 
                projectset.projects.order_by(ivle.database.Project.deadline):
719
 
                projecttmpl = self._loader.load(project_fragment)
 
715
            for project in projectset.projects:
 
716
                projecttmpl = loader.load(project_fragment)
720
717
                projectCtx = Context()
721
718
                projectCtx['req'] = req
722
719
                projectCtx['project'] = project
723
 
                projectCtx['ProjectEdit'] = ProjectEdit
724
 
                projectCtx['ProjectDelete'] = ProjectDelete
725
720
 
726
721
                setCtx['projects'].append(
727
722
                        projecttmpl.generate(projectCtx))
757
752
        self.plugin_styles[Plugin] = ["project.css"]
758
753
 
759
754
        ctx['req'] = req
760
 
        ctx['permissions'] = self.context.get_permissions(req.user,req.config)
761
755
        ctx['GroupsView'] = GroupsView
762
756
        ctx['EnrolView'] = EnrolView
763
 
        ctx['format_datetime'] = ivle.date.make_date_nice
764
757
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
765
758
        ctx['build_subversion_url'] = self.build_subversion_url
766
759
        ctx['svn_addr'] = req.config['urls']['svn_addr']
767
760
        ctx['project'] = self.context
768
761
        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
762
 
940
763
class Plugin(ViewPlugin, MediaPlugin):
941
764
    forward_routes = (root_to_subject, root_to_semester, subject_to_offering,
962
785
             (Enrolment, '+edit', EnrolmentEdit),
963
786
             (Enrolment, '+delete', EnrolmentDelete),
964
787
             (Offering, ('+projects', '+index'), OfferingProjectsView),
965
 
             (Offering, ('+projects', '+new-set'), ProjectSetNew),
966
 
             (ProjectSet, '+edit', ProjectSetEdit),
967
 
             (ProjectSet, '+new', ProjectNew),
968
788
             (Project, '+index', ProjectView),
969
 
             (Project, '+edit', ProjectEdit),
970
 
             (Project, '+delete', ProjectDelete),
 
789
 
 
790
             (Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
 
791
             (ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
971
792
             ]
972
793
 
973
794
    breadcrumbs = {Subject: SubjectBreadcrumb,