655
693
def populate(self, req, ctx):
656
694
self.plugin_styles[Plugin] = ["project.css"]
657
self.plugin_scripts[Plugin] = ["project.js"]
659
696
ctx['offering'] = self.context
660
697
ctx['projectsets'] = []
661
ctx['OfferingRESTView'] = OfferingRESTView
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)
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")
673
for projectset in self.context.project_sets:
674
settmpl = loader.load(set_fragment)
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
682
for project in projectset.projects:
683
projecttmpl = loader.load(project_fragment)
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
688
726
setCtx['projects'].append(
689
727
projecttmpl.generate(projectCtx))
719
757
self.plugin_styles[Plugin] = ["project.css"]
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
772
class ProjectUniquenessValidator(formencode.FancyValidator):
773
"""A FormEncode validator that checks that a project short_name is unique
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.
779
def _to_python(self, value, state):
780
if (state.store.find(
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."
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)
800
class ProjectEdit(BaseFormView):
801
"""A form to edit a project."""
802
template = 'templates/project-edit.html'
808
return ProjectSchema()
810
def populate(self, req, ctx):
811
super(ProjectEdit, self).populate(req, ctx)
812
ctx['projectset'] = self.context.project_set
814
def populate_state(self, state):
815
state.offering = self.context.project_set.offering
816
state.existing_project = self.context
818
def get_default_data(self, req):
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,
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']
835
class ProjectNew(BaseFormView):
836
"""A form to create a new project."""
837
template = 'templates/project-new.html'
843
return ProjectSchema()
845
def populate(self, req, ctx):
846
super(ProjectNew, self).populate(req, ctx)
847
ctx['projectset'] = self.context
849
def populate_state(self, state):
850
state.offering = self.context.offering
851
state.existing_project = None
853
def get_default_data(self, req):
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)
867
class ProjectDelete(XHTMLView):
868
"""A form to delete a project."""
869
template = 'templates/project-delete.html'
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'
882
self.template = 'templates/project-undeletable.html'
884
# If get and can delete, display a delete confirmation page
886
# Variables for the template
888
ctx['project'] = self.context
889
ctx['OfferingProjectsView'] = OfferingProjectsView
891
class ProjectSetSchema(formencode.Schema):
892
group_size = formencode.validators.Int(if_missing=None, not_empty=False)
894
class ProjectSetEdit(BaseFormView):
895
"""A form to edit a project set."""
896
template = 'templates/projectset-edit.html'
902
return ProjectSetSchema()
904
def populate(self, req, ctx):
905
super(ProjectSetEdit, self).populate(req, ctx)
907
def get_default_data(self, req):
909
'group_size': self.context.max_students_per_group,
912
def save_object(self, req, data):
913
self.context.max_students_per_group = data['group_size']
916
class ProjectSetNew(BaseFormView):
917
"""A form to create a new project set."""
918
template = 'templates/projectset-new.html'
921
breadcrumb_text = "Projects"
925
return ProjectSetSchema()
927
def populate(self, req, ctx):
928
super(ProjectSetNew, self).populate(req, ctx)
930
def get_default_data(self, req):
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)
730
940
class Plugin(ViewPlugin, MediaPlugin):
731
941
forward_routes = (root_to_subject, root_to_semester, subject_to_offering,
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),
755
(Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
756
(ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
969
(Project, '+edit', ProjectEdit),
970
(Project, '+delete', ProjectDelete),
759
973
breadcrumbs = {Subject: SubjectBreadcrumb,