23
23
# A sample / testing application for IVLE.
29
from common import util
33
"""Handler for the Subjects application. Links to subject home pages."""
35
req.styles = ["media/subjects/subjects.css"]
37
handle_toplevel_menu(req)
39
handle_subject_page(req, req.path)
41
def handle_toplevel_menu(req):
42
# This is represented as a directory. Redirect and add a slash if it is
44
if req.uri[-1] != '/':
45
req.throw_redirect(req.uri + '/')
47
(enrolled_subjects, unenrolled_subjects) = \
48
common.db.DB().get_subjects_status(req.user.login)
50
def print_subject(subject):
51
if subject['url'] is None:
52
req.write(' <li>%s (no home page)</li>\n'
53
% cgi.escape(subject['subj_name']))
55
req.write(' <li><a href="%s">%s</a></li>\n'
56
% (cgi.escape(subject['url']),
57
cgi.escape(subject['subj_name'])))
59
req.content_type = "text/html"
60
req.write_html_head_foot = True
61
req.write('<div id="ivle_padding">\n')
62
req.write("<h2>IVLE Subject Homepages</h2>\n")
63
req.write("<h2>Subjects</h2>\n<ul>\n")
64
for subject in enrolled_subjects:
65
print_subject(subject)
67
if len(unenrolled_subjects) > 0:
68
req.write("<h3>Other Subjects</h3>\n")
69
req.write("<p>You are not currently enrolled in these subjects</p>\n")
71
for subject in unenrolled_subjects:
72
print_subject(subject)
76
def handle_subject_page(req, path):
77
req.content_type = "text/html"
78
req.write_html_head_foot = True # Have dispatch print head and foot
80
# Just make the iframe pointing to media/subjects
81
serve_loc = util.make_path(os.path.join('media', 'subjects', path))
82
req.write('<object class="fullscreen" type="text/html" \
83
data="%s"></iframe>'% urllib.quote(serve_loc))
31
from storm.locals import Desc
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.errors import NotFound
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
51
class SubjectsView(XHTMLView):
52
'''The view of the list of subjects.'''
53
template = 'templates/subjects.html'
56
def authorize(self, req):
57
return req.user is not None
59
def populate(self, req, ctx):
60
ctx['user'] = req.user
62
for semester in req.store.find(Semester).order_by(Desc(Semester.year),
63
Desc(Semester.semester)):
64
enrolments = semester.enrolments.find(user=req.user)
65
if enrolments.count():
66
ctx['semesters'].append((semester, enrolments))
69
class UserValidator(formencode.FancyValidator):
70
"""A FormEncode validator that turns a username into a user.
72
The state must have a 'store' attribute, which is the Storm store
74
def _to_python(self, value, state):
75
user = User.get_by_login(state.store, value)
79
raise formencode.Invalid('User does not exist', value, state)
82
class NoEnrolmentValidator(formencode.FancyValidator):
83
"""A FormEncode validator that ensures absence of an enrolment.
85
The state must have an 'offering' attribute.
87
def _to_python(self, value, state):
88
if state.offering.get_enrolment(value):
89
raise formencode.Invalid('User already enrolled', value, state)
93
class EnrolSchema(formencode.Schema):
94
user = formencode.All(NoEnrolmentValidator(), UserValidator())
97
class EnrolView(XHTMLView):
98
"""A form to enrol a user in an offering."""
99
template = 'templates/enrol.html'
103
def __init__(self, req, subject, year, semester):
104
"""Find the given offering by subject, year and semester."""
105
self.context = req.store.find(Offering,
106
Offering.subject_id == Subject.id,
107
Subject.short_name == subject,
108
Offering.semester_id == Semester.id,
109
Semester.year == year,
110
Semester.semester == semester).one()
115
def filter(self, stream, ctx):
116
return stream | HTMLFormFiller(data=ctx['data'])
118
def populate(self, req, ctx):
119
if req.method == 'POST':
120
data = dict(req.get_fieldstorage())
122
validator = EnrolSchema()
123
req.offering = self.context # XXX: Getting into state.
124
data = validator.to_python(data, state=req)
125
self.context.enrol(data['user'])
127
req.throw_redirect(req.uri)
128
except formencode.Invalid, e:
129
errors = e.unpack_errors()
134
ctx['data'] = data or {}
135
ctx['offering'] = self.context
136
ctx['errors'] = errors
138
class OfferingProjectsView(XHTMLView):
139
"""View the projects for an offering."""
140
template = 'templates/offering_projects.html'
144
def __init__(self, req, subject, year, semester):
145
self.context = req.store.find(Offering,
146
Offering.subject_id == Subject.id,
147
Subject.short_name == subject,
148
Offering.semester_id == Semester.id,
149
Semester.year == year,
150
Semester.semester == semester).one()
155
def project_url(self, projectset, project):
156
return "/subjects/%s/%s/%s/+projects/%s" % (
157
self.context.subject.short_name,
158
self.context.semester.year,
159
self.context.semester.semester,
163
def new_project_url(self, projectset):
164
return "/api/subjects/" + self.context.subject.short_name + "/" +\
165
self.context.semester.year + "/" + \
166
self.context.semester.semester + "/+projectsets/" +\
167
str(projectset.id) + "/+projects/+new"
169
def populate(self, req, ctx):
170
self.plugin_styles[Plugin] = ["project.css"]
171
self.plugin_scripts[Plugin] = ["project.js"]
172
ctx['offering'] = self.context
173
ctx['projectsets'] = []
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)
188
setCtx['projectset'] = projectset
189
setCtx['new_project_url'] = self.new_project_url(projectset)
190
setCtx['projects'] = []
192
for project in projectset.projects:
193
projecttmpl = loader.load(project_fragment)
194
projectCtx = Context()
195
projectCtx['project'] = project
196
projectCtx['project_url'] = self.project_url(projectset, project)
198
setCtx['projects'].append(
199
projecttmpl.generate(projectCtx))
201
ctx['projectsets'].append(settmpl.generate(setCtx))
204
class ProjectView(XHTMLView):
205
"""View the submissions for a ProjectSet"""
206
template = "templates/project.html"
210
def __init__(self, req, subject, year, semester, project):
211
self.context = req.store.find(Project,
212
Project.short_name == project,
213
Project.project_set_id == ProjectSet.id,
214
ProjectSet.offering_id == Offering.id,
215
Offering.semester_id == Semester.id,
216
Semester.year == year,
217
Semester.semester == semester,
218
Offering.subject_id == Subject.id,
219
Subject.short_name == subject).one()
220
if self.context is None:
223
def build_subversion_url(self, svnroot, submission):
224
princ = submission.assessed.principal
226
if isinstance(princ, User):
227
path = 'users/%s' % princ.login
229
path = 'groups/%s_%s_%s_%s' % (
230
princ.project_set.offering.subject.short_name,
231
princ.project_set.offering.semester.year,
232
princ.project_set.offering.semester.semester,
235
return urlparse.urljoin(
237
os.path.join(path, submission.path[1:] if
238
submission.path.startswith(os.sep) else
241
def populate(self, req, ctx):
242
self.plugin_styles[Plugin] = ["project.css"]
244
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
245
ctx['build_subversion_url'] = self.build_subversion_url
246
ctx['svn_addr'] = req.config['urls']['svn_addr']
247
ctx['project'] = self.context
248
ctx['user'] = req.user
250
class Plugin(ViewPlugin, MediaPlugin):
252
('subjects/', SubjectsView),
253
('subjects/:subject/:year/:semester/+enrolments/+new', EnrolView),
254
('subjects/:subject/:year/:semester/+projects', OfferingProjectsView),
255
('subjects/:subject/:year/:semester/+projects/:project', ProjectView),
257
('api/subjects/:subject/:year/:semester/+projectsets/+new',
259
('api/subjects/:subject/:year/:semester/+projectsets/:projectset/+projects/+new',
261
('api/subjects/:subject/:year/:semester/+projects/:project',
267
('subjects', 'Subjects',
268
'View subject content and complete worksheets',
269
'subjects.png', 'subjects', 5)
272
media = 'subject-media'