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
38
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
31
from ivle.webapp.errors import NotFound
32
from ivle.database import Subject
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
47
from ivle.webapp.admin.offeringservice import OfferingRESTView
48
from ivle.webapp.admin.publishing import (root_to_subject,
49
subject_to_offering, offering_to_projectset, offering_to_project,
50
subject_url, offering_url, projectset_url, project_url)
51
from ivle.webapp.admin.breadcrumbs import (SubjectBreadcrumb,
52
OfferingBreadcrumb, UserBreadcrumb, ProjectBreadcrumb)
53
from ivle.webapp.groups import GroupsView
36
55
class SubjectsView(XHTMLView):
37
56
'''The view of the list of subjects.'''
38
template = 'subjects.html'
39
appname = 'subjects' # XXX
57
template = 'templates/subjects.html'
41
60
def authorize(self, req):
42
61
return req.user is not None
44
63
def populate(self, req, ctx):
45
enrolled_subjects = req.user.subjects
46
unenrolled_subjects = [subject for subject in
47
req.store.find(Subject)
48
if subject not in enrolled_subjects]
50
ctx['enrolled_subjects'] = []
51
ctx['other_subjects'] = []
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)
64
ctx['user'] = req.user
66
for semester in req.store.find(Semester).order_by(Desc(Semester.year),
67
Desc(Semester.semester)):
69
# For admins, show all subjects in the system
70
offerings = list(semester.offerings.find())
72
offerings = [enrolment.offering for enrolment in
73
semester.enrolments.find(user=req.user)]
75
ctx['semesters'].append((semester, offerings))
78
class UserValidator(formencode.FancyValidator):
79
"""A FormEncode validator that turns a username into a user.
81
The state must have a 'store' attribute, which is the Storm store
83
def _to_python(self, value, state):
84
user = User.get_by_login(state.store, value)
88
raise formencode.Invalid('User does not exist', value, state)
91
class NoEnrolmentValidator(formencode.FancyValidator):
92
"""A FormEncode validator that ensures absence of an enrolment.
94
The state must have an 'offering' attribute.
96
def _to_python(self, value, state):
97
if state.offering.get_enrolment(value):
98
raise formencode.Invalid('User already enrolled', value, state)
102
class RoleEnrolmentValidator(formencode.FancyValidator):
103
"""A FormEncode validator that checks permission to enrol users with a
106
The state must have an 'offering' attribute.
108
def _to_python(self, value, state):
109
if ("enrol_" + value) not in state.offering.get_permissions(state.user):
110
raise formencode.Invalid('Not allowed to assign users that role',
115
class EnrolSchema(formencode.Schema):
116
user = formencode.All(NoEnrolmentValidator(), UserValidator())
117
role = formencode.All(formencode.validators.OneOf(
118
["lecturer", "tutor", "student"]),
119
RoleEnrolmentValidator(),
120
formencode.validators.UnicodeString())
123
class EnrolmentsView(XHTMLView):
124
"""A page which displays all users enrolled in an offering."""
125
template = 'templates/enrolments.html'
128
def populate(self, req, ctx):
129
ctx['offering'] = self.context
131
class EnrolView(XHTMLView):
132
"""A form to enrol a user in an offering."""
133
template = 'templates/enrol.html'
137
def filter(self, stream, ctx):
138
return stream | HTMLFormFiller(data=ctx['data'])
140
def populate(self, req, ctx):
141
if req.method == 'POST':
142
data = dict(req.get_fieldstorage())
144
validator = EnrolSchema()
145
req.offering = self.context # XXX: Getting into state.
146
data = validator.to_python(data, state=req)
147
self.context.enrol(data['user'], data['role'])
149
req.throw_redirect(req.uri)
150
except formencode.Invalid, e:
151
errors = e.unpack_errors()
156
ctx['data'] = data or {}
157
ctx['offering'] = self.context
158
ctx['roles_auth'] = self.context.get_permissions(req.user)
159
ctx['errors'] = errors
161
class OfferingProjectsView(XHTMLView):
162
"""View the projects for an offering."""
163
template = 'templates/offering_projects.html'
167
def populate(self, req, ctx):
168
self.plugin_styles[Plugin] = ["project.css"]
169
self.plugin_scripts[Plugin] = ["project.js"]
171
ctx['offering'] = self.context
172
ctx['projectsets'] = []
173
ctx['OfferingRESTView'] = OfferingRESTView
175
#Open the projectset Fragment, and render it for inclusion
176
#into the ProjectSets page
177
#XXX: This could be a lot cleaner
178
loader = genshi.template.TemplateLoader(".", auto_reload=True)
180
set_fragment = os.path.join(os.path.dirname(__file__),
181
"templates/projectset_fragment.html")
182
project_fragment = os.path.join(os.path.dirname(__file__),
183
"templates/project_fragment.html")
185
for projectset in self.context.project_sets:
186
settmpl = loader.load(set_fragment)
189
setCtx['projectset'] = projectset
190
setCtx['projects'] = []
191
setCtx['GroupsView'] = GroupsView
192
setCtx['ProjectSetRESTView'] = ProjectSetRESTView
194
for project in projectset.projects:
195
projecttmpl = loader.load(project_fragment)
196
projectCtx = Context()
197
projectCtx['req'] = req
198
projectCtx['project'] = project
200
setCtx['projects'].append(
201
projecttmpl.generate(projectCtx))
203
ctx['projectsets'].append(settmpl.generate(setCtx))
206
class ProjectView(XHTMLView):
207
"""View the submissions for a ProjectSet"""
208
template = "templates/project.html"
212
def build_subversion_url(self, svnroot, submission):
213
princ = submission.assessed.principal
215
if isinstance(princ, User):
216
path = 'users/%s' % princ.login
218
path = 'groups/%s_%s_%s_%s' % (
219
princ.project_set.offering.subject.short_name,
220
princ.project_set.offering.semester.year,
221
princ.project_set.offering.semester.semester,
224
return urlparse.urljoin(
226
os.path.join(path, submission.path[1:] if
227
submission.path.startswith(os.sep) else
230
def populate(self, req, ctx):
231
self.plugin_styles[Plugin] = ["project.css"]
234
ctx['GroupsView'] = GroupsView
235
ctx['EnrolView'] = EnrolView
236
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
237
ctx['build_subversion_url'] = self.build_subversion_url
238
ctx['svn_addr'] = req.config['urls']['svn_addr']
239
ctx['project'] = self.context
240
ctx['user'] = req.user
67
242
class Plugin(ViewPlugin, MediaPlugin):
69
('subjects/', SubjectsView),
243
forward_routes = (root_to_subject, subject_to_offering,
244
offering_to_project, offering_to_projectset)
245
reverse_routes = (subject_url, offering_url, projectset_url, project_url)
247
views = [(ApplicationRoot, ('subjects', '+index'), SubjectsView),
248
(Offering, ('+enrolments', '+index'), EnrolmentsView),
249
(Offering, ('+enrolments', '+new'), EnrolView),
250
(Offering, ('+projects', '+index'), OfferingProjectsView),
251
(Project, '+index', ProjectView),
253
(Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
254
(ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
257
breadcrumbs = {Subject: SubjectBreadcrumb,
258
Offering: OfferingBreadcrumb,
259
User: UserBreadcrumb,
260
Project: ProjectBreadcrumb,
73
('subjects', 'Subjects', 'Announcements and information about the '
74
'subjects you are enrolled in.', 'subjects.png', 'subjects', 5)
264
('subjects', 'Subjects',
265
'View subject content and complete worksheets',
266
'subjects.png', 'subjects', 5)
77
269
media = 'subject-media'