655
695
def populate(self, req, ctx):
656
696
self.plugin_styles[Plugin] = ["project.css"]
657
self.plugin_scripts[Plugin] = ["project.js"]
659
698
ctx['offering'] = self.context
660
699
ctx['projectsets'] = []
661
ctx['OfferingRESTView'] = OfferingRESTView
663
701
#Open the projectset Fragment, and render it for inclusion
664
702
#into the ProjectSets page
665
#XXX: This could be a lot cleaner
666
loader = genshi.template.TemplateLoader(".", auto_reload=True)
668
703
set_fragment = os.path.join(os.path.dirname(__file__),
669
704
"templates/projectset_fragment.html")
670
705
project_fragment = os.path.join(os.path.dirname(__file__),
671
706
"templates/project_fragment.html")
673
for projectset in self.context.project_sets:
674
settmpl = loader.load(set_fragment)
709
self.context.project_sets.order_by(ivle.database.ProjectSet.id):
710
settmpl = self._loader.load(set_fragment)
675
711
setCtx = Context()
676
712
setCtx['req'] = req
677
713
setCtx['projectset'] = projectset
678
714
setCtx['projects'] = []
679
715
setCtx['GroupsView'] = GroupsView
680
setCtx['ProjectSetRESTView'] = ProjectSetRESTView
716
setCtx['ProjectSetEdit'] = ProjectSetEdit
717
setCtx['ProjectNew'] = ProjectNew
682
for project in projectset.projects:
683
projecttmpl = loader.load(project_fragment)
720
projectset.projects.order_by(ivle.database.Project.deadline):
721
projecttmpl = self._loader.load(project_fragment)
684
722
projectCtx = Context()
685
723
projectCtx['req'] = req
686
724
projectCtx['project'] = project
725
projectCtx['ProjectEdit'] = ProjectEdit
726
projectCtx['ProjectDelete'] = ProjectDelete
688
728
setCtx['projects'].append(
689
729
projecttmpl.generate(projectCtx))
697
737
permission = "view_project_submissions"
700
def build_subversion_url(self, svnroot, submission):
701
princ = submission.assessed.principal
703
if isinstance(princ, User):
704
path = 'users/%s' % princ.login
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,
712
return urlparse.urljoin(
714
os.path.join(path, submission.path[1:] if
715
submission.path.startswith(os.sep) else
718
740
def populate(self, req, ctx):
719
741
self.plugin_styles[Plugin] = ["project.css"]
744
ctx['permissions'] = self.context.get_permissions(req.user,req.config)
722
745
ctx['GroupsView'] = GroupsView
723
746
ctx['EnrolView'] = EnrolView
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']
727
ctx['project'] = self.context
728
ctx['user'] = req.user
747
ctx['format_datetime'] = ivle.date.make_date_nice
748
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
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"
761
def populate(self, req, ctx):
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
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),
971
(Project, ('+export', 'project-export.sh'),
972
ProjectBashExportView),
759
975
breadcrumbs = {Subject: SubjectBreadcrumb,