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
from storm.locals import Desc
37
31
from ivle.webapp.base.xhtml import XHTMLView
38
32
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
from ivle.webapp.errors import NotFound
34
from ivle.database import Subject, Semester
43
35
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
56
38
class SubjectsView(XHTMLView):
57
39
'''The view of the list of subjects.'''
58
template = 'templates/subjects.html'
40
template = 'subjects.html'
61
43
def authorize(self, req):
62
return req.user is not None
46
return req.user.enrolments.count() > 0
64
48
def populate(self, req, ctx):
65
ctx['user'] = req.user
66
49
ctx['semesters'] = []
67
50
for semester in req.store.find(Semester).order_by(Desc(Semester.year),
68
51
Desc(Semester.semester)):
70
53
if enrolments.count():
71
54
ctx['semesters'].append((semester, enrolments))
74
class UserValidator(formencode.FancyValidator):
75
"""A FormEncode validator that turns a username into a user.
77
The state must have a 'store' attribute, which is the Storm store
79
def _to_python(self, value, state):
80
user = User.get_by_login(state.store, value)
84
raise formencode.Invalid('User does not exist', value, state)
87
class NoEnrolmentValidator(formencode.FancyValidator):
88
"""A FormEncode validator that ensures absence of an enrolment.
90
The state must have an 'offering' attribute.
92
def _to_python(self, value, state):
93
if state.offering.get_enrolment(value):
94
raise formencode.Invalid('User already enrolled', value, state)
98
class EnrolSchema(formencode.Schema):
99
user = formencode.All(NoEnrolmentValidator(), UserValidator())
102
class EnrolmentsView(XHTMLView):
103
"""A page which displays all users enrolled in an offering."""
104
template = 'templates/enrolments.html'
107
def populate(self, req, ctx):
108
ctx['offering'] = self.context
110
class EnrolView(XHTMLView):
111
"""A form to enrol a user in an offering."""
112
template = 'templates/enrol.html'
116
def filter(self, stream, ctx):
117
return stream | HTMLFormFiller(data=ctx['data'])
119
def populate(self, req, ctx):
120
if req.method == 'POST':
121
data = dict(req.get_fieldstorage())
123
validator = EnrolSchema()
124
req.offering = self.context # XXX: Getting into state.
125
data = validator.to_python(data, state=req)
126
self.context.enrol(data['user'])
128
req.throw_redirect(req.uri)
129
except formencode.Invalid, e:
130
errors = e.unpack_errors()
135
ctx['data'] = data or {}
136
ctx['offering'] = self.context
137
ctx['errors'] = errors
139
class OfferingProjectsView(XHTMLView):
140
"""View the projects for an offering."""
141
template = 'templates/offering_projects.html'
145
def populate(self, req, ctx):
146
self.plugin_styles[Plugin] = ["project.css"]
147
self.plugin_scripts[Plugin] = ["project.js"]
149
ctx['offering'] = self.context
150
ctx['projectsets'] = []
151
ctx['OfferingRESTView'] = OfferingRESTView
153
#Open the projectset Fragment, and render it for inclusion
154
#into the ProjectSets page
155
#XXX: This could be a lot cleaner
156
loader = genshi.template.TemplateLoader(".", auto_reload=True)
158
set_fragment = os.path.join(os.path.dirname(__file__),
159
"templates/projectset_fragment.html")
160
project_fragment = os.path.join(os.path.dirname(__file__),
161
"templates/project_fragment.html")
163
for projectset in self.context.project_sets:
164
settmpl = loader.load(set_fragment)
167
setCtx['projectset'] = projectset
168
setCtx['projects'] = []
169
setCtx['GroupsView'] = GroupsView
170
setCtx['ProjectSetRESTView'] = ProjectSetRESTView
172
for project in projectset.projects:
173
projecttmpl = loader.load(project_fragment)
174
projectCtx = Context()
175
projectCtx['req'] = req
176
projectCtx['project'] = project
178
setCtx['projects'].append(
179
projecttmpl.generate(projectCtx))
181
ctx['projectsets'].append(settmpl.generate(setCtx))
184
class ProjectView(XHTMLView):
185
"""View the submissions for a ProjectSet"""
186
template = "templates/project.html"
190
def build_subversion_url(self, svnroot, submission):
191
princ = submission.assessed.principal
193
if isinstance(princ, User):
194
path = 'users/%s' % princ.login
196
path = 'groups/%s_%s_%s_%s' % (
197
princ.project_set.offering.subject.short_name,
198
princ.project_set.offering.semester.year,
199
princ.project_set.offering.semester.semester,
202
return urlparse.urljoin(
204
os.path.join(path, submission.path[1:] if
205
submission.path.startswith(os.sep) else
208
def populate(self, req, ctx):
209
self.plugin_styles[Plugin] = ["project.css"]
211
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
212
ctx['build_subversion_url'] = self.build_subversion_url
213
ctx['svn_addr'] = req.config['urls']['svn_addr']
214
ctx['project'] = self.context
215
ctx['user'] = req.user
217
class OfferingEnrolmentSet(object):
218
def __init__(self, offering):
219
self.offering = offering
221
56
class Plugin(ViewPlugin, MediaPlugin):
222
forward_routes = (root_to_subject, subject_to_offering,
223
offering_to_project, offering_to_projectset)
224
reverse_routes = (subject_url, offering_url, projectset_url, project_url)
226
views = [(ApplicationRoot, ('subjects', '+index'), SubjectsView),
227
(Offering, ('+enrolments', '+index'), EnrolmentsView),
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,
58
('subjects/', SubjectsView),
244
62
('subjects', 'Subjects',