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.
29
from storm.locals import Desc
31
from genshi.filters import HTMLFormFiller
32
from genshi.template import Context, TemplateLoader
35
from ivle.webapp.base.xhtml import XHTMLView
36
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
37
from ivle.webapp.errors import NotFound
39
from ivle.database import Subject, Semester, Offering, Enrolment, User,\
40
ProjectSet, Project, ProjectSubmission
44
from ivle.webapp.admin.projectservice import ProjectSetRESTView,\
46
from ivle.webapp.admin.offeringservice import OfferingRESTView
49
class SubjectsView(XHTMLView):
50
'''The view of the list of subjects.'''
51
template = 'templates/subjects.html'
54
def authorize(self, req):
55
return req.user is not None
57
def populate(self, req, ctx):
58
ctx['user'] = req.user
60
for semester in req.store.find(Semester).order_by(Desc(Semester.year),
61
Desc(Semester.semester)):
62
enrolments = semester.enrolments.find(user=req.user)
63
if enrolments.count():
64
ctx['semesters'].append((semester, enrolments))
67
class UserValidator(formencode.FancyValidator):
68
"""A FormEncode validator that turns a username into a user.
70
The state must have a 'store' attribute, which is the Storm store
72
def _to_python(self, value, state):
73
user = User.get_by_login(state.store, value)
77
raise formencode.Invalid('User does not exist', value, state)
80
class NoEnrolmentValidator(formencode.FancyValidator):
81
"""A FormEncode validator that ensures absence of an enrolment.
83
The state must have an 'offering' attribute.
85
def _to_python(self, value, state):
86
if state.offering.get_enrolment(value):
87
raise formencode.Invalid('User already enrolled', value, state)
91
class EnrolSchema(formencode.Schema):
92
user = formencode.All(NoEnrolmentValidator(), UserValidator())
95
class EnrolView(XHTMLView):
96
"""A form to enrol a user in an offering."""
97
template = 'templates/enrol.html'
101
def __init__(self, req, subject, year, semester):
102
"""Find the given offering by subject, year and semester."""
103
self.context = req.store.find(Offering,
104
Offering.subject_id == Subject.id,
105
Subject.short_name == subject,
106
Offering.semester_id == Semester.id,
107
Semester.year == year,
108
Semester.semester == semester).one()
113
def filter(self, stream, ctx):
114
return stream | HTMLFormFiller(data=ctx['data'])
116
def populate(self, req, ctx):
117
if req.method == 'POST':
118
data = dict(req.get_fieldstorage())
120
validator = EnrolSchema()
121
req.offering = self.context # XXX: Getting into state.
122
data = validator.to_python(data, state=req)
123
self.context.enrol(data['user'])
125
req.throw_redirect(req.uri)
126
except formencode.Invalid, e:
127
errors = e.unpack_errors()
132
ctx['data'] = data or {}
133
ctx['offering'] = self.context
134
ctx['errors'] = errors
136
class OfferingProjectsView(XHTMLView):
137
"""View the projects for an offering."""
138
template = 'templates/offering_projects.html'
142
def __init__(self, req, subject, year, semester):
143
self.context = req.store.find(Offering,
144
Offering.subject_id == Subject.id,
145
Subject.short_name == subject,
146
Offering.semester_id == Semester.id,
147
Semester.year == year,
148
Semester.semester == semester).one()
153
def project_url(self, projectset, project):
154
return "/subjects/%s/%s/%s/+projects/%s" % (
155
self.context.subject.short_name,
156
self.context.semester.year,
157
self.context.semester.semester,
161
def new_project_url(self, projectset):
162
return "/api/subjects/" + self.context.subject.short_name + "/" +\
163
self.context.semester.year + "/" + \
164
self.context.semester.semester + "/+projectsets/" +\
165
str(projectset.id) + "/+projects/+new"
167
def populate(self, req, ctx):
168
self.plugin_styles[Plugin] = ["project.css"]
169
self.plugin_scripts[Plugin] = ["project.js"]
170
ctx['offering'] = self.context
171
ctx['projectsets'] = []
173
#Open the projectset Fragment, and render it for inclusion
174
#into the ProjectSets page
175
#XXX: This could be a lot cleaner
176
loader = genshi.template.TemplateLoader(".", auto_reload=True)
178
set_fragment = os.path.join(os.path.dirname(__file__),
179
"templates/projectset_fragment.html")
180
project_fragment = os.path.join(os.path.dirname(__file__),
181
"templates/project_fragment.html")
183
for projectset in self.context.project_sets:
184
settmpl = loader.load(set_fragment)
186
setCtx['projectset'] = projectset
187
setCtx['new_project_url'] = self.new_project_url(projectset)
188
setCtx['projects'] = []
190
for project in projectset.projects:
191
projecttmpl = loader.load(project_fragment)
192
projectCtx = Context()
193
projectCtx['project'] = project
194
projectCtx['project_url'] = self.project_url(projectset, project)
196
setCtx['projects'].append(
197
projecttmpl.generate(projectCtx))
199
ctx['projectsets'].append(settmpl.generate(setCtx))
202
class ProjectView(XHTMLView):
203
"""View the submissions for a ProjectSet"""
204
template = "templates/project.html"
208
def __init__(self, req, subject, year, semester, project):
209
self.context = req.store.find(Project,
210
Project.short_name == project,
211
Project.project_set_id == ProjectSet.id,
212
ProjectSet.offering_id == Offering.id,
213
Offering.semester_id == Semester.id,
214
Semester.year == year,
215
Semester.semester == semester,
216
Offering.subject_id == Subject.id,
217
Subject.short_name == subject).one()
218
if self.context is None:
221
def populate(self, req, ctx):
222
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
224
ctx['project'] = self.context
225
ctx['assesseds'] = self.context.assesseds
227
ctx['submissions'] = []
228
for assessed in self.context.assesseds:
229
if assessed.submissions.count() > 0:
230
ctx['submissions'].append(
231
assessed.submissions.order_by(
232
ProjectSubmission.date_submitted)[-1])
233
ctx['assigned'] = self.context.project_set.get_assigned()
235
class Plugin(ViewPlugin, MediaPlugin):
237
('subjects/', SubjectsView),
238
('subjects/:subject/:year/:semester/+enrolments/+new', EnrolView),
239
('subjects/:subject/:year/:semester/+projects', OfferingProjectsView),
240
('subjects/:subject/:year/:semester/+projects/:project', ProjectView),
242
('api/subjects/:subject/:year/:semester/+projectsets/+new',
244
('api/subjects/:subject/:year/:semester/+projectsets/:projectset/+projects/+new',
246
('api/subjects/:subject/:year/:semester/+projects/:project',
252
('subjects', 'Subjects',
253
'View subject content and complete worksheets',
254
'subjects.png', 'subjects', 5)
257
media = 'subject-media'