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 |
1720
by William Grant
Share one TemplateLoader between every instance of every view, so we cache EVERYTHING. |
34 |
from genshi.template import Context |
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 |
|
1710.1.8
by Matt Giuca
Added 'New Project' non-AJAX UI to replace old AJAX one. Currently not linked anywhere; AJAX one still works. |
38 |
from ivle.webapp.base.forms import (BaseFormView, URLNameValidator, |
39 |
DateTimeValidator) |
|
1539
by William Grant
Move BaseFormView into ivle.webapp.base.forms. |
40 |
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 |
41 |
from ivle.webapp.base.xhtml import XHTMLView |
1603
by William Grant
Add UI to clone worksheets between offerings -- replacing ivle-cloneworksheets. |
42 |
from ivle.webapp.errors import BadRequest |
1294.2.52
by William Grant
Port subject-related views to object traversal. |
43 |
from ivle.webapp import ApplicationRoot |
1165.3.9
by Nick Chadwick
merge from trunk |
44 |
|
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
45 |
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 |
46 |
ProjectSet, Project, ProjectSubmission |
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
47 |
from ivle import util |
1165.3.14
by William Grant
Improve ProjectView's template substantially. |
48 |
import ivle.date |
621
by mattgiuca
Added 2 new apps: home and subjects. Both fairly incomplete, just a basic |
49 |
|
1592
by William Grant
Add routes for Semester. We'll need them for the admin UI. |
50 |
from ivle.webapp.admin.publishing import (root_to_subject, root_to_semester, |
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, |
1613
by William Grant
Add UI to edit/delete enrolments. |
52 |
offering_to_enrolment, subject_url, semester_url, offering_url, |
53 |
projectset_url, project_url, enrolment_url) |
|
1294.2.96
by William Grant
Add a UserBreadcrumb. |
54 |
from ivle.webapp.admin.breadcrumbs import (SubjectBreadcrumb, |
1615
by William Grant
Add breadcrumbs for enrolments. |
55 |
OfferingBreadcrumb, UserBreadcrumb, ProjectBreadcrumb, |
1710.1.5
by Matt Giuca
Added new project set edit view. Linked from projects page, project set page. |
56 |
ProjectsBreadcrumb, EnrolmentBreadcrumb) |
1533
by William Grant
Add a subject listing with new/edit icons. |
57 |
from ivle.webapp.core import Plugin as CorePlugin |
1358
by William Grant
Use the publishing framework to generate URLs to projectsets. |
58 |
from ivle.webapp.groups import GroupsView |
1533
by William Grant
Add a subject listing with new/edit icons. |
59 |
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. |
60 |
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 |
61 |
|
1099.1.20
by William Grant
ivle.webapp.admin.subject: Port www/apps/subjects to new framework. |
62 |
class SubjectsView(XHTMLView): |
63 |
'''The view of the list of subjects.'''
|
|
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
64 |
template = 'templates/subjects.html' |
1116
by William Grant
Move the old tutorial views into the 'subjects' tab, so they get the right |
65 |
tab = 'subjects' |
1683
by Matt Giuca
Added breadcrumb for Subjects page (previously each subject had its own top-level breadcrumb). |
66 |
breadcrumb_text = "Subjects" |
1099.1.20
by William Grant
ivle.webapp.admin.subject: Port www/apps/subjects to new framework. |
67 |
|
1099.1.110
by William Grant
Implement an authorization system in the new framework. This breaks the REST |
68 |
def authorize(self, req): |
1138
by William Grant
SubjectsView now tells users if they have no enrolments. |
69 |
return req.user is not None |
1099.1.110
by William Grant
Implement an authorization system in the new framework. This breaks the REST |
70 |
|
1099.1.20
by William Grant
ivle.webapp.admin.subject: Port www/apps/subjects to new framework. |
71 |
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. |
72 |
ctx['req'] = req |
1139
by William Grant
Show group administration links on SubjectsView where privileges allow it. |
73 |
ctx['user'] = req.user |
1125
by William Grant
Rework ivle.webapp.admin.subjects#SubjectsView to split offerings nicely by |
74 |
ctx['semesters'] = [] |
1533
by William Grant
Add a subject listing with new/edit icons. |
75 |
|
1125
by William Grant
Rework ivle.webapp.admin.subjects#SubjectsView to split offerings nicely by |
76 |
for semester in req.store.find(Semester).order_by(Desc(Semester.year), |
77 |
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. |
78 |
if req.user.admin: |
79 |
# For admins, show all subjects in the system
|
|
80 |
offerings = list(semester.offerings.find()) |
|
81 |
else: |
|
82 |
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. |
83 |
semester.enrolments.find(user=req.user)] |
84 |
if len(offerings): |
|
85 |
ctx['semesters'].append((semester, offerings)) |
|
1099.1.20
by William Grant
ivle.webapp.admin.subject: Port www/apps/subjects to new framework. |
86 |
|
1596
by William Grant
Split subject/semester management out onto a separate page, and link to SemesterEdit. |
87 |
|
88 |
class SubjectsManage(XHTMLView): |
|
89 |
'''Subject management view.'''
|
|
90 |
template = 'templates/subjects-manage.html' |
|
91 |
tab = 'subjects' |
|
92 |
||
93 |
def authorize(self, req): |
|
94 |
return req.user is not None and req.user.admin |
|
95 |
||
96 |
def populate(self, req, ctx): |
|
97 |
ctx['req'] = req |
|
98 |
ctx['mediapath'] = media_url(req, CorePlugin, 'images/') |
|
1678.1.1
by Matt Giuca
Added new view SubjectView, which shows all offerings for a subject. This is accessible from the SubjectsManage view, or by the subject name in the breadcrumbs. |
99 |
ctx['SubjectView'] = SubjectView |
1596
by William Grant
Split subject/semester management out onto a separate page, and link to SemesterEdit. |
100 |
ctx['SubjectEdit'] = SubjectEdit |
101 |
ctx['SemesterEdit'] = SemesterEdit |
|
102 |
||
103 |
ctx['subjects'] = req.store.find(Subject).order_by(Subject.name) |
|
104 |
ctx['semesters'] = req.store.find(Semester).order_by( |
|
105 |
Semester.year, Semester.semester) |
|
1533
by William Grant
Add a subject listing with new/edit icons. |
106 |
|
1532
by William Grant
Add subject creation/editing UI. Not linked just yet. |
107 |
|
108 |
class SubjectShortNameUniquenessValidator(formencode.FancyValidator): |
|
109 |
"""A FormEncode validator that checks that a subject name is unused.
|
|
110 |
||
111 |
The subject referenced by state.existing_subject is permitted
|
|
112 |
to hold that name. If any other object holds it, the input is rejected.
|
|
113 |
"""
|
|
114 |
def __init__(self, matching=None): |
|
115 |
self.matching = matching |
|
116 |
||
117 |
def _to_python(self, value, state): |
|
118 |
if (state.store.find( |
|
119 |
Subject, short_name=value).one() not in |
|
120 |
(None, state.existing_subject)): |
|
121 |
raise formencode.Invalid( |
|
122 |
'Short name already taken', value, state) |
|
123 |
return value |
|
124 |
||
125 |
||
126 |
class SubjectSchema(formencode.Schema): |
|
127 |
short_name = formencode.All( |
|
128 |
SubjectShortNameUniquenessValidator(), |
|
1606.1.3
by William Grant
Use URLNameValidator in existing schemas. |
129 |
URLNameValidator(not_empty=True)) |
1532
by William Grant
Add subject creation/editing UI. Not linked just yet. |
130 |
name = formencode.validators.UnicodeString(not_empty=True) |
131 |
code = formencode.validators.UnicodeString(not_empty=True) |
|
132 |
||
133 |
||
1546
by William Grant
Derive the subject forms from BaseFormView. |
134 |
class SubjectFormView(BaseFormView): |
1532
by William Grant
Add subject creation/editing UI. Not linked just yet. |
135 |
"""An abstract form to add or edit a subject."""
|
136 |
tab = 'subjects' |
|
137 |
||
138 |
def authorize(self, req): |
|
139 |
return req.user is not None and req.user.admin |
|
140 |
||
141 |
def populate_state(self, state): |
|
142 |
state.existing_subject = None |
|
143 |
||
1546
by William Grant
Derive the subject forms from BaseFormView. |
144 |
@property
|
145 |
def validator(self): |
|
146 |
return SubjectSchema() |
|
147 |
||
1532
by William Grant
Add subject creation/editing UI. Not linked just yet. |
148 |
|
149 |
class SubjectNew(SubjectFormView): |
|
150 |
"""A form to create a subject."""
|
|
151 |
template = 'templates/subject-new.html' |
|
152 |
||
153 |
def get_default_data(self, req): |
|
154 |
return {} |
|
155 |
||
1546
by William Grant
Derive the subject forms from BaseFormView. |
156 |
def save_object(self, req, data): |
1532
by William Grant
Add subject creation/editing UI. Not linked just yet. |
157 |
new_subject = Subject() |
158 |
new_subject.short_name = data['short_name'] |
|
159 |
new_subject.name = data['name'] |
|
160 |
new_subject.code = data['code'] |
|
161 |
||
162 |
req.store.add(new_subject) |
|
163 |
return new_subject |
|
164 |
||
165 |
||
166 |
class SubjectEdit(SubjectFormView): |
|
167 |
"""A form to edit a subject."""
|
|
168 |
template = 'templates/subject-edit.html' |
|
169 |
||
170 |
def populate_state(self, state): |
|
171 |
state.existing_subject = self.context |
|
172 |
||
173 |
def get_default_data(self, req): |
|
174 |
return { |
|
175 |
'short_name': self.context.short_name, |
|
176 |
'name': self.context.name, |
|
177 |
'code': self.context.code, |
|
178 |
}
|
|
179 |
||
1546
by William Grant
Derive the subject forms from BaseFormView. |
180 |
def save_object(self, req, data): |
1532
by William Grant
Add subject creation/editing UI. Not linked just yet. |
181 |
self.context.short_name = data['short_name'] |
182 |
self.context.name = data['name'] |
|
183 |
self.context.code = data['code'] |
|
184 |
||
185 |
return self.context |
|
186 |
||
187 |
||
1543
by William Grant
Semester creation UI. |
188 |
class SemesterUniquenessValidator(formencode.FancyValidator): |
189 |
"""A FormEncode validator that checks that a semester is unique.
|
|
190 |
||
191 |
There cannot be more than one semester for the same year and semester.
|
|
192 |
"""
|
|
193 |
def _to_python(self, value, state): |
|
194 |
if (state.store.find( |
|
195 |
Semester, year=value['year'], semester=value['semester'] |
|
1594
by William Grant
Add semester edit UI. |
196 |
).one() not in (None, state.existing_semester)): |
1543
by William Grant
Semester creation UI. |
197 |
raise formencode.Invalid( |
198 |
'Semester already exists', value, state) |
|
199 |
return value |
|
200 |
||
201 |
||
202 |
class SemesterSchema(formencode.Schema): |
|
1606.1.3
by William Grant
Use URLNameValidator in existing schemas. |
203 |
year = URLNameValidator() |
204 |
semester = URLNameValidator() |
|
1594
by William Grant
Add semester edit UI. |
205 |
state = formencode.All( |
206 |
formencode.validators.OneOf(["past", "current", "future"]), |
|
207 |
formencode.validators.UnicodeString()) |
|
1543
by William Grant
Semester creation UI. |
208 |
chained_validators = [SemesterUniquenessValidator()] |
209 |
||
210 |
||
1594
by William Grant
Add semester edit UI. |
211 |
class SemesterFormView(BaseFormView): |
212 |
tab = 'subjects' |
|
213 |
||
214 |
def authorize(self, req): |
|
215 |
return req.user is not None and req.user.admin |
|
216 |
||
217 |
@property
|
|
218 |
def validator(self): |
|
219 |
return SemesterSchema() |
|
220 |
||
221 |
def get_return_url(self, obj): |
|
222 |
return '/subjects/+manage' |
|
223 |
||
224 |
||
225 |
class SemesterNew(SemesterFormView): |
|
1543
by William Grant
Semester creation UI. |
226 |
"""A form to create a semester."""
|
227 |
template = 'templates/semester-new.html' |
|
228 |
tab = 'subjects' |
|
229 |
||
1594
by William Grant
Add semester edit UI. |
230 |
def populate_state(self, state): |
231 |
state.existing_semester = None |
|
1543
by William Grant
Semester creation UI. |
232 |
|
233 |
def get_default_data(self, req): |
|
234 |
return {} |
|
235 |
||
236 |
def save_object(self, req, data): |
|
237 |
new_semester = Semester() |
|
238 |
new_semester.year = data['year'] |
|
239 |
new_semester.semester = data['semester'] |
|
1594
by William Grant
Add semester edit UI. |
240 |
new_semester.state = data['state'] |
1543
by William Grant
Semester creation UI. |
241 |
|
242 |
req.store.add(new_semester) |
|
243 |
return new_semester |
|
244 |
||
1594
by William Grant
Add semester edit UI. |
245 |
|
246 |
class SemesterEdit(SemesterFormView): |
|
247 |
"""A form to edit a semester."""
|
|
248 |
template = 'templates/semester-edit.html' |
|
249 |
||
250 |
def populate_state(self, state): |
|
251 |
state.existing_semester = self.context |
|
252 |
||
253 |
def get_default_data(self, req): |
|
254 |
return { |
|
255 |
'year': self.context.year, |
|
256 |
'semester': self.context.semester, |
|
257 |
'state': self.context.state, |
|
258 |
}
|
|
259 |
||
260 |
def save_object(self, req, data): |
|
261 |
self.context.year = data['year'] |
|
262 |
self.context.semester = data['semester'] |
|
263 |
self.context.state = data['state'] |
|
264 |
||
265 |
return self.context |
|
1543
by William Grant
Semester creation UI. |
266 |
|
1678.1.1
by Matt Giuca
Added new view SubjectView, which shows all offerings for a subject. This is accessible from the SubjectsManage view, or by the subject name in the breadcrumbs. |
267 |
class SubjectView(XHTMLView): |
268 |
'''The view of the list of offerings in a given subject.'''
|
|
269 |
template = 'templates/subject.html' |
|
270 |
tab = 'subjects' |
|
271 |
||
272 |
def authorize(self, req): |
|
273 |
return req.user is not None |
|
274 |
||
275 |
def populate(self, req, ctx): |
|
276 |
ctx['context'] = self.context |
|
277 |
ctx['req'] = req |
|
278 |
ctx['user'] = req.user |
|
279 |
ctx['offerings'] = list(self.context.offerings) |
|
1678.1.3
by Matt Giuca
SubjectView: Added edit button to the top of the subject page, for admins. |
280 |
ctx['permissions'] = self.context.get_permissions(req.user,req.config) |
281 |
ctx['SubjectEdit'] = SubjectEdit |
|
1678.1.5
by Matt Giuca
Added new offering SubjectOfferingNew (+new-offering under a subject name). This is identical to +new-offering, but it is locked to a particular subject. The 'Create new offering' button on the subject page now links to this. |
282 |
ctx['SubjectOfferingNew'] = SubjectOfferingNew |
1678.1.1
by Matt Giuca
Added new view SubjectView, which shows all offerings for a subject. This is accessible from the SubjectsManage view, or by the subject name in the breadcrumbs. |
283 |
|
1543
by William Grant
Semester creation UI. |
284 |
|
1442.1.2
by William Grant
Add basic (ie. pretty much empty) offering index. |
285 |
class OfferingView(XHTMLView): |
286 |
"""The home page of an offering."""
|
|
287 |
template = 'templates/offering.html' |
|
288 |
tab = 'subjects' |
|
289 |
permission = 'view' |
|
290 |
||
291 |
def populate(self, req, ctx): |
|
1442.1.31
by William Grant
Show the worksheet listing with marks and schtuff on the offering index. |
292 |
# Need the worksheet result styles.
|
293 |
self.plugin_styles[TutorialPlugin] = ['tutorial.css'] |
|
1442.1.2
by William Grant
Add basic (ie. pretty much empty) offering index. |
294 |
ctx['context'] = self.context |
295 |
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. |
296 |
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. |
297 |
ctx['format_submission_principal'] = util.format_submission_principal |
1442.1.10
by William Grant
Add a nice padded list of projects. |
298 |
ctx['format_datetime'] = ivle.date.make_date_nice |
299 |
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. |
300 |
ctx['OfferingEdit'] = OfferingEdit |
1603
by William Grant
Add UI to clone worksheets between offerings -- replacing ivle-cloneworksheets. |
301 |
ctx['OfferingCloneWorksheets'] = OfferingCloneWorksheets |
1558
by William Grant
Allow tutors to manage groups. |
302 |
ctx['GroupsView'] = GroupsView |
1610
by William Grant
Replace OfferingView's link to EnrolView with one to EnrolmentsView, and link from there to EnrolView. |
303 |
ctx['EnrolmentsView'] = EnrolmentsView |
1442.1.2
by William Grant
Add basic (ie. pretty much empty) offering index. |
304 |
|
1442.1.31
by William Grant
Show the worksheet listing with marks and schtuff on the offering index. |
305 |
# As we go, calculate the total score for this subject
|
306 |
# (Assessable worksheets only, mandatory problems only)
|
|
307 |
||
308 |
ctx['worksheets'], problems_total, problems_done = ( |
|
309 |
ivle.worksheet.utils.create_list_of_fake_worksheets_and_stats( |
|
1695.1.6
by William Grant
Hide unpublished worksheets if edit_worksheets is not held. |
310 |
req.config, req.store, req.user, self.context)) |
1442.1.31
by William Grant
Show the worksheet listing with marks and schtuff on the offering index. |
311 |
|
312 |
ctx['exercises_total'] = problems_total |
|
313 |
ctx['exercises_done'] = problems_done |
|
314 |
if problems_total > 0: |
|
315 |
if problems_done >= problems_total: |
|
316 |
ctx['worksheets_complete_class'] = "complete" |
|
317 |
elif problems_done > 0: |
|
318 |
ctx['worksheets_complete_class'] = "semicomplete" |
|
319 |
else: |
|
320 |
ctx['worksheets_complete_class'] = "incomplete" |
|
321 |
# Calculate the final percentage and mark for the subject
|
|
322 |
(ctx['exercises_pct'], ctx['worksheet_mark'], |
|
323 |
ctx['worksheet_max_mark']) = ( |
|
324 |
ivle.worksheet.utils.calculate_mark( |
|
325 |
problems_done, problems_total)) |
|
326 |
||
1442.1.2
by William Grant
Add basic (ie. pretty much empty) offering index. |
327 |
|
1537
by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings. |
328 |
class SubjectValidator(formencode.FancyValidator): |
329 |
"""A FormEncode validator that turns a subject name into a subject.
|
|
330 |
||
331 |
The state must have a 'store' attribute, which is the Storm store
|
|
332 |
to use.
|
|
333 |
"""
|
|
334 |
def _to_python(self, value, state): |
|
335 |
subject = state.store.find(Subject, short_name=value).one() |
|
336 |
if subject: |
|
337 |
return subject |
|
338 |
else: |
|
339 |
raise formencode.Invalid('Subject does not exist', value, state) |
|
340 |
||
341 |
||
342 |
class SemesterValidator(formencode.FancyValidator): |
|
343 |
"""A FormEncode validator that turns a string into a semester.
|
|
344 |
||
345 |
The string should be of the form 'year/semester', eg. '2009/1'.
|
|
346 |
||
347 |
The state must have a 'store' attribute, which is the Storm store
|
|
348 |
to use.
|
|
349 |
"""
|
|
350 |
def _to_python(self, value, state): |
|
351 |
try: |
|
352 |
year, semester = value.split('/') |
|
353 |
except ValueError: |
|
354 |
year = semester = None |
|
355 |
||
356 |
semester = state.store.find( |
|
357 |
Semester, year=year, semester=semester).one() |
|
358 |
if semester: |
|
359 |
return semester |
|
360 |
else: |
|
361 |
raise formencode.Invalid('Semester does not exist', value, state) |
|
362 |
||
363 |
||
364 |
class OfferingUniquenessValidator(formencode.FancyValidator): |
|
365 |
"""A FormEncode validator that checks that an offering is unique.
|
|
366 |
||
367 |
There cannot be more than one offering in the same year and semester.
|
|
368 |
||
369 |
The offering referenced by state.existing_offering is permitted to
|
|
370 |
hold that year and semester tuple. If any other object holds it, the
|
|
371 |
input is rejected.
|
|
372 |
"""
|
|
373 |
def _to_python(self, value, state): |
|
374 |
if (state.store.find( |
|
375 |
Offering, subject=value['subject'], |
|
376 |
semester=value['semester']).one() not in |
|
377 |
(None, state.existing_offering)): |
|
378 |
raise formencode.Invalid( |
|
379 |
'Offering already exists', value, state) |
|
380 |
return value |
|
381 |
||
382 |
||
1451.1.5
by William Grant
Add an OfferingEdit view, for setting the description and URL. |
383 |
class OfferingSchema(formencode.Schema): |
1451.1.8
by William Grant
Allow unsetting of the URL or description. |
384 |
description = formencode.validators.UnicodeString( |
385 |
if_missing=None, not_empty=False) |
|
386 |
url = formencode.validators.URL(if_missing=None, not_empty=False) |
|
1695.1.4
by William Grant
Expose Offering.show_worksheet_marks in the forms. |
387 |
show_worksheet_marks = formencode.validators.StringBoolean( |
388 |
if_missing=False) |
|
1451.1.5
by William Grant
Add an OfferingEdit view, for setting the description and URL. |
389 |
|
390 |
||
1537
by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings. |
391 |
class OfferingAdminSchema(OfferingSchema): |
392 |
subject = formencode.All( |
|
393 |
SubjectValidator(), formencode.validators.UnicodeString()) |
|
394 |
semester = formencode.All( |
|
395 |
SemesterValidator(), formencode.validators.UnicodeString()) |
|
396 |
chained_validators = [OfferingUniquenessValidator()] |
|
397 |
||
398 |
||
399 |
class OfferingEdit(BaseFormView): |
|
1451.1.5
by William Grant
Add an OfferingEdit view, for setting the description and URL. |
400 |
"""A form to edit an offering's details."""
|
401 |
template = 'templates/offering-edit.html' |
|
1523
by William Grant
Declare appropriate tabs on the rest of the views. |
402 |
tab = 'subjects' |
1451.1.5
by William Grant
Add an OfferingEdit view, for setting the description and URL. |
403 |
permission = 'edit' |
404 |
||
1537
by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings. |
405 |
@property
|
406 |
def validator(self): |
|
407 |
if self.req.user.admin: |
|
408 |
return OfferingAdminSchema() |
|
1451.1.5
by William Grant
Add an OfferingEdit view, for setting the description and URL. |
409 |
else: |
1537
by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings. |
410 |
return OfferingSchema() |
411 |
||
412 |
def populate(self, req, ctx): |
|
413 |
super(OfferingEdit, self).populate(req, ctx) |
|
1598
by William Grant
Sort subjects and semesters sanely in the offering forms. |
414 |
ctx['subjects'] = req.store.find(Subject).order_by(Subject.name) |
415 |
ctx['semesters'] = req.store.find(Semester).order_by( |
|
416 |
Semester.year, Semester.semester) |
|
1678.1.5
by Matt Giuca
Added new offering SubjectOfferingNew (+new-offering under a subject name). This is identical to +new-offering, but it is locked to a particular subject. The 'Create new offering' button on the subject page now links to this. |
417 |
ctx['force_subject'] = None |
1537
by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings. |
418 |
|
419 |
def populate_state(self, state): |
|
420 |
state.existing_offering = self.context |
|
421 |
||
422 |
def get_default_data(self, req): |
|
423 |
return { |
|
424 |
'subject': self.context.subject.short_name, |
|
425 |
'semester': self.context.semester.year + '/' + |
|
426 |
self.context.semester.semester, |
|
427 |
'url': self.context.url, |
|
428 |
'description': self.context.description, |
|
1695.1.4
by William Grant
Expose Offering.show_worksheet_marks in the forms. |
429 |
'show_worksheet_marks': self.context.show_worksheet_marks, |
1451.1.5
by William Grant
Add an OfferingEdit view, for setting the description and URL. |
430 |
}
|
1537
by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings. |
431 |
|
432 |
def save_object(self, req, data): |
|
433 |
if req.user.admin: |
|
434 |
self.context.subject = data['subject'] |
|
435 |
self.context.semester = data['semester'] |
|
436 |
self.context.description = data['description'] |
|
437 |
self.context.url = unicode(data['url']) if data['url'] else None |
|
1695.1.4
by William Grant
Expose Offering.show_worksheet_marks in the forms. |
438 |
self.context.show_worksheet_marks = data['show_worksheet_marks'] |
1537
by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings. |
439 |
return self.context |
440 |
||
441 |
||
442 |
class OfferingNew(BaseFormView): |
|
443 |
"""A form to create an offering."""
|
|
444 |
template = 'templates/offering-new.html' |
|
445 |
tab = 'subjects' |
|
446 |
||
447 |
def authorize(self, req): |
|
448 |
return req.user is not None and req.user.admin |
|
449 |
||
450 |
@property
|
|
451 |
def validator(self): |
|
452 |
return OfferingAdminSchema() |
|
453 |
||
454 |
def populate(self, req, ctx): |
|
455 |
super(OfferingNew, self).populate(req, ctx) |
|
1599
by William Grant
Sort subjects and semesters sanely in the offering new form too. |
456 |
ctx['subjects'] = req.store.find(Subject).order_by(Subject.name) |
457 |
ctx['semesters'] = req.store.find(Semester).order_by( |
|
458 |
Semester.year, Semester.semester) |
|
1678.1.5
by Matt Giuca
Added new offering SubjectOfferingNew (+new-offering under a subject name). This is identical to +new-offering, but it is locked to a particular subject. The 'Create new offering' button on the subject page now links to this. |
459 |
ctx['force_subject'] = None |
1537
by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings. |
460 |
|
461 |
def populate_state(self, state): |
|
462 |
state.existing_offering = None |
|
463 |
||
464 |
def get_default_data(self, req): |
|
465 |
return {} |
|
466 |
||
467 |
def save_object(self, req, data): |
|
468 |
new_offering = Offering() |
|
469 |
new_offering.subject = data['subject'] |
|
470 |
new_offering.semester = data['semester'] |
|
471 |
new_offering.description = data['description'] |
|
472 |
new_offering.url = unicode(data['url']) if data['url'] else None |
|
1695.1.4
by William Grant
Expose Offering.show_worksheet_marks in the forms. |
473 |
new_offering.show_worksheet_marks = data['show_worksheet_marks'] |
1537
by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings. |
474 |
|
475 |
req.store.add(new_offering) |
|
476 |
return new_offering |
|
1451.1.5
by William Grant
Add an OfferingEdit view, for setting the description and URL. |
477 |
|
1678.1.5
by Matt Giuca
Added new offering SubjectOfferingNew (+new-offering under a subject name). This is identical to +new-offering, but it is locked to a particular subject. The 'Create new offering' button on the subject page now links to this. |
478 |
class SubjectOfferingNew(OfferingNew): |
479 |
"""A form to create an offering for a given subject."""
|
|
480 |
# Identical to OfferingNew, except it forces the subject to be the subject
|
|
481 |
# in context
|
|
482 |
def populate(self, req, ctx): |
|
483 |
super(SubjectOfferingNew, self).populate(req, ctx) |
|
484 |
ctx['force_subject'] = self.context |
|
1451.1.5
by William Grant
Add an OfferingEdit view, for setting the description and URL. |
485 |
|
1603
by William Grant
Add UI to clone worksheets between offerings -- replacing ivle-cloneworksheets. |
486 |
class OfferingCloneWorksheetsSchema(formencode.Schema): |
487 |
subject = formencode.All( |
|
488 |
SubjectValidator(), formencode.validators.UnicodeString()) |
|
489 |
semester = formencode.All( |
|
490 |
SemesterValidator(), formencode.validators.UnicodeString()) |
|
491 |
||
492 |
||
493 |
class OfferingCloneWorksheets(BaseFormView): |
|
494 |
"""A form to clone worksheets from one offering to another."""
|
|
495 |
template = 'templates/offering-clone-worksheets.html' |
|
496 |
tab = 'subjects' |
|
497 |
||
498 |
def authorize(self, req): |
|
499 |
return req.user is not None and req.user.admin |
|
500 |
||
501 |
@property
|
|
502 |
def validator(self): |
|
503 |
return OfferingCloneWorksheetsSchema() |
|
504 |
||
505 |
def populate(self, req, ctx): |
|
506 |
super(OfferingCloneWorksheets, self).populate(req, ctx) |
|
507 |
ctx['subjects'] = req.store.find(Subject).order_by(Subject.name) |
|
508 |
ctx['semesters'] = req.store.find(Semester).order_by( |
|
509 |
Semester.year, Semester.semester) |
|
510 |
||
511 |
def get_default_data(self, req): |
|
512 |
return {} |
|
513 |
||
514 |
def save_object(self, req, data): |
|
515 |
if self.context.worksheets.count() > 0: |
|
516 |
raise BadRequest( |
|
517 |
"Cannot clone to target with existing worksheets.") |
|
518 |
offering = req.store.find( |
|
519 |
Offering, subject=data['subject'], semester=data['semester']).one() |
|
520 |
if offering is None: |
|
521 |
raise BadRequest("No such offering.") |
|
522 |
if offering.worksheets.count() == 0: |
|
523 |
raise BadRequest("Source offering has no worksheets.") |
|
524 |
||
525 |
self.context.clone_worksheets(offering) |
|
526 |
return self.context |
|
527 |
||
528 |
||
1149
by William Grant
Allow tutors and lecturers to enrol people in their offerings. |
529 |
class UserValidator(formencode.FancyValidator): |
530 |
"""A FormEncode validator that turns a username into a user.
|
|
531 |
||
532 |
The state must have a 'store' attribute, which is the Storm store
|
|
533 |
to use."""
|
|
534 |
def _to_python(self, value, state): |
|
535 |
user = User.get_by_login(state.store, value) |
|
536 |
if user: |
|
537 |
return user |
|
538 |
else: |
|
1150
by William Grant
Refuse a +enrol if the user is already enrolled. This stops overwriting |
539 |
raise formencode.Invalid('User does not exist', value, state) |
540 |
||
541 |
||
542 |
class NoEnrolmentValidator(formencode.FancyValidator): |
|
543 |
"""A FormEncode validator that ensures absence of an enrolment.
|
|
544 |
||
545 |
The state must have an 'offering' attribute.
|
|
546 |
"""
|
|
547 |
def _to_python(self, value, state): |
|
548 |
if state.offering.get_enrolment(value): |
|
549 |
raise formencode.Invalid('User already enrolled', value, state) |
|
550 |
return value |
|
1149
by William Grant
Allow tutors and lecturers to enrol people in their offerings. |
551 |
|
552 |
||
1377
by Matt Giuca
database: Added finer-grained enrol permissions on offerings. |
553 |
class RoleEnrolmentValidator(formencode.FancyValidator): |
554 |
"""A FormEncode validator that checks permission to enrol users with a
|
|
555 |
particular role.
|
|
556 |
||
557 |
The state must have an 'offering' attribute.
|
|
558 |
"""
|
|
559 |
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. |
560 |
if (("enrol_" + value) not in |
561 |
state.offering.get_permissions(state.user, state.config)): |
|
1377
by Matt Giuca
database: Added finer-grained enrol permissions on offerings. |
562 |
raise formencode.Invalid('Not allowed to assign users that role', |
563 |
value, state) |
|
564 |
return value |
|
565 |
||
566 |
||
1149
by William Grant
Allow tutors and lecturers to enrol people in their offerings. |
567 |
class EnrolSchema(formencode.Schema): |
1150
by William Grant
Refuse a +enrol if the user is already enrolled. This stops overwriting |
568 |
user = formencode.All(NoEnrolmentValidator(), UserValidator()) |
1377
by Matt Giuca
database: Added finer-grained enrol permissions on offerings. |
569 |
role = formencode.All(formencode.validators.OneOf( |
570 |
["lecturer", "tutor", "student"]), |
|
571 |
RoleEnrolmentValidator(), |
|
572 |
formencode.validators.UnicodeString()) |
|
1149
by William Grant
Allow tutors and lecturers to enrol people in their offerings. |
573 |
|
574 |
||
1365
by Matt Giuca
Added a new view under Offering/+enrolments to display all staff and students in an offering. |
575 |
class EnrolmentsView(XHTMLView): |
576 |
"""A page which displays all users enrolled in an offering."""
|
|
577 |
template = 'templates/enrolments.html' |
|
1523
by William Grant
Declare appropriate tabs on the rest of the views. |
578 |
tab = 'subjects' |
1365
by Matt Giuca
Added a new view under Offering/+enrolments to display all staff and students in an offering. |
579 |
permission = 'edit' |
1615
by William Grant
Add breadcrumbs for enrolments. |
580 |
breadcrumb_text = 'Enrolments' |
1365
by Matt Giuca
Added a new view under Offering/+enrolments to display all staff and students in an offering. |
581 |
|
582 |
def populate(self, req, ctx): |
|
1610
by William Grant
Replace OfferingView's link to EnrolView with one to EnrolmentsView, and link from there to EnrolView. |
583 |
ctx['req'] = req |
1365
by Matt Giuca
Added a new view under Offering/+enrolments to display all staff and students in an offering. |
584 |
ctx['offering'] = self.context |
1613
by William Grant
Add UI to edit/delete enrolments. |
585 |
ctx['mediapath'] = media_url(req, CorePlugin, 'images/') |
1614
by William Grant
Only show edit/delete links for enrolments that you can actually touch. |
586 |
ctx['offering_perms'] = self.context.get_permissions( |
587 |
req.user, req.config) |
|
1610
by William Grant
Replace OfferingView's link to EnrolView with one to EnrolmentsView, and link from there to EnrolView. |
588 |
ctx['EnrolView'] = EnrolView |
1613
by William Grant
Add UI to edit/delete enrolments. |
589 |
ctx['EnrolmentEdit'] = EnrolmentEdit |
590 |
ctx['EnrolmentDelete'] = EnrolmentDelete |
|
1610
by William Grant
Replace OfferingView's link to EnrolView with one to EnrolmentsView, and link from there to EnrolView. |
591 |
|
1365
by Matt Giuca
Added a new view under Offering/+enrolments to display all staff and students in an offering. |
592 |
|
1149
by William Grant
Allow tutors and lecturers to enrol people in their offerings. |
593 |
class EnrolView(XHTMLView): |
594 |
"""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 |
595 |
template = 'templates/enrol.html' |
1149
by William Grant
Allow tutors and lecturers to enrol people in their offerings. |
596 |
tab = 'subjects' |
1376
by Matt Giuca
database: More granular permissions on offerings: Added 'enrol' permission. |
597 |
permission = 'enrol' |
1149
by William Grant
Allow tutors and lecturers to enrol people in their offerings. |
598 |
|
599 |
def filter(self, stream, ctx): |
|
600 |
return stream | HTMLFormFiller(data=ctx['data']) |
|
601 |
||
602 |
def populate(self, req, ctx): |
|
603 |
if req.method == 'POST': |
|
604 |
data = dict(req.get_fieldstorage()) |
|
605 |
try: |
|
606 |
validator = EnrolSchema() |
|
1150
by William Grant
Refuse a +enrol if the user is already enrolled. This stops overwriting |
607 |
req.offering = self.context # XXX: Getting into state. |
1149
by William Grant
Allow tutors and lecturers to enrol people in their offerings. |
608 |
data = validator.to_python(data, state=req) |
1377
by Matt Giuca
database: Added finer-grained enrol permissions on offerings. |
609 |
self.context.enrol(data['user'], data['role']) |
1149
by William Grant
Allow tutors and lecturers to enrol people in their offerings. |
610 |
req.store.commit() |
611 |
req.throw_redirect(req.uri) |
|
612 |
except formencode.Invalid, e: |
|
613 |
errors = e.unpack_errors() |
|
614 |
else: |
|
615 |
data = {} |
|
616 |
errors = {} |
|
617 |
||
618 |
ctx['data'] = data or {} |
|
619 |
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. |
620 |
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. |
621 |
ctx['errors'] = errors |
1700
by William Grant
Don't hide global form errors on the enrolment form. |
622 |
# If all of the fields validated, set the global form error.
|
623 |
if isinstance(errors, basestring): |
|
624 |
ctx['error_value'] = errors |
|
1149
by William Grant
Allow tutors and lecturers to enrol people in their offerings. |
625 |
|
1613
by William Grant
Add UI to edit/delete enrolments. |
626 |
|
627 |
class EnrolmentEditSchema(formencode.Schema): |
|
628 |
role = formencode.All(formencode.validators.OneOf( |
|
629 |
["lecturer", "tutor", "student"]), |
|
630 |
RoleEnrolmentValidator(), |
|
631 |
formencode.validators.UnicodeString()) |
|
632 |
||
633 |
||
634 |
class EnrolmentEdit(BaseFormView): |
|
635 |
"""A form to alter an enrolment's role."""
|
|
636 |
template = 'templates/enrolment-edit.html' |
|
637 |
tab = 'subjects' |
|
638 |
permission = 'edit' |
|
639 |
||
640 |
def populate_state(self, state): |
|
641 |
state.offering = self.context.offering |
|
642 |
||
643 |
def get_default_data(self, req): |
|
644 |
return {'role': self.context.role} |
|
645 |
||
646 |
@property
|
|
647 |
def validator(self): |
|
648 |
return EnrolmentEditSchema() |
|
649 |
||
650 |
def save_object(self, req, data): |
|
651 |
self.context.role = data['role'] |
|
652 |
||
653 |
def get_return_url(self, obj): |
|
654 |
return self.req.publisher.generate( |
|
655 |
self.context.offering, EnrolmentsView) |
|
656 |
||
657 |
def populate(self, req, ctx): |
|
658 |
super(EnrolmentEdit, self).populate(req, ctx) |
|
659 |
ctx['offering_perms'] = self.context.offering.get_permissions( |
|
660 |
req.user, req.config) |
|
661 |
||
662 |
||
663 |
class EnrolmentDelete(XHTMLView): |
|
664 |
"""A form to alter an enrolment's role."""
|
|
665 |
template = 'templates/enrolment-delete.html' |
|
666 |
tab = 'subjects' |
|
667 |
permission = 'edit' |
|
668 |
||
669 |
def populate(self, req, ctx): |
|
670 |
# If POSTing, delete delete delete.
|
|
671 |
if req.method == 'POST': |
|
672 |
self.context.delete() |
|
673 |
req.store.commit() |
|
674 |
req.throw_redirect(req.publisher.generate( |
|
675 |
self.context.offering, EnrolmentsView)) |
|
676 |
||
677 |
ctx['enrolment'] = self.context |
|
678 |
||
679 |
||
1165.3.19
by William Grant
Rename SubjectProjectSetView to OfferingProjectsView. |
680 |
class OfferingProjectsView(XHTMLView): |
681 |
"""View the projects for an offering."""
|
|
682 |
template = 'templates/offering_projects.html' |
|
1165.2.3
by Nick Chadwick
Added a new Admin view, which allows for the administration of projects |
683 |
permission = 'edit' |
1165.3.18
by William Grant
Put the project listing and view in the Subjects tab. |
684 |
tab = 'subjects' |
1616
by William Grant
Add a Projects breadcrumb. |
685 |
breadcrumb_text = 'Projects' |
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
686 |
|
1165.2.3
by Nick Chadwick
Added a new Admin view, which allows for the administration of projects |
687 |
def populate(self, req, ctx): |
688 |
self.plugin_styles[Plugin] = ["project.css"] |
|
1361
by William Grant
Remove the last +projectsets hardcoding. |
689 |
ctx['req'] = req |
1165.2.3
by Nick Chadwick
Added a new Admin view, which allows for the administration of projects |
690 |
ctx['offering'] = self.context |
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
691 |
ctx['projectsets'] = [] |
692 |
||
693 |
#Open the projectset Fragment, and render it for inclusion
|
|
694 |
#into the ProjectSets page
|
|
695 |
set_fragment = os.path.join(os.path.dirname(__file__), |
|
696 |
"templates/projectset_fragment.html") |
|
697 |
project_fragment = os.path.join(os.path.dirname(__file__), |
|
698 |
"templates/project_fragment.html") |
|
699 |
||
1718
by Matt Giuca
Projects view: Sort project sets by database ID and projects by deadline, rather than random database ordering. Fixes Launchpad bug #527559. |
700 |
for projectset in \ |
701 |
self.context.project_sets.order_by(ivle.database.ProjectSet.id): |
|
1720
by William Grant
Share one TemplateLoader between every instance of every view, so we cache EVERYTHING. |
702 |
settmpl = self._loader.load(set_fragment) |
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
703 |
setCtx = Context() |
1358
by William Grant
Use the publishing framework to generate URLs to projectsets. |
704 |
setCtx['req'] = req |
1165.3.30
by William Grant
Clean out the projectset fragment context. |
705 |
setCtx['projectset'] = projectset |
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
706 |
setCtx['projects'] = [] |
1358
by William Grant
Use the publishing framework to generate URLs to projectsets. |
707 |
setCtx['GroupsView'] = GroupsView |
1710.1.5
by Matt Giuca
Added new project set edit view. Linked from projects page, project set page. |
708 |
setCtx['ProjectSetEdit'] = ProjectSetEdit |
1710.1.11
by Matt Giuca
Project page: Replace AJAX project creation UI with link to ProjectNew view. Removed project creation UI including JSON API. |
709 |
setCtx['ProjectNew'] = ProjectNew |
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
710 |
|
1718
by Matt Giuca
Projects view: Sort project sets by database ID and projects by deadline, rather than random database ordering. Fixes Launchpad bug #527559. |
711 |
for project in \ |
712 |
projectset.projects.order_by(ivle.database.Project.deadline): |
|
1720
by William Grant
Share one TemplateLoader between every instance of every view, so we cache EVERYTHING. |
713 |
projecttmpl = self._loader.load(project_fragment) |
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
714 |
projectCtx = Context() |
1358
by William Grant
Use the publishing framework to generate URLs to projectsets. |
715 |
projectCtx['req'] = req |
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
716 |
projectCtx['project'] = project |
717 |
||
718 |
setCtx['projects'].append( |
|
719 |
projecttmpl.generate(projectCtx)) |
|
720 |
||
721 |
ctx['projectsets'].append(settmpl.generate(setCtx)) |
|
722 |
||
723 |
||
724 |
class ProjectView(XHTMLView): |
|
1165.2.3
by Nick Chadwick
Added a new Admin view, which allows for the administration of projects |
725 |
"""View the submissions for a ProjectSet"""
|
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
726 |
template = "templates/project.html" |
1556
by William Grant
Allow tutors to view project submissions. |
727 |
permission = "view_project_submissions" |
1165.3.18
by William Grant
Put the project listing and view in the Subjects tab. |
728 |
tab = 'subjects' |
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
729 |
|
1165.3.61
by William Grant
Provide a Subversion command to grab each submission. |
730 |
def build_subversion_url(self, svnroot, submission): |
731 |
princ = submission.assessed.principal |
|
732 |
||
733 |
if isinstance(princ, User): |
|
734 |
path = 'users/%s' % princ.login |
|
735 |
else: |
|
736 |
path = 'groups/%s_%s_%s_%s' % ( |
|
737 |
princ.project_set.offering.subject.short_name, |
|
738 |
princ.project_set.offering.semester.year, |
|
739 |
princ.project_set.offering.semester.semester, |
|
740 |
princ.name |
|
741 |
)
|
|
742 |
return urlparse.urljoin( |
|
743 |
svnroot, |
|
744 |
os.path.join(path, submission.path[1:] if |
|
745 |
submission.path.startswith(os.sep) else |
|
746 |
submission.path)) |
|
747 |
||
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
748 |
def populate(self, req, ctx): |
1165.3.66
by William Grant
Prettify the submissions table. |
749 |
self.plugin_styles[Plugin] = ["project.css"] |
750 |
||
1375.1.4
by William Grant
Indicate when there is nobody assigned to a project, and link to the page to fix that. |
751 |
ctx['req'] = req |
752 |
ctx['GroupsView'] = GroupsView |
|
753 |
ctx['EnrolView'] = EnrolView |
|
1719
by Matt Giuca
Project page: Added URL and deadline, so this page now shows all fields of the project. Fixes Launchpad bug #527560. |
754 |
ctx['format_datetime'] = ivle.date.make_date_nice |
1165.3.14
by William Grant
Improve ProjectView's template substantially. |
755 |
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph |
1165.3.61
by William Grant
Provide a Subversion command to grab each submission. |
756 |
ctx['build_subversion_url'] = self.build_subversion_url |
757 |
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 |
758 |
ctx['project'] = self.context |
1165.3.61
by William Grant
Provide a Subversion command to grab each submission. |
759 |
ctx['user'] = req.user |
1165.3.2
by Nick Chadwick
Created a new view for IVLE, allowing lecturers and tutors to |
760 |
|
1710.1.8
by Matt Giuca
Added 'New Project' non-AJAX UI to replace old AJAX one. Currently not linked anywhere; AJAX one still works. |
761 |
class ProjectUniquenessValidator(formencode.FancyValidator): |
762 |
"""A FormEncode validator that checks that a project short_name is unique
|
|
763 |
in a given offering.
|
|
764 |
||
765 |
The project referenced by state.existing_project is permitted to
|
|
766 |
hold that short_name. If any other project holds it, the input is rejected.
|
|
767 |
"""
|
|
768 |
def _to_python(self, value, state): |
|
1710.1.15
by Matt Giuca
subject.py: ProjectUniquenessValidator now allows a project to have the same shortname as the one it is replacing (allowing you to edit a project without changing its short name, once edit is implemented). |
769 |
if (state.store.find( |
1710.1.8
by Matt Giuca
Added 'New Project' non-AJAX UI to replace old AJAX one. Currently not linked anywhere; AJAX one still works. |
770 |
Project, |
771 |
Project.short_name == unicode(value), |
|
772 |
Project.project_set_id == ProjectSet.id, |
|
1710.1.15
by Matt Giuca
subject.py: ProjectUniquenessValidator now allows a project to have the same shortname as the one it is replacing (allowing you to edit a project without changing its short name, once edit is implemented). |
773 |
ProjectSet.offering == state.offering).one() not in |
774 |
(None, state.existing_project)): |
|
1710.1.8
by Matt Giuca
Added 'New Project' non-AJAX UI to replace old AJAX one. Currently not linked anywhere; AJAX one still works. |
775 |
raise formencode.Invalid( |
776 |
"A project with that URL name already exists in this offering."
|
|
777 |
, value, state) |
|
778 |
return value |
|
779 |
||
780 |
class ProjectSchema(formencode.Schema): |
|
781 |
name = formencode.validators.UnicodeString(not_empty=True) |
|
782 |
short_name = formencode.All( |
|
783 |
URLNameValidator(not_empty=True), |
|
784 |
ProjectUniquenessValidator()) |
|
785 |
deadline = DateTimeValidator(not_empty=True) |
|
786 |
url = formencode.validators.URL(if_missing=None, not_empty=False) |
|
787 |
synopsis = formencode.validators.UnicodeString(not_empty=True) |
|
788 |
||
789 |
class ProjectNew(BaseFormView): |
|
790 |
"""A form to create a new project."""
|
|
791 |
template = 'templates/project-new.html' |
|
792 |
tab = 'subjects' |
|
793 |
permission = 'edit' |
|
794 |
||
795 |
@property
|
|
796 |
def validator(self): |
|
797 |
return ProjectSchema() |
|
798 |
||
799 |
def populate(self, req, ctx): |
|
800 |
super(ProjectNew, self).populate(req, ctx) |
|
1710.1.16
by Matt Giuca
project-form no longer assumes the type of context; pass an extra projectset value. |
801 |
ctx['projectset'] = self.context |
1710.1.8
by Matt Giuca
Added 'New Project' non-AJAX UI to replace old AJAX one. Currently not linked anywhere; AJAX one still works. |
802 |
|
803 |
def populate_state(self, state): |
|
804 |
state.offering = self.context.offering |
|
805 |
state.existing_project = None |
|
806 |
||
807 |
def get_default_data(self, req): |
|
808 |
return {} |
|
809 |
||
810 |
def save_object(self, req, data): |
|
811 |
new_project = Project() |
|
812 |
new_project.project_set = self.context |
|
813 |
new_project.name = data['name'] |
|
814 |
new_project.short_name = data['short_name'] |
|
815 |
new_project.deadline = data['deadline'] |
|
1710.1.10
by Matt Giuca
ProjectNew view: Convert URLs to Unicode. This would previously break if a URL was entered. |
816 |
new_project.url = unicode(data['url']) if data['url'] else None |
1710.1.8
by Matt Giuca
Added 'New Project' non-AJAX UI to replace old AJAX one. Currently not linked anywhere; AJAX one still works. |
817 |
new_project.synopsis = data['synopsis'] |
818 |
req.store.add(new_project) |
|
819 |
return new_project |
|
820 |
||
1710.1.1
by Matt Giuca
Added new view for adding a new project set (offering/+projects/+new-set). This replaces the AJAX UI. The 'Add a new project set' link now links to the static 'new project set' page. |
821 |
class ProjectSetSchema(formencode.Schema): |
822 |
group_size = formencode.validators.Int(if_missing=None, not_empty=False) |
|
823 |
||
1710.1.5
by Matt Giuca
Added new project set edit view. Linked from projects page, project set page. |
824 |
class ProjectSetEdit(BaseFormView): |
825 |
"""A form to edit a project set."""
|
|
826 |
template = 'templates/projectset-edit.html' |
|
827 |
tab = 'subjects' |
|
828 |
permission = 'edit' |
|
829 |
||
830 |
@property
|
|
831 |
def validator(self): |
|
832 |
return ProjectSetSchema() |
|
833 |
||
834 |
def populate(self, req, ctx): |
|
835 |
super(ProjectSetEdit, self).populate(req, ctx) |
|
836 |
||
837 |
def get_default_data(self, req): |
|
838 |
return { |
|
839 |
'group_size': self.context.max_students_per_group, |
|
840 |
}
|
|
841 |
||
842 |
def save_object(self, req, data): |
|
843 |
self.context.max_students_per_group = data['group_size'] |
|
844 |
return self.context |
|
845 |
||
1710.1.1
by Matt Giuca
Added new view for adding a new project set (offering/+projects/+new-set). This replaces the AJAX UI. The 'Add a new project set' link now links to the static 'new project set' page. |
846 |
class ProjectSetNew(BaseFormView): |
847 |
"""A form to create a new project set."""
|
|
848 |
template = 'templates/projectset-new.html' |
|
849 |
tab = 'subjects' |
|
850 |
permission = 'edit' |
|
851 |
breadcrumb_text = "Projects" |
|
852 |
||
853 |
@property
|
|
854 |
def validator(self): |
|
855 |
return ProjectSetSchema() |
|
856 |
||
857 |
def populate(self, req, ctx): |
|
858 |
super(ProjectSetNew, self).populate(req, ctx) |
|
859 |
||
860 |
def get_default_data(self, req): |
|
861 |
return {} |
|
862 |
||
863 |
def save_object(self, req, data): |
|
864 |
new_set = ProjectSet() |
|
865 |
new_set.offering = self.context |
|
866 |
new_set.max_students_per_group = data['group_size'] |
|
867 |
req.store.add(new_set) |
|
868 |
return new_set |
|
869 |
||
1099.1.115
by William Grant
Add tabs to the new framework. Move the app icons into the apps themselves. |
870 |
class Plugin(ViewPlugin, MediaPlugin): |
1592
by William Grant
Add routes for Semester. We'll need them for the admin UI. |
871 |
forward_routes = (root_to_subject, root_to_semester, subject_to_offering, |
1613
by William Grant
Add UI to edit/delete enrolments. |
872 |
offering_to_project, offering_to_projectset, |
873 |
offering_to_enrolment) |
|
1592
by William Grant
Add routes for Semester. We'll need them for the admin UI. |
874 |
reverse_routes = ( |
1613
by William Grant
Add UI to edit/delete enrolments. |
875 |
subject_url, semester_url, offering_url, projectset_url, project_url, |
876 |
enrolment_url) |
|
1294.2.52
by William Grant
Port subject-related views to object traversal. |
877 |
|
878 |
views = [(ApplicationRoot, ('subjects', '+index'), SubjectsView), |
|
1596
by William Grant
Split subject/semester management out onto a separate page, and link to SemesterEdit. |
879 |
(ApplicationRoot, ('subjects', '+manage'), SubjectsManage), |
1532
by William Grant
Add subject creation/editing UI. Not linked just yet. |
880 |
(ApplicationRoot, ('subjects', '+new'), SubjectNew), |
1537
by William Grant
Add offering creation UI, and allow admins to change the subject or semester of existing offerings. |
881 |
(ApplicationRoot, ('subjects', '+new-offering'), OfferingNew), |
1594
by William Grant
Add semester edit UI. |
882 |
(ApplicationRoot, ('+semesters', '+new'), SemesterNew), |
1678.1.1
by Matt Giuca
Added new view SubjectView, which shows all offerings for a subject. This is accessible from the SubjectsManage view, or by the subject name in the breadcrumbs. |
883 |
(Subject, '+index', SubjectView), |
1532
by William Grant
Add subject creation/editing UI. Not linked just yet. |
884 |
(Subject, '+edit', SubjectEdit), |
1678.1.5
by Matt Giuca
Added new offering SubjectOfferingNew (+new-offering under a subject name). This is identical to +new-offering, but it is locked to a particular subject. The 'Create new offering' button on the subject page now links to this. |
885 |
(Subject, '+new-offering', SubjectOfferingNew), |
1594
by William Grant
Add semester edit UI. |
886 |
(Semester, '+edit', SemesterEdit), |
1442.1.2
by William Grant
Add basic (ie. pretty much empty) offering index. |
887 |
(Offering, '+index', OfferingView), |
1451.1.5
by William Grant
Add an OfferingEdit view, for setting the description and URL. |
888 |
(Offering, '+edit', OfferingEdit), |
1603
by William Grant
Add UI to clone worksheets between offerings -- replacing ivle-cloneworksheets. |
889 |
(Offering, '+clone-worksheets', OfferingCloneWorksheets), |
1365
by Matt Giuca
Added a new view under Offering/+enrolments to display all staff and students in an offering. |
890 |
(Offering, ('+enrolments', '+index'), EnrolmentsView), |
1294.2.52
by William Grant
Port subject-related views to object traversal. |
891 |
(Offering, ('+enrolments', '+new'), EnrolView), |
1613
by William Grant
Add UI to edit/delete enrolments. |
892 |
(Enrolment, '+edit', EnrolmentEdit), |
893 |
(Enrolment, '+delete', EnrolmentDelete), |
|
1294.2.52
by William Grant
Port subject-related views to object traversal. |
894 |
(Offering, ('+projects', '+index'), OfferingProjectsView), |
1710.1.1
by Matt Giuca
Added new view for adding a new project set (offering/+projects/+new-set). This replaces the AJAX UI. The 'Add a new project set' link now links to the static 'new project set' page. |
895 |
(Offering, ('+projects', '+new-set'), ProjectSetNew), |
1710.1.5
by Matt Giuca
Added new project set edit view. Linked from projects page, project set page. |
896 |
(ProjectSet, '+edit', ProjectSetEdit), |
1710.1.8
by Matt Giuca
Added 'New Project' non-AJAX UI to replace old AJAX one. Currently not linked anywhere; AJAX one still works. |
897 |
(ProjectSet, '+new', ProjectNew), |
1294.2.52
by William Grant
Port subject-related views to object traversal. |
898 |
(Project, '+index', ProjectView), |
899 |
]
|
|
1099.1.115
by William Grant
Add tabs to the new framework. Move the app icons into the apps themselves. |
900 |
|
1294.2.94
by William Grant
Add a SubjectBreadcrumb. |
901 |
breadcrumbs = {Subject: SubjectBreadcrumb, |
902 |
Offering: OfferingBreadcrumb, |
|
1294.2.96
by William Grant
Add a UserBreadcrumb. |
903 |
User: UserBreadcrumb, |
1294.2.98
by William Grant
Add a ProjectBreadcrumb. |
904 |
Project: ProjectBreadcrumb, |
1615
by William Grant
Add breadcrumbs for enrolments. |
905 |
Enrolment: EnrolmentBreadcrumb, |
1294.2.89
by William Grant
Add an Offering breadcrumb. |
906 |
}
|
907 |
||
1099.1.115
by William Grant
Add tabs to the new framework. Move the app icons into the apps themselves. |
908 |
tabs = [ |
1118
by matt.giuca
Rewrote tooltips for the four tabs visible by default. |
909 |
('subjects', 'Subjects', |
910 |
'View subject content and complete worksheets', |
|
911 |
'subjects.png', 'subjects', 5) |
|
1099.1.115
by William Grant
Add tabs to the new framework. Move the app icons into the apps themselves. |
912 |
]
|
913 |
||
914 |
media = 'subject-media' |