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
29
37
from ivle.webapp.base.xhtml import XHTMLView
30
from ivle.webapp.base.plugins import BasePlugin
31
from ivle.webapp.errors import NotFound
32
from ivle.database import Subject
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
33
43
from ivle import util
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)
54
from ivle.webapp.groups import GroupsView
36
56
class SubjectsView(XHTMLView):
37
57
'''The view of the list of subjects.'''
38
template = 'subjects.html'
39
appname = 'subjects' # XXX
41
def populate(self, req, ctx):
42
req.styles = ["media/subjects/subjects.css"]
44
enrolled_subjects = req.user.subjects
45
unenrolled_subjects = [subject for subject in
46
req.store.find(Subject)
47
if subject not in enrolled_subjects]
49
ctx['enrolled_subjects'] = []
50
ctx['other_subjects'] = []
52
req.content_type = "text/html"
53
req.write_html_head_foot = True
55
for subject in enrolled_subjects:
57
new_subj['name'] = subject.name
58
new_subj['url'] = subject.url
59
ctx['enrolled_subjects'].append(new_subj)
61
if len(unenrolled_subjects) > 0:
62
for subject in unenrolled_subjects:
64
new_subj['name'] = subject.name
65
new_subj['url'] = subject.url
66
ctx['other_subjects'].append(new_subj)
69
class SubjectView(XHTMLView):
70
'''The view of a subject.'''
71
template = 'subject.html'
72
appname = 'subjects' # XXX
74
def __init__(self, req, subject, path):
75
self.subject = req.store.find(Subject, code=subject).one()
78
def populate(self, req, ctx):
79
if self.subject is None:
82
ctx['serve_loc'] = urllib.quote(util.make_path(os.path.join('media',
83
'subjects', self.subject.code, self.path)))
85
class Plugin(BasePlugin):
87
('subjects/', SubjectsView),
88
('subjects/:subject', SubjectView, {'path': ''}),
89
('subjects/:subject/*(path)', SubjectView),
58
template = 'templates/subjects.html'
61
def authorize(self, req):
62
return req.user is not None
64
def populate(self, req, ctx):
65
ctx['user'] = req.user
67
for semester in req.store.find(Semester).order_by(Desc(Semester.year),
68
Desc(Semester.semester)):
70
# For admins, show all subjects in the system
71
offerings = list(semester.offerings.find())
73
offerings = [enrolment.offering for enrolment in
74
semester.enrolments.find(user=req.user)]
76
ctx['semesters'].append((semester, offerings))
79
class UserValidator(formencode.FancyValidator):
80
"""A FormEncode validator that turns a username into a user.
82
The state must have a 'store' attribute, which is the Storm store
84
def _to_python(self, value, state):
85
user = User.get_by_login(state.store, value)
89
raise formencode.Invalid('User does not exist', value, state)
92
class NoEnrolmentValidator(formencode.FancyValidator):
93
"""A FormEncode validator that ensures absence of an enrolment.
95
The state must have an 'offering' attribute.
97
def _to_python(self, value, state):
98
if state.offering.get_enrolment(value):
99
raise formencode.Invalid('User already enrolled', value, state)
103
class EnrolSchema(formencode.Schema):
104
user = formencode.All(NoEnrolmentValidator(), UserValidator())
107
class EnrolmentsView(XHTMLView):
108
"""A page which displays all users enrolled in an offering."""
109
template = 'templates/enrolments.html'
112
def populate(self, req, ctx):
113
ctx['offering'] = self.context
115
class EnrolView(XHTMLView):
116
"""A form to enrol a user in an offering."""
117
template = 'templates/enrol.html'
121
def filter(self, stream, ctx):
122
return stream | HTMLFormFiller(data=ctx['data'])
124
def populate(self, req, ctx):
125
if req.method == 'POST':
126
data = dict(req.get_fieldstorage())
128
validator = EnrolSchema()
129
req.offering = self.context # XXX: Getting into state.
130
data = validator.to_python(data, state=req)
131
self.context.enrol(data['user'])
133
req.throw_redirect(req.uri)
134
except formencode.Invalid, e:
135
errors = e.unpack_errors()
140
ctx['data'] = data or {}
141
ctx['offering'] = self.context
142
ctx['errors'] = errors
144
class OfferingProjectsView(XHTMLView):
145
"""View the projects for an offering."""
146
template = 'templates/offering_projects.html'
150
def populate(self, req, ctx):
151
self.plugin_styles[Plugin] = ["project.css"]
152
self.plugin_scripts[Plugin] = ["project.js"]
154
ctx['offering'] = self.context
155
ctx['projectsets'] = []
156
ctx['OfferingRESTView'] = OfferingRESTView
158
#Open the projectset Fragment, and render it for inclusion
159
#into the ProjectSets page
160
#XXX: This could be a lot cleaner
161
loader = genshi.template.TemplateLoader(".", auto_reload=True)
163
set_fragment = os.path.join(os.path.dirname(__file__),
164
"templates/projectset_fragment.html")
165
project_fragment = os.path.join(os.path.dirname(__file__),
166
"templates/project_fragment.html")
168
for projectset in self.context.project_sets:
169
settmpl = loader.load(set_fragment)
172
setCtx['projectset'] = projectset
173
setCtx['projects'] = []
174
setCtx['GroupsView'] = GroupsView
175
setCtx['ProjectSetRESTView'] = ProjectSetRESTView
177
for project in projectset.projects:
178
projecttmpl = loader.load(project_fragment)
179
projectCtx = Context()
180
projectCtx['req'] = req
181
projectCtx['project'] = project
183
setCtx['projects'].append(
184
projecttmpl.generate(projectCtx))
186
ctx['projectsets'].append(settmpl.generate(setCtx))
189
class ProjectView(XHTMLView):
190
"""View the submissions for a ProjectSet"""
191
template = "templates/project.html"
195
def build_subversion_url(self, svnroot, submission):
196
princ = submission.assessed.principal
198
if isinstance(princ, User):
199
path = 'users/%s' % princ.login
201
path = 'groups/%s_%s_%s_%s' % (
202
princ.project_set.offering.subject.short_name,
203
princ.project_set.offering.semester.year,
204
princ.project_set.offering.semester.semester,
207
return urlparse.urljoin(
209
os.path.join(path, submission.path[1:] if
210
submission.path.startswith(os.sep) else
213
def populate(self, req, ctx):
214
self.plugin_styles[Plugin] = ["project.css"]
216
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
217
ctx['build_subversion_url'] = self.build_subversion_url
218
ctx['svn_addr'] = req.config['urls']['svn_addr']
219
ctx['project'] = self.context
220
ctx['user'] = req.user
222
class OfferingEnrolmentSet(object):
223
def __init__(self, offering):
224
self.offering = offering
226
class Plugin(ViewPlugin, MediaPlugin):
227
forward_routes = (root_to_subject, subject_to_offering,
228
offering_to_project, offering_to_projectset)
229
reverse_routes = (subject_url, offering_url, projectset_url, project_url)
231
views = [(ApplicationRoot, ('subjects', '+index'), SubjectsView),
232
(Offering, ('+enrolments', '+index'), EnrolmentsView),
233
(Offering, ('+enrolments', '+new'), EnrolView),
234
(Offering, ('+projects', '+index'), OfferingProjectsView),
235
(Project, '+index', ProjectView),
237
(Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
238
(ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
239
(Project, '+index', ProjectRESTView, 'api'),
242
breadcrumbs = {Subject: SubjectBreadcrumb,
243
Offering: OfferingBreadcrumb,
244
User: UserBreadcrumb,
245
Project: ProjectBreadcrumb,
249
('subjects', 'Subjects',
250
'View subject content and complete worksheets',
251
'subjects.png', 'subjects', 5)
254
media = 'subject-media'