65
52
if enrolments.count():
66
53
ctx['semesters'].append((semester, enrolments))
69
class UserValidator(formencode.FancyValidator):
70
"""A FormEncode validator that turns a username into a user.
72
The state must have a 'store' attribute, which is the Storm store
74
def _to_python(self, value, state):
75
user = User.get_by_login(state.store, value)
79
raise formencode.Invalid('User does not exist', value, state)
82
class NoEnrolmentValidator(formencode.FancyValidator):
83
"""A FormEncode validator that ensures absence of an enrolment.
85
The state must have an 'offering' attribute.
87
def _to_python(self, value, state):
88
if state.offering.get_enrolment(value):
89
raise formencode.Invalid('User already enrolled', value, state)
93
class EnrolSchema(formencode.Schema):
94
user = formencode.All(NoEnrolmentValidator(), UserValidator())
97
class EnrolView(XHTMLView):
98
"""A form to enrol a user in an offering."""
99
template = 'templates/enrol.html'
103
def __init__(self, req, subject, year, semester):
104
"""Find the given offering by subject, year and semester."""
105
self.context = req.store.find(Offering,
106
Offering.subject_id == Subject.id,
107
Subject.short_name == subject,
108
Offering.semester_id == Semester.id,
109
Semester.year == year,
110
Semester.semester == semester).one()
115
def filter(self, stream, ctx):
116
return stream | HTMLFormFiller(data=ctx['data'])
118
def populate(self, req, ctx):
119
if req.method == 'POST':
120
data = dict(req.get_fieldstorage())
122
validator = EnrolSchema()
123
req.offering = self.context # XXX: Getting into state.
124
data = validator.to_python(data, state=req)
125
self.context.enrol(data['user'])
127
req.throw_redirect(req.uri)
128
except formencode.Invalid, e:
129
errors = e.unpack_errors()
134
ctx['data'] = data or {}
135
ctx['offering'] = self.context
136
ctx['errors'] = errors
138
class OfferingProjectsView(XHTMLView):
139
"""View the projects for an offering."""
140
template = 'templates/offering_projects.html'
144
def __init__(self, req, subject, year, semester):
145
self.context = req.store.find(Offering,
146
Offering.subject_id == Subject.id,
147
Subject.short_name == subject,
148
Offering.semester_id == Semester.id,
149
Semester.year == year,
150
Semester.semester == semester).one()
155
def project_url(self, projectset, project):
156
return "/subjects/%s/%s/%s/+projects/%s" % (
157
self.context.subject.short_name,
158
self.context.semester.year,
159
self.context.semester.semester,
163
def new_project_url(self, projectset):
164
return "/api/subjects/" + self.context.subject.short_name + "/" +\
165
self.context.semester.year + "/" + \
166
self.context.semester.semester + "/+projectsets/" +\
167
str(projectset.id) + "/+projects/+new"
169
def populate(self, req, ctx):
170
self.plugin_styles[Plugin] = ["project.css"]
171
self.plugin_scripts[Plugin] = ["project.js"]
172
ctx['offering'] = self.context
173
ctx['projectsets'] = []
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)
188
setCtx['projectset'] = projectset
189
setCtx['new_project_url'] = self.new_project_url(projectset)
190
setCtx['projects'] = []
192
for project in projectset.projects:
193
projecttmpl = loader.load(project_fragment)
194
projectCtx = Context()
195
projectCtx['project'] = project
196
projectCtx['project_url'] = self.project_url(projectset, project)
198
setCtx['projects'].append(
199
projecttmpl.generate(projectCtx))
201
ctx['projectsets'].append(settmpl.generate(setCtx))
204
class ProjectView(XHTMLView):
205
"""View the submissions for a ProjectSet"""
206
template = "templates/project.html"
210
def __init__(self, req, subject, year, semester, project):
211
self.context = req.store.find(Project,
212
Project.short_name == project,
213
Project.project_set_id == ProjectSet.id,
214
ProjectSet.offering_id == Offering.id,
215
Offering.semester_id == Semester.id,
216
Semester.year == year,
217
Semester.semester == semester,
218
Offering.subject_id == Subject.id,
219
Subject.short_name == subject).one()
220
if self.context is None:
223
def build_subversion_url(self, svnroot, submission):
224
princ = submission.assessed.principal
226
if isinstance(princ, User):
227
path = 'users/%s' % princ.login
229
path = 'groups/%s_%s_%s_%s' % (
230
princ.project_set.offering.subject.short_name,
231
princ.project_set.offering.semester.year,
232
princ.project_set.offering.semester.semester,
235
return urlparse.urljoin(
237
os.path.join(path, submission.path[1:] if
238
submission.path.startswith(os.sep) else
241
def populate(self, req, ctx):
242
self.plugin_styles[Plugin] = ["project.css"]
244
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
245
ctx['build_subversion_url'] = self.build_subversion_url
246
ctx['svn_addr'] = req.config['urls']['svn_addr']
247
ctx['project'] = self.context
248
ctx['user'] = req.user
250
55
class Plugin(ViewPlugin, MediaPlugin):
252
57
('subjects/', SubjectsView),
253
('subjects/:subject/:year/:semester/+enrolments/+new', EnrolView),
254
('subjects/:subject/:year/:semester/+projects', OfferingProjectsView),
255
('subjects/:subject/:year/:semester/+projects/:project', ProjectView),
257
('api/subjects/:subject/:year/:semester/+projectsets/+new',
259
('api/subjects/:subject/:year/:semester/+projectsets/:projectset/+projects/+new',
261
('api/subjects/:subject/:year/:semester/+projects/:project',