621
by mattgiuca
Added 2 new apps: home and subjects. Both fairly incomplete, just a basic |
1 |
# IVLE
|
2 |
# Copyright (C) 2007-2008 The University of Melbourne
|
|
3 |
#
|
|
4 |
# This program is free software; you can redistribute it and/or modify
|
|
5 |
# it under the terms of the GNU General Public License as published by
|
|
6 |
# the Free Software Foundation; either version 2 of the License, or
|
|
7 |
# (at your option) any later version.
|
|
8 |
#
|
|
9 |
# This program is distributed in the hope that it will be useful,
|
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12 |
# GNU General Public License for more details.
|
|
13 |
#
|
|
14 |
# You should have received a copy of the GNU General Public License
|
|
15 |
# along with this program; if not, write to the Free Software
|
|
16 |
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17 |
||
18 |
# App: subjects
|
|
19 |
# Author: Matt Giuca
|
|
20 |
# Date: 29/2/2008
|
|
21 |
||
22 |
# This is an IVLE application.
|
|
23 |
# A sample / testing application for IVLE.
|
|
24 |
||
627
by mattgiuca
subjects: Now presents a top-level subjects menu (same as tutorials). |
25 |
import os |
1165.3.61
by William Grant
Provide a Subversion command to grab each submission. |
26 |
import os.path |
627
by mattgiuca
subjects: Now presents a top-level subjects menu (same as tutorials). |
27 |
import urllib |
1165.3.61
by William Grant
Provide a Subversion command to grab each submission. |
28 |
import urlparse |
627
by mattgiuca
subjects: Now presents a top-level subjects menu (same as tutorials). |
29 |
import cgi |
30 |
||
1294.2.52
by William Grant
Port subject-related views to object traversal. |
31 |
from storm.locals import Desc, Store |
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
32 |
import genshi |
1149
by William Grant
Allow tutors and lecturers to enrol people in their offerings. |
33 |
from genshi.filters import HTMLFormFiller |
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
34 |
from genshi.template import Context, TemplateLoader |
1149
by William Grant
Allow tutors and lecturers to enrol people in their offerings. |
35 |
import formencode |
1532
by William Grant
Add subject creation/editing UI. Not linked just yet. |
36 |
import formencode.validators |
1125
by William Grant
Rework ivle.webapp.admin.subjects#SubjectsView to split offerings nicely by |
37 |
|
1539
by William Grant
Move BaseFormView into ivle.webapp.base.forms. |
38 |
from ivle.webapp.base.forms import BaseFormView |
39 |
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin |
|
1099.1.34
by William Grant
Split up ivle.webapp.base.views into ivle.webapp.base.{rest,xhtml}, as it was |
40 |
from ivle.webapp.base.xhtml import XHTMLView |
1294.2.52
by William Grant
Port subject-related views to object traversal. |
41 |
from ivle.webapp import ApplicationRoot |
1165.3.9
by Nick Chadwick
merge from trunk |
42 |
|
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
43 |
from ivle.database import Subject, Semester, Offering, Enrolment, User,\ |
1165.3.8
by Nick Chadwick
Added a total submissions and total assesseds to the project view |
44 |
ProjectSet, Project, ProjectSubmission |
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
45 |
from ivle import util |
1165.3.14
by William Grant
Improve ProjectView's template substantially. |
46 |
import ivle.date |
621
by mattgiuca
Added 2 new apps: home and subjects. Both fairly incomplete, just a basic |
47 |
|
1375.1.2
by William Grant
Remove ProjectRESTView, which did nothing and was unused. |
48 |
from ivle.webapp.admin.projectservice import ProjectSetRESTView |
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
49 |
from ivle.webapp.admin.offeringservice import OfferingRESTView |
1294.3.2
by William Grant
Router->Publisher |
50 |
from ivle.webapp.admin.publishing import (root_to_subject, |
1294.2.70
by William Grant
Split out ivle.webapp.admin's routes into annotated functions in ivle.webapp.traversal. |
51 |
subject_to_offering, offering_to_projectset, offering_to_project, |
52 |
subject_url, offering_url, projectset_url, project_url) |
|
1294.2.96
by William Grant
Add a UserBreadcrumb. |
53 |
from ivle.webapp.admin.breadcrumbs import (SubjectBreadcrumb, |
1294.2.98
by William Grant
Add a ProjectBreadcrumb. |
54 |
OfferingBreadcrumb, UserBreadcrumb, ProjectBreadcrumb) |
1533
by William Grant
Add a subject listing with new/edit icons. |
55 |
from ivle.webapp.core import Plugin as CorePlugin |
1358
by William Grant
Use the publishing framework to generate URLs to projectsets. |
56 |
from ivle.webapp.groups import GroupsView |
1533
by William Grant
Add a subject listing with new/edit icons. |
57 |
from ivle.webapp.media import media_url |
1442.1.31
by William Grant
Show the worksheet listing with marks and schtuff on the offering index. |
58 |
from ivle.webapp.tutorial import Plugin as TutorialPlugin |
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
59 |
|
1099.1.20
by William Grant
ivle.webapp.admin.subject: Port www/apps/subjects to new framework. |
60 |
class SubjectsView(XHTMLView): |
61 |
'''The view of the list of subjects.'''
|
|
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
62 |
template = 'templates/subjects.html' |
1116
by William Grant
Move the old tutorial views into the 'subjects' tab, so they get the right |
63 |
tab = 'subjects' |
1099.1.20
by William Grant
ivle.webapp.admin.subject: Port www/apps/subjects to new framework. |
64 |
|
1099.1.110
by William Grant
Implement an authorization system in the new framework. This breaks the REST |
65 |
def authorize(self, req): |
1138
by William Grant
SubjectsView now tells users if they have no enrolments. |
66 |
return req.user is not None |
1099.1.110
by William Grant
Implement an authorization system in the new framework. This breaks the REST |
67 |
|
1099.1.20
by William Grant
ivle.webapp.admin.subject: Port www/apps/subjects to new framework. |
68 |
def populate(self, req, ctx): |
1525
by Matt Giuca
ivle/webapp/admin/templates/subjects.html: Use req.publisher.generate rather than rolling its own offering_url function. |
69 |
ctx['req'] = req |
1139
by William Grant
Show group administration links on SubjectsView where privileges allow it. |
70 |
ctx['user'] = req.user |
1125
by William Grant
Rework ivle.webapp.admin.subjects#SubjectsView to split offerings nicely by |
71 |
ctx['semesters'] = [] |
1533
by William Grant
Add a subject listing with new/edit icons. |
72 |
ctx['mediapath'] = media_url(req, CorePlugin, 'images/') |
73 |
ctx['SubjectEdit'] = SubjectEdit |
|
74 |
||
1125
by William Grant
Rework ivle.webapp.admin.subjects#SubjectsView to split offerings nicely by |
75 |
for semester in req.store.find(Semester).order_by(Desc(Semester.year), |
76 |
Desc(Semester.semester)): |
|
1372
by Matt Giuca
admin/subject: Admin users now see all offerings in the system, not just the ones they are enrolled in. |
77 |
if req.user.admin: |
78 |
# For admins, show all subjects in the system
|
|
79 |
offerings = list(semester.offerings.find()) |
|
80 |
else: |
|
81 |
offerings = [enrolment.offering for enrolment in |
|
1371
by Matt Giuca
admin/subject: Now sends a list of offerings the user is enrolled in to the Genshi template, rather than a list of enrolment objects (of which only the offering is observed). This allows us to send non-enrolment offerings to the template. |
82 |
semester.enrolments.find(user=req.user)] |
83 |
if len(offerings): |
|
84 |
ctx['semesters'].append((semester, offerings)) |
|
1099.1.20
by William Grant
ivle.webapp.admin.subject: Port www/apps/subjects to new framework. |
85 |
|
1533
by William Grant
Add a subject listing with new/edit icons. |
86 |
# Admins get a separate list of subjects so they can add/edit.
|
87 |
if req.user.admin: |
|
88 |
ctx['subjects'] = req.store.find(Subject).order_by(Subject.name) |
|
89 |
||
1532
by William Grant
Add subject creation/editing UI. Not linked just yet. |
90 |
|
91 |
class SubjectShortNameUniquenessValidator(formencode.FancyValidator): |
|
92 |
"""A FormEncode validator that checks that a subject name is unused.
|
|
93 |
||
94 |
The subject referenced by state.existing_subject is permitted
|
|
95 |
to hold that name. If any other object holds it, the input is rejected.
|
|
96 |
"""
|
|
97 |
def __init__(self, matching=None): |
|
98 |
self.matching = matching |
|
99 |
||
100 |
def _to_python(self, value, state): |
|
101 |
if (state.store.find( |
|
102 |
Subject, short_name=value).one() not in |
|
103 |
(None, state.existing_subject)): |
|
104 |
raise formencode.Invalid( |
|
105 |
'Short name already taken', value, state) |
|
106 |
return value |
|
107 |
||
108 |
||
109 |
class SubjectSchema(formencode.Schema): |
|
110 |
short_name = formencode.All( |
|
111 |
SubjectShortNameUniquenessValidator(), |
|
112 |
formencode.validators.UnicodeString(not_empty=True)) |
|
113 |
name = formencode.validators.UnicodeString(not_empty=True) |
|
114 |
code = formencode.validators.UnicodeString(not_empty=True) |
|
115 |
||
116 |
||
1546
by William Grant
Derive the subject forms from BaseFormView. |
117 |
class SubjectFormView(BaseFormView): |
1532
by William Grant
Add subject creation/editing UI. Not linked just yet. |
118 |
"""An abstract form to add or edit a subject."""
|
119 |
tab = 'subjects' |
|
120 |
||
121 |
def authorize(self, req): |
|
122 |
return req.user is not None and req.user.admin |
|
123 |
||
124 |
def populate_state(self, state): |
|
125 |
state.existing_subject = None |
|
126 |
||
1546
by William Grant
Derive the subject forms from BaseFormView. |
127 |
@property
|
128 |
def validator(self): |
|
129 |
return SubjectSchema() |
|
130 |
||
131 |
def get_return_url(self, obj): |
|
132 |
return '/subjects' |
|
1532
by William Grant
Add subject creation/editing UI. Not linked just yet. |
133 |
|
134 |
||
135 |
class SubjectNew(SubjectFormView): |
|
136 |
"""A form to create a subject."""
|
|
137 |
template = 'templates/subject-new.html' |
|
138 |
||
139 |
def get_default_data(self, req): |
|
140 |
return {} |
|
141 |
||
1546
by William Grant
Derive the subject forms from BaseFormView. |
142 |
def save_object(self, req, data): |
1532
by William Grant
Add subject creation/editing UI. Not linked just yet. |
143 |
new_subject = Subject() |
144 |
new_subject.short_name = data['short_name'] |
|
145 |
new_subject.name = data['name'] |
|
146 |
new_subject.code = data['code'] |
|
147 |
||
148 |
req.store.add(new_subject) |
|
149 |
return new_subject |
|
150 |
||
151 |
||
152 |
class SubjectEdit(SubjectFormView): |
|
153 |
"""A form to edit a subject."""
|
|
154 |
template = 'templates/subject-edit.html' |
|
155 |
||
156 |
def populate_state(self, state): |
|
157 |
state.existing_subject = self.context |
|
158 |
||
159 |
def get_default_data(self, req): |
|
160 |
return { |
|
161 |
'short_name': self.context.short_name, |
|
162 |
'name': self.context.name, |
|
163 |
'code': self.context.code, |
|
164 |
}
|
|
165 |
||
1546
by William Grant
Derive the subject forms from BaseFormView. |
166 |
def save_object(self, req, data): |
1532
by William Grant
Add subject creation/editing UI. Not linked just yet. |
167 |
self.context.short_name = data['short_name'] |
168 |
self.context.name = data['name'] |
|
169 |
self.context.code = data['code'] |
|
170 |
||
171 |
return self.context |
|
172 |
||
173 |
||
1543
by William Grant
Semester creation UI. |
174 |
class SemesterUniquenessValidator(formencode.FancyValidator): |
175 |
"""A FormEncode validator that checks that a semester is unique.
|
|
176 |
||
177 |
There cannot be more than one semester for the same year and semester.
|
|
178 |
"""
|
|
179 |
def _to_python(self, value, state): |
|
180 |
if (state.store.find( |
|
181 |
Semester, year=value['year'], semester=value['semester'] |
|
182 |
).count() > 0): |
|
183 |
raise formencode.Invalid( |
|
184 |
'Semester already exists', value, state) |
|
185 |
return value |
|
186 |
||
187 |
||
188 |
class SemesterSchema(formencode.Schema): |
|
189 |
year = formencode.validators.UnicodeString() |
|
190 |
semester = formencode.validators.UnicodeString() |
|
191 |
chained_validators = [SemesterUniquenessValidator()] |
|
192 |
||
193 |
||
194 |
class SemesterNew(BaseFormView): |
|
195 |
"""A form to create a semester."""
|
|
196 |
template = 'templates/semester-new.html' |
|
197 |
tab = 'subjects' |
|
198 |
||
199 |
def authorize(self, req): |
|
200 |
return req.user is not None and req.user.admin |
|
201 |
||
202 |
@property
|
|
203 |
def validator(self): |
|
204 |
return SemesterSchema() |
|
205 |
||
206 |
def get_default_data(self, req): |
|
207 |
return {} |
|
208 |
||
209 |
def save_object(self, req, data): |
|
210 |
new_semester = Semester() |
|
211 |
new_semester.year = data['year'] |
|
212 |
new_semester.semester = data['semester'] |
|
213 |
||
214 |
req.store.add(new_semester) |
|
215 |
return new_semester |
|
216 |
||
217 |
def get_return_url(self, obj): |
|
218 |
return '/subjects' |
|
219 |
||
220 |
||
1442.1.2
by William Grant
Add basic (ie. pretty much empty) offering index. |
221 |
class OfferingView(XHTMLView): |
222 |
"""The home page of an offering."""
|
|
223 |
template = 'templates/offering.html' |
|
224 |
tab = 'subjects' |
|
225 |
permission = 'view' |
|
226 |
||
227 |
def populate(self, req, ctx): |
|
1442.1.31
by William Grant
Show the worksheet listing with marks and schtuff on the offering index. |
228 |
# Need the worksheet result styles.
|
229 |
self.plugin_styles[TutorialPlugin] = ['tutorial.css'] |
|
1442.1.2
by William Grant
Add basic (ie. pretty much empty) offering index. |
230 |
ctx['context'] = self.context |
231 |
ctx['req'] = req |
|
1544
by Matt Giuca
Added an argument 'config' to every single get_permissions method throughout the program. All calls to get_permissions pass a config. This is to allow per-site policy configurations on permissions. |
232 |
ctx['permissions'] = self.context.get_permissions(req.user,req.config) |
1515
by Matt Giuca
Submit view: The projects list is now identical (except for radio buttons) to the view on the subjects page. It is much clearer and contains more info. The code is somewhat different, because it's a table, not a list, so I didn't abstract it. Moved a function out of subject.py to ivle.util, as it is shared by both views. |
233 |
ctx['format_submission_principal'] = util.format_submission_principal |
1442.1.10
by William Grant
Add a nice padded list of projects. |
234 |
ctx['format_datetime'] = ivle.date.make_date_nice |
235 |
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph |
|
1451.1.7
by William Grant
Add a 'Change details' link on the offering index, pointing to +edit. |
236 |
ctx['OfferingEdit'] = OfferingEdit |
1558
by William Grant
Allow tutors to manage groups. |
237 |
ctx['GroupsView'] = GroupsView |
1442.1.2
by William Grant
Add basic (ie. pretty much empty) offering index. |
238 |
|
1442.1.31
by William Grant
Show the worksheet listing with marks and schtuff on the offering index. |
239 |
# As we go, calculate the total score for this subject
|
240 |
# (Assessable worksheets only, mandatory problems only)
|
|
241 |
||
242 |
ctx['worksheets'], problems_total, problems_done = ( |
|
243 |
ivle.worksheet.utils.create_list_of_fake_worksheets_and_stats( |
|
244 |
req.store, req.user, self.context)) |
|
245 |
||
246 |
ctx['exercises_total'] = problems_total |
|
247 |
ctx['exercises_done'] = problems_done |
|
248 |
if problems_total > 0: |
|
249 |
if problems_done >= problems_total: |
|
250 |
ctx['worksheets_complete_class'] = "complete" |
|
251 |
elif problems_done > 0: |
|
252 |
ctx['worksheets_complete_class'] = "semicomplete" |
|
253 |
else: |
|
254 |
ctx['worksheets_complete_class'] = "incomplete" |
|
255 |
# Calculate the final percentage and mark for the subject
|
|
256 |
(ctx['exercises_pct'], ctx['worksheet_mark'], |
|
257 |
ctx['worksheet_max_mark']) = ( |
|
258 |
ivle.worksheet.utils.calculate_mark( |
|
259 |
problems_done, problems_total)) |
|
260 |
||
1442.1.2
by William Grant
Add basic (ie. pretty much empty) offering index. |
261 |
|
1537
by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings. |
262 |
class SubjectValidator(formencode.FancyValidator): |
263 |
"""A FormEncode validator that turns a subject name into a subject.
|
|
264 |
||
265 |
The state must have a 'store' attribute, which is the Storm store
|
|
266 |
to use.
|
|
267 |
"""
|
|
268 |
def _to_python(self, value, state): |
|
269 |
subject = state.store.find(Subject, short_name=value).one() |
|
270 |
if subject: |
|
271 |
return subject |
|
272 |
else: |
|
273 |
raise formencode.Invalid('Subject does not exist', value, state) |
|
274 |
||
275 |
||
276 |
class SemesterValidator(formencode.FancyValidator): |
|
277 |
"""A FormEncode validator that turns a string into a semester.
|
|
278 |
||
279 |
The string should be of the form 'year/semester', eg. '2009/1'.
|
|
280 |
||
281 |
The state must have a 'store' attribute, which is the Storm store
|
|
282 |
to use.
|
|
283 |
"""
|
|
284 |
def _to_python(self, value, state): |
|
285 |
try: |
|
286 |
year, semester = value.split('/') |
|
287 |
except ValueError: |
|
288 |
year = semester = None |
|
289 |
||
290 |
semester = state.store.find( |
|
291 |
Semester, year=year, semester=semester).one() |
|
292 |
if semester: |
|
293 |
return semester |
|
294 |
else: |
|
295 |
raise formencode.Invalid('Semester does not exist', value, state) |
|
296 |
||
297 |
||
298 |
class OfferingUniquenessValidator(formencode.FancyValidator): |
|
299 |
"""A FormEncode validator that checks that an offering is unique.
|
|
300 |
||
301 |
There cannot be more than one offering in the same year and semester.
|
|
302 |
||
303 |
The offering referenced by state.existing_offering is permitted to
|
|
304 |
hold that year and semester tuple. If any other object holds it, the
|
|
305 |
input is rejected.
|
|
306 |
"""
|
|
307 |
def _to_python(self, value, state): |
|
308 |
if (state.store.find( |
|
309 |
Offering, subject=value['subject'], |
|
310 |
semester=value['semester']).one() not in |
|
311 |
(None, state.existing_offering)): |
|
312 |
raise formencode.Invalid( |
|
313 |
'Offering already exists', value, state) |
|
314 |
return value |
|
315 |
||
316 |
||
1451.1.5
by William Grant
Add an OfferingEdit view, for setting the description and URL. |
317 |
class OfferingSchema(formencode.Schema): |
1451.1.8
by William Grant
Allow unsetting of the URL or description. |
318 |
description = formencode.validators.UnicodeString( |
319 |
if_missing=None, not_empty=False) |
|
320 |
url = formencode.validators.URL(if_missing=None, not_empty=False) |
|
1451.1.5
by William Grant
Add an OfferingEdit view, for setting the description and URL. |
321 |
|
322 |
||
1537
by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings. |
323 |
class OfferingAdminSchema(OfferingSchema): |
324 |
subject = formencode.All( |
|
325 |
SubjectValidator(), formencode.validators.UnicodeString()) |
|
326 |
semester = formencode.All( |
|
327 |
SemesterValidator(), formencode.validators.UnicodeString()) |
|
328 |
chained_validators = [OfferingUniquenessValidator()] |
|
329 |
||
330 |
||
331 |
class OfferingEdit(BaseFormView): |
|
1451.1.5
by William Grant
Add an OfferingEdit view, for setting the description and URL. |
332 |
"""A form to edit an offering's details."""
|
333 |
template = 'templates/offering-edit.html' |
|
1523
by William Grant
Declare appropriate tabs on the rest of the views. |
334 |
tab = 'subjects' |
1451.1.5
by William Grant
Add an OfferingEdit view, for setting the description and URL. |
335 |
permission = 'edit' |
336 |
||
1537
by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings. |
337 |
@property
|
338 |
def validator(self): |
|
339 |
if self.req.user.admin: |
|
340 |
return OfferingAdminSchema() |
|
1451.1.5
by William Grant
Add an OfferingEdit view, for setting the description and URL. |
341 |
else: |
1537
by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings. |
342 |
return OfferingSchema() |
343 |
||
344 |
def populate(self, req, ctx): |
|
345 |
super(OfferingEdit, self).populate(req, ctx) |
|
346 |
ctx['subjects'] = req.store.find(Subject) |
|
347 |
ctx['semesters'] = req.store.find(Semester) |
|
348 |
||
349 |
def populate_state(self, state): |
|
350 |
state.existing_offering = self.context |
|
351 |
||
352 |
def get_default_data(self, req): |
|
353 |
return { |
|
354 |
'subject': self.context.subject.short_name, |
|
355 |
'semester': self.context.semester.year + '/' + |
|
356 |
self.context.semester.semester, |
|
357 |
'url': self.context.url, |
|
358 |
'description': self.context.description, |
|
1451.1.5
by William Grant
Add an OfferingEdit view, for setting the description and URL. |
359 |
}
|
1537
by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings. |
360 |
|
361 |
def save_object(self, req, data): |
|
362 |
if req.user.admin: |
|
363 |
self.context.subject = data['subject'] |
|
364 |
self.context.semester = data['semester'] |
|
365 |
self.context.description = data['description'] |
|
366 |
self.context.url = unicode(data['url']) if data['url'] else None |
|
367 |
return self.context |
|
368 |
||
369 |
||
370 |
class OfferingNew(BaseFormView): |
|
371 |
"""A form to create an offering."""
|
|
372 |
template = 'templates/offering-new.html' |
|
373 |
tab = 'subjects' |
|
374 |
||
375 |
def authorize(self, req): |
|
376 |
return req.user is not None and req.user.admin |
|
377 |
||
378 |
@property
|
|
379 |
def validator(self): |
|
380 |
return OfferingAdminSchema() |
|
381 |
||
382 |
def populate(self, req, ctx): |
|
383 |
super(OfferingNew, self).populate(req, ctx) |
|
384 |
ctx['subjects'] = req.store.find(Subject) |
|
385 |
ctx['semesters'] = req.store.find(Semester) |
|
386 |
||
387 |
def populate_state(self, state): |
|
388 |
state.existing_offering = None |
|
389 |
||
390 |
def get_default_data(self, req): |
|
391 |
return {} |
|
392 |
||
393 |
def save_object(self, req, data): |
|
394 |
new_offering = Offering() |
|
395 |
new_offering.subject = data['subject'] |
|
396 |
new_offering.semester = data['semester'] |
|
397 |
new_offering.description = data['description'] |
|
398 |
new_offering.url = unicode(data['url']) if data['url'] else None |
|
399 |
||
400 |
req.store.add(new_offering) |
|
401 |
return new_offering |
|
1451.1.5
by William Grant
Add an OfferingEdit view, for setting the description and URL. |
402 |
|
403 |
||
1149
by William Grant
Allow tutors and lecturers to enrol people in their offerings. |
404 |
class UserValidator(formencode.FancyValidator): |
405 |
"""A FormEncode validator that turns a username into a user.
|
|
406 |
||
407 |
The state must have a 'store' attribute, which is the Storm store
|
|
408 |
to use."""
|
|
409 |
def _to_python(self, value, state): |
|
410 |
user = User.get_by_login(state.store, value) |
|
411 |
if user: |
|
412 |
return user |
|
413 |
else: |
|
1150
by William Grant
Refuse a +enrol if the user is already enrolled. This stops overwriting |
414 |
raise formencode.Invalid('User does not exist', value, state) |
415 |
||
416 |
||
417 |
class NoEnrolmentValidator(formencode.FancyValidator): |
|
418 |
"""A FormEncode validator that ensures absence of an enrolment.
|
|
419 |
||
420 |
The state must have an 'offering' attribute.
|
|
421 |
"""
|
|
422 |
def _to_python(self, value, state): |
|
423 |
if state.offering.get_enrolment(value): |
|
424 |
raise formencode.Invalid('User already enrolled', value, state) |
|
425 |
return value |
|
1149
by William Grant
Allow tutors and lecturers to enrol people in their offerings. |
426 |
|
427 |
||
1377
by Matt Giuca
database: Added finer-grained enrol permissions on offerings. |
428 |
class RoleEnrolmentValidator(formencode.FancyValidator): |
429 |
"""A FormEncode validator that checks permission to enrol users with a
|
|
430 |
particular role.
|
|
431 |
||
432 |
The state must have an 'offering' attribute.
|
|
433 |
"""
|
|
434 |
def _to_python(self, value, state): |
|
1544
by Matt Giuca
Added an argument 'config' to every single get_permissions method throughout the program. All calls to get_permissions pass a config. This is to allow per-site policy configurations on permissions. |
435 |
if (("enrol_" + value) not in |
436 |
state.offering.get_permissions(state.user, state.config)): |
|
1377
by Matt Giuca
database: Added finer-grained enrol permissions on offerings. |
437 |
raise formencode.Invalid('Not allowed to assign users that role', |
438 |
value, state) |
|
439 |
return value |
|
440 |
||
441 |
||
1149
by William Grant
Allow tutors and lecturers to enrol people in their offerings. |
442 |
class EnrolSchema(formencode.Schema): |
1150
by William Grant
Refuse a +enrol if the user is already enrolled. This stops overwriting |
443 |
user = formencode.All(NoEnrolmentValidator(), UserValidator()) |
1377
by Matt Giuca
database: Added finer-grained enrol permissions on offerings. |
444 |
role = formencode.All(formencode.validators.OneOf( |
445 |
["lecturer", "tutor", "student"]), |
|
446 |
RoleEnrolmentValidator(), |
|
447 |
formencode.validators.UnicodeString()) |
|
1149
by William Grant
Allow tutors and lecturers to enrol people in their offerings. |
448 |
|
449 |
||
1365
by Matt Giuca
Added a new view under Offering/+enrolments to display all staff and students in an offering. |
450 |
class EnrolmentsView(XHTMLView): |
451 |
"""A page which displays all users enrolled in an offering."""
|
|
452 |
template = 'templates/enrolments.html' |
|
1523
by William Grant
Declare appropriate tabs on the rest of the views. |
453 |
tab = 'subjects' |
1365
by Matt Giuca
Added a new view under Offering/+enrolments to display all staff and students in an offering. |
454 |
permission = 'edit' |
455 |
||
456 |
def populate(self, req, ctx): |
|
457 |
ctx['offering'] = self.context |
|
458 |
||
1149
by William Grant
Allow tutors and lecturers to enrol people in their offerings. |
459 |
class EnrolView(XHTMLView): |
460 |
"""A form to enrol a user in an offering."""
|
|
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
461 |
template = 'templates/enrol.html' |
1149
by William Grant
Allow tutors and lecturers to enrol people in their offerings. |
462 |
tab = 'subjects' |
1376
by Matt Giuca
database: More granular permissions on offerings: Added 'enrol' permission. |
463 |
permission = 'enrol' |
1149
by William Grant
Allow tutors and lecturers to enrol people in their offerings. |
464 |
|
465 |
def filter(self, stream, ctx): |
|
466 |
return stream | HTMLFormFiller(data=ctx['data']) |
|
467 |
||
468 |
def populate(self, req, ctx): |
|
469 |
if req.method == 'POST': |
|
470 |
data = dict(req.get_fieldstorage()) |
|
471 |
try: |
|
472 |
validator = EnrolSchema() |
|
1150
by William Grant
Refuse a +enrol if the user is already enrolled. This stops overwriting |
473 |
req.offering = self.context # XXX: Getting into state. |
1149
by William Grant
Allow tutors and lecturers to enrol people in their offerings. |
474 |
data = validator.to_python(data, state=req) |
1377
by Matt Giuca
database: Added finer-grained enrol permissions on offerings. |
475 |
self.context.enrol(data['user'], data['role']) |
1149
by William Grant
Allow tutors and lecturers to enrol people in their offerings. |
476 |
req.store.commit() |
477 |
req.throw_redirect(req.uri) |
|
478 |
except formencode.Invalid, e: |
|
479 |
errors = e.unpack_errors() |
|
480 |
else: |
|
481 |
data = {} |
|
482 |
errors = {} |
|
483 |
||
484 |
ctx['data'] = data or {} |
|
485 |
ctx['offering'] = self.context |
|
1544
by Matt Giuca
Added an argument 'config' to every single get_permissions method throughout the program. All calls to get_permissions pass a config. This is to allow per-site policy configurations on permissions. |
486 |
ctx['roles_auth'] = self.context.get_permissions(req.user, req.config) |
1149
by William Grant
Allow tutors and lecturers to enrol people in their offerings. |
487 |
ctx['errors'] = errors |
488 |
||
1165.3.19
by William Grant
Rename SubjectProjectSetView to OfferingProjectsView. |
489 |
class OfferingProjectsView(XHTMLView): |
490 |
"""View the projects for an offering."""
|
|
491 |
template = 'templates/offering_projects.html' |
|
1165.2.3
by Nick Chadwick
Added a new Admin view, which allows for the administration of projects |
492 |
permission = 'edit' |
1165.3.18
by William Grant
Put the project listing and view in the Subjects tab. |
493 |
tab = 'subjects' |
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
494 |
|
1165.2.3
by Nick Chadwick
Added a new Admin view, which allows for the administration of projects |
495 |
def populate(self, req, ctx): |
496 |
self.plugin_styles[Plugin] = ["project.css"] |
|
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
497 |
self.plugin_scripts[Plugin] = ["project.js"] |
1361
by William Grant
Remove the last +projectsets hardcoding. |
498 |
ctx['req'] = req |
1165.2.3
by Nick Chadwick
Added a new Admin view, which allows for the administration of projects |
499 |
ctx['offering'] = self.context |
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
500 |
ctx['projectsets'] = [] |
1361
by William Grant
Remove the last +projectsets hardcoding. |
501 |
ctx['OfferingRESTView'] = OfferingRESTView |
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
502 |
|
503 |
#Open the projectset Fragment, and render it for inclusion
|
|
504 |
#into the ProjectSets page
|
|
505 |
#XXX: This could be a lot cleaner
|
|
506 |
loader = genshi.template.TemplateLoader(".", auto_reload=True) |
|
507 |
||
508 |
set_fragment = os.path.join(os.path.dirname(__file__), |
|
509 |
"templates/projectset_fragment.html") |
|
510 |
project_fragment = os.path.join(os.path.dirname(__file__), |
|
511 |
"templates/project_fragment.html") |
|
512 |
||
513 |
for projectset in self.context.project_sets: |
|
514 |
settmpl = loader.load(set_fragment) |
|
515 |
setCtx = Context() |
|
1358
by William Grant
Use the publishing framework to generate URLs to projectsets. |
516 |
setCtx['req'] = req |
1165.3.30
by William Grant
Clean out the projectset fragment context. |
517 |
setCtx['projectset'] = projectset |
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
518 |
setCtx['projects'] = [] |
1358
by William Grant
Use the publishing framework to generate URLs to projectsets. |
519 |
setCtx['GroupsView'] = GroupsView |
520 |
setCtx['ProjectSetRESTView'] = ProjectSetRESTView |
|
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
521 |
|
522 |
for project in projectset.projects: |
|
523 |
projecttmpl = loader.load(project_fragment) |
|
524 |
projectCtx = Context() |
|
1358
by William Grant
Use the publishing framework to generate URLs to projectsets. |
525 |
projectCtx['req'] = req |
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
526 |
projectCtx['project'] = project |
527 |
||
528 |
setCtx['projects'].append( |
|
529 |
projecttmpl.generate(projectCtx)) |
|
530 |
||
531 |
ctx['projectsets'].append(settmpl.generate(setCtx)) |
|
532 |
||
533 |
||
534 |
class ProjectView(XHTMLView): |
|
1165.2.3
by Nick Chadwick
Added a new Admin view, which allows for the administration of projects |
535 |
"""View the submissions for a ProjectSet"""
|
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
536 |
template = "templates/project.html" |
1556
by William Grant
Allow tutors to view project submissions. |
537 |
permission = "view_project_submissions" |
1165.3.18
by William Grant
Put the project listing and view in the Subjects tab. |
538 |
tab = 'subjects' |
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
539 |
|
1165.3.61
by William Grant
Provide a Subversion command to grab each submission. |
540 |
def build_subversion_url(self, svnroot, submission): |
541 |
princ = submission.assessed.principal |
|
542 |
||
543 |
if isinstance(princ, User): |
|
544 |
path = 'users/%s' % princ.login |
|
545 |
else: |
|
546 |
path = 'groups/%s_%s_%s_%s' % ( |
|
547 |
princ.project_set.offering.subject.short_name, |
|
548 |
princ.project_set.offering.semester.year, |
|
549 |
princ.project_set.offering.semester.semester, |
|
550 |
princ.name |
|
551 |
)
|
|
552 |
return urlparse.urljoin( |
|
553 |
svnroot, |
|
554 |
os.path.join(path, submission.path[1:] if |
|
555 |
submission.path.startswith(os.sep) else |
|
556 |
submission.path)) |
|
557 |
||
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
558 |
def populate(self, req, ctx): |
1165.3.66
by William Grant
Prettify the submissions table. |
559 |
self.plugin_styles[Plugin] = ["project.css"] |
560 |
||
1375.1.4
by William Grant
Indicate when there is nobody assigned to a project, and link to the page to fix that. |
561 |
ctx['req'] = req |
562 |
ctx['GroupsView'] = GroupsView |
|
563 |
ctx['EnrolView'] = EnrolView |
|
1165.3.14
by William Grant
Improve ProjectView's template substantially. |
564 |
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph |
1165.3.61
by William Grant
Provide a Subversion command to grab each submission. |
565 |
ctx['build_subversion_url'] = self.build_subversion_url |
566 |
ctx['svn_addr'] = req.config['urls']['svn_addr'] |
|
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
567 |
ctx['project'] = self.context |
1165.3.61
by William Grant
Provide a Subversion command to grab each submission. |
568 |
ctx['user'] = req.user |
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
569 |
|
1099.1.115
by William Grant
Add tabs to the new framework. Move the app icons into the apps themselves. |
570 |
class Plugin(ViewPlugin, MediaPlugin): |
1294.2.70
by William Grant
Split out ivle.webapp.admin's routes into annotated functions in ivle.webapp.traversal. |
571 |
forward_routes = (root_to_subject, subject_to_offering, |
572 |
offering_to_project, offering_to_projectset) |
|
573 |
reverse_routes = (subject_url, offering_url, projectset_url, project_url) |
|
1294.2.52
by William Grant
Port subject-related views to object traversal. |
574 |
|
575 |
views = [(ApplicationRoot, ('subjects', '+index'), SubjectsView), |
|
1532
by William Grant
Add subject creation/editing UI. Not linked just yet. |
576 |
(ApplicationRoot, ('subjects', '+new'), SubjectNew), |
1537
by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings. |
577 |
(ApplicationRoot, ('subjects', '+new-offering'), OfferingNew), |
1543
by William Grant
Semester creation UI. |
578 |
(ApplicationRoot, ('subjects', '+new-semester'), SemesterNew), |
1532
by William Grant
Add subject creation/editing UI. Not linked just yet. |
579 |
(Subject, '+edit', SubjectEdit), |
1442.1.2
by William Grant
Add basic (ie. pretty much empty) offering index. |
580 |
(Offering, '+index', OfferingView), |
1451.1.5
by William Grant
Add an OfferingEdit view, for setting the description and URL. |
581 |
(Offering, '+edit', OfferingEdit), |
1365
by Matt Giuca
Added a new view under Offering/+enrolments to display all staff and students in an offering. |
582 |
(Offering, ('+enrolments', '+index'), EnrolmentsView), |
1294.2.52
by William Grant
Port subject-related views to object traversal. |
583 |
(Offering, ('+enrolments', '+new'), EnrolView), |
584 |
(Offering, ('+projects', '+index'), OfferingProjectsView), |
|
585 |
(Project, '+index', ProjectView), |
|
586 |
||
587 |
(Offering, ('+projectsets', '+new'), OfferingRESTView, 'api'), |
|
588 |
(ProjectSet, ('+projects', '+new'), ProjectSetRESTView, 'api'), |
|
589 |
]
|
|
1099.1.115
by William Grant
Add tabs to the new framework. Move the app icons into the apps themselves. |
590 |
|
1294.2.94
by William Grant
Add a SubjectBreadcrumb. |
591 |
breadcrumbs = {Subject: SubjectBreadcrumb, |
592 |
Offering: OfferingBreadcrumb, |
|
1294.2.96
by William Grant
Add a UserBreadcrumb. |
593 |
User: UserBreadcrumb, |
1294.2.98
by William Grant
Add a ProjectBreadcrumb. |
594 |
Project: ProjectBreadcrumb, |
1294.2.89
by William Grant
Add an Offering breadcrumb. |
595 |
}
|
596 |
||
1099.1.115
by William Grant
Add tabs to the new framework. Move the app icons into the apps themselves. |
597 |
tabs = [ |
1118
by matt.giuca
Rewrote tooltips for the four tabs visible by default. |
598 |
('subjects', 'Subjects', |
599 |
'View subject content and complete worksheets', |
|
600 |
'subjects.png', 'subjects', 5) |
|
1099.1.115
by William Grant
Add tabs to the new framework. Move the app icons into the apps themselves. |
601 |
]
|
602 |
||
603 |
media = 'subject-media' |