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.publishing import (root_to_subject,
50
subject_to_offering, offering_to_projectset, offering_to_project,
51
subject_url, offering_url, projectset_url, project_url)
52
from ivle.webapp.admin.breadcrumbs import (SubjectBreadcrumb,
53
OfferingBreadcrumb, UserBreadcrumb, ProjectBreadcrumb)
54
from ivle.webapp.groups import GroupsView
56
class SubjectsView(XHTMLView):
57
'''The view of the list of subjects.'''
58
template = 'templates/subjects.html'
61
def authorize(self, req):
62
return req.user is not None
64
def populate(self, req, ctx):
65
ctx['user'] = req.user
67
for semester in req.store.find(Semester).order_by(Desc(Semester.year),
68
Desc(Semester.semester)):
70
# For admins, show all subjects in the system
71
offerings = list(semester.offerings.find())
73
offerings = [enrolment.offering for enrolment in
74
semester.enrolments.find(user=req.user)]
76
ctx['semesters'].append((semester, offerings))
79
class UserValidator(formencode.FancyValidator):
80
"""A FormEncode validator that turns a username into a user.
82
The state must have a 'store' attribute, which is the Storm store
84
def _to_python(self, value, state):
85
user = User.get_by_login(state.store, value)
89
raise formencode.Invalid('User does not exist', value, state)
92
class NoEnrolmentValidator(formencode.FancyValidator):
93
"""A FormEncode validator that ensures absence of an enrolment.
95
The state must have an 'offering' attribute.
97
def _to_python(self, value, state):
98
if state.offering.get_enrolment(value):
99
raise formencode.Invalid('User already enrolled', value, state)
103
class RoleEnrolmentValidator(formencode.FancyValidator):
104
"""A FormEncode validator that checks permission to enrol users with a
107
The state must have an 'offering' attribute.
109
def _to_python(self, value, state):
110
if ("enrol_" + value) not in state.offering.get_permissions(state.user):
111
raise formencode.Invalid('Not allowed to assign users that role',
116
class EnrolSchema(formencode.Schema):
117
user = formencode.All(NoEnrolmentValidator(), UserValidator())
118
role = formencode.All(formencode.validators.OneOf(
119
["lecturer", "tutor", "student"]),
120
RoleEnrolmentValidator(),
121
formencode.validators.UnicodeString())
124
class EnrolmentsView(XHTMLView):
125
"""A page which displays all users enrolled in an offering."""
126
template = 'templates/enrolments.html'
129
def populate(self, req, ctx):
130
ctx['offering'] = self.context
132
class EnrolView(XHTMLView):
133
"""A form to enrol a user in an offering."""
134
template = 'templates/enrol.html'
138
def filter(self, stream, ctx):
139
return stream | HTMLFormFiller(data=ctx['data'])
141
def populate(self, req, ctx):
142
if req.method == 'POST':
143
data = dict(req.get_fieldstorage())
145
validator = EnrolSchema()
146
req.offering = self.context # XXX: Getting into state.
147
data = validator.to_python(data, state=req)
148
self.context.enrol(data['user'], data['role'])
150
req.throw_redirect(req.uri)
151
except formencode.Invalid, e:
152
errors = e.unpack_errors()
157
ctx['data'] = data or {}
158
ctx['offering'] = self.context
159
ctx['roles_auth'] = self.context.get_permissions(req.user)
160
ctx['errors'] = errors
162
class OfferingProjectsView(XHTMLView):
163
"""View the projects for an offering."""
164
template = 'templates/offering_projects.html'
168
def populate(self, req, ctx):
169
self.plugin_styles[Plugin] = ["project.css"]
170
self.plugin_scripts[Plugin] = ["project.js"]
172
ctx['offering'] = self.context
173
ctx['projectsets'] = []
174
ctx['OfferingRESTView'] = OfferingRESTView
176
#Open the projectset Fragment, and render it for inclusion
177
#into the ProjectSets page
178
#XXX: This could be a lot cleaner
179
loader = genshi.template.TemplateLoader(".", auto_reload=True)
181
set_fragment = os.path.join(os.path.dirname(__file__),
182
"templates/projectset_fragment.html")
183
project_fragment = os.path.join(os.path.dirname(__file__),
184
"templates/project_fragment.html")
186
for projectset in self.context.project_sets:
187
settmpl = loader.load(set_fragment)
190
setCtx['projectset'] = projectset
191
setCtx['projects'] = []
192
setCtx['GroupsView'] = GroupsView
193
setCtx['ProjectSetRESTView'] = ProjectSetRESTView
195
for project in projectset.projects:
196
projecttmpl = loader.load(project_fragment)
197
projectCtx = Context()
198
projectCtx['req'] = req
199
projectCtx['project'] = project
201
setCtx['projects'].append(
202
projecttmpl.generate(projectCtx))
204
ctx['projectsets'].append(settmpl.generate(setCtx))
207
class ProjectView(XHTMLView):
208
"""View the submissions for a ProjectSet"""
209
template = "templates/project.html"
213
def build_subversion_url(self, svnroot, submission):
214
princ = submission.assessed.principal
216
if isinstance(princ, User):
217
path = 'users/%s' % princ.login
219
path = 'groups/%s_%s_%s_%s' % (
220
princ.project_set.offering.subject.short_name,
221
princ.project_set.offering.semester.year,
222
princ.project_set.offering.semester.semester,
225
return urlparse.urljoin(
227
os.path.join(path, submission.path[1:] if
228
submission.path.startswith(os.sep) else
231
def populate(self, req, ctx):
232
self.plugin_styles[Plugin] = ["project.css"]
234
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
235
ctx['build_subversion_url'] = self.build_subversion_url
236
ctx['svn_addr'] = req.config['urls']['svn_addr']
237
ctx['project'] = self.context
238
ctx['user'] = req.user
240
class OfferingEnrolmentSet(object):
241
def __init__(self, offering):
242
self.offering = offering
244
class Plugin(ViewPlugin, MediaPlugin):
245
forward_routes = (root_to_subject, subject_to_offering,
246
offering_to_project, offering_to_projectset)
247
reverse_routes = (subject_url, offering_url, projectset_url, project_url)
249
views = [(ApplicationRoot, ('subjects', '+index'), SubjectsView),
250
(Offering, ('+enrolments', '+index'), EnrolmentsView),
251
(Offering, ('+enrolments', '+new'), EnrolView),
252
(Offering, ('+projects', '+index'), OfferingProjectsView),
253
(Project, '+index', ProjectView),
255
(Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
256
(ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
257
(Project, '+index', ProjectRESTView, 'api'),
260
breadcrumbs = {Subject: SubjectBreadcrumb,
261
Offering: OfferingBreadcrumb,
262
User: UserBreadcrumb,
263
Project: ProjectBreadcrumb,
267
('subjects', 'Subjects',
268
'View subject content and complete worksheets',
269
'subjects.png', 'subjects', 5)
272
media = 'subject-media'