760
761
ctx['svn_addr'] = req.config['urls']['svn_addr']
761
762
ctx['project'] = self.context
762
763
ctx['user'] = req.user
764
ctx['ProjectEdit'] = ProjectEdit
765
ctx['ProjectDelete'] = ProjectDelete
767
class ProjectUniquenessValidator(formencode.FancyValidator):
768
"""A FormEncode validator that checks that a project short_name is unique
771
The project referenced by state.existing_project is permitted to
772
hold that short_name. If any other project holds it, the input is rejected.
774
def _to_python(self, value, state):
775
if (state.store.find(
777
Project.short_name == unicode(value),
778
Project.project_set_id == ProjectSet.id,
779
ProjectSet.offering == state.offering).one() not in
780
(None, state.existing_project)):
781
raise formencode.Invalid(
782
"A project with that URL name already exists in this offering."
786
class ProjectSchema(formencode.Schema):
787
name = formencode.validators.UnicodeString(not_empty=True)
788
short_name = formencode.All(
789
URLNameValidator(not_empty=True),
790
ProjectUniquenessValidator())
791
deadline = DateTimeValidator(not_empty=True)
792
url = formencode.validators.URL(if_missing=None, not_empty=False)
793
synopsis = formencode.validators.UnicodeString(not_empty=True)
795
class ProjectEdit(BaseFormView):
796
"""A form to edit a project."""
797
template = 'templates/project-edit.html'
803
return ProjectSchema()
805
def populate(self, req, ctx):
806
super(ProjectEdit, self).populate(req, ctx)
807
ctx['projectset'] = self.context.project_set
809
def populate_state(self, state):
810
state.offering = self.context.project_set.offering
811
state.existing_project = self.context
813
def get_default_data(self, req):
815
'name': self.context.name,
816
'short_name': self.context.short_name,
817
'deadline': self.context.deadline,
818
'url': self.context.url,
819
'synopsis': self.context.synopsis,
822
def save_object(self, req, data):
823
self.context.name = data['name']
824
self.context.short_name = data['short_name']
825
self.context.deadline = data['deadline']
826
self.context.url = unicode(data['url']) if data['url'] else None
827
self.context.synopsis = data['synopsis']
830
class ProjectNew(BaseFormView):
831
"""A form to create a new project."""
832
template = 'templates/project-new.html'
838
return ProjectSchema()
840
def populate(self, req, ctx):
841
super(ProjectNew, self).populate(req, ctx)
842
ctx['projectset'] = self.context
844
def populate_state(self, state):
845
state.offering = self.context.offering
846
state.existing_project = None
848
def get_default_data(self, req):
851
def save_object(self, req, data):
852
new_project = Project()
853
new_project.project_set = self.context
854
new_project.name = data['name']
855
new_project.short_name = data['short_name']
856
new_project.deadline = data['deadline']
857
new_project.url = unicode(data['url']) if data['url'] else None
858
new_project.synopsis = data['synopsis']
859
req.store.add(new_project)
862
class ProjectDelete(XHTMLView):
863
"""A form to delete a project."""
864
template = 'templates/project-delete.html'
868
def populate(self, req, ctx):
869
# If post, delete the project, or display a message explaining that
870
# the project cannot be deleted
871
if self.context.can_delete:
872
if req.method == 'POST':
873
self.context.delete()
874
self.template = 'templates/project-deleted.html'
877
self.template = 'templates/project-undeletable.html'
879
# If get and can delete, display a delete confirmation page
881
# Variables for the template
883
ctx['project'] = self.context
884
ctx['OfferingProjectsView'] = OfferingProjectsView
886
class ProjectSetSchema(formencode.Schema):
887
group_size = formencode.validators.Int(if_missing=None, not_empty=False)
889
class ProjectSetEdit(BaseFormView):
890
"""A form to edit a project set."""
891
template = 'templates/projectset-edit.html'
897
return ProjectSetSchema()
899
def populate(self, req, ctx):
900
super(ProjectSetEdit, self).populate(req, ctx)
902
def get_default_data(self, req):
904
'group_size': self.context.max_students_per_group,
907
def save_object(self, req, data):
908
self.context.max_students_per_group = data['group_size']
911
class ProjectSetNew(BaseFormView):
912
"""A form to create a new project set."""
913
template = 'templates/projectset-new.html'
916
breadcrumb_text = "Projects"
920
return ProjectSetSchema()
922
def populate(self, req, ctx):
923
super(ProjectSetNew, self).populate(req, ctx)
925
def get_default_data(self, req):
928
def save_object(self, req, data):
929
new_set = ProjectSet()
930
new_set.offering = self.context
931
new_set.max_students_per_group = data['group_size']
932
req.store.add(new_set)
764
935
class Plugin(ViewPlugin, MediaPlugin):
765
936
forward_routes = (root_to_subject, root_to_semester, subject_to_offering,
786
957
(Enrolment, '+edit', EnrolmentEdit),
787
958
(Enrolment, '+delete', EnrolmentDelete),
788
959
(Offering, ('+projects', '+index'), OfferingProjectsView),
960
(Offering, ('+projects', '+new-set'), ProjectSetNew),
961
(ProjectSet, '+edit', ProjectSetEdit),
962
(ProjectSet, '+new', ProjectNew),
789
963
(Project, '+index', ProjectView),
791
(Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
792
(ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
964
(Project, '+edit', ProjectEdit),
965
(Project, '+delete', ProjectDelete),
795
968
breadcrumbs = {Subject: SubjectBreadcrumb,