2
# Copyright (C) 2007-2009 The University of Melbourne
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20
"""Project submissions user interface."""
25
from storm.locals import Store
27
from ivle.database import (User, ProjectGroup, Offering, Subject, Semester,
28
ProjectSet, Project, Enrolment)
29
from ivle.webapp.errors import NotFound, BadRequest
30
from ivle.webapp.base.xhtml import XHTMLView
31
from ivle.webapp.base.plugins import ViewPlugin
37
class SubmitView(XHTMLView):
38
"""A view to submit a Subversion repository path for a project."""
39
template = 'submit.html'
41
permission = 'submit_project'
43
def __init__(self, req, name, path=u"/"):
44
# We need to work out which entity owns the repository, so we look
45
# at the first two path segments. The first tells us the type.
46
self.context = self.get_repository_owner(req.store, name)
48
# Ensure that the path is absolute (required for SVNAuthzFile).
49
# XXX Re-convert to unicode (os.path.normpath(u"/") returns a str).
50
self.path = os.path.join(u"/", unicode(os.path.normpath(path)))
52
if self.context is None:
55
self.offering = self.get_offering()
57
def get_repository_owner(self, store, name):
58
"""Return the owner of the repository given the name and a Store."""
59
raise NotImplementedError()
61
def get_offering(self):
62
"""Return the offering that this path can be submitted to."""
63
raise NotImplementedError()
65
def populate(self, req, ctx):
66
if req.method == 'POST':
67
data = dict(req.get_fieldstorage())
68
if 'revision' not in data:
69
raise BadRequest('No revision selected.')
72
revision = int(data['revision'])
74
raise BadRequest('Revision must be an integer.')
76
if 'project' not in data:
77
raise BadRequest('No project selected.')
80
projectid = int(data['project'])
82
raise BadRequest('Project must be an integer.')
84
project = req.store.find(Project, Project.id == projectid).one()
86
# This view's offering will be the sole offering for which the
87
# path is permissible. We need to check that.
88
if project.project_set.offering is not self.offering:
89
raise BadRequest('Path is not permissible for this offering')
92
raise BadRequest('Specified project does not exist')
94
project.submit(self.context, self.path, revision, req.user)
96
# The Subversion configuration needs to be updated, to grant
97
# tutors and lecturers access to this submission. We have to
98
# commit early so usrmgt-server can see the new submission.
101
# Instruct usrmgt-server to rebuild the SVN group authz file.
102
msg = {'rebuild_svn_group_config': {}}
103
usrmgt = ivle.chat.chat(req.config['usrmgt']['host'],
104
req.config['usrmgt']['port'],
106
req.config['usrmgt']['magic'],
109
if usrmgt.get('response') in (None, 'failure'):
110
raise Exception("Failure creating repository: " + str(usrmgt))
112
# Instruct usrmgt-server to rebuild the SVN user authz file.
113
msg = {'rebuild_svn_config': {}}
114
usrmgt = ivle.chat.chat(req.config['usrmgt']['host'],
115
req.config['usrmgt']['port'],
117
req.config['usrmgt']['magic'],
120
if usrmgt.get('response') in (None, 'failure'):
121
raise Exception("Failure creating repository: " + str(usrmgt))
123
self.template = 'submitted.html'
124
ctx['project'] = project
127
ctx['principal'] = self.context
128
ctx['offering'] = self.offering
129
ctx['path'] = self.path
130
ctx['now'] = datetime.datetime.now()
131
ctx['format_datetime'] = ivle.date.make_date_nice
132
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
135
class UserSubmitView(SubmitView):
136
def get_repository_owner(self, store, name):
137
'''Resolve the user name into a user.'''
138
return User.get_by_login(store, name)
140
def get_offering(self):
141
return Store.of(self.context).find(Offering,
142
Offering.id == Enrolment.offering_id,
143
Enrolment.user_id == self.context.id,
144
Offering.semester_id == Semester.id,
145
Semester.state == u'current',
146
Offering.subject_id == Subject.id,
147
Subject.short_name == self.path.split('/')[1],
151
class GroupSubmitView(SubmitView):
152
def get_repository_owner(self, store, name):
153
'''Resolve the subject_year_semester_group name into a group.'''
154
namebits = name.split('_', 3)
155
if len(namebits) != 4:
158
# Find the project group with the given name in any project set in the
159
# offering of the given subject in the semester with the given year
161
return store.find(ProjectGroup,
162
ProjectGroup.name == namebits[3],
163
ProjectGroup.project_set_id == ProjectSet.id,
164
ProjectSet.offering_id == Offering.id,
165
Offering.subject_id == Subject.id,
166
Subject.short_name == namebits[0],
167
Offering.semester_id == Semester.id,
168
Semester.year == namebits[1],
169
Semester.semester == namebits[2]).one()
171
def get_offering(self):
172
return self.context.project_set.offering
175
class Plugin(ViewPlugin):
177
('+submit/users/:name', UserSubmitView),
178
('+submit/groups/:name', GroupSubmitView),
179
('+submit/users/:name/*path', UserSubmitView),
180
('+submit/groups/:name/*path', GroupSubmitView),