23
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
30
43
from ivle import util
33
import genshi.template
36
"""Handler for the Subjects application. Links to subject home pages."""
38
req.styles = ["media/subjects/subjects.css"]
39
ctx = genshi.template.Context()
41
# This is represented as a directory. Redirect and add a slash if it is
43
if req.uri[-1] != '/':
44
req.throw_redirect(req.uri + '/')
45
ctx['whichpage'] = "toplevel"
46
handle_toplevel_menu(req, ctx)
48
ctx['whichpage'] = "subject"
49
handle_subject_page(req, req.path, ctx)
51
loader = genshi.template.TemplateLoader(".", auto_reload=True)
52
tmpl = loader.load(util.make_local_path("apps/subjects/template.html"))
53
req.write(tmpl.generate(ctx).render('html')) #'xhtml', doctype='xhtml'))
55
def handle_toplevel_menu(req, ctx):
57
enrolled_subjects = req.user.subjects
58
unenrolled_subjects = [subject for subject in
59
req.store.find(ivle.database.Subject)
60
if subject not in enrolled_subjects]
62
ctx['enrolled_subjects'] = []
63
ctx['other_subjects'] = []
65
req.content_type = "text/html"
66
req.write_html_head_foot = True
68
for subject in enrolled_subjects:
70
new_subj['name'] = subject.name
71
new_subj['url'] = subject.url
72
ctx['enrolled_subjects'].append(new_subj)
74
if len(unenrolled_subjects) > 0:
75
for subject in unenrolled_subjects:
77
new_subj['name'] = subject.name
78
new_subj['url'] = subject.url
79
ctx['other_subjects'].append(new_subj)
82
def handle_subject_page(req, path, ctx):
83
req.content_type = "text/html"
84
req.write_html_head_foot = True # Have dispatch print head and foot
86
# Just make the iframe pointing to media/subjects
87
ctx['serve_loc'] = urllib.quote(util.make_path(os.path.join('media', 'subjects', path)))
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)
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)):
68
enrolments = semester.enrolments.find(user=req.user)
69
if enrolments.count():
70
ctx['semesters'].append((semester, enrolments))
73
class UserValidator(formencode.FancyValidator):
74
"""A FormEncode validator that turns a username into a user.
76
The state must have a 'store' attribute, which is the Storm store
78
def _to_python(self, value, state):
79
user = User.get_by_login(state.store, value)
83
raise formencode.Invalid('User does not exist', value, state)
86
class NoEnrolmentValidator(formencode.FancyValidator):
87
"""A FormEncode validator that ensures absence of an enrolment.
89
The state must have an 'offering' attribute.
91
def _to_python(self, value, state):
92
if state.offering.get_enrolment(value):
93
raise formencode.Invalid('User already enrolled', value, state)
97
class EnrolSchema(formencode.Schema):
98
user = formencode.All(NoEnrolmentValidator(), UserValidator())
101
class EnrolView(XHTMLView):
102
"""A form to enrol a user in an offering."""
103
template = 'templates/enrol.html'
107
def filter(self, stream, ctx):
108
return stream | HTMLFormFiller(data=ctx['data'])
110
def populate(self, req, ctx):
111
if req.method == 'POST':
112
data = dict(req.get_fieldstorage())
114
validator = EnrolSchema()
115
req.offering = self.context # XXX: Getting into state.
116
data = validator.to_python(data, state=req)
117
self.context.enrol(data['user'])
119
req.throw_redirect(req.uri)
120
except formencode.Invalid, e:
121
errors = e.unpack_errors()
126
ctx['data'] = data or {}
127
ctx['offering'] = self.context
128
ctx['errors'] = errors
130
class OfferingProjectsView(XHTMLView):
131
"""View the projects for an offering."""
132
template = 'templates/offering_projects.html'
136
def project_url(self, projectset, project):
137
return "/subjects/%s/%s/%s/+projects/%s" % (
138
self.context.subject.short_name,
139
self.context.semester.year,
140
self.context.semester.semester,
144
def new_project_url(self, projectset):
145
return "/api/subjects/" + self.context.subject.short_name + "/" +\
146
self.context.semester.year + "/" + \
147
self.context.semester.semester + "/+projectsets/" +\
148
str(projectset.id) + "/+projects/+new"
150
def populate(self, req, ctx):
151
self.plugin_styles[Plugin] = ["project.css"]
152
self.plugin_scripts[Plugin] = ["project.js"]
153
ctx['offering'] = self.context
154
ctx['projectsets'] = []
156
#Open the projectset Fragment, and render it for inclusion
157
#into the ProjectSets page
158
#XXX: This could be a lot cleaner
159
loader = genshi.template.TemplateLoader(".", auto_reload=True)
161
set_fragment = os.path.join(os.path.dirname(__file__),
162
"templates/projectset_fragment.html")
163
project_fragment = os.path.join(os.path.dirname(__file__),
164
"templates/project_fragment.html")
166
for projectset in self.context.project_sets:
167
settmpl = loader.load(set_fragment)
169
setCtx['projectset'] = projectset
170
setCtx['new_project_url'] = self.new_project_url(projectset)
171
setCtx['projects'] = []
173
for project in projectset.projects:
174
projecttmpl = loader.load(project_fragment)
175
projectCtx = Context()
176
projectCtx['project'] = project
177
projectCtx['project_url'] = self.project_url(projectset, project)
179
setCtx['projects'].append(
180
projecttmpl.generate(projectCtx))
182
ctx['projectsets'].append(settmpl.generate(setCtx))
185
class ProjectView(XHTMLView):
186
"""View the submissions for a ProjectSet"""
187
template = "templates/project.html"
191
def build_subversion_url(self, svnroot, submission):
192
princ = submission.assessed.principal
194
if isinstance(princ, User):
195
path = 'users/%s' % princ.login
197
path = 'groups/%s_%s_%s_%s' % (
198
princ.project_set.offering.subject.short_name,
199
princ.project_set.offering.semester.year,
200
princ.project_set.offering.semester.semester,
203
return urlparse.urljoin(
205
os.path.join(path, submission.path[1:] if
206
submission.path.startswith(os.sep) else
209
def populate(self, req, ctx):
210
self.plugin_styles[Plugin] = ["project.css"]
212
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
213
ctx['build_subversion_url'] = self.build_subversion_url
214
ctx['svn_addr'] = req.config['urls']['svn_addr']
215
ctx['project'] = self.context
216
ctx['user'] = req.user
218
class OfferingEnrolmentSet(object):
219
def __init__(self, offering):
220
self.offering = offering
222
class Plugin(ViewPlugin, MediaPlugin):
223
forward_routes = (root_to_subject, subject_to_offering,
224
offering_to_project, offering_to_projectset)
225
reverse_routes = (subject_url, offering_url, projectset_url, project_url)
227
views = [(ApplicationRoot, ('subjects', '+index'), SubjectsView),
228
(Offering, ('+enrolments', '+new'), EnrolView),
229
(Offering, ('+projects', '+index'), OfferingProjectsView),
230
(Project, '+index', ProjectView),
232
(Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
233
(ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
234
(Project, '+index', ProjectRESTView, 'api'),
237
breadcrumbs = {Subject: SubjectBreadcrumb,
238
Offering: OfferingBreadcrumb,
239
User: UserBreadcrumb,
240
Project: ProjectBreadcrumb,
244
('subjects', 'Subjects',
245
'View subject content and complete worksheets',
246
'subjects.png', 'subjects', 5)
249
media = 'subject-media'