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)
36
53
class SubjectsView(XHTMLView):
37
54
'''The view of the list of subjects.'''
38
template = 'subjects.html'
55
template = 'templates/subjects.html'
41
58
def authorize(self, req):
42
59
return req.user is not None
44
61
def populate(self, req, ctx):
45
ctx['enrolments'] = req.user.active_enrolments
62
ctx['user'] = req.user
64
for semester in req.store.find(Semester).order_by(Desc(Semester.year),
65
Desc(Semester.semester)):
66
enrolments = semester.enrolments.find(user=req.user)
67
if enrolments.count():
68
ctx['semesters'].append((semester, enrolments))
71
class UserValidator(formencode.FancyValidator):
72
"""A FormEncode validator that turns a username into a user.
74
The state must have a 'store' attribute, which is the Storm store
76
def _to_python(self, value, state):
77
user = User.get_by_login(state.store, value)
81
raise formencode.Invalid('User does not exist', value, state)
84
class NoEnrolmentValidator(formencode.FancyValidator):
85
"""A FormEncode validator that ensures absence of an enrolment.
87
The state must have an 'offering' attribute.
89
def _to_python(self, value, state):
90
if state.offering.get_enrolment(value):
91
raise formencode.Invalid('User already enrolled', value, state)
95
class EnrolSchema(formencode.Schema):
96
user = formencode.All(NoEnrolmentValidator(), UserValidator())
99
class EnrolView(XHTMLView):
100
"""A form to enrol a user in an offering."""
101
template = 'templates/enrol.html'
105
def filter(self, stream, ctx):
106
return stream | HTMLFormFiller(data=ctx['data'])
108
def populate(self, req, ctx):
109
if req.method == 'POST':
110
data = dict(req.get_fieldstorage())
112
validator = EnrolSchema()
113
req.offering = self.context # XXX: Getting into state.
114
data = validator.to_python(data, state=req)
115
self.context.enrol(data['user'])
117
req.throw_redirect(req.uri)
118
except formencode.Invalid, e:
119
errors = e.unpack_errors()
124
ctx['data'] = data or {}
125
ctx['offering'] = self.context
126
ctx['errors'] = errors
128
class OfferingProjectsView(XHTMLView):
129
"""View the projects for an offering."""
130
template = 'templates/offering_projects.html'
134
def project_url(self, projectset, project):
135
return "/subjects/%s/%s/%s/+projects/%s" % (
136
self.context.subject.short_name,
137
self.context.semester.year,
138
self.context.semester.semester,
142
def new_project_url(self, projectset):
143
return "/api/subjects/" + self.context.subject.short_name + "/" +\
144
self.context.semester.year + "/" + \
145
self.context.semester.semester + "/+projectsets/" +\
146
str(projectset.id) + "/+projects/+new"
148
def populate(self, req, ctx):
149
self.plugin_styles[Plugin] = ["project.css"]
150
self.plugin_scripts[Plugin] = ["project.js"]
151
ctx['offering'] = self.context
152
ctx['projectsets'] = []
154
#Open the projectset Fragment, and render it for inclusion
155
#into the ProjectSets page
156
#XXX: This could be a lot cleaner
157
loader = genshi.template.TemplateLoader(".", auto_reload=True)
159
set_fragment = os.path.join(os.path.dirname(__file__),
160
"templates/projectset_fragment.html")
161
project_fragment = os.path.join(os.path.dirname(__file__),
162
"templates/project_fragment.html")
164
for projectset in self.context.project_sets:
165
settmpl = loader.load(set_fragment)
167
setCtx['projectset'] = projectset
168
setCtx['new_project_url'] = self.new_project_url(projectset)
169
setCtx['projects'] = []
171
for project in projectset.projects:
172
projecttmpl = loader.load(project_fragment)
173
projectCtx = Context()
174
projectCtx['project'] = project
175
projectCtx['project_url'] = self.project_url(projectset, project)
177
setCtx['projects'].append(
178
projecttmpl.generate(projectCtx))
180
ctx['projectsets'].append(settmpl.generate(setCtx))
183
class ProjectView(XHTMLView):
184
"""View the submissions for a ProjectSet"""
185
template = "templates/project.html"
189
def build_subversion_url(self, svnroot, submission):
190
princ = submission.assessed.principal
192
if isinstance(princ, User):
193
path = 'users/%s' % princ.login
195
path = 'groups/%s_%s_%s_%s' % (
196
princ.project_set.offering.subject.short_name,
197
princ.project_set.offering.semester.year,
198
princ.project_set.offering.semester.semester,
201
return urlparse.urljoin(
203
os.path.join(path, submission.path[1:] if
204
submission.path.startswith(os.sep) else
207
def populate(self, req, ctx):
208
self.plugin_styles[Plugin] = ["project.css"]
210
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
211
ctx['build_subversion_url'] = self.build_subversion_url
212
ctx['svn_addr'] = req.config['urls']['svn_addr']
213
ctx['project'] = self.context
214
ctx['user'] = req.user
216
class OfferingEnrolmentSet(object):
217
def __init__(self, offering):
218
self.offering = offering
47
220
class Plugin(ViewPlugin, MediaPlugin):
49
('subjects/', SubjectsView),
221
forward_routes = (root_to_subject, subject_to_offering,
222
offering_to_project, offering_to_projectset)
223
reverse_routes = (subject_url, offering_url, projectset_url, project_url)
225
views = [(ApplicationRoot, ('subjects', '+index'), SubjectsView),
226
(Offering, ('+enrolments', '+new'), EnrolView),
227
(Offering, ('+projects', '+index'), OfferingProjectsView),
228
(Project, '+index', ProjectView),
230
(Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'),
231
(ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'),
232
(Project, '+index', ProjectRESTView, 'api'),
53
236
('subjects', 'Subjects',