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 ViewPlugin
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)
36
55
class SubjectsView(XHTMLView):
37
56
'''The view of the list of subjects.'''
38
template = 'subjects.html'
39
appname = 'subjects' # XXX
41
def populate(self, req, ctx):
42
enrolled_subjects = req.user.subjects
43
unenrolled_subjects = [subject for subject in
44
req.store.find(Subject)
45
if subject not in enrolled_subjects]
47
ctx['enrolled_subjects'] = []
48
ctx['other_subjects'] = []
50
req.content_type = "text/html"
51
req.write_html_head_foot = True
53
for subject in enrolled_subjects:
55
new_subj['name'] = subject.name
56
new_subj['url'] = subject.url
57
ctx['enrolled_subjects'].append(new_subj)
59
if len(unenrolled_subjects) > 0:
60
for subject in unenrolled_subjects:
62
new_subj['name'] = subject.name
63
new_subj['url'] = subject.url
64
ctx['other_subjects'].append(new_subj)
67
class Plugin(ViewPlugin):
69
('subjects/', SubjectsView),
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'