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))
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(ProjectSubmission.date_submitted)[-1])
234
class Plugin(ViewPlugin, MediaPlugin):
236
('subjects/', SubjectsView),
237
('subjects/:subject/:year/:semester/+enrolments/+new', EnrolView),
238
('subjects/:subject/:year/:semester/+projects', OfferingProjectsView),
239
('subjects/:subject/:year/:semester/+projects/:project', ProjectView),
241
('api/subjects/:subject/:year/:semester/+projectsets/+new',
243
('api/subjects/:subject/:year/:semester/+projectsets/:projectset/+projects/+new',
245
('api/subjects/:subject/:year/:semester/+projects/:project',
251
('subjects', 'Subjects',
252
'View subject content and complete worksheets',
253
'subjects.png', 'subjects', 5)
256
media = 'subject-media'