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,\
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
58
template = 'templates/subjects.html'
41
61
def authorize(self, req):
42
62
return req.user is not None
44
64
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)
65
ctx['user'] = req.user
67
for semester in req.store.find(Semester).order_by(Desc(Semester.year),
68
Desc(Semester.semester)):
69
enrolments = semester.enrolments.find(user=req.user)
70
if enrolments.count():
71
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 EnrolView(XHTMLView):
103
"""A form to enrol a user in an offering."""
104
template = 'templates/enrol.html'
108
def filter(self, stream, ctx):
109
return stream | HTMLFormFiller(data=ctx['data'])
111
def populate(self, req, ctx):
112
if req.method == 'POST':
113
data = dict(req.get_fieldstorage())
115
validator = EnrolSchema()
116
req.offering = self.context # XXX: Getting into state.
117
data = validator.to_python(data, state=req)
118
self.context.enrol(data['user'])
120
req.throw_redirect(req.uri)
121
except formencode.Invalid, e:
122
errors = e.unpack_errors()
127
ctx['data'] = data or {}
128
ctx['offering'] = self.context
129
ctx['errors'] = errors
131
class OfferingProjectsView(XHTMLView):
132
"""View the projects for an offering."""
133
template = 'templates/offering_projects.html'
137
def project_url(self, projectset, project):
138
return "/subjects/%s/%s/%s/+projects/%s" % (
139
self.context.subject.short_name,
140
self.context.semester.year,
141
self.context.semester.semester,
145
def populate(self, req, ctx):
146
self.plugin_styles[Plugin] = ["project.css"]
147
self.plugin_scripts[Plugin] = ["project.js"]
148
ctx['offering'] = self.context
149
ctx['projectsets'] = []
151
#Open the projectset Fragment, and render it for inclusion
152
#into the ProjectSets page
153
#XXX: This could be a lot cleaner
154
loader = genshi.template.TemplateLoader(".", auto_reload=True)
156
set_fragment = os.path.join(os.path.dirname(__file__),
157
"templates/projectset_fragment.html")
158
project_fragment = os.path.join(os.path.dirname(__file__),
159
"templates/project_fragment.html")
161
for projectset in self.context.project_sets:
162
settmpl = loader.load(set_fragment)
165
setCtx['projectset'] = projectset
166
setCtx['projects'] = []
167
setCtx['GroupsView'] = GroupsView
168
setCtx['ProjectSetRESTView'] = ProjectSetRESTView
170
for project in projectset.projects:
171
projecttmpl = loader.load(project_fragment)
172
projectCtx = Context()
173
projectCtx['req'] = req
174
projectCtx['project'] = project
176
setCtx['projects'].append(
177
projecttmpl.generate(projectCtx))
179
ctx['projectsets'].append(settmpl.generate(setCtx))
182
class ProjectView(XHTMLView):
183
"""View the submissions for a ProjectSet"""
184
template = "templates/project.html"
188
def build_subversion_url(self, svnroot, submission):
189
princ = submission.assessed.principal
191
if isinstance(princ, User):
192
path = 'users/%s' % princ.login
194
path = 'groups/%s_%s_%s_%s' % (
195
princ.project_set.offering.subject.short_name,
196
princ.project_set.offering.semester.year,
197
princ.project_set.offering.semester.semester,
200
return urlparse.urljoin(
202
os.path.join(path, submission.path[1:] if
203
submission.path.startswith(os.sep) else
206
def populate(self, req, ctx):
207
self.plugin_styles[Plugin] = ["project.css"]
209
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
210
ctx['build_subversion_url'] = self.build_subversion_url
211
ctx['svn_addr'] = req.config['urls']['svn_addr']
212
ctx['project'] = self.context
213
ctx['user'] = req.user
215
class OfferingEnrolmentSet(object):
216
def __init__(self, offering):
217
self.offering = offering
67
219
class Plugin(ViewPlugin, MediaPlugin):
69
('subjects/', SubjectsView),
220
forward_routes = (root_to_subject, subject_to_offering,
221
offering_to_project, offering_to_projectset)
222
reverse_routes = (subject_url, offering_url, projectset_url, project_url)
224
views = [(ApplicationRoot, ('subjects', '+index'), SubjectsView),
225
(Offering, ('+enrolments', '+new'), EnrolView),
226
(Offering, ('+projects', '+index'), OfferingProjectsView),
227
(Project, '+index', ProjectView),
229
(Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
230
(ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
231
(Project, '+index', ProjectRESTView, 'api'),
234
breadcrumbs = {Subject: SubjectBreadcrumb,
235
Offering: OfferingBreadcrumb,
236
User: UserBreadcrumb,
237
Project: ProjectBreadcrumb,
73
('subjects', 'Subjects', 'Announcements and information about the '
74
'subjects you are enrolled in.', 'subjects.png', 'subjects', 5)
241
('subjects', 'Subjects',
242
'View subject content and complete worksheets',
243
'subjects.png', 'subjects', 5)
77
246
media = 'subject-media'