29
from storm.locals import Desc
31
from genshi.filters import HTMLFormFiller
32
from genshi.template import Context, TemplateLoader
35
29
from ivle.webapp.base.xhtml import XHTMLView
36
30
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
37
31
from ivle.webapp.errors import NotFound
39
from ivle.database import Subject, Semester, Offering, Enrolment, User,\
40
ProjectSet, Project, ProjectSubmission
32
from ivle.database import Subject
41
33
from ivle import util
43
from ivle.webapp.admin.projectservice import ProjectSetRESTView,\
45
from ivle.webapp.admin.offeringservice import OfferingRESTView
48
36
class SubjectsView(XHTMLView):
49
37
'''The view of the list of subjects.'''
50
template = 'templates/subjects.html'
38
template = 'subjects.html'
53
41
def authorize(self, req):
54
42
return req.user is not None
56
44
def populate(self, req, ctx):
57
ctx['user'] = req.user
59
for semester in req.store.find(Semester).order_by(Desc(Semester.year),
60
Desc(Semester.semester)):
61
enrolments = semester.enrolments.find(user=req.user)
62
if enrolments.count():
63
ctx['semesters'].append((semester, enrolments))
66
class UserValidator(formencode.FancyValidator):
67
"""A FormEncode validator that turns a username into a user.
69
The state must have a 'store' attribute, which is the Storm store
71
def _to_python(self, value, state):
72
user = User.get_by_login(state.store, value)
76
raise formencode.Invalid('User does not exist', value, state)
79
class NoEnrolmentValidator(formencode.FancyValidator):
80
"""A FormEncode validator that ensures absence of an enrolment.
82
The state must have an 'offering' attribute.
84
def _to_python(self, value, state):
85
if state.offering.get_enrolment(value):
86
raise formencode.Invalid('User already enrolled', value, state)
90
class EnrolSchema(formencode.Schema):
91
user = formencode.All(NoEnrolmentValidator(), UserValidator())
94
class EnrolView(XHTMLView):
95
"""A form to enrol a user in an offering."""
96
template = 'templates/enrol.html'
100
def __init__(self, req, subject, year, semester):
101
"""Find the given offering by subject, year and semester."""
102
self.context = req.store.find(Offering,
103
Offering.subject_id == Subject.id,
104
Subject.short_name == subject,
105
Offering.semester_id == Semester.id,
106
Semester.year == year,
107
Semester.semester == semester).one()
112
def filter(self, stream, ctx):
113
return stream | HTMLFormFiller(data=ctx['data'])
115
def populate(self, req, ctx):
116
if req.method == 'POST':
117
data = dict(req.get_fieldstorage())
119
validator = EnrolSchema()
120
req.offering = self.context # XXX: Getting into state.
121
data = validator.to_python(data, state=req)
122
self.context.enrol(data['user'])
124
req.throw_redirect(req.uri)
125
except formencode.Invalid, e:
126
errors = e.unpack_errors()
131
ctx['data'] = data or {}
132
ctx['offering'] = self.context
133
ctx['errors'] = errors
135
class SubjectProjectSetView(XHTMLView):
136
"""View the ProjectSets for a subject."""
137
template = 'templates/subject_projects.html'
140
def __init__(self, req, subject, year, semester):
141
self.context = req.store.find(Offering,
142
Offering.subject_id == Subject.id,
143
Subject.short_name == subject,
144
Offering.semester_id == Semester.id,
145
Semester.year == year,
146
Semester.semester == semester).one()
151
def project_url(self, projectset, project):
152
return "/subjects/" + self.context.subject.short_name + "/" +\
153
self.context.semester.year + "/" + \
154
self.context.semester.semester + "/+projectsets/" +\
155
str(projectset.id) + "/+projects/" + project.short_name
157
def new_project_url(self, projectset):
158
return "/api/subjects/" + self.context.subject.short_name + "/" +\
159
self.context.semester.year + "/" + \
160
self.context.semester.semester + "/+projectsets/" +\
161
str(projectset.id) + "/+projects/+new"
163
def populate(self, req, ctx):
164
self.plugin_styles[Plugin] = ["project.css"]
165
self.plugin_scripts[Plugin] = ["project.js"]
166
ctx['offering'] = self.context
167
ctx['subject'] = self.context.subject.short_name
168
ctx['year'] = self.context.semester.year
169
ctx['semester'] = self.context.semester.semester
171
ctx['projectsets'] = []
173
#Open the projectset Fragment, and render it for inclusion
174
#into the ProjectSets page
175
#XXX: This could be a lot cleaner
176
loader = genshi.template.TemplateLoader(".", auto_reload=True)
178
set_fragment = os.path.join(os.path.dirname(__file__),
179
"templates/projectset_fragment.html")
180
project_fragment = os.path.join(os.path.dirname(__file__),
181
"templates/project_fragment.html")
183
for projectset in self.context.project_sets:
184
settmpl = loader.load(set_fragment)
186
setCtx['group_size'] = projectset.max_students_per_group
187
setCtx['projectset_id'] = projectset.id
188
setCtx['new_project_url'] = self.new_project_url(projectset)
189
setCtx['projects'] = []
191
for project in projectset.projects:
192
projecttmpl = loader.load(project_fragment)
193
projectCtx = Context()
194
projectCtx['project'] = project
195
projectCtx['project_url'] = self.project_url(projectset, project)
197
setCtx['projects'].append(
198
projecttmpl.generate(projectCtx))
200
ctx['projectsets'].append(settmpl.generate(setCtx))
203
class ProjectView(XHTMLView):
204
"""View the submissions for a ProjectSet"""
205
template = "templates/project.html"
208
def __init__(self, req, subject, year, semester, projectset, project):
209
self.context = req.store.find(Project,
210
Project.short_name == project,
211
Project.project_set_id == ProjectSet.id,
212
ProjectSet.offering_id == Offering.id,
213
Offering.semester_id == Semester.id,
214
Semester.year == year,
215
Semester.semester == semester,
216
Offering.subject_id == Subject.id,
217
Subject.short_name == subject).one()
218
if self.context is None:
221
def populate(self, req, ctx):
222
ctx['project'] = self.context
223
ctx['assesseds'] = self.context.assesseds
225
ctx['submissions'] = []
226
for assessed in self.context.assesseds:
227
if assessed.submissions.count() > 0:
228
ctx['submissions'].append(
229
assessed.submissions.order_by(ProjectSubmission.date_submitted)[:-1])
45
ctx['enrolments'] = req.user.active_enrolments
232
47
class Plugin(ViewPlugin, MediaPlugin):
234
49
('subjects/', SubjectsView),
235
('subjects/:subject/:year/:semester/+enrolments/+new', EnrolView),
236
('subjects/:subject/:year/:semester/+projects', SubjectProjectSetView),
237
('subjects/:subject/:year/:semester/+projectsets/:projectset/+projects/:project', ProjectView),
239
('api/subjects/:subject/:year/:semester/+projectsets/+new',
241
('api/subjects/:subject/:year/:semester/+projectsets/:projectset/+projects/+new',
243
('api/subjects/:subject/:year/:semester/+projectsets/:projectset/+projects/:project',