~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-25 08:21:47 UTC
  • Revision ID: matt.giuca@gmail.com-20100225082147-tus03ah630n74156
ProjectSet view: Prettied up the group management interface, including fixing delete icons.

Show diffs side-by-side

added added

removed removed

Lines of Context:
27
27
import urllib
28
28
import urlparse
29
29
import cgi
30
 
import datetime
31
30
 
32
31
from storm.locals import Desc, Store
33
32
import genshi
36
35
import formencode
37
36
import formencode.validators
38
37
 
39
 
from ivle.webapp.base.forms import (BaseFormView, URLNameValidator,
40
 
                                    DateTimeValidator)
 
38
from ivle.webapp.base.forms import BaseFormView, URLNameValidator
41
39
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
42
40
from ivle.webapp.base.xhtml import XHTMLView
43
 
from ivle.webapp.base.text import TextView
44
41
from ivle.webapp.errors import BadRequest
45
42
from ivle.webapp import ApplicationRoot
46
43
 
49
46
from ivle import util
50
47
import ivle.date
51
48
 
 
49
from ivle.webapp.admin.projectservice import ProjectSetRESTView
 
50
from ivle.webapp.admin.offeringservice import OfferingRESTView
52
51
from ivle.webapp.admin.publishing import (root_to_subject, root_to_semester,
53
52
            subject_to_offering, offering_to_projectset, offering_to_project,
54
53
            offering_to_enrolment, subject_url, semester_url, offering_url,
55
54
            projectset_url, project_url, enrolment_url)
56
55
from ivle.webapp.admin.breadcrumbs import (SubjectBreadcrumb,
57
56
            OfferingBreadcrumb, UserBreadcrumb, ProjectBreadcrumb,
58
 
            ProjectsBreadcrumb, EnrolmentBreadcrumb)
 
57
            EnrolmentBreadcrumb)
59
58
from ivle.webapp.core import Plugin as CorePlugin
60
59
from ivle.webapp.groups import GroupsView
61
60
from ivle.webapp.media import media_url
310
309
 
311
310
        ctx['worksheets'], problems_total, problems_done = (
312
311
            ivle.worksheet.utils.create_list_of_fake_worksheets_and_stats(
313
 
                req.config, req.store, req.user, self.context,
314
 
                as_of=self.context.worksheet_cutoff))
 
312
                req.config, req.store, req.user, self.context))
315
313
 
316
314
        ctx['exercises_total'] = problems_total
317
315
        ctx['exercises_done'] = problems_done
388
386
    description = formencode.validators.UnicodeString(
389
387
        if_missing=None, not_empty=False)
390
388
    url = formencode.validators.URL(if_missing=None, not_empty=False)
391
 
    worksheet_cutoff = DateTimeValidator(if_missing=None, not_empty=False)
392
389
    show_worksheet_marks = formencode.validators.StringBoolean(
393
390
        if_missing=False)
394
391
 
431
428
                        self.context.semester.semester,
432
429
            'url': self.context.url,
433
430
            'description': self.context.description,
434
 
            'worksheet_cutoff': self.context.worksheet_cutoff,
435
431
            'show_worksheet_marks': self.context.show_worksheet_marks,
436
432
            }
437
433
 
441
437
            self.context.semester = data['semester']
442
438
        self.context.description = data['description']
443
439
        self.context.url = unicode(data['url']) if data['url'] else None
444
 
        self.context.worksheet_cutoff = data['worksheet_cutoff']
445
440
        self.context.show_worksheet_marks = data['show_worksheet_marks']
446
441
        return self.context
447
442
 
477
472
        new_offering.semester = data['semester']
478
473
        new_offering.description = data['description']
479
474
        new_offering.url = unicode(data['url']) if data['url'] else None
480
 
        new_offering.worksheet_cutoff = data['worksheet_cutoff']
481
475
        new_offering.show_worksheet_marks = data['show_worksheet_marks']
482
476
 
483
477
        req.store.add(new_offering)
694
688
 
695
689
    def populate(self, req, ctx):
696
690
        self.plugin_styles[Plugin] = ["project.css"]
 
691
        self.plugin_scripts[Plugin] = ["project.js"]
697
692
        ctx['req'] = req
698
693
        ctx['offering'] = self.context
699
694
        ctx['projectsets'] = []
 
695
        ctx['OfferingRESTView'] = OfferingRESTView
700
696
 
701
697
        #Open the projectset Fragment, and render it for inclusion
702
698
        #into the ProjectSets page
713
709
            setCtx['projectset'] = projectset
714
710
            setCtx['projects'] = []
715
711
            setCtx['GroupsView'] = GroupsView
716
 
            setCtx['ProjectSetEdit'] = ProjectSetEdit
717
 
            setCtx['ProjectNew'] = ProjectNew
 
712
            setCtx['ProjectSetRESTView'] = ProjectSetRESTView
718
713
 
719
714
            for project in \
720
715
                projectset.projects.order_by(ivle.database.Project.deadline):
722
717
                projectCtx = Context()
723
718
                projectCtx['req'] = req
724
719
                projectCtx['project'] = project
725
 
                projectCtx['ProjectEdit'] = ProjectEdit
726
 
                projectCtx['ProjectDelete'] = ProjectDelete
727
720
 
728
721
                setCtx['projects'].append(
729
722
                        projecttmpl.generate(projectCtx))
737
730
    permission = "view_project_submissions"
738
731
    tab = 'subjects'
739
732
 
 
733
    def build_subversion_url(self, svnroot, submission):
 
734
        princ = submission.assessed.principal
 
735
 
 
736
        if isinstance(princ, User):
 
737
            path = 'users/%s' % princ.login
 
738
        else:
 
739
            path = 'groups/%s_%s_%s_%s' % (
 
740
                    princ.project_set.offering.subject.short_name,
 
741
                    princ.project_set.offering.semester.year,
 
742
                    princ.project_set.offering.semester.semester,
 
743
                    princ.name
 
744
                    )
 
745
        return urlparse.urljoin(
 
746
                    svnroot,
 
747
                    os.path.join(path, submission.path[1:] if
 
748
                                       submission.path.startswith(os.sep) else
 
749
                                       submission.path))
 
750
 
740
751
    def populate(self, req, ctx):
741
752
        self.plugin_styles[Plugin] = ["project.css"]
742
753
 
743
754
        ctx['req'] = req
744
 
        ctx['permissions'] = self.context.get_permissions(req.user,req.config)
745
755
        ctx['GroupsView'] = GroupsView
746
756
        ctx['EnrolView'] = EnrolView
747
757
        ctx['format_datetime'] = ivle.date.make_date_nice
748
758
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
749
 
        ctx['project'] = self.context
750
 
        ctx['user'] = req.user
751
 
        ctx['ProjectEdit'] = ProjectEdit
752
 
        ctx['ProjectDelete'] = ProjectDelete
753
 
        ctx['ProjectExport'] = ProjectBashExportView
754
 
 
755
 
class ProjectBashExportView(TextView):
756
 
    """Produce a Bash script for exporting projects"""
757
 
    template = "templates/project-export.sh"
758
 
    content_type = "text/x-sh"
759
 
    permission = "view_project_submissions"
760
 
 
761
 
    def populate(self, req, ctx):
762
 
        ctx['req'] = req
763
 
        ctx['permissions'] = self.context.get_permissions(req.user,req.config)
764
 
        ctx['format_datetime'] = ivle.date.make_date_nice
765
 
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
766
 
        ctx['project'] = self.context
767
 
        ctx['user'] = req.user
768
 
        ctx['now'] = datetime.datetime.now()
769
 
        ctx['format_datetime'] = ivle.date.make_date_nice
770
 
        ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
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
 
759
        ctx['build_subversion_url'] = self.build_subversion_url
 
760
        ctx['svn_addr'] = req.config['urls']['svn_addr']
 
761
        ctx['project'] = self.context
 
762
        ctx['user'] = req.user
939
763
 
940
764
class Plugin(ViewPlugin, MediaPlugin):
941
765
    forward_routes = (root_to_subject, root_to_semester, subject_to_offering,
962
786
             (Enrolment, '+edit', EnrolmentEdit),
963
787
             (Enrolment, '+delete', EnrolmentDelete),
964
788
             (Offering, ('+projects', '+index'), OfferingProjectsView),
965
 
             (Offering, ('+projects', '+new-set'), ProjectSetNew),
966
 
             (ProjectSet, '+edit', ProjectSetEdit),
967
 
             (ProjectSet, '+new', ProjectNew),
968
789
             (Project, '+index', ProjectView),
969
 
             (Project, '+edit', ProjectEdit),
970
 
             (Project, '+delete', ProjectDelete),
971
 
             (Project, ('+export', 'project-export.sh'),
972
 
                ProjectBashExportView),
 
790
 
 
791
             (Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
 
792
             (ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
973
793
             ]
974
794
 
975
795
    breadcrumbs = {Subject: SubjectBreadcrumb,