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
47
from ivle.webapp.admin.offeringservice import OfferingRESTView
48
from ivle.webapp.admin.publishing import (root_to_subject,
49
subject_to_offering, offering_to_projectset, offering_to_project,
50
subject_url, offering_url, projectset_url, project_url)
51
from ivle.webapp.admin.breadcrumbs import (SubjectBreadcrumb,
52
OfferingBreadcrumb, UserBreadcrumb, ProjectBreadcrumb)
53
from ivle.webapp.groups import GroupsView
55
class SubjectsView(XHTMLView):
56
'''The view of the list of subjects.'''
57
template = 'templates/subjects.html'
60
def authorize(self, req):
61
return req.user is not None
63
def populate(self, req, ctx):
64
ctx['user'] = req.user
66
for semester in req.store.find(Semester).order_by(Desc(Semester.year),
67
Desc(Semester.semester)):
69
# For admins, show all subjects in the system
70
offerings = list(semester.offerings.find())
72
offerings = [enrolment.offering for enrolment in
73
semester.enrolments.find(user=req.user)]
75
ctx['semesters'].append((semester, offerings))
78
class UserValidator(formencode.FancyValidator):
79
"""A FormEncode validator that turns a username into a user.
81
The state must have a 'store' attribute, which is the Storm store
83
def _to_python(self, value, state):
84
user = User.get_by_login(state.store, value)
88
raise formencode.Invalid('User does not exist', value, state)
91
class NoEnrolmentValidator(formencode.FancyValidator):
92
"""A FormEncode validator that ensures absence of an enrolment.
94
The state must have an 'offering' attribute.
96
def _to_python(self, value, state):
97
if state.offering.get_enrolment(value):
98
raise formencode.Invalid('User already enrolled', value, state)
102
class EnrolSchema(formencode.Schema):
103
user = formencode.All(NoEnrolmentValidator(), UserValidator())
106
class EnrolmentsView(XHTMLView):
107
"""A page which displays all users enrolled in an offering."""
108
template = 'templates/enrolments.html'
111
def populate(self, req, ctx):
112
ctx['offering'] = self.context
114
class EnrolView(XHTMLView):
115
"""A form to enrol a user in an offering."""
116
template = 'templates/enrol.html'
120
def filter(self, stream, ctx):
121
return stream | HTMLFormFiller(data=ctx['data'])
123
def populate(self, req, ctx):
124
if req.method == 'POST':
125
data = dict(req.get_fieldstorage())
127
validator = EnrolSchema()
128
req.offering = self.context # XXX: Getting into state.
129
data = validator.to_python(data, state=req)
130
self.context.enrol(data['user'])
132
req.throw_redirect(req.uri)
133
except formencode.Invalid, e:
134
errors = e.unpack_errors()
139
ctx['data'] = data or {}
140
ctx['offering'] = self.context
141
ctx['errors'] = errors
143
class OfferingProjectsView(XHTMLView):
144
"""View the projects for an offering."""
145
template = 'templates/offering_projects.html'
149
def populate(self, req, ctx):
150
self.plugin_styles[Plugin] = ["project.css"]
151
self.plugin_scripts[Plugin] = ["project.js"]
153
ctx['offering'] = self.context
154
ctx['projectsets'] = []
155
ctx['OfferingRESTView'] = OfferingRESTView
157
#Open the projectset Fragment, and render it for inclusion
158
#into the ProjectSets page
159
#XXX: This could be a lot cleaner
160
loader = genshi.template.TemplateLoader(".", auto_reload=True)
162
set_fragment = os.path.join(os.path.dirname(__file__),
163
"templates/projectset_fragment.html")
164
project_fragment = os.path.join(os.path.dirname(__file__),
165
"templates/project_fragment.html")
167
for projectset in self.context.project_sets:
168
settmpl = loader.load(set_fragment)
171
setCtx['projectset'] = projectset
172
setCtx['projects'] = []
173
setCtx['GroupsView'] = GroupsView
174
setCtx['ProjectSetRESTView'] = ProjectSetRESTView
176
for project in projectset.projects:
177
projecttmpl = loader.load(project_fragment)
178
projectCtx = Context()
179
projectCtx['req'] = req
180
projectCtx['project'] = project
182
setCtx['projects'].append(
183
projecttmpl.generate(projectCtx))
185
ctx['projectsets'].append(settmpl.generate(setCtx))
188
class ProjectView(XHTMLView):
189
"""View the submissions for a ProjectSet"""
190
template = "templates/project.html"
194
def build_subversion_url(self, svnroot, submission):
195
princ = submission.assessed.principal
197
if isinstance(princ, User):
198
path = 'users/%s' % princ.login
200
path = 'groups/%s_%s_%s_%s' % (
201
princ.project_set.offering.subject.short_name,
202
princ.project_set.offering.semester.year,
203
princ.project_set.offering.semester.semester,
206
return urlparse.urljoin(
208
os.path.join(path, submission.path[1:] if
209
submission.path.startswith(os.sep) else
212
def populate(self, req, ctx):
213
self.plugin_styles[Plugin] = ["project.css"]
215
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
216
ctx['build_subversion_url'] = self.build_subversion_url
217
ctx['svn_addr'] = req.config['urls']['svn_addr']
218
ctx['project'] = self.context
219
ctx['user'] = req.user
221
class Plugin(ViewPlugin, MediaPlugin):
222
forward_routes = (root_to_subject, subject_to_offering,
223
offering_to_project, offering_to_projectset)
224
reverse_routes = (subject_url, offering_url, projectset_url, project_url)
226
views = [(ApplicationRoot, ('subjects', '+index'), SubjectsView),
227
(Offering, ('+enrolments', '+index'), EnrolmentsView),
228
(Offering, ('+enrolments', '+new'), EnrolView),
229
(Offering, ('+projects', '+index'), OfferingProjectsView),
230
(Project, '+index', ProjectView),
232
(Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
233
(ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
236
breadcrumbs = {Subject: SubjectBreadcrumb,
237
Offering: OfferingBreadcrumb,
238
User: UserBreadcrumb,
239
Project: ProjectBreadcrumb,
243
('subjects', 'Subjects',
244
'View subject content and complete worksheets',
245
'subjects.png', 'subjects', 5)
248
media = 'subject-media'