729
735
permission = "view_project_submissions"
732
def build_subversion_url(self, svnroot, submission):
738
def build_subversion_url(self, req, submission):
733
739
princ = submission.assessed.principal
735
if isinstance(princ, User):
736
path = 'users/%s' % princ.login
738
path = 'groups/%s_%s_%s_%s' % (
739
princ.project_set.offering.subject.short_name,
740
princ.project_set.offering.semester.year,
741
princ.project_set.offering.semester.semester,
744
return urlparse.urljoin(
746
os.path.join(path, submission.path[1:] if
747
submission.path.startswith(os.sep) else
741
return os.path.join(princ.get_svn_url(req.config, req),
742
submission.path[1:] if
743
submission.path.startswith(os.sep) else
750
746
def populate(self, req, ctx):
751
747
self.plugin_styles[Plugin] = ["project.css"]
750
ctx['permissions'] = self.context.get_permissions(req.user,req.config)
754
751
ctx['GroupsView'] = GroupsView
755
752
ctx['EnrolView'] = EnrolView
756
753
ctx['format_datetime'] = ivle.date.make_date_nice
757
754
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
758
755
ctx['build_subversion_url'] = self.build_subversion_url
759
ctx['svn_addr'] = req.config['urls']['svn_addr']
760
756
ctx['project'] = self.context
761
757
ctx['user'] = req.user
758
ctx['ProjectEdit'] = ProjectEdit
759
ctx['ProjectDelete'] = ProjectDelete
761
class ProjectUniquenessValidator(formencode.FancyValidator):
762
"""A FormEncode validator that checks that a project short_name is unique
765
The project referenced by state.existing_project is permitted to
766
hold that short_name. If any other project holds it, the input is rejected.
768
def _to_python(self, value, state):
769
if (state.store.find(
771
Project.short_name == unicode(value),
772
Project.project_set_id == ProjectSet.id,
773
ProjectSet.offering == state.offering).one() not in
774
(None, state.existing_project)):
775
raise formencode.Invalid(
776
"A project with that URL name already exists in this offering."
780
class ProjectSchema(formencode.Schema):
781
name = formencode.validators.UnicodeString(not_empty=True)
782
short_name = formencode.All(
783
URLNameValidator(not_empty=True),
784
ProjectUniquenessValidator())
785
deadline = DateTimeValidator(not_empty=True)
786
url = formencode.validators.URL(if_missing=None, not_empty=False)
787
synopsis = formencode.validators.UnicodeString(not_empty=True)
789
class ProjectEdit(BaseFormView):
790
"""A form to edit a project."""
791
template = 'templates/project-edit.html'
797
return ProjectSchema()
799
def populate(self, req, ctx):
800
super(ProjectEdit, self).populate(req, ctx)
801
ctx['projectset'] = self.context.project_set
803
def populate_state(self, state):
804
state.offering = self.context.project_set.offering
805
state.existing_project = self.context
807
def get_default_data(self, req):
809
'name': self.context.name,
810
'short_name': self.context.short_name,
811
'deadline': self.context.deadline,
812
'url': self.context.url,
813
'synopsis': self.context.synopsis,
816
def save_object(self, req, data):
817
self.context.name = data['name']
818
self.context.short_name = data['short_name']
819
self.context.deadline = data['deadline']
820
self.context.url = unicode(data['url']) if data['url'] else None
821
self.context.synopsis = data['synopsis']
824
class ProjectNew(BaseFormView):
825
"""A form to create a new project."""
826
template = 'templates/project-new.html'
832
return ProjectSchema()
834
def populate(self, req, ctx):
835
super(ProjectNew, self).populate(req, ctx)
836
ctx['projectset'] = self.context
838
def populate_state(self, state):
839
state.offering = self.context.offering
840
state.existing_project = None
842
def get_default_data(self, req):
845
def save_object(self, req, data):
846
new_project = Project()
847
new_project.project_set = self.context
848
new_project.name = data['name']
849
new_project.short_name = data['short_name']
850
new_project.deadline = data['deadline']
851
new_project.url = unicode(data['url']) if data['url'] else None
852
new_project.synopsis = data['synopsis']
853
req.store.add(new_project)
856
class ProjectDelete(XHTMLView):
857
"""A form to delete a project."""
858
template = 'templates/project-delete.html'
862
def populate(self, req, ctx):
863
# If post, delete the project, or display a message explaining that
864
# the project cannot be deleted
865
if self.context.can_delete:
866
if req.method == 'POST':
867
self.context.delete()
868
self.template = 'templates/project-deleted.html'
871
self.template = 'templates/project-undeletable.html'
873
# If get and can delete, display a delete confirmation page
875
# Variables for the template
877
ctx['project'] = self.context
878
ctx['OfferingProjectsView'] = OfferingProjectsView
880
class ProjectSetSchema(formencode.Schema):
881
group_size = formencode.validators.Int(if_missing=None, not_empty=False)
883
class ProjectSetEdit(BaseFormView):
884
"""A form to edit a project set."""
885
template = 'templates/projectset-edit.html'
891
return ProjectSetSchema()
893
def populate(self, req, ctx):
894
super(ProjectSetEdit, self).populate(req, ctx)
896
def get_default_data(self, req):
898
'group_size': self.context.max_students_per_group,
901
def save_object(self, req, data):
902
self.context.max_students_per_group = data['group_size']
905
class ProjectSetNew(BaseFormView):
906
"""A form to create a new project set."""
907
template = 'templates/projectset-new.html'
910
breadcrumb_text = "Projects"
914
return ProjectSetSchema()
916
def populate(self, req, ctx):
917
super(ProjectSetNew, self).populate(req, ctx)
919
def get_default_data(self, req):
922
def save_object(self, req, data):
923
new_set = ProjectSet()
924
new_set.offering = self.context
925
new_set.max_students_per_group = data['group_size']
926
req.store.add(new_set)
763
929
class Plugin(ViewPlugin, MediaPlugin):
764
930
forward_routes = (root_to_subject, root_to_semester, subject_to_offering,
785
951
(Enrolment, '+edit', EnrolmentEdit),
786
952
(Enrolment, '+delete', EnrolmentDelete),
787
953
(Offering, ('+projects', '+index'), OfferingProjectsView),
954
(Offering, ('+projects', '+new-set'), ProjectSetNew),
955
(ProjectSet, '+edit', ProjectSetEdit),
956
(ProjectSet, '+new', ProjectNew),
788
957
(Project, '+index', ProjectView),
790
(Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
791
(ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
958
(Project, '+edit', ProjectEdit),
959
(Project, '+delete', ProjectDelete),
794
962
breadcrumbs = {Subject: SubjectBreadcrumb,