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 RoleEnrolmentValidator(formencode.FancyValidator):
103
"""A FormEncode validator that checks permission to enrol users with a
106
The state must have an 'offering' attribute.
108
def _to_python(self, value, state):
109
if ("enrol_" + value) not in state.offering.get_permissions(state.user):
110
raise formencode.Invalid('Not allowed to assign users that role',
115
class EnrolSchema(formencode.Schema):
116
user = formencode.All(NoEnrolmentValidator(), UserValidator())
117
role = formencode.All(formencode.validators.OneOf(
118
["lecturer", "tutor", "student"]),
119
RoleEnrolmentValidator(),
120
formencode.validators.UnicodeString())
123
class EnrolmentsView(XHTMLView):
124
"""A page which displays all users enrolled in an offering."""
125
template = 'templates/enrolments.html'
128
def populate(self, req, ctx):
129
ctx['offering'] = self.context
131
class EnrolView(XHTMLView):
132
"""A form to enrol a user in an offering."""
133
template = 'templates/enrol.html'
137
def filter(self, stream, ctx):
138
return stream | HTMLFormFiller(data=ctx['data'])
140
def populate(self, req, ctx):
141
if req.method == 'POST':
142
data = dict(req.get_fieldstorage())
144
validator = EnrolSchema()
145
req.offering = self.context # XXX: Getting into state.
146
data = validator.to_python(data, state=req)
147
self.context.enrol(data['user'], data['role'])
149
req.throw_redirect(req.uri)
150
except formencode.Invalid, e:
151
errors = e.unpack_errors()
156
ctx['data'] = data or {}
157
ctx['offering'] = self.context
158
ctx['roles_auth'] = self.context.get_permissions(req.user)
159
ctx['errors'] = errors
161
class OfferingProjectsView(XHTMLView):
162
"""View the projects for an offering."""
163
template = 'templates/offering_projects.html'
167
def populate(self, req, ctx):
168
self.plugin_styles[Plugin] = ["project.css"]
169
self.plugin_scripts[Plugin] = ["project.js"]
171
ctx['offering'] = self.context
172
ctx['projectsets'] = []
173
ctx['OfferingRESTView'] = OfferingRESTView
175
#Open the projectset Fragment, and render it for inclusion
176
#into the ProjectSets page
177
#XXX: This could be a lot cleaner
178
loader = genshi.template.TemplateLoader(".", auto_reload=True)
180
set_fragment = os.path.join(os.path.dirname(__file__),
181
"templates/projectset_fragment.html")
182
project_fragment = os.path.join(os.path.dirname(__file__),
183
"templates/project_fragment.html")
185
for projectset in self.context.project_sets:
186
settmpl = loader.load(set_fragment)
189
setCtx['projectset'] = projectset
190
setCtx['projects'] = []
191
setCtx['GroupsView'] = GroupsView
192
setCtx['ProjectSetRESTView'] = ProjectSetRESTView
194
for project in projectset.projects:
195
projecttmpl = loader.load(project_fragment)
196
projectCtx = Context()
197
projectCtx['req'] = req
198
projectCtx['project'] = project
200
setCtx['projects'].append(
201
projecttmpl.generate(projectCtx))
203
ctx['projectsets'].append(settmpl.generate(setCtx))
206
class ProjectView(XHTMLView):
207
"""View the submissions for a ProjectSet"""
208
template = "templates/project.html"
212
def build_subversion_url(self, svnroot, submission):
213
princ = submission.assessed.principal
215
if isinstance(princ, User):
216
path = 'users/%s' % princ.login
218
path = 'groups/%s_%s_%s_%s' % (
219
princ.project_set.offering.subject.short_name,
220
princ.project_set.offering.semester.year,
221
princ.project_set.offering.semester.semester,
224
return urlparse.urljoin(
226
os.path.join(path, submission.path[1:] if
227
submission.path.startswith(os.sep) else
230
def populate(self, req, ctx):
231
self.plugin_styles[Plugin] = ["project.css"]
234
ctx['GroupsView'] = GroupsView
235
ctx['EnrolView'] = EnrolView
236
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
237
ctx['build_subversion_url'] = self.build_subversion_url
238
ctx['svn_addr'] = req.config['urls']['svn_addr']
239
ctx['project'] = self.context
240
ctx['user'] = req.user
242
class Plugin(ViewPlugin, MediaPlugin):
243
forward_routes = (root_to_subject, subject_to_offering,
244
offering_to_project, offering_to_projectset)
245
reverse_routes = (subject_url, offering_url, projectset_url, project_url)
247
views = [(ApplicationRoot, ('subjects', '+index'), SubjectsView),
248
(Offering, ('+enrolments', '+index'), EnrolmentsView),
249
(Offering, ('+enrolments', '+new'), EnrolView),
250
(Offering, ('+projects', '+index'), OfferingProjectsView),
251
(Project, '+index', ProjectView),
253
(Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
254
(ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
257
breadcrumbs = {Subject: SubjectBreadcrumb,
258
Offering: OfferingBreadcrumb,
259
User: UserBreadcrumb,
260
Project: ProjectBreadcrumb,
264
('subjects', 'Subjects',
265
'View subject content and complete worksheets',
266
'subjects.png', 'subjects', 5)
269
media = 'subject-media'