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, 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.traversal import (root_to_subject,
50
subject_to_offering, offering_to_projectset, offering_to_project,
51
subject_url, offering_url, projectset_url, project_url)
53
class SubjectsView(XHTMLView):
54
'''The view of the list of subjects.'''
55
template = 'templates/subjects.html'
58
def authorize(self, req):
59
return req.user is not None
61
def populate(self, req, ctx):
62
ctx['user'] = req.user
64
for semester in req.store.find(Semester).order_by(Desc(Semester.year),
65
Desc(Semester.semester)):
66
enrolments = semester.enrolments.find(user=req.user)
67
if enrolments.count():
68
ctx['semesters'].append((semester, enrolments))
71
class UserValidator(formencode.FancyValidator):
72
"""A FormEncode validator that turns a username into a user.
74
The state must have a 'store' attribute, which is the Storm store
76
def _to_python(self, value, state):
77
user = User.get_by_login(state.store, value)
81
raise formencode.Invalid('User does not exist', value, state)
84
class NoEnrolmentValidator(formencode.FancyValidator):
85
"""A FormEncode validator that ensures absence of an enrolment.
87
The state must have an 'offering' attribute.
89
def _to_python(self, value, state):
90
if state.offering.get_enrolment(value):
91
raise formencode.Invalid('User already enrolled', value, state)
95
class EnrolSchema(formencode.Schema):
96
user = formencode.All(NoEnrolmentValidator(), UserValidator())
99
class EnrolView(XHTMLView):
100
"""A form to enrol a user in an offering."""
101
template = 'templates/enrol.html'
105
def filter(self, stream, ctx):
106
return stream | HTMLFormFiller(data=ctx['data'])
108
def populate(self, req, ctx):
109
if req.method == 'POST':
110
data = dict(req.get_fieldstorage())
112
validator = EnrolSchema()
113
req.offering = self.context # XXX: Getting into state.
114
data = validator.to_python(data, state=req)
115
self.context.enrol(data['user'])
117
req.throw_redirect(req.uri)
118
except formencode.Invalid, e:
119
errors = e.unpack_errors()
124
ctx['data'] = data or {}
125
ctx['offering'] = self.context
126
ctx['errors'] = errors
128
class OfferingProjectsView(XHTMLView):
129
"""View the projects for an offering."""
130
template = 'templates/offering_projects.html'
134
def project_url(self, projectset, project):
135
return "/subjects/%s/%s/%s/+projects/%s" % (
136
self.context.subject.short_name,
137
self.context.semester.year,
138
self.context.semester.semester,
142
def new_project_url(self, projectset):
143
return "/api/subjects/" + self.context.subject.short_name + "/" +\
144
self.context.semester.year + "/" + \
145
self.context.semester.semester + "/+projectsets/" +\
146
str(projectset.id) + "/+projects/+new"
148
def populate(self, req, ctx):
149
self.plugin_styles[Plugin] = ["project.css"]
150
self.plugin_scripts[Plugin] = ["project.js"]
151
ctx['offering'] = self.context
152
ctx['projectsets'] = []
154
#Open the projectset Fragment, and render it for inclusion
155
#into the ProjectSets page
156
#XXX: This could be a lot cleaner
157
loader = genshi.template.TemplateLoader(".", auto_reload=True)
159
set_fragment = os.path.join(os.path.dirname(__file__),
160
"templates/projectset_fragment.html")
161
project_fragment = os.path.join(os.path.dirname(__file__),
162
"templates/project_fragment.html")
164
for projectset in self.context.project_sets:
165
settmpl = loader.load(set_fragment)
167
setCtx['projectset'] = projectset
168
setCtx['new_project_url'] = self.new_project_url(projectset)
169
setCtx['projects'] = []
171
for project in projectset.projects:
172
projecttmpl = loader.load(project_fragment)
173
projectCtx = Context()
174
projectCtx['project'] = project
175
projectCtx['project_url'] = self.project_url(projectset, project)
177
setCtx['projects'].append(
178
projecttmpl.generate(projectCtx))
180
ctx['projectsets'].append(settmpl.generate(setCtx))
183
class ProjectView(XHTMLView):
184
"""View the submissions for a ProjectSet"""
185
template = "templates/project.html"
189
def build_subversion_url(self, svnroot, submission):
190
princ = submission.assessed.principal
192
if isinstance(princ, User):
193
path = 'users/%s' % princ.login
195
path = 'groups/%s_%s_%s_%s' % (
196
princ.project_set.offering.subject.short_name,
197
princ.project_set.offering.semester.year,
198
princ.project_set.offering.semester.semester,
201
return urlparse.urljoin(
203
os.path.join(path, submission.path[1:] if
204
submission.path.startswith(os.sep) else
207
def populate(self, req, ctx):
208
self.plugin_styles[Plugin] = ["project.css"]
210
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
211
ctx['build_subversion_url'] = self.build_subversion_url
212
ctx['svn_addr'] = req.config['urls']['svn_addr']
213
ctx['project'] = self.context
214
ctx['user'] = req.user
216
class OfferingEnrolmentSet(object):
217
def __init__(self, offering):
218
self.offering = offering
220
class Plugin(ViewPlugin, MediaPlugin):
221
forward_routes = (root_to_subject, subject_to_offering,
222
offering_to_project, offering_to_projectset)
223
reverse_routes = (subject_url, offering_url, projectset_url, project_url)
225
views = [(ApplicationRoot, ('subjects', '+index'), SubjectsView),
226
(Offering, ('+enrolments', '+new'), EnrolView),
227
(Offering, ('+projects', '+index'), OfferingProjectsView),
228
(Project, '+index', ProjectView),
230
(Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
231
(ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
232
(Project, '+index', ProjectRESTView, 'api'),
236
('subjects', 'Subjects',
237
'View subject content and complete worksheets',
238
'subjects.png', 'subjects', 5)
241
media = 'subject-media'