2
# Copyright (C) 2007-2008 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
22
# This is an IVLE application.
23
# A sample / testing application for IVLE.
31
from storm.locals import Desc, Store
33
from genshi.filters import HTMLFormFiller
34
from genshi.template import Context, TemplateLoader
37
from ivle.webapp.base.xhtml import XHTMLView
38
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
39
from ivle.webapp import ApplicationRoot
41
from ivle.database import Subject, Semester, Offering, Enrolment, User,\
42
ProjectSet, Project, ProjectSubmission
46
from ivle.webapp.admin.projectservice import ProjectSetRESTView,\
48
from ivle.webapp.admin.offeringservice import OfferingRESTView
49
from ivle.webapp.admin.traversal import (root_to_subject,
50
subject_to_offering, offering_to_projectset, offering_to_project,
51
subject_url, offering_url, projectset_url, project_url)
53
class SubjectsView(XHTMLView):
54
'''The view of the list of subjects.'''
55
template = 'templates/subjects.html'
58
def authorize(self, req):
59
return req.user is not None
61
def populate(self, req, ctx):
62
ctx['user'] = req.user
64
for semester in req.store.find(Semester).order_by(Desc(Semester.year),
65
Desc(Semester.semester)):
66
enrolments = semester.enrolments.find(user=req.user)
67
if enrolments.count():
68
ctx['semesters'].append((semester, enrolments))
71
class UserValidator(formencode.FancyValidator):
72
"""A FormEncode validator that turns a username into a user.
74
The state must have a 'store' attribute, which is the Storm store
76
def _to_python(self, value, state):
77
user = User.get_by_login(state.store, value)
81
raise formencode.Invalid('User does not exist', value, state)
84
class NoEnrolmentValidator(formencode.FancyValidator):
85
"""A FormEncode validator that ensures absence of an enrolment.
87
The state must have an 'offering' attribute.
89
def _to_python(self, value, state):
90
if state.offering.get_enrolment(value):
91
raise formencode.Invalid('User already enrolled', value, state)
95
class EnrolSchema(formencode.Schema):
96
user = formencode.All(NoEnrolmentValidator(), UserValidator())
99
class EnrolView(XHTMLView):
100
"""A form to enrol a user in an offering."""
101
template = 'templates/enrol.html'
105
def filter(self, stream, ctx):
106
return stream | HTMLFormFiller(data=ctx['data'])
108
def populate(self, req, ctx):
109
if req.method == 'POST':
110
data = dict(req.get_fieldstorage())
112
validator = EnrolSchema()
113
req.offering = self.context # XXX: Getting into state.
114
data = validator.to_python(data, state=req)
115
self.context.enrol(data['user'])
117
req.throw_redirect(req.uri)
118
except formencode.Invalid, e:
119
errors = e.unpack_errors()
124
ctx['data'] = data or {}
125
ctx['offering'] = self.context
126
ctx['errors'] = errors
128
class OfferingProjectsView(XHTMLView):
129
"""View the projects for an offering."""
130
template = 'templates/offering_projects.html'
134
def project_url(self, projectset, project):
135
return "/subjects/%s/%s/%s/+projects/%s" % (
136
self.context.subject.short_name,
137
self.context.semester.year,
138
self.context.semester.semester,
142
def new_project_url(self, projectset):
143
return "/api/subjects/" + self.context.subject.short_name + "/" +\
144
self.context.semester.year + "/" + \
145
self.context.semester.semester + "/+projectsets/" +\
146
str(projectset.id) + "/+projects/+new"
148
def populate(self, req, ctx):
149
self.plugin_styles[Plugin] = ["project.css"]
150
self.plugin_scripts[Plugin] = ["project.js"]
151
ctx['offering'] = self.context
152
ctx['projectsets'] = []
154
#Open the projectset Fragment, and render it for inclusion
155
#into the ProjectSets page
156
#XXX: This could be a lot cleaner
157
loader = genshi.template.TemplateLoader(".", auto_reload=True)
159
set_fragment = os.path.join(os.path.dirname(__file__),
160
"templates/projectset_fragment.html")
161
project_fragment = os.path.join(os.path.dirname(__file__),
162
"templates/project_fragment.html")
164
for projectset in self.context.project_sets:
165
settmpl = loader.load(set_fragment)
167
setCtx['projectset'] = projectset
168
setCtx['new_project_url'] = self.new_project_url(projectset)
169
setCtx['projects'] = []
171
for project in projectset.projects:
172
projecttmpl = loader.load(project_fragment)
173
projectCtx = Context()
174
projectCtx['project'] = project
175
projectCtx['project_url'] = self.project_url(projectset, project)
177
setCtx['projects'].append(
178
projecttmpl.generate(projectCtx))
180
ctx['projectsets'].append(settmpl.generate(setCtx))
183
class ProjectView(XHTMLView):
184
"""View the submissions for a ProjectSet"""
185
template = "templates/project.html"
189
def build_subversion_url(self, svnroot, submission):
190
princ = submission.assessed.principal
192
if isinstance(princ, User):
193
path = 'users/%s' % princ.login
195
path = 'groups/%s_%s_%s_%s' % (
196
princ.project_set.offering.subject.short_name,
197
princ.project_set.offering.semester.year,
198
princ.project_set.offering.semester.semester,
201
return urlparse.urljoin(
203
os.path.join(path, submission.path[1:] if
204
submission.path.startswith(os.sep) else
207
def populate(self, req, ctx):
208
self.plugin_styles[Plugin] = ["project.css"]
210
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
211
ctx['build_subversion_url'] = self.build_subversion_url
212
ctx['svn_addr'] = req.config['urls']['svn_addr']
213
ctx['project'] = self.context
214
ctx['user'] = req.user
216
class OfferingEnrolmentSet(object):
217
def __init__(self, offering):
218
self.offering = offering
220
class Plugin(ViewPlugin, MediaPlugin):
221
forward_routes = (root_to_subject, subject_to_offering,
222
offering_to_project, offering_to_projectset)
223
reverse_routes = (subject_url, offering_url, projectset_url, project_url)
225
views = [(ApplicationRoot, ('subjects', '+index'), SubjectsView),
226
(Offering, ('+enrolments', '+new'), EnrolView),
227
(Offering, ('+projects', '+index'), OfferingProjectsView),
228
(Project, '+index', ProjectView),
230
(Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
231
(ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
232
(Project, '+index', ProjectRESTView, 'api'),
236
('subjects', 'Subjects',
237
'View subject content and complete worksheets',
238
'subjects.png', 'subjects', 5)
241
media = 'subject-media'