Merge lp:~aelkner/schooltool/schooltool.gradebook_column_preferences into lp:~schooltool-owners/schooltool/schooltool.gradebook
- schooltool.gradebook_column_preferences
- Merge into schooltool.gradebook
Proposed by
Alan Elkner
Status: | Merged | ||||
---|---|---|---|---|---|
Merge reported by: | Justas Sadzevičius | ||||
Merged at revision: | not available | ||||
Proposed branch: | lp:~aelkner/schooltool/schooltool.gradebook_column_preferences | ||||
Merge into: | lp:~schooltool-owners/schooltool/schooltool.gradebook | ||||
Diff against target: | None lines | ||||
To merge this branch: | bzr merge lp:~aelkner/schooltool/schooltool.gradebook_column_preferences | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Justas Sadzevičius (community) | Approve | ||
Review via email: mp+7191@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Justas Sadzevičius (justas.sadzevicius) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/schooltool/gradebook/browser/README.txt' |
2 | --- src/schooltool/gradebook/browser/README.txt 2009-05-08 01:30:55 +0000 |
3 | +++ src/schooltool/gradebook/browser/README.txt 2009-06-01 21:32:36 +0000 |
4 | @@ -7,8 +7,8 @@ |
5 | SchoolTool setup is the configuration of the categories. So let's log in as a |
6 | manager: |
7 | |
8 | - >>> from schooltool.app.browser.ftests import setup |
9 | - >>> manager = setup.logInManager() |
10 | + >>> from schooltool.app.browser.ftests import setup |
11 | + >>> manager = setup.logIn('manager', 'schooltool') |
12 | |
13 | |
14 | Initial School Setup |
15 | @@ -557,6 +557,74 @@ |
16 | '62' |
17 | |
18 | |
19 | +Column Preferences |
20 | +------------------ |
21 | + |
22 | +Teachers may want to hide or change the label of the summary columns or, in |
23 | +the case of the average column, they may want to choose a score system to |
24 | +be used in converting the average to a discrete value. To support this, we |
25 | +provide the column preferences view. |
26 | + |
27 | +First we will add a custom score system which we will use as a column |
28 | +preference. |
29 | + |
30 | + >>> manager.getLink('Manage').click() |
31 | + >>> manager.getLink('Score Systems').click() |
32 | + >>> manager.getLink('Add Score System').click() |
33 | + >>> url = manager.url + '?form-submitted&SAVE&title=Good/Bad' |
34 | + >>> url = url + '&displayed1=G&value1=1&percent1=60' |
35 | + >>> url = url + '&displayed2=B&value2=0&percent2=0' |
36 | + >>> manager.open(url) |
37 | + |
38 | +We'll start by calling up the current column preferences and note that there |
39 | +are none set yet. |
40 | + |
41 | + >>> stephan.getLink('Return to Gradebook').click() |
42 | + >>> stephan.getLink('Column Preferences').click() |
43 | + >>> analyze.printQuery("id('content-body')/form/table//input", stephan.contents) |
44 | + <input type="checkbox" name="hide_total" /> |
45 | + <input type="text" name="label_total" value="" /> |
46 | + <input type="checkbox" name="hide_average" /> |
47 | + <input type="text" name="label_average" value="" /> |
48 | + |
49 | + >>> analyze.printQuery("id('content-body')/form/table//option", stephan.contents) |
50 | + <option selected="selected" value="">-- No score system --</option> |
51 | + <option value="extended-letter-grade">Extended Letter Grade</option> |
52 | + <option value="goodbad">Good/Bad</option> |
53 | + <option value="letter-grade">Letter Grade</option> |
54 | + <option value="passfail">Pass/Fail</option> |
55 | + |
56 | +Now we'll set all of the preferences to something and test that the changes |
57 | +were saved. |
58 | + |
59 | + >>> url = stephan.url + '?form-submitted&UPDATE_SUBMIT' |
60 | + >>> url += '&hide_total=on&label_total=Summe' |
61 | + >>> url += '&hide_average=on&label_average=Durchschnitt' |
62 | + >>> url += '&scoresystem_average=goodbad' |
63 | + >>> stephan.open(url) |
64 | + |
65 | + >>> stephan.getLink('Column Preferences').click() |
66 | + >>> analyze.printQuery("id('content-body')/form/table//input", stephan.contents) |
67 | + <input type="checkbox" checked="checked" name="hide_total" /> |
68 | + <input type="text" name="label_total" value="Summe" /> |
69 | + <input type="checkbox" checked="checked" name="hide_average" /> |
70 | + <input type="text" name="label_average" value="Durchschnitt" /> |
71 | + |
72 | + >>> analyze.printQuery("id('content-body')/form/table//option", stephan.contents) |
73 | + <option value="">-- No score system --</option> |
74 | + <option value="extended-letter-grade">Extended Letter Grade</option> |
75 | + <option selected="selected" value="goodbad">Good/Bad</option> |
76 | + <option value="letter-grade">Letter Grade</option> |
77 | + <option value="passfail">Pass/Fail</option> |
78 | + |
79 | +Finally, we will reset the preferences to none so that the rest of the tests |
80 | +pass. |
81 | + |
82 | + >>> url = stephan.url + '?form-submitted&UPDATE_SUBMIT' |
83 | + >>> url += '&scoresystem_average=' |
84 | + >>> stephan.open(url) |
85 | + |
86 | + |
87 | My Grades |
88 | --------- |
89 | |
90 | @@ -575,7 +643,7 @@ |
91 | True |
92 | >>> 'HW 2' in claudia.contents or 'Final' in claudia.contents |
93 | False |
94 | - >>> claudia.contents.find('Current Grade: 86%') \ |
95 | + >>> claudia.contents.find('Ave.: 86%') \ |
96 | ... < claudia.contents.find('HW 1') \ |
97 | ... < claudia.contents.find('Quiz') \ |
98 | ... < claudia.contents.find('86 / 100') |
99 | @@ -737,12 +805,12 @@ |
100 | >>> claudia.open('http://localhost/schoolyears/2007/winter/sections/1/mygrades') |
101 | >>> print claudia.contents |
102 | <BLANKLINE> |
103 | - ... Current Grade: 86%... |
104 | + ... Ave.: 86%... |
105 | >>> tom = setup.logIn('tom', 'pwd') |
106 | >>> tom.open('http://localhost/schoolyears/2007/winter/sections/1/mygrades') |
107 | >>> print tom.contents |
108 | <BLANKLINE> |
109 | - ...Current Grade: 88%... |
110 | + ...Ave.: 88%... |
111 | |
112 | Students should not be able to view a teacher's gradebook. |
113 | |
114 | |
115 | === modified file 'src/schooltool/gradebook/browser/configure.zcml' |
116 | --- src/schooltool/gradebook/browser/configure.zcml 2009-05-08 01:30:55 +0000 |
117 | +++ src/schooltool/gradebook/browser/configure.zcml 2009-06-08 01:43:13 +0000 |
118 | @@ -352,6 +352,13 @@ |
119 | action="../weights.html" |
120 | permission="schooltool.view" |
121 | /> |
122 | + <menuItem |
123 | + menu="schooltool_actions" |
124 | + title="Column Preferences" |
125 | + for="..interfaces.IGradebook" |
126 | + action="column_preferences.html" |
127 | + permission="schooltool.view" |
128 | + /> |
129 | |
130 | <!-- Special navagation viewlet for update linked activity grades action --> |
131 | <navigationViewlet |
132 | @@ -420,9 +427,15 @@ |
133 | class=".gradebook.UpdateLinkedActivityGrades" |
134 | permission="schooltool.edit" |
135 | /> |
136 | + <page |
137 | + name="column_preferences.html" |
138 | + for="..interfaces.IGradebook" |
139 | + class=".gradebook.GradebookColumnPreferences" |
140 | + template="gradebook_column_preferences.pt" |
141 | + permission="schooltool.view" |
142 | + /> |
143 | |
144 | <!-- Terms --> |
145 | - |
146 | <zope:adapter |
147 | for="schooltool.gradebook.interfaces.IExternalActivitiesSource |
148 | zope.publisher.interfaces.browser.IBrowserRequest" |
149 | @@ -488,7 +501,7 @@ |
150 | permission="schooltool.edit" |
151 | /> |
152 | |
153 | - <!-- Views for IBasicPerson--> |
154 | + <!-- Views for IBasicPerson --> |
155 | <page |
156 | name="print_report_card.html" |
157 | for="schooltool.person.interfaces.IPerson" |
158 | @@ -513,6 +526,15 @@ |
159 | permission="schooltool.edit" |
160 | /> |
161 | |
162 | + <!-- Views for ISchoolToolApplication --> |
163 | + <page |
164 | + name="no_current_term.html" |
165 | + for="schooltool.app.interfaces.ISchoolToolApplication" |
166 | + class=".gradebook.NoCurrentTerm" |
167 | + template="no_current_term.pt" |
168 | + permission="schooltool.view" |
169 | + /> |
170 | + |
171 | <zope:adapter |
172 | factory=".report_card.ExistingScoresSystem" /> |
173 | |
174 | |
175 | === modified file 'src/schooltool/gradebook/browser/gradebook.css' |
176 | --- src/schooltool/gradebook/browser/gradebook.css 2009-05-08 01:30:55 +0000 |
177 | +++ src/schooltool/gradebook/browser/gradebook.css 2009-06-01 21:32:36 +0000 |
178 | @@ -148,3 +148,7 @@ |
179 | border: solid 1px black; |
180 | } |
181 | |
182 | +.bold { |
183 | + font-weight: bold; |
184 | +} |
185 | + |
186 | |
187 | === modified file 'src/schooltool/gradebook/browser/gradebook.py' |
188 | --- src/schooltool/gradebook/browser/gradebook.py 2009-05-11 13:16:00 +0000 |
189 | +++ src/schooltool/gradebook/browser/gradebook.py 2009-06-08 01:43:13 +0000 |
190 | @@ -20,38 +20,71 @@ |
191 | Gradebook Views |
192 | """ |
193 | |
194 | -from schooltool.course.interfaces import ILearner |
195 | -from schooltool.course.interfaces import IInstructor |
196 | __docformat__ = 'reStructuredText' |
197 | -import zope.schema |
198 | + |
199 | +import datetime |
200 | +import decimal |
201 | + |
202 | +from zope.app.keyreference.interfaces import IKeyReference |
203 | +from zope.component import queryUtility |
204 | +from zope.publisher.browser import BrowserView |
205 | +from zope.schema import ValidationError |
206 | +from zope.schema.interfaces import IVocabularyFactory |
207 | from zope.security import proxy |
208 | +from zope.traversing.api import getName |
209 | from zope.traversing.browser.absoluteurl import absoluteURL |
210 | -from zope.app.keyreference.interfaces import IKeyReference |
211 | from zope.viewlet import viewlet |
212 | -from zope.traversing.api import getName |
213 | -from zope.publisher.browser import BrowserView |
214 | |
215 | from schooltool.app import app |
216 | from schooltool.app.interfaces import ISchoolToolApplication |
217 | from schooltool.course.interfaces import ISection |
218 | +from schooltool.course.interfaces import ILearner, IInstructor |
219 | from schooltool.gradebook import interfaces |
220 | from schooltool.gradebook.activity import ensureAtLeastOneWorksheet |
221 | from schooltool.person.interfaces import IPerson |
222 | from schooltool.requirement.scoresystem import UNSCORED |
223 | -from schooltool.common import SchoolToolMessage as _ |
224 | from schooltool.requirement.interfaces import IValuesScoreSystem |
225 | from schooltool.requirement.interfaces import IDiscreteValuesScoreSystem |
226 | from schooltool.requirement.interfaces import IRangedValuesScoreSystem |
227 | from schooltool.term.interfaces import ITerm |
228 | |
229 | -import datetime |
230 | -import decimal |
231 | +from schooltool.common import SchoolToolMessage as _ |
232 | + |
233 | |
234 | GradebookCSSViewlet = viewlet.CSSViewlet("gradebook.css") |
235 | |
236 | DISCRETE_SCORE_SYSTEM = 'd' |
237 | RANGED_SCORE_SYSTEM = 'r' |
238 | |
239 | +column_keys = [('total', _("Total")), ('average', _("Ave."))] |
240 | + |
241 | + |
242 | +def escName(name): |
243 | + """converts title-based scoresystem name to querystring format""" |
244 | + chars = [c for c in name.lower() if c.isalnum() or c == ' '] |
245 | + return u''.join(chars).replace(' ', '-') |
246 | + |
247 | + |
248 | +def getScoreSystemFromEscName(name): |
249 | + """converts escaped scoresystem title to scoresystem""" |
250 | + factory = queryUtility(IVocabularyFactory, |
251 | + 'schooltool.requirement.discretescoresystems') |
252 | + vocab = factory(None) |
253 | + for term in vocab: |
254 | + if name == escName(term.token): |
255 | + return term.value |
256 | + return None |
257 | + |
258 | + |
259 | +def convertAverage(average, scoresystem): |
260 | + """converts average to display value of the given scoresystem""" |
261 | + if scoresystem is None: |
262 | + return '%s%%' % average |
263 | + for score in scoresystem.scores: |
264 | + if average >= score[2]: |
265 | + return score[0] |
266 | + raise ValueError |
267 | + |
268 | |
269 | class GradebookStartup(object): |
270 | """A view for entry into into the gradebook or mygrades views.""" |
271 | @@ -241,6 +274,37 @@ |
272 | return True |
273 | return False |
274 | |
275 | + def processColumnPreferences(self): |
276 | + gradebook = proxy.removeSecurityProxy(self.context) |
277 | + if self.isTeacher: |
278 | + person = self.person |
279 | + else: |
280 | + section = ISection(gradebook) |
281 | + instructors = list(section.instructors) |
282 | + if len(instructors) == 0: |
283 | + return {} |
284 | + person = instructors[0] |
285 | + columnPreferences = gradebook.getColumnPreferences(person) |
286 | + column_keys_dict = dict(column_keys) |
287 | + prefs = columnPreferences.get('total', {}) |
288 | + self.total_hide = prefs.get('hide', False) |
289 | + self.total_label = prefs.get('label', '') |
290 | + if len(self.total_label) == 0: |
291 | + self.total_label = column_keys_dict['total'] |
292 | + prefs = columnPreferences.get('average', {}) |
293 | + self.average_hide = prefs.get('hide', False) |
294 | + self.average_label = prefs.get('label', '') |
295 | + if len(self.average_label) == 0: |
296 | + self.average_label = column_keys_dict['average'] |
297 | + self.average_scoresystem = getScoreSystemFromEscName( |
298 | + prefs.get('scoresystem', '')) |
299 | + self.apply_all_colspan = 1 |
300 | + if not self.total_hide: |
301 | + self.apply_all_colspan += 1 |
302 | + if not self.average_hide: |
303 | + self.apply_all_colspan += 1 |
304 | + |
305 | + |
306 | class GradebookOverview(SectionFinder): |
307 | """Gradebook Overview/Table""" |
308 | |
309 | @@ -255,6 +319,9 @@ |
310 | worksheet = gradebook.context |
311 | gradebook.setCurrentWorksheet(self.person, worksheet) |
312 | |
313 | + """Retrieve column preferences.""" |
314 | + self.processColumnPreferences() |
315 | + |
316 | """Retrieve sorting information and store changes of it.""" |
317 | if 'sort_by' in self.request: |
318 | sort_by = self.request['sort_by'] |
319 | @@ -297,7 +364,7 @@ |
320 | try: |
321 | score = activity.scoresystem.fromUnicode( |
322 | self.request[cell_name]) |
323 | - except (zope.schema.ValidationError, ValueError): |
324 | + except (ValidationError, ValueError): |
325 | self.message = _( |
326 | 'The grade $value for activity $name is not valid.', |
327 | mapping={'value': self.request[cell_name], |
328 | @@ -387,6 +454,8 @@ |
329 | total, average = gradebook.getWorksheetTotalAverage(worksheet, |
330 | student) |
331 | |
332 | + average = convertAverage(average, self.average_scoresystem) |
333 | + |
334 | rows.append( |
335 | {'student': {'title': student.title, |
336 | 'id': student.username, |
337 | @@ -518,7 +587,7 @@ |
338 | try: |
339 | score = activity.scoresystem.fromUnicode( |
340 | self.request[id]) |
341 | - except (zope.schema.ValidationError, ValueError): |
342 | + except (ValidationError, ValueError): |
343 | self.message = _( |
344 | 'The grade $value for $name is not valid.', |
345 | mapping={'value': self.request[id], |
346 | @@ -579,6 +648,9 @@ |
347 | if self.handleSectionChange(): |
348 | return |
349 | |
350 | + """Retrieve column preferences.""" |
351 | + self.processColumnPreferences() |
352 | + |
353 | self.table = [] |
354 | total = 0 |
355 | count = 0 |
356 | @@ -602,8 +674,10 @@ |
357 | |
358 | self.table.append({'activity': activity.title, |
359 | 'grade': grade}) |
360 | + |
361 | if count: |
362 | - self.average = int((float(100 * total) / float(count)) + 0.5) |
363 | + average = int((float(100 * total) / float(count)) + 0.5) |
364 | + self.average = convertAverage(average, self.average_scoresystem) |
365 | else: |
366 | self.average = None |
367 | |
368 | @@ -638,3 +712,80 @@ |
369 | next_url = absoluteURL(self.context.__parent__, self.request) + \ |
370 | '/gradebook' |
371 | self.request.response.redirect(next_url) |
372 | + |
373 | + |
374 | +class GradebookColumnPreferences(BrowserView): |
375 | + """A view for editing a teacher's gradebook column preferences.""" |
376 | + |
377 | + def update(self): |
378 | + self.person = IPerson(self.request.principal) |
379 | + gradebook = proxy.removeSecurityProxy(self.context) |
380 | + |
381 | + if 'UPDATE_SUBMIT' in self.request: |
382 | + columnPreferences = gradebook.getColumnPreferences(self.person) |
383 | + for key, name in column_keys: |
384 | + prefs = columnPreferences.setdefault(key, {}) |
385 | + if 'hide_' + key in self.request: |
386 | + prefs['hide'] = True |
387 | + else: |
388 | + prefs['hide'] = False |
389 | + if 'label_' + key in self.request: |
390 | + prefs['label'] = self.request['label_' + key] |
391 | + else: |
392 | + prefs['label'] = '' |
393 | + if key != 'total': |
394 | + prefs['scoresystem'] = self.request['scoresystem_' + key] |
395 | + gradebook.setColumnPreferences(self.person, columnPreferences) |
396 | + |
397 | + if 'CANCEL' in self.request or 'UPDATE_SUBMIT' in self.request: |
398 | + self.request.response.redirect('index.html') |
399 | + |
400 | + @property |
401 | + def columns(self): |
402 | + self.person = IPerson(self.request.principal) |
403 | + gradebook = proxy.removeSecurityProxy(self.context) |
404 | + results = [] |
405 | + columnPreferences = gradebook.getColumnPreferences(self.person) |
406 | + for key, name in column_keys: |
407 | + prefs = columnPreferences.get(key, {}) |
408 | + hide = prefs.get('hide', False) |
409 | + label = prefs.get('label', '') |
410 | + scoresystem = prefs.get('scoresystem', '') |
411 | + result = { |
412 | + 'name': name, |
413 | + 'hide_name': 'hide_' + key, |
414 | + 'hide_value': hide, |
415 | + 'label_name': 'label_' + key, |
416 | + 'label_value': label, |
417 | + 'scoresystem_name': 'scoresystem_' + key, |
418 | + 'scoresystem_value': scoresystem, |
419 | + } |
420 | + results.append(result) |
421 | + return results |
422 | + |
423 | + @property |
424 | + def scoresystems(self): |
425 | + factory = queryUtility(IVocabularyFactory, |
426 | + 'schooltool.requirement.discretescoresystems') |
427 | + vocab = factory(None) |
428 | + result = { |
429 | + 'name': _('-- No score system --'), |
430 | + 'value': '', |
431 | + } |
432 | + results = [result] |
433 | + for term in vocab: |
434 | + result = { |
435 | + 'name': term.token, |
436 | + 'value': escName(term.token), |
437 | + } |
438 | + results.append(result) |
439 | + return results |
440 | + |
441 | + |
442 | +class NoCurrentTerm(BrowserView): |
443 | + """A view for informing the user of the need to set up a schoolyear |
444 | + and at least one term.""" |
445 | + |
446 | + def update(self): |
447 | + pass |
448 | + |
449 | |
450 | === added file 'src/schooltool/gradebook/browser/gradebook_column_preferences.pt' |
451 | --- src/schooltool/gradebook/browser/gradebook_column_preferences.pt 1970-01-01 00:00:00 +0000 |
452 | +++ src/schooltool/gradebook/browser/gradebook_column_preferences.pt 2009-05-14 10:35:19 +0000 |
453 | @@ -0,0 +1,67 @@ |
454 | +<tal:define define="dummy view/update"/> |
455 | +<html metal:use-macro="view/@@standard_macros/page" i18n:domain="schooltool"> |
456 | +<head> |
457 | + <title metal:fill-slot="title" i18n:translate="">Set Column Preferences</title> |
458 | +</head> |
459 | +<body> |
460 | + |
461 | +<h1 metal:fill-slot="content-header" |
462 | + i18n:translate="">Set Column Preferences</h1> |
463 | + |
464 | +<metal:block metal:fill-slot="body"> |
465 | + <form method="post" |
466 | + tal:attributes="action string:${context/@@absolute_url}/column_preferences.html"> |
467 | + <input type="hidden" name="form-submitted" value="" /> |
468 | + |
469 | + <table class="schooltool_gradebook"> |
470 | + <tr> |
471 | + <th class="cell header fully_padded">Column Type</th> |
472 | + <th class="cell header fully_padded">Hide?</th> |
473 | + <th class="cell header fully_padded">Label</th> |
474 | + <th class="cell header fully_padded">Score System</th> |
475 | + </tr> |
476 | + |
477 | + <tal:block repeat="column view/columns"> |
478 | + <tr class="bordered"> |
479 | + <td class="cell fully_padded"> |
480 | + <div tal:content="column/name" /> |
481 | + </td> |
482 | + <td class="cell fully_padded"> |
483 | + <input type="checkbox" |
484 | + tal:attributes="name column/hide_name; checked column/hide_value" /> |
485 | + </td> |
486 | + <td class="cell fully_padded"> |
487 | + <input type="text" |
488 | + tal:attributes="name column/label_name; value column/label_value" /> |
489 | + </td> |
490 | + <td class="cell fully_padded"> |
491 | + <tal:block condition="python: column['hide_name'] != 'hide_total'"> |
492 | + <select tal:attributes="id column/scoresystem_name; name column/scoresystem_name"> |
493 | + <tal:block repeat="scoresystem view/scoresystems"> |
494 | + <option tal:condition="python: scoresystem['value'] == column['scoresystem_value']" |
495 | + selected |
496 | + tal:attributes="value scoresystem/value" |
497 | + tal:content="scoresystem/name" /> |
498 | + <option tal:condition="python: scoresystem['value'] != column['scoresystem_value']" |
499 | + tal:attributes="value scoresystem/value" |
500 | + tal:content="scoresystem/name" /> |
501 | + </tal:block> |
502 | + </select> |
503 | + </tal:block> |
504 | + </td> |
505 | + </tr> |
506 | + </tal:block> |
507 | + |
508 | + </table> |
509 | + <div style="height: 11px;"></div> |
510 | + |
511 | + <div class="controls"> |
512 | + <input type="submit" class="button-ok" name="UPDATE_SUBMIT" value="Update" /> |
513 | + <input type="submit" class="button-cancel" name="CANCEL" value="Cancel" /> |
514 | + </div> |
515 | + </form> |
516 | + |
517 | +</metal:block> |
518 | +</body> |
519 | +</html> |
520 | + |
521 | |
522 | === modified file 'src/schooltool/gradebook/browser/gradebook_overview.js.pt' |
523 | --- src/schooltool/gradebook/browser/gradebook_overview.js.pt 2009-04-17 04:22:56 +0000 |
524 | +++ src/schooltool/gradebook/browser/gradebook_overview.js.pt 2009-04-30 19:20:05 +0000 |
525 | @@ -160,7 +160,6 @@ |
526 | } |
527 | if (keynum < 48 || (keynum > 57 && keynum < 65) || (keynum > 90 && keynum < 97) || keynum > 122) |
528 | { |
529 | - document.getElementById(name).select(); |
530 | return true; |
531 | } |
532 | |
533 | |
534 | === modified file 'src/schooltool/gradebook/browser/gradebook_overview.pt' |
535 | --- src/schooltool/gradebook/browser/gradebook_overview.pt 2009-04-29 00:39:08 +0000 |
536 | +++ src/schooltool/gradebook/browser/gradebook_overview.pt 2009-05-15 06:01:23 +0000 |
537 | @@ -114,7 +114,7 @@ |
538 | <table class="schooltool_gradebook"> |
539 | <tr tal:condition="python:len(view.descriptions) > 0"> |
540 | <td id="description" class="description" |
541 | - tal:attributes="colspan python:len(view.descriptions) + 3" /> |
542 | + tal:attributes="colspan python:len(view.descriptions) + view.apply_all_colspan" /> |
543 | </tr> |
544 | |
545 | <tr> |
546 | @@ -122,11 +122,11 @@ |
547 | <div i18n:translate="">Name</div> |
548 | <a href="?sort_by=student" i18n:translate="">(sort)</a> |
549 | </th> |
550 | - <th class="cell header"> |
551 | - <div i18n:translate="">Total</div> |
552 | + <th tal:condition="not: view/total_hide" class="cell padded header"> |
553 | + <div tal:content="view/total_label" /> |
554 | </th> |
555 | - <th class="cell header"> |
556 | - <div i18n:translate="">Ave.</div> |
557 | + <th tal:condition="not: view/average_hide" class="cell padded header"> |
558 | + <div tal:content="view/average_label" /> |
559 | <a tal:condition="nothing" href="?sort_by=average" i18n:translate="">(sort)</a> |
560 | </th> |
561 | <th class="cell title header" tal:repeat="activity view/activities"> |
562 | @@ -152,7 +152,8 @@ |
563 | </tr> |
564 | |
565 | <tr> |
566 | - <td colspan="3" class="cell padded fd_cell empty" i18n:translate="">Apply a grade for all students:</td> |
567 | + <td tal:attributes="colspan view/apply_all_colspan" |
568 | + class="cell padded fd_cell empty" i18n:translate="">Apply a grade for all students:</td> |
569 | <td class="cell padded fd_cell" tal:repeat="activity view/activities" |
570 | tal:attributes="id string:fd_${activity/hash}_cell"> |
571 | <input type="text" size="4" class="data fd" |
572 | @@ -178,14 +179,14 @@ |
573 | Tom Hoffman |
574 | </a> |
575 | </td> |
576 | - <td class="cell padded even"> |
577 | + <td tal:condition="not: view/total_hide" class="cell padded even"> |
578 | <tal:if condition="row/total|nothing"> |
579 | <b><span tal:replace="row/total" /></b> |
580 | </tal:if> |
581 | </td> |
582 | - <td class="cell padded even"> |
583 | + <td tal:condition="not: view/average_hide" class="cell padded even"> |
584 | <tal:if condition="row/average|nothing"> |
585 | - <b><span tal:replace="row/average" />%</b> |
586 | + <b><span tal:replace="row/average" /></b> |
587 | </tal:if> |
588 | </td> |
589 | <td class="cell even" tal:repeat="grade row/grades" |
590 | @@ -207,14 +208,14 @@ |
591 | Tom Hoffman |
592 | </a> |
593 | </td> |
594 | - <td class="cell padded odd"> |
595 | + <td tal:condition="not: view/total_hide" class="cell padded odd"> |
596 | <tal:if condition="row/total|nothing"> |
597 | <b><span tal:replace="row/total" /></b> |
598 | </tal:if> |
599 | </td> |
600 | - <td class="cell padded odd"> |
601 | + <td tal:condition="not: view/average_hide" class="cell padded odd"> |
602 | <tal:if condition="row/average|nothing"> |
603 | - <b><span tal:replace="row/average" />%</b> |
604 | + <b><span tal:replace="row/average" /></b> |
605 | </tal:if> |
606 | </td> |
607 | <td class="cell odd" tal:repeat="grade row/grades" |
608 | |
609 | === modified file 'src/schooltool/gradebook/browser/mygrades.pt' |
610 | --- src/schooltool/gradebook/browser/mygrades.pt 2009-04-29 00:39:08 +0000 |
611 | +++ src/schooltool/gradebook/browser/mygrades.pt 2009-05-15 06:01:23 +0000 |
612 | @@ -72,10 +72,10 @@ |
613 | </table> |
614 | |
615 | <table class="schooltool_gradebook"> |
616 | - <tr> |
617 | + <tr tal:condition="not: view/average_hide"> |
618 | <td colspan="2" class="description"> |
619 | <div tal:condition="view/average" |
620 | - tal:content="string: Current Grade: ${view/average}%">Average</div> |
621 | + tal:content="string: ${view/average_label}: ${view/average}">Average</div> |
622 | <div tal:condition="not: view/average">Nothing Graded</div> |
623 | </td> |
624 | </tr> |
625 | |
626 | === added file 'src/schooltool/gradebook/browser/no_current_term.pt' |
627 | --- src/schooltool/gradebook/browser/no_current_term.pt 1970-01-01 00:00:00 +0000 |
628 | +++ src/schooltool/gradebook/browser/no_current_term.pt 2009-06-08 01:43:13 +0000 |
629 | @@ -0,0 +1,16 @@ |
630 | +<tal:tag condition="view/update" /> |
631 | +<html metal:use-macro="context/@@standard_macros/page" |
632 | + i18n:domain="schooltool"> |
633 | +<head> |
634 | + <title metal:fill-slot="title" i18n:translate=""> |
635 | + No Current Term |
636 | + </title> |
637 | +</head> |
638 | +<body> |
639 | +<div metal:fill-slot="body"> |
640 | + <h1>The operation you attempted cannot be completed because there are curently |
641 | + no terms set up in your SchoolTool instance. Please have a user with |
642 | + administation access set up at least one term and try the operation again.</h1> |
643 | +</div> |
644 | +</body> |
645 | +</html> |
646 | |
647 | === added file 'src/schooltool/gradebook/browser/no_current_term.txt' |
648 | --- src/schooltool/gradebook/browser/no_current_term.txt 1970-01-01 00:00:00 +0000 |
649 | +++ src/schooltool/gradebook/browser/no_current_term.txt 2009-06-08 01:43:13 +0000 |
650 | @@ -0,0 +1,25 @@ |
651 | +=============== |
652 | +No Current Term |
653 | +=============== |
654 | + |
655 | +If a schooltool operation relies on there being at least one term set up, and |
656 | +the administator has not gotten around to doing that, we need to fail |
657 | +gracefully. That means giving the user an error message rather than crashing. |
658 | + |
659 | +Let's log in as manager and create a student. |
660 | + |
661 | + >>> from schooltool.app.browser.ftests import setup |
662 | + >>> manager = Browser('manager', 'schooltool') |
663 | + >>> setup.addPerson('Student One', 'student1', 'pwd') |
664 | + |
665 | +We'll navigate to the student and request a report card, an operation that is |
666 | +impossible without there being at least one term set up. We'll test that the |
667 | +user gets redirected to the error page. |
668 | + |
669 | + >>> manager.getLink('Manage').click() |
670 | + >>> manager.getLink('Persons').click() |
671 | + >>> manager.getLink('Student One').click() |
672 | + >>> manager.getLink('Print Report Card').click() |
673 | + >>> manager.url |
674 | + 'http://localhost/no_current_term.html' |
675 | + |
676 | |
677 | === modified file 'src/schooltool/gradebook/browser/pdf_views.py' |
678 | --- src/schooltool/gradebook/browser/pdf_views.py 2009-05-11 13:08:19 +0000 |
679 | +++ src/schooltool/gradebook/browser/pdf_views.py 2009-06-08 01:43:13 +0000 |
680 | @@ -35,6 +35,7 @@ |
681 | from zope.component import getUtility, queryAdapter |
682 | from zope.i18n import translate |
683 | from zope.publisher.browser import BrowserView |
684 | +from zope.traversing.browser.absoluteurl import absoluteURL |
685 | |
686 | from schooltool.app.interfaces import ISchoolToolApplication |
687 | from schooltool.app.browser import pdfcal |
688 | @@ -256,6 +257,13 @@ |
689 | |
690 | def __call__(self): |
691 | """Return the PDF of a report card for each student.""" |
692 | + current_term = getUtility(IDateManager).current_term |
693 | + if current_term is None: |
694 | + next_url = absoluteURL(ISchoolToolApplication(None), self.request) |
695 | + next_url += '/no_current_term.html' |
696 | + self.request.response.redirect(next_url) |
697 | + return |
698 | + |
699 | if self.pdf_support_disabled: |
700 | return translate(self.pdf_disabled_text, context=self.request) |
701 | |
702 | |
703 | === modified file 'src/schooltool/gradebook/configure.zcml' |
704 | --- src/schooltool/gradebook/configure.zcml 2009-04-17 13:26:14 +0000 |
705 | +++ src/schooltool/gradebook/configure.zcml 2009-06-05 00:11:04 +0000 |
706 | @@ -284,4 +284,11 @@ |
707 | <adapter factory=".gradebook_init.GradebookAppStartup" |
708 | name="gradebook_startup_init" /> |
709 | |
710 | + <!-- generations --> |
711 | + <utility |
712 | + name="schooltool.gradebook" |
713 | + provides="zope.app.generations.interfaces.ISchemaManager" |
714 | + component=".generations.schemaManager" |
715 | + /> |
716 | + |
717 | </configure> |
718 | |
719 | === added directory 'src/schooltool/gradebook/generations' |
720 | === added file 'src/schooltool/gradebook/generations/__init__.py' |
721 | --- src/schooltool/gradebook/generations/__init__.py 1970-01-01 00:00:00 +0000 |
722 | +++ src/schooltool/gradebook/generations/__init__.py 2009-06-05 00:11:04 +0000 |
723 | @@ -0,0 +1,28 @@ |
724 | +# |
725 | +# SchoolTool - common information systems platform for school administration |
726 | +# Copyright (c) 2008 Shuttleworth Foundation |
727 | +# |
728 | +# This program is free software; you can redistribute it and/or modify |
729 | +# it under the terms of the GNU General Public License as published by |
730 | +# the Free Software Foundation; either version 2 of the License, or |
731 | +# (at your option) any later version. |
732 | +# |
733 | +# This program is distributed in the hope that it will be useful, |
734 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
735 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
736 | +# GNU General Public License for more details. |
737 | +# |
738 | +# You should have received a copy of the GNU General Public License |
739 | +# along with this program; if not, write to the Free Software |
740 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
741 | +# |
742 | +""" |
743 | +Generations for database version upgrades. |
744 | +""" |
745 | + |
746 | +from zope.app.generations.generations import SchemaManager |
747 | + |
748 | +schemaManager = SchemaManager( |
749 | + minimum_generation=1, |
750 | + generation=1, |
751 | + package_name='schooltool.gradebook.generations') |
752 | |
753 | === added file 'src/schooltool/gradebook/generations/evolve1.py' |
754 | --- src/schooltool/gradebook/generations/evolve1.py 1970-01-01 00:00:00 +0000 |
755 | +++ src/schooltool/gradebook/generations/evolve1.py 2009-06-05 00:11:04 +0000 |
756 | @@ -0,0 +1,89 @@ |
757 | +# |
758 | +# SchoolTool - common information systems platform for school administration |
759 | +# Copyright (c) 2008 Shuttleworth Foundation |
760 | +# |
761 | +# This program is free software; you can redistribute it and/or modify |
762 | +# it under the terms of the GNU General Public License as published by |
763 | +# the Free Software Foundation; either version 2 of the License, or |
764 | +# (at your option) any later version. |
765 | +# |
766 | +# This program is distributed in the hope that it will be useful, |
767 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
768 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
769 | +# GNU General Public License for more details. |
770 | +# |
771 | +# You should have received a copy of the GNU General Public License |
772 | +# along with this program; if not, write to the Free Software |
773 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
774 | +# |
775 | +""" |
776 | +Evolve database to generation 1. |
777 | + |
778 | +Moves hard-coded score system utilities to the app site manager. |
779 | +""" |
780 | + |
781 | +from zope.app.zopeappgenerations import getRootFolder |
782 | + |
783 | +from schooltool.app.interfaces import ISchoolToolApplication |
784 | +from schooltool.course.interfaces import ISectionContainer |
785 | +from schooltool.gradebook.interfaces import IActivities, IGradebookRoot |
786 | +from schooltool.person.interfaces import IPersonContainer |
787 | +from schooltool.schoolyear.interfaces import ISchoolYearContainer |
788 | +from schooltool.requirement.interfaces import IScoreSystemsProxy |
789 | +from schooltool.requirement.interfaces import IDiscreteValuesScoreSystem |
790 | +from schooltool.requirement.interfaces import IEvaluations |
791 | +from schooltool.requirement.scoresystem import CustomScoreSystem, PassFail |
792 | +from schooltool.requirement.scoresystem import AmericanLetterScoreSystem |
793 | +from schooltool.requirement.scoresystem import ExtendedAmericanLetterScoreSystem |
794 | + |
795 | + |
796 | +def updateEvaluations(app, ss, custom_ss): |
797 | + """Update all evaluations using the hard-coded score system to use the |
798 | + newly created custom score system""" |
799 | + |
800 | + for person in app['persons'].values(): |
801 | + evaluations = IEvaluations(person) |
802 | + for evaluation in evaluations.values(): |
803 | + if evaluation.scoreSystem == ss: |
804 | + evaluation.scoreSystem = custom_ss |
805 | + person._p_changed = True |
806 | + |
807 | + |
808 | +def updateObjActivities(obj, activities, ss, custom_ss): |
809 | + """Update the obj activities using the hard-coded score system to use the |
810 | + newly created custom score system""" |
811 | + |
812 | + for worksheet in activities.values(): |
813 | + for activity in worksheet.values(): |
814 | + if activity.scoresystem == ss: |
815 | + activity.scoresystem = custom_ss |
816 | + obj._p_changed = True |
817 | + |
818 | + |
819 | +def updateAllActivities(app, ss, custom_ss): |
820 | + """Update all activities using the hard-coded score system to use the |
821 | + newly created custom score system""" |
822 | + |
823 | + for sections in app['schooltool.course.section'].values(): |
824 | + for section in sections.values(): |
825 | + updateObjActivities(section, IActivities(section), ss, custom_ss) |
826 | + |
827 | + root = IGradebookRoot(app) |
828 | + updateObjActivities(root, root.templates, ss, custom_ss) |
829 | + updateObjActivities(root, root.deployed, ss, custom_ss) |
830 | + |
831 | + |
832 | +def evolve(context): |
833 | + """Migrates hard-coded discrete values score systems found in |
834 | + schooltool.requirement.scoresystem to the app site manager""" |
835 | + |
836 | + app = getRootFolder(context) |
837 | + ssProxy = IScoreSystemsProxy(app) |
838 | + for ss in [PassFail, AmericanLetterScoreSystem, |
839 | + ExtendedAmericanLetterScoreSystem]: |
840 | + custom_ss = CustomScoreSystem(ss.title, ss.description, ss.scores, |
841 | + ss._bestScore, ss._minPassingScore) |
842 | + ssProxy.addScoreSystem(custom_ss) |
843 | + updateEvaluations(app, ss, custom_ss) |
844 | + updateAllActivities(app, ss, custom_ss) |
845 | + |
846 | |
847 | === added file 'src/schooltool/gradebook/generations/install.py' |
848 | --- src/schooltool/gradebook/generations/install.py 1970-01-01 00:00:00 +0000 |
849 | +++ src/schooltool/gradebook/generations/install.py 2009-06-05 00:11:04 +0000 |
850 | @@ -0,0 +1,31 @@ |
851 | +# |
852 | +# SchoolTool - common information systems platform for school administration |
853 | +# Copyright (c) 2008 Shuttleworth Foundation |
854 | +# |
855 | +# This program is free software; you can redistribute it and/or modify |
856 | +# it under the terms of the GNU General Public License as published by |
857 | +# the Free Software Foundation; either version 2 of the License, or |
858 | +# (at your option) any later version. |
859 | +# |
860 | +# This program is distributed in the hope that it will be useful, |
861 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
862 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
863 | +# GNU General Public License for more details. |
864 | +# |
865 | +# You should have received a copy of the GNU General Public License |
866 | +# along with this program; if not, write to the Free Software |
867 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
868 | +# |
869 | +""" |
870 | +Initial deployment of generations to schooltool.gradebook. |
871 | + |
872 | +Manually evolves to generation 1. Done to avoid double deployment when |
873 | +introducing generations. |
874 | +""" |
875 | + |
876 | +import evolve1 |
877 | + |
878 | + |
879 | +def evolve(context): |
880 | + evolve1.evolve(context) |
881 | + |
882 | |
883 | === added directory 'src/schooltool/gradebook/generations/tests' |
884 | === added file 'src/schooltool/gradebook/generations/tests/__init__.py' |
885 | --- src/schooltool/gradebook/generations/tests/__init__.py 1970-01-01 00:00:00 +0000 |
886 | +++ src/schooltool/gradebook/generations/tests/__init__.py 2009-06-05 00:11:04 +0000 |
887 | @@ -0,0 +1,72 @@ |
888 | +""" |
889 | +Tests for generation scripts. |
890 | +""" |
891 | + |
892 | +from persistent.interfaces import IPersistent |
893 | + |
894 | +from zope.app.keyreference.interfaces import IKeyReference |
895 | +from zope.app.publication.zopepublication import ZopePublication |
896 | +from zope.app.testing.setup import setUpAnnotations |
897 | +from zope.component import provideAdapter |
898 | +from zope.interface import implements |
899 | + |
900 | +from schooltool.app.app import SchoolToolApplication |
901 | +from schooltool.app.interfaces import ISchoolToolApplication |
902 | +from schooltool.course.interfaces import ISection |
903 | +from schooltool.gradebook.activity import getSectionActivities |
904 | +from schooltool.gradebook.interfaces import IGradebookRoot, IActivities |
905 | +from schooltool.gradebook.gradebook_init import getGradebookRoot |
906 | +from schooltool.requirement.evaluation import getEvaluations |
907 | +from schooltool.requirement.interfaces import IEvaluations |
908 | +from schooltool.requirement.interfaces import IHaveEvaluations |
909 | +from schooltool.requirement.scoresystem import ScoreSystemsProxy |
910 | + |
911 | + |
912 | +class ContextStub(object): |
913 | + """Stub for the context argument passed to evolve scripts. |
914 | + |
915 | + >>> from zope.app.zopeappgenerations import getRootFolder |
916 | + >>> context = ContextStub() |
917 | + >>> getRootFolder(context) is context.root_folder |
918 | + True |
919 | + """ |
920 | + |
921 | + class ConnectionStub(object): |
922 | + def __init__(self, root_folder): |
923 | + self.root_folder = root_folder |
924 | + def root(self): |
925 | + return {ZopePublication.root_name: self.root_folder} |
926 | + |
927 | + def __init__(self): |
928 | + self.root_folder = SchoolToolApplication() |
929 | + self.connection = self.ConnectionStub(self.root_folder) |
930 | + |
931 | + |
932 | +_d = {} |
933 | + |
934 | +class StupidKeyReference(object): |
935 | + implements(IKeyReference) |
936 | + key_type_id = 'StupidKeyReference' |
937 | + def __init__(self, ob): |
938 | + global _d |
939 | + self.id = id(ob) |
940 | + _d[self.id] = ob |
941 | + def __call__(self): |
942 | + return _d[self.id] |
943 | + def __hash__(self): |
944 | + return self.id |
945 | + def __cmp__(self, other): |
946 | + return cmp(hash(self), hash(other)) |
947 | + |
948 | + |
949 | +def provideAdapters(): |
950 | + setUpAnnotations() |
951 | + provideAdapter(StupidKeyReference, [IPersistent], IKeyReference) |
952 | + provideAdapter(ScoreSystemsProxy) |
953 | + provideAdapter(getGradebookRoot, adapts=(ISchoolToolApplication,), |
954 | + provides=IGradebookRoot) |
955 | + provideAdapter(getSectionActivities, adapts=(ISection,), |
956 | + provides=IActivities) |
957 | + provideAdapter(getEvaluations, adapts=(IHaveEvaluations,), |
958 | + provides=IEvaluations) |
959 | + |
960 | |
961 | === added file 'src/schooltool/gradebook/generations/tests/test_evolve1.py' |
962 | --- src/schooltool/gradebook/generations/tests/test_evolve1.py 1970-01-01 00:00:00 +0000 |
963 | +++ src/schooltool/gradebook/generations/tests/test_evolve1.py 2009-06-05 00:11:04 +0000 |
964 | @@ -0,0 +1,140 @@ |
965 | +# coding=UTF8 |
966 | +# |
967 | +# SchoolTool - common information systems platform for school administration |
968 | +# Copyright (c) 2008 Shuttleworth Foundation |
969 | +# |
970 | +# This program is free software; you can redistribute it and/or modify |
971 | +# it under the terms of the GNU General Public License as published by |
972 | +# the Free Software Foundation; either version 2 of the License, or |
973 | +# (at your option) any later version. |
974 | +# |
975 | +# This program is distributed in the hope that it will be useful, |
976 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
977 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
978 | +# GNU General Public License for more details. |
979 | +# |
980 | +# You should have received a copy of the GNU General Public License |
981 | +# along with this program; if not, write to the Free Software |
982 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
983 | +# |
984 | +""" |
985 | +Unit tests for schooltool.gradebook.generations.evolve1 |
986 | +""" |
987 | + |
988 | +import unittest |
989 | + |
990 | +from zope.app.zopeappgenerations import getRootFolder |
991 | +from zope.testing import doctest |
992 | + |
993 | +from schooltool.app.interfaces import ISchoolToolApplication |
994 | +from schooltool.gradebook.generations.tests import ContextStub |
995 | +from schooltool.gradebook.generations.tests import provideAdapters |
996 | +from schooltool.gradebook.generations.evolve1 import evolve |
997 | +from schooltool.requirement.interfaces import IHaveEvaluations |
998 | + |
999 | + |
1000 | +def doctest_evolve1(): |
1001 | + """Evolution to generation 1. |
1002 | + |
1003 | + First, we'll set up the app object: |
1004 | + |
1005 | + >>> provideAdapters() |
1006 | + >>> context = ContextStub() |
1007 | + >>> app = getRootFolder(context) |
1008 | + |
1009 | + >>> from zope.app.component.site import LocalSiteManager |
1010 | + >>> app.setSiteManager(LocalSiteManager(app)) |
1011 | + |
1012 | + We'll set up our test with data that will be effected by running the |
1013 | + evolve script: |
1014 | + |
1015 | + >>> from schooltool.person.person import PersonContainer, Person |
1016 | + >>> app['persons'] = PersonContainer() |
1017 | + >>> student = Person('student') |
1018 | + >>> app['persons']['student'] = student |
1019 | + |
1020 | + >>> from schooltool.course.section import SectionContainerContainer |
1021 | + >>> from schooltool.course.section import SectionContainer, Section |
1022 | + >>> app['schooltool.course.section'] = SectionContainerContainer() |
1023 | + >>> app['schooltool.course.section']['1'] = SectionContainer() |
1024 | + >>> section = Section('section') |
1025 | + >>> app['schooltool.course.section']['1']['1'] = section |
1026 | + |
1027 | + >>> from schooltool.gradebook.gradebook_init import setUpGradebookRoot |
1028 | + >>> from schooltool.gradebook.interfaces import IGradebookRoot |
1029 | + >>> setUpGradebookRoot(app) |
1030 | + >>> root = IGradebookRoot(app) |
1031 | + |
1032 | + >>> from schooltool.requirement.scoresystem import PassFail |
1033 | + >>> from schooltool.requirement.scoresystem import AmericanLetterScoreSystem |
1034 | + >>> from schooltool.gradebook.activity import ReportWorksheet |
1035 | + >>> from schooltool.gradebook.activity import ReportActivity |
1036 | + >>> root.templates['1'] = temp_ws = ReportWorksheet('1') |
1037 | + >>> temp_ws['1'] = temp_act = ReportActivity('1', None, PassFail) |
1038 | + >>> root.deployed['1'] = dep_ws = ReportWorksheet('1') |
1039 | + >>> dep_ws['1'] = dep_act = ReportActivity('1', None, AmericanLetterScoreSystem) |
1040 | + |
1041 | + >>> from schooltool.gradebook.interfaces import IActivities |
1042 | + >>> activities = IActivities(section) |
1043 | + |
1044 | + >>> from schooltool.requirement.scoresystem import ExtendedAmericanLetterScoreSystem |
1045 | + >>> from schooltool.gradebook.activity import Worksheet, Activity |
1046 | + >>> activities['1'] = worksheet = Worksheet('1') |
1047 | + >>> worksheet['1'] = activity = Activity('1', None, ExtendedAmericanLetterScoreSystem) |
1048 | + |
1049 | + >>> from schooltool.requirement.interfaces import IHaveEvaluations |
1050 | + >>> from schooltool.requirement.interfaces import IEvaluations |
1051 | + >>> from zope.interface import alsoProvides |
1052 | + >>> alsoProvides(student, IHaveEvaluations) |
1053 | + >>> evaluations = IEvaluations(student) |
1054 | + |
1055 | + >>> from schooltool.requirement.evaluation import Evaluation |
1056 | + >>> ev = Evaluation(activity, ExtendedAmericanLetterScoreSystem, 'A+', None) |
1057 | + >>> evaluations.addEvaluation(ev) |
1058 | + |
1059 | + Finally, we'll run the evolve script, testing the effected values before and |
1060 | + after: |
1061 | + |
1062 | + >>> from schooltool.requirement.interfaces import IScoreSystemsProxy |
1063 | + >>> proxy = IScoreSystemsProxy(app) |
1064 | + >>> proxy.getScoreSystems() |
1065 | + [] |
1066 | + |
1067 | + >>> temp_act.scoresystem |
1068 | + <GlobalDiscreteValuesScoreSystem u'Pass/Fail'> |
1069 | + >>> dep_act.scoresystem |
1070 | + <GlobalDiscreteValuesScoreSystem u'Letter Grade'> |
1071 | + >>> activity.scoresystem |
1072 | + <GlobalDiscreteValuesScoreSystem u'Extended Letter Grade'> |
1073 | + >>> ev.scoreSystem |
1074 | + <GlobalDiscreteValuesScoreSystem u'Extended Letter Grade'> |
1075 | + |
1076 | + >>> evolve(context) |
1077 | + |
1078 | + >>> proxy.getScoreSystems() |
1079 | + [(u'Extended Letter Grade', <CustomScoreSystem u'Extended Letter Grade'>), |
1080 | + (u'Letter Grade', <CustomScoreSystem u'Letter Grade'>), |
1081 | + (u'Pass/Fail', <CustomScoreSystem u'Pass/Fail'>)] |
1082 | + |
1083 | + >>> temp_act.scoresystem |
1084 | + <CustomScoreSystem u'Pass/Fail'> |
1085 | + >>> dep_act.scoresystem |
1086 | + <CustomScoreSystem u'Letter Grade'> |
1087 | + >>> activity.scoresystem |
1088 | + <CustomScoreSystem u'Extended Letter Grade'> |
1089 | + >>> ev.scoreSystem |
1090 | + <CustomScoreSystem u'Extended Letter Grade'> |
1091 | + """ |
1092 | + |
1093 | + |
1094 | +def test_suite(): |
1095 | + return unittest.TestSuite([ |
1096 | + doctest.DocTestSuite(optionflags=doctest.ELLIPSIS |
1097 | + | doctest.NORMALIZE_WHITESPACE |
1098 | + | doctest.REPORT_NDIFF |
1099 | + | doctest.REPORT_ONLY_FIRST_FAILURE), |
1100 | + ]) |
1101 | + |
1102 | +if __name__ == '__main__': |
1103 | + unittest.main(defaultTest='test_suite') |
1104 | + |
1105 | |
1106 | === modified file 'src/schooltool/gradebook/gradebook.py' |
1107 | --- src/schooltool/gradebook/gradebook.py 2009-05-08 22:20:00 +0000 |
1108 | +++ src/schooltool/gradebook/gradebook.py 2009-06-01 21:32:36 +0000 |
1109 | @@ -23,7 +23,7 @@ |
1110 | from zope.component import adapts, queryMultiAdapter |
1111 | from schooltool.app.interfaces import ISchoolToolApplication |
1112 | __docformat__ = 'reStructuredText' |
1113 | -import persistent.dict |
1114 | +from persistent.dict import PersistentDict |
1115 | |
1116 | from decimal import Decimal |
1117 | |
1118 | @@ -53,6 +53,7 @@ |
1119 | GRADEBOOK_SORTING_KEY = 'schooltool.gradebook.sorting' |
1120 | CURRENT_WORKSHEET_KEY = 'schooltool.gradebook.currentworksheet' |
1121 | DUE_DATE_FILTER_KEY = 'schooltool.gradebook.duedatefilter' |
1122 | +COLUMN_PREFERENCES_KEY = 'schooltool.gradebook.columnpreferences' |
1123 | |
1124 | |
1125 | class WorksheetGradebookTraverser(object): |
1126 | @@ -235,7 +236,7 @@ |
1127 | person = proxy.removeSecurityProxy(person) |
1128 | ann = annotation.interfaces.IAnnotations(person) |
1129 | if CURRENT_WORKSHEET_KEY not in ann: |
1130 | - ann[CURRENT_WORKSHEET_KEY] = persistent.dict.PersistentDict() |
1131 | + ann[CURRENT_WORKSHEET_KEY] = PersistentDict() |
1132 | if self.worksheets: |
1133 | default = self.worksheets[0] |
1134 | else: |
1135 | @@ -248,7 +249,7 @@ |
1136 | worksheet = proxy.removeSecurityProxy(worksheet) |
1137 | ann = annotation.interfaces.IAnnotations(person) |
1138 | if CURRENT_WORKSHEET_KEY not in ann: |
1139 | - ann[CURRENT_WORKSHEET_KEY] = persistent.dict.PersistentDict() |
1140 | + ann[CURRENT_WORKSHEET_KEY] = PersistentDict() |
1141 | section_id = hash(IKeyReference(self.section)) |
1142 | ann[CURRENT_WORKSHEET_KEY][section_id] = worksheet |
1143 | |
1144 | @@ -264,6 +265,18 @@ |
1145 | ann = annotation.interfaces.IAnnotations(person) |
1146 | ann[DUE_DATE_FILTER_KEY] = (flag, weeks) |
1147 | |
1148 | + def getColumnPreferences(self, person): |
1149 | + person = proxy.removeSecurityProxy(person) |
1150 | + ann = annotation.interfaces.IAnnotations(person) |
1151 | + if COLUMN_PREFERENCES_KEY not in ann: |
1152 | + return PersistentDict() |
1153 | + return ann[COLUMN_PREFERENCES_KEY] |
1154 | + |
1155 | + def setColumnPreferences(self, person, columnPreferences): |
1156 | + person = proxy.removeSecurityProxy(person) |
1157 | + ann = annotation.interfaces.IAnnotations(person) |
1158 | + ann[COLUMN_PREFERENCES_KEY] = PersistentDict(columnPreferences) |
1159 | + |
1160 | def getCurrentActivities(self, person): |
1161 | worksheet = self.getCurrentWorksheet(person) |
1162 | return self.getWorksheetActivities(worksheet) |
1163 | @@ -297,7 +310,7 @@ |
1164 | person = proxy.removeSecurityProxy(person) |
1165 | ann = annotation.interfaces.IAnnotations(person) |
1166 | if GRADEBOOK_SORTING_KEY not in ann: |
1167 | - ann[GRADEBOOK_SORTING_KEY] = persistent.dict.PersistentDict() |
1168 | + ann[GRADEBOOK_SORTING_KEY] = PersistentDict() |
1169 | section_id = hash(IKeyReference(self.section)) |
1170 | return ann[GRADEBOOK_SORTING_KEY].get(section_id, ('student', False)) |
1171 | |
1172 | @@ -305,7 +318,7 @@ |
1173 | person = proxy.removeSecurityProxy(person) |
1174 | ann = annotation.interfaces.IAnnotations(person) |
1175 | if GRADEBOOK_SORTING_KEY not in ann: |
1176 | - ann[GRADEBOOK_SORTING_KEY] = persistent.dict.PersistentDict() |
1177 | + ann[GRADEBOOK_SORTING_KEY] = PersistentDict() |
1178 | section_id = hash(IKeyReference(self.section)) |
1179 | ann[GRADEBOOK_SORTING_KEY][section_id] = value |
1180 | |
1181 | |
1182 | === modified file 'src/schooltool/gradebook/interfaces.py' |
1183 | --- src/schooltool/gradebook/interfaces.py 2009-05-08 01:30:55 +0000 |
1184 | +++ src/schooltool/gradebook/interfaces.py 2009-06-01 21:32:36 +0000 |
1185 | @@ -236,6 +236,12 @@ |
1186 | def setDueDateFilter(person, flag, weeks): |
1187 | """Set the user's current due date filter setting.""" |
1188 | |
1189 | + def getColumnPreferences(person): |
1190 | + """Get the user's column preferences.""" |
1191 | + |
1192 | + def setColumnPreferences(columnPreferences): |
1193 | + """Set the user's column preferences.""" |
1194 | + |
1195 | def getCurrentActivities(person): |
1196 | """Get the activities for the user's currently active worksheet.""" |
1197 | |
1198 | |
1199 | === modified file 'src/schooltool/requirement/README.txt' |
1200 | --- src/schooltool/requirement/README.txt 2008-09-25 03:01:46 +0000 |
1201 | +++ src/schooltool/requirement/README.txt 2009-05-14 08:34:27 +0000 |
1202 | @@ -179,7 +179,8 @@ |
1203 | >>> from decimal import Decimal |
1204 | >>> check = scoresystem.DiscreteValuesScoreSystem( |
1205 | ... u'Check', u'Check-mark score system', |
1206 | - ... [('+', Decimal(1)), ('v', Decimal(0)), ('-', Decimal(-1))]) |
1207 | + ... [('+', Decimal(1), Decimal(80)), ('v', Decimal(0), Decimal(60)), |
1208 | + ... ('-', Decimal(-1), Decimal(0))]) |
1209 | |
1210 | The first and second arguments of the constructor are the title and |
1211 | description. The third argument is a list that really represents a mapping |
1212 | @@ -247,9 +248,9 @@ |
1213 | >>> from schooltool.requirement import scoresystem |
1214 | >>> check = scoresystem.DiscreteValuesScoreSystem( |
1215 | ... u'Check', u'Check-mark score system', |
1216 | - ... [('+', Decimal(1)), |
1217 | - ... ('v', Decimal(0)), |
1218 | - ... ('-', Decimal(-1))], |
1219 | + ... [('+', Decimal(1), Decimal(80)), |
1220 | + ... ('v', Decimal(0), Decimal(60)), |
1221 | + ... ('-', Decimal(-1), Decimal(0))], |
1222 | ... minPassingScore='v') |
1223 | >>> check |
1224 | <DiscreteValuesScoreSystem u'Check'> |
1225 | @@ -279,7 +280,8 @@ |
1226 | >>> from schooltool.requirement import scoresystem |
1227 | >>> check = scoresystem.DiscreteValuesScoreSystem( |
1228 | ... u'Check', u'Check-mark score system', |
1229 | - ... [('+', Decimal(1)), ('v', Decimal(0)), ('-', Decimal(-1))], |
1230 | + ... [('+', Decimal(1), Decimal(80)), ('v', Decimal(0), Decimal(60)), |
1231 | + ... ('-', Decimal(-1)), Decimal(0)], |
1232 | ... bestScore='+', minPassingScore='v') |
1233 | |
1234 | >>> check.getBestScore() |
1235 | @@ -297,7 +299,8 @@ |
1236 | >>> scoresystem.PassFail.title |
1237 | u'Pass/Fail' |
1238 | >>> scoresystem.PassFail.scores |
1239 | - [(u'Pass', Decimal("1")), (u'Fail', Decimal("0"))] |
1240 | + [(u'Pass', Decimal("1"), Decimal("60")), |
1241 | + (u'Fail', Decimal("0"), Decimal("0"))] |
1242 | >>> scoresystem.PassFail.isValidScore('Pass') |
1243 | True |
1244 | >>> scoresystem.PassFail.isPassingScore('Pass') |
1245 | @@ -324,8 +327,9 @@ |
1246 | >>> scoresystem.AmericanLetterScoreSystem.title |
1247 | u'Letter Grade' |
1248 | >>> scoresystem.AmericanLetterScoreSystem.scores |
1249 | - [('A', Decimal("4")), ('B', Decimal("3")), ('C', Decimal("2")), |
1250 | - ('D', Decimal("1")), ('F', Decimal("0"))] |
1251 | + [('A', Decimal("4"), Decimal("90")), ('B', Decimal("3"), Decimal("80")), |
1252 | + ('C', Decimal("2"), Decimal("70")), ('D', Decimal("1"), Decimal("60")), |
1253 | + ('F', Decimal("0"), Decimal("0"))] |
1254 | >>> scoresystem.AmericanLetterScoreSystem.isValidScore('C') |
1255 | True |
1256 | >>> scoresystem.AmericanLetterScoreSystem.isValidScore('E') |
1257 | @@ -353,7 +357,7 @@ |
1258 | 'ExtendedAmericanLetterScoreSystem' |
1259 | >>> scoresystem.ExtendedAmericanLetterScoreSystem.title |
1260 | u'Extended Letter Grade' |
1261 | - >>> [s for s, v in scoresystem.ExtendedAmericanLetterScoreSystem.scores] |
1262 | + >>> [s for s, v, p in scoresystem.ExtendedAmericanLetterScoreSystem.scores] |
1263 | ['A+', 'A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'D-', 'F'] |
1264 | >>> scoresystem.ExtendedAmericanLetterScoreSystem.isValidScore('B-') |
1265 | True |
1266 | @@ -842,6 +846,126 @@ |
1267 | >>> len(evals) |
1268 | 2 |
1269 | |
1270 | + |
1271 | +The ScoresSystemsProxy |
1272 | +~~~~~~~~~~~~~~~~~~~~~~ |
1273 | + |
1274 | +Score systems are utilities that are pre-created in code (eventually we will |
1275 | +migrate these to the application's site manager) and user created ones that |
1276 | +we add to the site manager by way of the ScoresSystemsProxy class. |
1277 | + |
1278 | +First we'll set up the site and initialize the app object. |
1279 | + |
1280 | + >>> from schooltool.testing import setup |
1281 | + >>> app = setup.setUpSchoolToolSite() |
1282 | + |
1283 | +The ScoresSystemsProxy class is itself an adapter of the app object. |
1284 | + |
1285 | + >>> from schooltool.app.interfaces import ISchoolToolApplication |
1286 | + >>> from schooltool.requirement.interfaces import IScoreSystemsProxy |
1287 | + >>> from schooltool.requirement.scoresystem import ScoreSystemsProxy |
1288 | + >>> from zope.component import provideAdapter |
1289 | + >>> provideAdapter(ScoreSystemsProxy) |
1290 | + >>> ssProxy = IScoreSystemsProxy(app) |
1291 | + >>> ssProxy |
1292 | + <schooltool.requirement.scoresystem.ScoreSystemsProxy object at ...> |
1293 | + |
1294 | +At fist, there are no scoresystems registered with the app. |
1295 | + |
1296 | + >>> ssProxy.getScoreSystems() |
1297 | + [] |
1298 | + |
1299 | +We'll create a couple of custom score systems and add them to the proxy. |
1300 | + |
1301 | + >>> from schooltool.requirement.scoresystem import CustomScoreSystem |
1302 | + >>> custom1 = CustomScoreSystem('Custom 1') |
1303 | + >>> ssProxy.addScoreSystem( custom1) |
1304 | + >>> custom2 = CustomScoreSystem('Custom 2') |
1305 | + >>> ssProxy.addScoreSystem(custom2) |
1306 | + |
1307 | +Now, when we ask the proxy for what score systems it has, it will return both |
1308 | +of the newly added ones. |
1309 | + |
1310 | + >>> ssProxy.getScoreSystems() |
1311 | + [(u'Custom 1', <CustomScoreSystem 'Custom 1'>), |
1312 | + (u'Custom 2', <CustomScoreSystem 'Custom 2'>)] |
1313 | + |
1314 | +We can get one of them by name. |
1315 | + |
1316 | + >>> ssProxy.getScoreSystem('Custom 1') |
1317 | + <CustomScoreSystem 'Custom 1'> |
1318 | + |
1319 | + |
1320 | +Score System Vocabularies |
1321 | +------------------------- |
1322 | + |
1323 | +Score System vocabularies are used to provide a pulldown for fields |
1324 | +requiring scoresystem input. Let's register the utilities. |
1325 | + |
1326 | + >>> from zope.component import provideUtility |
1327 | + >>> from schooltool.requirement import scoresystem |
1328 | + |
1329 | +First, the discrete values score systems. |
1330 | + |
1331 | + >>> zope.component.provideUtility( |
1332 | + ... scoresystem.PassFail, interfaces.IDiscreteValuesScoreSystem, |
1333 | + ... u'Pass/Fail') |
1334 | + >>> zope.component.provideUtility( |
1335 | + ... scoresystem.AmericanLetterScoreSystem, interfaces.IDiscreteValuesScoreSystem, |
1336 | + ... u'Letter Grade') |
1337 | + >>> zope.component.provideUtility( |
1338 | + ... scoresystem.ExtendedAmericanLetterScoreSystem, interfaces.IDiscreteValuesScoreSystem, |
1339 | + ... u'Extended Letter Grade') |
1340 | + |
1341 | +Secondly, the ranged values score systems. |
1342 | + |
1343 | + >>> zope.component.provideUtility( |
1344 | + ... scoresystem.PercentScoreSystem, interfaces.IScoreSystem, |
1345 | + ... u'Percent') |
1346 | + >>> zope.component.provideUtility( |
1347 | + ... scoresystem.HundredPointsScoreSystem, interfaces.IScoreSystem, |
1348 | + ... u'100 Points') |
1349 | + |
1350 | +Finally, the vocabularies. |
1351 | + |
1352 | + >>> from zope.schema.vocabulary import getVocabularyRegistry |
1353 | + >>> getVocabularyRegistry().register( |
1354 | + ... 'schooltool.requirement.scoresystems', |
1355 | + ... scoresystem.ScoreSystemsVocabulary) |
1356 | + >>> getVocabularyRegistry().register( |
1357 | + ... 'schooltool.requirement.discretescoresystems', |
1358 | + ... scoresystem.DiscreteScoreSystemsVocabulary) |
1359 | + |
1360 | +Now, when we access the vocabularies as the application views will, we can test |
1361 | +that they deliver the desired list of utilities. |
1362 | + |
1363 | +First the discrete values score systems vocabulary, used when only discrete |
1364 | +values score systems are valid, like when converting an average into a discrete |
1365 | +grade. Note the presence of the custom score systems we just created. |
1366 | + |
1367 | + >>> from zope.app.component.vocabulary import UtilityVocabulary |
1368 | + >>> vocab = UtilityVocabulary(None, interface=interfaces.IDiscreteValuesScoreSystem) |
1369 | + >>> for v in vocab: print v |
1370 | + <UtilityTerm Custom 1, instance of CustomScoreSystem> |
1371 | + <UtilityTerm Custom 2, instance of CustomScoreSystem> |
1372 | + <UtilityTerm Extended Letter Grade, instance of GlobalDiscreteValuesScoreSystem> |
1373 | + <UtilityTerm Letter Grade, instance of GlobalDiscreteValuesScoreSystem> |
1374 | + <UtilityTerm Pass/Fail, instance of GlobalDiscreteValuesScoreSystem> |
1375 | + |
1376 | +Secondly, we have the general score systems vocabulary, returning all score |
1377 | +systems registered. This is used for report sheet activities. |
1378 | + |
1379 | + >>> vocab = UtilityVocabulary(None, interface=interfaces.IScoreSystem) |
1380 | + >>> for v in vocab: print v |
1381 | + <UtilityTerm 100 Points, instance of GlobalRangedValuesScoreSystem> |
1382 | + <UtilityTerm Custom 1, instance of CustomScoreSystem> |
1383 | + <UtilityTerm Custom 2, instance of CustomScoreSystem> |
1384 | + <UtilityTerm Extended Letter Grade, instance of GlobalDiscreteValuesScoreSystem> |
1385 | + <UtilityTerm Letter Grade, instance of GlobalDiscreteValuesScoreSystem> |
1386 | + <UtilityTerm Pass/Fail, instance of GlobalDiscreteValuesScoreSystem> |
1387 | + <UtilityTerm Percent, instance of GlobalRangedValuesScoreSystem> |
1388 | + |
1389 | + |
1390 | Epilogue |
1391 | -------- |
1392 | |
1393 | |
1394 | === modified file 'src/schooltool/requirement/browser/README.txt' |
1395 | --- src/schooltool/requirement/browser/README.txt 2008-04-17 17:22:08 +0000 |
1396 | +++ src/schooltool/requirement/browser/README.txt 2009-06-05 00:11:04 +0000 |
1397 | @@ -64,3 +64,69 @@ |
1398 | >>> browser.getControl('1').click() |
1399 | >>> 'Be kind to your fellow students.' not in browser.contents |
1400 | True |
1401 | + |
1402 | + |
1403 | +Score System Management |
1404 | +----------------------- |
1405 | + |
1406 | +Score Systems can be created by the user to supplement the ones that are |
1407 | +already delivered with schooltool. To do so, as manager, the user goes |
1408 | +to the 'Manage' tab and clicks on the 'Score Systems' link. |
1409 | + |
1410 | + >>> from schooltool.app.browser.ftests import setup |
1411 | + >>> manager = setup.logIn('manager', 'schooltool') |
1412 | + >>> manager.getLink('Manage').click() |
1413 | + >>> manager.getLink('Score Systems').click() |
1414 | + |
1415 | +We see the score systems that come with schooltool. |
1416 | + |
1417 | + >>> analyze.printQuery("id('content-body')/form//a", manager.contents) |
1418 | + <a href="http://localhost/scoresystems/view.html?name=extended-letter-grade">Extended Letter Grade</a> |
1419 | + <a href="http://localhost/scoresystems/view.html?name=letter-grade">Letter Grade</a> |
1420 | + <a href="http://localhost/scoresystems/view.html?name=passfail">Pass/Fail</a> |
1421 | + |
1422 | +To add a new score system, the user clicks 'Add Score System'. |
1423 | + |
1424 | + >>> manager.getLink('Add Score System').click() |
1425 | + >>> base_url = manager.url + '?form-submitted' |
1426 | + >>> cancel_url = base_url + '&CANCEL' |
1427 | + >>> update_url = base_url + '&UPDATE_SUBMIT' |
1428 | + >>> save_url = base_url + '&SAVE' |
1429 | + |
1430 | +We'll send the form values necessary to add a score system called 'Good/Bad'. |
1431 | + |
1432 | + >>> url = save_url + '&title=Good/Bad&displayed1=G&value1=1&percent1=60' |
1433 | + >>> url = url + '&displayed2=B&value2=0&percent2=0' |
1434 | + >>> manager.open(url) |
1435 | + |
1436 | +Now we see the two score systems in the list. |
1437 | + |
1438 | + >>> analyze.printQuery("id('content-body')/form//a", manager.contents) |
1439 | + <a href="http://localhost/scoresystems/view.html?name=extended-letter-grade">Extended Letter Grade</a> |
1440 | + <a href="http://localhost/scoresystems/view.html?name=goodbad">Good/Bad</a> |
1441 | + <a href="http://localhost/scoresystems/view.html?name=letter-grade">Letter Grade</a> |
1442 | + <a href="http://localhost/scoresystems/view.html?name=passfail">Pass/Fail</a> |
1443 | + |
1444 | +Let's hide the 'Pass/Fail' one. |
1445 | + |
1446 | + >>> hide_url = manager.url + '?form-submitted&hide_passfail' |
1447 | + >>> manager.open(hide_url) |
1448 | + |
1449 | +Now we won't see it in the list. |
1450 | + |
1451 | + >>> analyze.printQuery("id('content-body')/form//a", manager.contents) |
1452 | + <a href="http://localhost/scoresystems/view.html?name=extended-letter-grade">Extended Letter Grade</a> |
1453 | + <a href="http://localhost/scoresystems/view.html?name=goodbad">Good/Bad</a> |
1454 | + <a href="http://localhost/scoresystems/view.html?name=letter-grade">Letter Grade</a> |
1455 | + |
1456 | +Let's click on 'Good/Bad' and test it's view. |
1457 | + |
1458 | + >>> manager.getLink('Good/Bad').click() |
1459 | + >>> analyze.printQuery("id('content-body')/table//span", manager.contents) |
1460 | + <span>G</span> |
1461 | + <span>1</span> |
1462 | + <span>60</span> |
1463 | + <span>B</span> |
1464 | + <span>0</span> |
1465 | + <span>0</span> |
1466 | + |
1467 | |
1468 | === modified file 'src/schooltool/requirement/browser/configure.zcml' |
1469 | --- src/schooltool/requirement/browser/configure.zcml 2008-03-20 20:43:52 +0000 |
1470 | +++ src/schooltool/requirement/browser/configure.zcml 2009-05-08 18:19:21 +0000 |
1471 | @@ -3,8 +3,54 @@ |
1472 | xmlns:zope="http://namespaces.zope.org/zope" |
1473 | i18n_domain="schooltool"> |
1474 | |
1475 | + <!-- Manage Tab --> |
1476 | + <configure package="schooltool.skin"> |
1477 | + <navigationViewlet |
1478 | + name="scoresystems" |
1479 | + for="*" |
1480 | + manager="schooltool.app.browser.interfaces.IManageMenuViewletManager" |
1481 | + template="templates/navigationViewlet.pt" |
1482 | + class="schooltool.skin.skin.NavigationViewlet" |
1483 | + permission="schooltool.edit" |
1484 | + link="scoresystems" |
1485 | + title="Score Systems" |
1486 | + order="220" |
1487 | + /> |
1488 | + </configure> |
1489 | + |
1490 | + <!-- Menu items for IScoreSystemsProxy --> |
1491 | + <menuItem |
1492 | + menu="schooltool_actions" |
1493 | + title="Add Score System" |
1494 | + for="schooltool.requirement.interfaces.IScoreSystemsProxy" |
1495 | + action="add.html" |
1496 | + permission="schooltool.edit" |
1497 | + /> |
1498 | + |
1499 | + <!-- Score System Views --> |
1500 | + <page |
1501 | + name="index.html" |
1502 | + for="schooltool.requirement.interfaces.IScoreSystemsProxy" |
1503 | + class=".scoresystem.ScoreSystemsView" |
1504 | + template="scoresystems_overview.pt" |
1505 | + permission="schooltool.edit" |
1506 | + /> |
1507 | + <page |
1508 | + name="add.html" |
1509 | + for="schooltool.requirement.interfaces.IScoreSystemsProxy" |
1510 | + class=".scoresystem.ScoreSystemAddView" |
1511 | + template="scoresystem_add.pt" |
1512 | + permission="schooltool.edit" |
1513 | + /> |
1514 | + <page |
1515 | + name="view.html" |
1516 | + for="schooltool.requirement.interfaces.IScoreSystemsProxy" |
1517 | + class=".scoresystem.ScoreSystemViewView" |
1518 | + template="scoresystem_view.pt" |
1519 | + permission="schooltool.edit" |
1520 | + /> |
1521 | + |
1522 | <!-- Score system widget registration --> |
1523 | - |
1524 | <zope:view |
1525 | type="zope.publisher.interfaces.browser.IBrowserRequest" |
1526 | for="schooltool.requirement.scoresystem.IScoreSystemField" |
1527 | @@ -12,7 +58,6 @@ |
1528 | factory=".scoresystem.ScoreSystemWidget" |
1529 | permission="zope.Public" |
1530 | /> |
1531 | - |
1532 | <page |
1533 | name="index.html" |
1534 | for="schooltool.requirement.interfaces.IRequirement" |
1535 | @@ -22,14 +67,12 @@ |
1536 | menu="zmi_views" |
1537 | title="View" |
1538 | /> |
1539 | - |
1540 | <page |
1541 | name="treenode" |
1542 | for="schooltool.requirement.interfaces.IRequirement" |
1543 | template="treenode.pt" |
1544 | permission="schooltool.view" |
1545 | /> |
1546 | - |
1547 | <containerViews |
1548 | for="..interfaces.IRequirement" |
1549 | contents="zope.ManageContent" |
1550 | |
1551 | === modified file 'src/schooltool/requirement/browser/ftesting.zcml' |
1552 | --- src/schooltool/requirement/browser/ftesting.zcml 2008-10-22 15:50:26 +0000 |
1553 | +++ src/schooltool/requirement/browser/ftesting.zcml 2009-05-08 18:19:21 +0000 |
1554 | @@ -9,6 +9,7 @@ |
1555 | <include package="schooltool.schoolyear" /> |
1556 | <include package="schooltool.timetable" /> |
1557 | <include package="schooltool.requirement" /> |
1558 | + <include package="schooltool.gradebook" /> |
1559 | |
1560 | <configure |
1561 | package="schooltool.skin" |
1562 | |
1563 | === modified file 'src/schooltool/requirement/browser/scoresystem.py' |
1564 | --- src/schooltool/requirement/browser/scoresystem.py 2008-04-16 18:57:28 +0000 |
1565 | +++ src/schooltool/requirement/browser/scoresystem.py 2009-05-14 08:34:27 +0000 |
1566 | @@ -21,14 +21,216 @@ |
1567 | $Id$ |
1568 | """ |
1569 | __docformat__ = 'reStructuredText' |
1570 | + |
1571 | +from decimal import Decimal |
1572 | + |
1573 | import zope.interface |
1574 | import zope.schema |
1575 | from zope.app import form |
1576 | from zope.app.pagetemplate import ViewPageTemplateFile |
1577 | +from zope.publisher.browser import BrowserView |
1578 | +from zope.security.proxy import removeSecurityProxy |
1579 | +from zope.traversing.browser.absoluteurl import absoluteURL |
1580 | |
1581 | from schooltool.common import SchoolToolMessage as _ |
1582 | from schooltool.requirement import interfaces, scoresystem |
1583 | |
1584 | + |
1585 | +MISSING_TITLE = _('The Title field must not be empty.') |
1586 | +VALUE_NOT_NUMERIC = _('Value field must contain a valid number.') |
1587 | +PERCENT_NOT_NUMERIC = _('Percent field must contain a valid number.') |
1588 | +NO_NEGATIVE_VALUES = _('All values must be non-negative.') |
1589 | +NO_NEGATIVE_PERCENTS = _('All percentages must be non-negative.') |
1590 | +NO_PERCENTS_OVER_100 = _('Percentages cannot be greater than 100.') |
1591 | +MUST_HAVE_AT_LEAST_2_SCORES = _('A score system must have at least two scores.') |
1592 | +VALUES_MUST_DESCEND = _('Score values must go in descending order.') |
1593 | +PERCENTS_MUST_DESCEND = _('Score percentages must go in descending order.') |
1594 | +LAST_PERCENT_NOT_ZERO = _('The last percentage must be zero.') |
1595 | + |
1596 | + |
1597 | +def escName(name): |
1598 | + """converts title-based scoresystem name to querystring format""" |
1599 | + chars = [c for c in name.lower() if c.isalnum() or c == ' '] |
1600 | + return u''.join(chars).replace(' ', '-') |
1601 | + |
1602 | + |
1603 | +class ScoreSystemsView(BrowserView): |
1604 | + """A view for maintaining user-created scoresystem utilities""" |
1605 | + |
1606 | + def update(self): |
1607 | + if 'form-submitted' in self.request: |
1608 | + for name, ss in self.context.getScoreSystems(): |
1609 | + ss = removeSecurityProxy(ss) |
1610 | + if 'hide_' + escName(name) in self.request: |
1611 | + ss.hidden = True |
1612 | + |
1613 | + def scoresystems(self): |
1614 | + url = absoluteURL(self.context, self.request) + '/view.html' |
1615 | + results = [] |
1616 | + for name, ss in self.context.getScoreSystems(): |
1617 | + ss = removeSecurityProxy(ss) |
1618 | + result = { |
1619 | + 'title': ss.title, |
1620 | + 'url': '%s?name=%s' % (url, escName(name)), |
1621 | + 'hide_name': 'hide_' + escName(name), |
1622 | + } |
1623 | + results.append(result) |
1624 | + return results |
1625 | + |
1626 | + |
1627 | +class ScoreSystemAddView(BrowserView): |
1628 | + """A view for adding a user-created scoresystem utility""" |
1629 | + |
1630 | + def update(self): |
1631 | + self.message = '' |
1632 | + |
1633 | + if 'form-submitted' in self.request: |
1634 | + if 'CANCEL' in self.request: |
1635 | + self.request.response.redirect(self.nextURL()) |
1636 | + |
1637 | + if not self.validateForm(): |
1638 | + return |
1639 | + if 'SAVE' in self.request: |
1640 | + if not self.validateScores(): |
1641 | + return |
1642 | + target = scoresystem.CustomScoreSystem() |
1643 | + self.updateScoreSystem(target) |
1644 | + self.context.addScoreSystem(target) |
1645 | + self.request.response.redirect(self.nextURL()) |
1646 | + |
1647 | + def nextURL(self): |
1648 | + return absoluteURL(self.context, self.request) |
1649 | + |
1650 | + def scores(self): |
1651 | + rownum = 1 |
1652 | + results = [] |
1653 | + for displayed, value, percent in self.getRequestScores(): |
1654 | + results.append(self.buildScoreRow(rownum, displayed, value, |
1655 | + percent)) |
1656 | + rownum += 1 |
1657 | + results.append(self.buildScoreRow(rownum, '', '', '')) |
1658 | + return results |
1659 | + |
1660 | + def buildScoreRow(self, rownum, displayed, value, percent): |
1661 | + return { |
1662 | + 'displayed_name': 'displayed' + unicode(rownum), |
1663 | + 'displayed_value': displayed, |
1664 | + 'value_name': 'value' + unicode(rownum), |
1665 | + 'value_value': value, |
1666 | + 'percent_name': 'percent' + unicode(rownum), |
1667 | + 'percent_value': percent, |
1668 | + } |
1669 | + |
1670 | + def getRequestScores(self): |
1671 | + rownum = 0 |
1672 | + results = [] |
1673 | + while True: |
1674 | + rownum += 1 |
1675 | + displayed_name = 'displayed' + unicode(rownum) |
1676 | + value_name = 'value' + unicode(rownum) |
1677 | + percent_name = 'percent' + unicode(rownum) |
1678 | + if displayed_name not in self.request: |
1679 | + break |
1680 | + if not len(self.request[displayed_name]): |
1681 | + continue |
1682 | + result = (self.request[displayed_name], |
1683 | + self.request[value_name], |
1684 | + self.request[percent_name]) |
1685 | + results.append(result) |
1686 | + return results |
1687 | + |
1688 | + def validateForm(self): |
1689 | + title = self.request['title'] |
1690 | + if not len(title): |
1691 | + return self.setMessage(MISSING_TITLE) |
1692 | + |
1693 | + scores = [] |
1694 | + for displayed, value, percent in self.getRequestScores(): |
1695 | + try: |
1696 | + decimal_value = Decimal(value) |
1697 | + except: |
1698 | + return self.setMessage(VALUE_NOT_NUMERIC) |
1699 | + if decimal_value < 0: |
1700 | + return self.setMessage(NO_NEGATIVE_VALUES) |
1701 | + try: |
1702 | + decimal_percent = Decimal(percent) |
1703 | + except: |
1704 | + return self.setMessage(PERCENT_NOT_NUMERIC) |
1705 | + if decimal_percent < 0: |
1706 | + return self.setMessage(NO_NEGATIVE_PERCENTS) |
1707 | + if decimal_percent > 100: |
1708 | + return self.setMessage(NO_PERCENTS_OVER_100) |
1709 | + scores.append([displayed, decimal_value, decimal_percent]) |
1710 | + |
1711 | + self.validTitle = title |
1712 | + self.validScores = scores |
1713 | + return True |
1714 | + |
1715 | + def validateScores(self): |
1716 | + if len(self.validScores) < 2: |
1717 | + return self.setMessage(MUST_HAVE_AT_LEAST_2_SCORES) |
1718 | + |
1719 | + last_value, last_percent = None, None |
1720 | + for displayed, value, percent in self.validScores: |
1721 | + if last_value is not None: |
1722 | + if value >= last_value: |
1723 | + return self.setMessage(VALUES_MUST_DESCEND) |
1724 | + if last_percent is not None: |
1725 | + if percent >= last_percent: |
1726 | + return self.setMessage(PERCENTS_MUST_DESCEND) |
1727 | + last_value = value |
1728 | + last_percent = percent |
1729 | + |
1730 | + if last_percent <> 0: |
1731 | + return self.setMessage(LAST_PERCENT_NOT_ZERO) |
1732 | + |
1733 | + return True |
1734 | + |
1735 | + def setMessage(self, message): |
1736 | + self.message = message |
1737 | + return False |
1738 | + |
1739 | + def updateScoreSystem(self, target): |
1740 | + target.title = self.validTitle |
1741 | + target.scores = self.validScores |
1742 | + target._bestScore = target.scores[0][1] |
1743 | + |
1744 | + @property |
1745 | + def title_value(self): |
1746 | + if 'form-submitted' in self.request: |
1747 | + return self.request['title'] |
1748 | + else: |
1749 | + return '' |
1750 | + |
1751 | + |
1752 | +class ScoreSystemViewView(BrowserView): |
1753 | + """A view for viewing a user-created scoresystem utility""" |
1754 | + |
1755 | + def scores(self): |
1756 | + target = self.getScoreSystem() |
1757 | + return [self.buildScoreRow(displayed, value, percent) |
1758 | + for displayed, value, percent in target.scores] |
1759 | + |
1760 | + def buildScoreRow(self, displayed, value, percent): |
1761 | + return { |
1762 | + 'displayed_value': displayed, |
1763 | + 'value_value': value, |
1764 | + 'percent_value': percent, |
1765 | + } |
1766 | + |
1767 | + @property |
1768 | + def title_value(self): |
1769 | + target = self.getScoreSystem() |
1770 | + return target.title |
1771 | + |
1772 | + def getScoreSystem(self): |
1773 | + name = self.request['QUERY_STRING'].split('=')[1] |
1774 | + for n, ss in self.context.getScoreSystems(): |
1775 | + if escName(n) == name: |
1776 | + return removeSecurityProxy(ss) |
1777 | + raise KeyError |
1778 | + |
1779 | + |
1780 | class IWidgetData(interfaces.IRangedValuesScoreSystem): |
1781 | """A schema used to generate the score system widget.""" |
1782 | |
1783 | |
1784 | === added file 'src/schooltool/requirement/browser/scoresystem_add.pt' |
1785 | --- src/schooltool/requirement/browser/scoresystem_add.pt 1970-01-01 00:00:00 +0000 |
1786 | +++ src/schooltool/requirement/browser/scoresystem_add.pt 2009-05-08 18:19:21 +0000 |
1787 | @@ -0,0 +1,65 @@ |
1788 | +<tal:define define="dummy view/update"/> |
1789 | +<html metal:use-macro="view/@@standard_macros/page" i18n:domain="schooltool"> |
1790 | +<head> |
1791 | + <title metal:fill-slot="title" i18n:translate="">Add Score System</title> |
1792 | +</head> |
1793 | +<body> |
1794 | + |
1795 | +<h1 metal:fill-slot="content-header" |
1796 | + i18n:translate="">Add Score System</h1> |
1797 | + |
1798 | +<metal:block metal:fill-slot="body"> |
1799 | + |
1800 | + <div class="message" style="color:red; padding:1em" |
1801 | + tal:condition="view/message" |
1802 | + tal:content="view/message"> |
1803 | + Message |
1804 | + </div> |
1805 | + |
1806 | + |
1807 | + <form method="post" |
1808 | + tal:attributes="action string:${context/@@absolute_url}/add.html"> |
1809 | + <input type="hidden" name="form-submitted" value="" /> |
1810 | + |
1811 | + <label for="title" class="bold padded" i18n:translate="">Title</label> |
1812 | + <input type="text" name="title" id="title" |
1813 | + tal:attributes="value view/title_value" /> |
1814 | + <span>  </span> |
1815 | + <input type="submit" class="button-ok" name="SAVE" value="Submit" /> |
1816 | + <div style="height: 31px;"></div> |
1817 | + |
1818 | + <table class="schooltool_gradebook"> |
1819 | + <tr> |
1820 | + <th class="cell header fully_padded">Score Displayed</th> |
1821 | + <th class="cell header fully_padded">Score Value</th> |
1822 | + <th class="cell header fully_padded">Low Persntage</th> |
1823 | + </tr> |
1824 | + |
1825 | + <tal:block repeat="score view/scores"> |
1826 | + <tr class="bordered"> |
1827 | + <td class="cell fully_padded"> |
1828 | + <input type="text" |
1829 | + tal:attributes="name score/displayed_name; value score/displayed_value" /> |
1830 | + </td> |
1831 | + <td class="cell fully_padded"> |
1832 | + <input type="text" |
1833 | + tal:attributes="name score/value_name; value score/value_value" /> |
1834 | + </td> |
1835 | + <td class="cell fully_padded"> |
1836 | + <input type="text" |
1837 | + tal:attributes="name score/percent_name; value score/percent_value" /> |
1838 | + </td> |
1839 | + </tr> |
1840 | + </tal:block> |
1841 | + |
1842 | + </table> |
1843 | + <div style="height: 11px;"></div> |
1844 | + |
1845 | + <div class="controls"> |
1846 | + <input type="submit" class="button-ok" name="UPDATE_SUBMIT" value="Update" /> |
1847 | + <input type="submit" class="button-cancel" name="CANCEL" value="Cancel" /> |
1848 | + </div> |
1849 | + </form> |
1850 | +</metal:block> |
1851 | +</body> |
1852 | +</html> |
1853 | |
1854 | === added file 'src/schooltool/requirement/browser/scoresystem_view.pt' |
1855 | --- src/schooltool/requirement/browser/scoresystem_view.pt 1970-01-01 00:00:00 +0000 |
1856 | +++ src/schooltool/requirement/browser/scoresystem_view.pt 2009-06-05 00:11:04 +0000 |
1857 | @@ -0,0 +1,41 @@ |
1858 | +<html metal:use-macro="view/@@standard_macros/page" i18n:domain="schooltool"> |
1859 | +<head> |
1860 | + <title metal:fill-slot="title" i18n:translate="">View Score System</title> |
1861 | +</head> |
1862 | +<body> |
1863 | + |
1864 | +<h1 metal:fill-slot="content-header" |
1865 | + i18n:translate="">View Score System</h1> |
1866 | + |
1867 | +<metal:block metal:fill-slot="body"> |
1868 | + |
1869 | + <b>Title:</b> |
1870 | + <span tal:content="view/title_value" /> |
1871 | + <div style="height: 31px;"></div> |
1872 | + |
1873 | + <table class="schooltool_gradebook"> |
1874 | + <tr> |
1875 | + <th class="cell header fully_padded">Score Displayed</th> |
1876 | + <th class="cell header fully_padded">Score Value</th> |
1877 | + <th class="cell header fully_padded">Low Percentage</th> |
1878 | + </tr> |
1879 | + |
1880 | + <tal:block repeat="score view/scores"> |
1881 | + <tr class="bordered"> |
1882 | + <td class="cell fully_padded"> |
1883 | + <span tal:content="score/displayed_value" /> |
1884 | + </td> |
1885 | + <td class="cell fully_padded"> |
1886 | + <span tal:content="score/value_value" /> |
1887 | + </td> |
1888 | + <td class="cell fully_padded"> |
1889 | + <span tal:content="score/percent_value" /> |
1890 | + </td> |
1891 | + </tr> |
1892 | + </tal:block> |
1893 | + |
1894 | + </table> |
1895 | + |
1896 | +</metal:block> |
1897 | +</body> |
1898 | +</html> |
1899 | |
1900 | === added file 'src/schooltool/requirement/browser/scoresystems_overview.pt' |
1901 | --- src/schooltool/requirement/browser/scoresystems_overview.pt 1970-01-01 00:00:00 +0000 |
1902 | +++ src/schooltool/requirement/browser/scoresystems_overview.pt 2009-05-08 18:19:21 +0000 |
1903 | @@ -0,0 +1,44 @@ |
1904 | +<tal:define define="dummy view/update"/> |
1905 | +<html metal:use-macro="view/@@standard_macros/page" i18n:domain="schooltool"> |
1906 | +<head> |
1907 | + <title metal:fill-slot="title" i18n:translate="">Score Systems</title> |
1908 | +</head> |
1909 | +<body> |
1910 | + |
1911 | +<h1 metal:fill-slot="content-header" |
1912 | + i18n:translate="">Score Systems</h1> |
1913 | + |
1914 | +<metal:block metal:fill-slot="body" |
1915 | + tal:define="scoresystems view/scoresystems"> |
1916 | + |
1917 | + <form method="post" |
1918 | + tal:attributes="action string:${context/@@absolute_url}/index.html"> |
1919 | + <input type="hidden" name="form-submitted" value="" /> |
1920 | + |
1921 | + <table class="schooltool_gradebook"> |
1922 | + <tr> |
1923 | + <th class="cell header fully_padded">Hide?</th> |
1924 | + <th class="cell header fully_padded">Score System</th> |
1925 | + </tr> |
1926 | + |
1927 | + <tr class="bordered" |
1928 | + tal:repeat="scoresystem scoresystems"> |
1929 | + <td class="cell fully_padded"> |
1930 | + <input type="checkbox" |
1931 | + tal:attributes="name scoresystem/hide_name;" /> |
1932 | + </td> |
1933 | + <td class="cell fully_padded"> |
1934 | + <a tal:content="scoresystem/title" |
1935 | + tal:attributes="href scoresystem/url">Score System</a> |
1936 | + </td> |
1937 | + </tr> |
1938 | + </table> |
1939 | + |
1940 | + <div class="controls"> |
1941 | + <input type="submit" class="button-ok" name="UPDATE_SUBMIT" value="Update" /> |
1942 | + </div> |
1943 | + |
1944 | + </form> |
1945 | +</metal:block> |
1946 | +</body> |
1947 | +</html> |
1948 | |
1949 | === modified file 'src/schooltool/requirement/browser/tests.py' |
1950 | --- src/schooltool/requirement/browser/tests.py 2008-03-19 04:22:15 +0000 |
1951 | +++ src/schooltool/requirement/browser/tests.py 2009-05-08 18:19:21 +0000 |
1952 | @@ -243,12 +243,14 @@ |
1953 | return unittest.TestSuite(( |
1954 | doctest.DocTestSuite(setUp=setUp, tearDown=tearDown, |
1955 | optionflags=doctest.ELLIPSIS| |
1956 | - doctest.REPORT_NDIFF), |
1957 | + doctest.REPORT_NDIFF| |
1958 | + doctest.REPORT_ONLY_FIRST_FAILURE), |
1959 | doctest.DocFileSuite('scoresystem.txt', |
1960 | setUp=setUp, tearDown=tearDown, |
1961 | globs={'pprint': doctestunit.pprint}, |
1962 | optionflags=doctest.NORMALIZE_WHITESPACE| |
1963 | - doctest.ELLIPSIS), |
1964 | + doctest.ELLIPSIS| |
1965 | + doctest.REPORT_ONLY_FIRST_FAILURE), |
1966 | )) |
1967 | |
1968 | |
1969 | |
1970 | === modified file 'src/schooltool/requirement/configure.zcml' |
1971 | --- src/schooltool/requirement/configure.zcml 2008-09-14 22:04:05 +0000 |
1972 | +++ src/schooltool/requirement/configure.zcml 2009-06-05 00:11:04 +0000 |
1973 | @@ -10,29 +10,26 @@ |
1974 | factory=".requirement.getRequirement" |
1975 | trusted="true" |
1976 | /> |
1977 | - |
1978 | <adapter |
1979 | for=".interfaces.IHaveEvaluations" |
1980 | provides=".interfaces.IEvaluations" |
1981 | factory=".evaluation.getEvaluations" |
1982 | trusted="true" |
1983 | /> |
1984 | - |
1985 | <view |
1986 | name="requirement" type="*" |
1987 | provides="zope.traversing.interfaces.ITraversable" |
1988 | - for="schooltool.requirement.interfaces.IHaveRequirement" |
1989 | + for=".interfaces.IHaveRequirement" |
1990 | factory=".requirement.requirementNamespace" |
1991 | /> |
1992 | <adapter |
1993 | name="requirement" |
1994 | provides="zope.traversing.interfaces.ITraversable" |
1995 | - for="schooltool.requirement.interfaces.IHaveRequirement" |
1996 | + for=".interfaces.IHaveRequirement" |
1997 | factory=".requirement.requirementNamespace" |
1998 | /> |
1999 | |
2000 | <!-- Requirement Content --> |
2001 | - |
2002 | <class class=".requirement.Requirement"> |
2003 | <allow interface="zope.app.container.interfaces.ISimpleReadContainer" /> |
2004 | <require |
2005 | @@ -50,7 +47,6 @@ |
2006 | </class> |
2007 | |
2008 | <!-- Scoresystem Content --> |
2009 | - |
2010 | <class class=".scoresystem.DiscreteValuesScoreSystem"> |
2011 | <require |
2012 | permission="zope.View" |
2013 | @@ -61,7 +57,6 @@ |
2014 | set_schema=".interfaces.IScoreSystem" |
2015 | /> |
2016 | </class> |
2017 | - |
2018 | <class class=".scoresystem.GlobalDiscreteValuesScoreSystem"> |
2019 | <require |
2020 | permission="zope.View" |
2021 | @@ -72,7 +67,6 @@ |
2022 | set_schema=".interfaces.IScoreSystem" |
2023 | /> |
2024 | </class> |
2025 | - |
2026 | <class class=".scoresystem.RangedValuesScoreSystem"> |
2027 | <require |
2028 | permission="zope.View" |
2029 | @@ -83,7 +77,6 @@ |
2030 | set_schema=".interfaces.IScoreSystem" |
2031 | /> |
2032 | </class> |
2033 | - |
2034 | <class class=".scoresystem.GlobalRangedValuesScoreSystem"> |
2035 | <require |
2036 | permission="zope.View" |
2037 | @@ -94,55 +87,46 @@ |
2038 | set_schema=".interfaces.IScoreSystem" |
2039 | /> |
2040 | </class> |
2041 | + <class class=".scoresystem.CustomScoreSystem"> |
2042 | + <require |
2043 | + permission="zope.View" |
2044 | + interface=".interfaces.ICustomScoreSystem" |
2045 | + /> |
2046 | + <require |
2047 | + permission="schooltool.edit" |
2048 | + set_schema=".interfaces.ICustomScoreSystem" |
2049 | + /> |
2050 | + </class> |
2051 | |
2052 | <!-- Score System registrations --> |
2053 | - |
2054 | <utility |
2055 | provides="zope.schema.interfaces.IVocabularyFactory" |
2056 | - component="schooltool.requirement.scoresystem.ScoreSystemsVocabulary" |
2057 | + component=".scoresystem.ScoreSystemsVocabulary" |
2058 | name="schooltool.requirement.scoresystems" |
2059 | /> |
2060 | - |
2061 | - <utility |
2062 | - provides="schooltool.requirement.interfaces.IScoreSystem" |
2063 | - component="schooltool.requirement.scoresystem.PassFail" |
2064 | - name="Pass/Fail" |
2065 | - /> |
2066 | - |
2067 | - <utility |
2068 | - provides="schooltool.requirement.interfaces.IScoreSystem" |
2069 | - component="schooltool.requirement.scoresystem.AmericanLetterScoreSystem" |
2070 | - name="Letter Grade" |
2071 | - /> |
2072 | - |
2073 | - <utility |
2074 | - provides="schooltool.requirement.interfaces.IScoreSystem" |
2075 | - component=" |
2076 | - schooltool.requirement.scoresystem.ExtendedAmericanLetterScoreSystem" |
2077 | - name="Extended Letter Grade" |
2078 | - /> |
2079 | - |
2080 | - <utility |
2081 | - provides="schooltool.requirement.interfaces.IScoreSystem" |
2082 | - component="schooltool.requirement.scoresystem.PercentScoreSystem" |
2083 | + <utility |
2084 | + provides="zope.schema.interfaces.IVocabularyFactory" |
2085 | + component=".scoresystem.DiscreteScoreSystemsVocabulary" |
2086 | + name="schooltool.requirement.discretescoresystems" |
2087 | + /> |
2088 | + <utility |
2089 | + provides="schooltool.requirement.interfaces.IScoreSystem" |
2090 | + component=".scoresystem.PercentScoreSystem" |
2091 | name="Percent" |
2092 | /> |
2093 | - |
2094 | <utility |
2095 | provides="schooltool.requirement.interfaces.IScoreSystem" |
2096 | - component="schooltool.requirement.scoresystem.HundredPointsScoreSystem" |
2097 | + component=".scoresystem.HundredPointsScoreSystem" |
2098 | name="100 Points" |
2099 | /> |
2100 | |
2101 | <!-- Evaluations Content --> |
2102 | - |
2103 | <class class=".evaluation.Evaluations"> |
2104 | <require |
2105 | permission="schooltool.edit" |
2106 | interface=".interfaces.IEvaluations" |
2107 | /> |
2108 | </class> |
2109 | - |
2110 | <class class=".evaluation.Evaluation"> |
2111 | <require |
2112 | permission="schooltool.view" |
2113 | @@ -155,24 +139,42 @@ |
2114 | </class> |
2115 | |
2116 | <!-- These declarations should go somewhere else eventually --> |
2117 | - |
2118 | <class class="schooltool.app.app.SchoolToolApplication"> |
2119 | <implements |
2120 | - interface="schooltool.requirement.interfaces.IHaveRequirement" /> |
2121 | + interface=".interfaces.IHaveRequirement" /> |
2122 | </class> |
2123 | - |
2124 | <class class="schooltool.course.course.Course"> |
2125 | <implements interface=".interfaces.IHaveRequirement" /> |
2126 | </class> |
2127 | - |
2128 | <class class="schooltool.course.section.Section"> |
2129 | <implements interface=".interfaces.IHaveRequirement" /> |
2130 | </class> |
2131 | - |
2132 | <class class="schooltool.person.person.Person"> |
2133 | <implements interface=".interfaces.IHaveEvaluations" /> |
2134 | </class> |
2135 | |
2136 | + <!-- ScoreSystemsProxy adapter --> |
2137 | + <class class=".scoresystem.ScoreSystemsProxy"> |
2138 | + <require |
2139 | + permission="schooltool.edit" |
2140 | + interface=".interfaces.IScoreSystemsProxy" /> |
2141 | + </class> |
2142 | + <adapter |
2143 | + for="schooltool.app.app.SchoolToolApplication" |
2144 | + provides=".interfaces.IScoreSystemsProxy" |
2145 | + factory=".scoresystem.ScoreSystemsProxy" |
2146 | + trusted="true" |
2147 | + /> |
2148 | + |
2149 | + <!-- Pluggable traverser plugins for HTTP paths --> |
2150 | + <adapterTraverserPlugin |
2151 | + for="schooltool.app.interfaces.ISchoolToolApplication" |
2152 | + layer="zope.publisher.interfaces.http.IHTTPRequest" |
2153 | + name="scoresystems" |
2154 | + adapter=".interfaces.IScoreSystemsProxy" |
2155 | + permission="schooltool.edit" |
2156 | + /> |
2157 | + |
2158 | <include package=".browser" /> |
2159 | |
2160 | </configure> |
2161 | |
2162 | === modified file 'src/schooltool/requirement/interfaces.py' |
2163 | --- src/schooltool/requirement/interfaces.py 2009-04-29 00:26:02 +0000 |
2164 | +++ src/schooltool/requirement/interfaces.py 2009-05-08 18:19:21 +0000 |
2165 | @@ -116,13 +116,22 @@ |
2166 | class IDiscreteValuesScoreSystem(IValuesScoreSystem): |
2167 | """A score system that consists of discrete values.""" |
2168 | |
2169 | + hidden = zope.schema.Bool( |
2170 | + title=u"Hidden Score System", |
2171 | + required=False |
2172 | + ) |
2173 | + |
2174 | scores = zope.schema.List( |
2175 | title=u'Scores', |
2176 | - description=u'A list of 2-tuples of the form (score, numerical value).', |
2177 | + description=u'A list of 3-tuples of the form (score, value, percent).', |
2178 | value_type=zope.schema.Tuple(), |
2179 | required=True) |
2180 | |
2181 | |
2182 | +class ICustomScoreSystem(IDiscreteValuesScoreSystem): |
2183 | + """A user-created score system that consists of discrete values.""" |
2184 | + |
2185 | + |
2186 | class IRangedValuesScoreSystem(IValuesScoreSystem): |
2187 | """A score system that allows for a randge of values.""" |
2188 | |
2189 | @@ -232,3 +241,16 @@ |
2190 | name that the original ``IEvaluations`` object had. |
2191 | """ |
2192 | |
2193 | + |
2194 | +class IScoreSystemsProxy(zope.interface.Interface): |
2195 | + """The Proxy class for adding/editing score systems""" |
2196 | + |
2197 | + def getScoreSystems(): |
2198 | + """Return list of tuples (name, scoresystem)""" |
2199 | + |
2200 | + def addScoreSystem(scoresystem): |
2201 | + """Add scoresystem to app utilitiles""" |
2202 | + |
2203 | + def getScoreSystem(name): |
2204 | + """Get scoresystem from app utilitiles by the given name""" |
2205 | + |
2206 | |
2207 | === modified file 'src/schooltool/requirement/scoresystem.py' |
2208 | --- src/schooltool/requirement/scoresystem.py 2008-09-25 03:01:46 +0000 |
2209 | +++ src/schooltool/requirement/scoresystem.py 2009-05-14 08:34:27 +0000 |
2210 | @@ -22,20 +22,34 @@ |
2211 | """ |
2212 | __docformat__ = 'restructuredtext' |
2213 | |
2214 | +from decimal import Decimal |
2215 | + |
2216 | +from persistent import Persistent |
2217 | + |
2218 | +from zope.app.component.vocabulary import UtilityVocabulary |
2219 | +from zope.component import adapts, queryMultiAdapter |
2220 | +from zope.interface import implements |
2221 | import zope.interface |
2222 | import zope.schema |
2223 | import zope.security.checker |
2224 | -from zope.app.component.vocabulary import UtilityVocabulary |
2225 | - |
2226 | -from decimal import Decimal |
2227 | - |
2228 | +from zope.security.proxy import removeSecurityProxy |
2229 | + |
2230 | +from schooltool.app.interfaces import ISchoolToolApplication |
2231 | from schooltool.requirement import interfaces |
2232 | +from schooltool.requirement.interfaces import IDiscreteValuesScoreSystem |
2233 | +from schooltool.requirement.interfaces import IScoreSystemsProxy |
2234 | |
2235 | |
2236 | def ScoreSystemsVocabulary(context): |
2237 | return UtilityVocabulary(context, |
2238 | interface=interfaces.IScoreSystem) |
2239 | |
2240 | + |
2241 | +def DiscreteScoreSystemsVocabulary(context): |
2242 | + return UtilityVocabulary(context, |
2243 | + interface=interfaces.IDiscreteValuesScoreSystem) |
2244 | + |
2245 | + |
2246 | class UNSCORED(object): |
2247 | """This object behaves like a string. |
2248 | |
2249 | @@ -49,9 +63,11 @@ |
2250 | def __repr__(self): |
2251 | return 'UNSCORED' |
2252 | |
2253 | + |
2254 | zope.security.checker.BasicTypes[UNSCORED] = zope.security.checker.NoProxy |
2255 | UNSCORED = UNSCORED() |
2256 | |
2257 | + |
2258 | class AbstractScoreSystem(object): |
2259 | zope.interface.implements(interfaces.IScoreSystem) |
2260 | |
2261 | @@ -88,6 +104,7 @@ |
2262 | def __reduce__(self): |
2263 | return 'CommentScoreSystem' |
2264 | |
2265 | + |
2266 | # Singelton |
2267 | CommentScoreSystem = CommentScoreSystem( |
2268 | u'Comments', u'Scores are commentary text.') |
2269 | @@ -126,6 +143,7 @@ |
2270 | scores = None |
2271 | _minPassingScore = None |
2272 | _bestScore = None |
2273 | + hidden = False |
2274 | |
2275 | def __init__(self, title=None, description=None, |
2276 | scores=None, bestScore=None, minPassingScore=None): |
2277 | @@ -141,12 +159,12 @@ |
2278 | return None |
2279 | if self._minPassingScore is None: |
2280 | return None |
2281 | - scores = dict(self.scores) |
2282 | + scores = self.scoresDict() |
2283 | return scores[score] >= scores[self._minPassingScore] |
2284 | |
2285 | def isValidScore(self, score): |
2286 | """See interfaces.IScoreSystem""" |
2287 | - scores = dict(self.scores).keys() |
2288 | + scores = self.scoresDict().keys() |
2289 | return score in scores + [UNSCORED] |
2290 | |
2291 | def getBestScore(self): |
2292 | @@ -167,7 +185,7 @@ |
2293 | """See interfaces.IScoreSystem""" |
2294 | if score is UNSCORED: |
2295 | return None |
2296 | - scores = dict(self.scores) |
2297 | + scores = self.scoresDict() |
2298 | return scores[score] |
2299 | |
2300 | def getFractionalValue(self, score): |
2301 | @@ -179,6 +197,10 @@ |
2302 | value = self.getNumericalValue(score) - minimum |
2303 | return value / (maximum - minimum) |
2304 | |
2305 | + def scoresDict(self): |
2306 | + scores = [(score, value) for score, value, percent in self.scores] |
2307 | + return dict(scores) |
2308 | + |
2309 | class GlobalDiscreteValuesScoreSystem(DiscreteValuesScoreSystem): |
2310 | |
2311 | def __init__(self, name, *args, **kwargs): |
2312 | @@ -188,25 +210,41 @@ |
2313 | def __reduce__(self): |
2314 | return self.__name__ |
2315 | |
2316 | + |
2317 | PassFail = GlobalDiscreteValuesScoreSystem( |
2318 | 'PassFail', |
2319 | u'Pass/Fail', u'Pass or Fail score system.', |
2320 | - [(u'Pass', Decimal(1)), (u'Fail', Decimal(0))], u'Pass', u'Pass') |
2321 | + [(u'Pass', Decimal(1), Decimal(60)), |
2322 | + (u'Fail', Decimal(0), Decimal(0))], |
2323 | + u'Pass', u'Pass') |
2324 | |
2325 | AmericanLetterScoreSystem = GlobalDiscreteValuesScoreSystem( |
2326 | 'AmericanLetterScoreSystem', |
2327 | u'Letter Grade', u'American Letter Grade', |
2328 | - [('A', Decimal(4)), ('B', Decimal(3)), ('C', Decimal(2)), |
2329 | - ('D', Decimal(1)), ('F', Decimal(0))], 'A', 'D') |
2330 | + [('A', Decimal(4), Decimal(90)), |
2331 | + ('B', Decimal(3), Decimal(80)), |
2332 | + ('C', Decimal(2), Decimal(70)), |
2333 | + ('D', Decimal(1), Decimal(60)), |
2334 | + ('F', Decimal(0), Decimal(0))], |
2335 | + 'A', 'D') |
2336 | |
2337 | ExtendedAmericanLetterScoreSystem = GlobalDiscreteValuesScoreSystem( |
2338 | 'ExtendedAmericanLetterScoreSystem', |
2339 | u'Extended Letter Grade', u'American Extended Letter Grade', |
2340 | - [('A+', Decimal('4.0')), ('A', Decimal('4.0')), ('A-', Decimal('3.7')), |
2341 | - ('B+', Decimal('3.3')), ('B', Decimal('3.0')), ('B-', Decimal('2.7')), |
2342 | - ('C+', Decimal('2.3')), ('C', Decimal('2.0')), ('C-', Decimal('1.7')), |
2343 | - ('D+', Decimal('1.3')), ('D', Decimal('1.0')), ('D-', Decimal('0.7')), |
2344 | - ('F', Decimal('0.0'))], 'A+', 'D-') |
2345 | + [('A+', Decimal('4.0'), Decimal(98)), |
2346 | + ('A', Decimal('4.0'), Decimal(93)), |
2347 | + ('A-', Decimal('3.7'), Decimal(90)), |
2348 | + ('B+', Decimal('3.3'), Decimal(88)), |
2349 | + ('B', Decimal('3.0'), Decimal(83)), |
2350 | + ('B-', Decimal('2.7'), Decimal(80)), |
2351 | + ('C+', Decimal('2.3'), Decimal(78)), |
2352 | + ('C', Decimal('2.0'), Decimal(73)), |
2353 | + ('C-', Decimal('1.7'), Decimal(70)), |
2354 | + ('D+', Decimal('1.3'), Decimal(68)), |
2355 | + ('D', Decimal('1.0'), Decimal(63)), |
2356 | + ('D-', Decimal('0.7'), Decimal(60)), |
2357 | + ('F', Decimal('0.0'), Decimal(0))], |
2358 | + 'A+', 'D-') |
2359 | |
2360 | |
2361 | class RangedValuesScoreSystem(AbstractValuesScoreSystem): |
2362 | @@ -271,6 +309,7 @@ |
2363 | value = self.getNumericalValue(score) - self.min |
2364 | return value / (self.max - self.min) |
2365 | |
2366 | + |
2367 | class GlobalRangedValuesScoreSystem(RangedValuesScoreSystem): |
2368 | |
2369 | def __init__(self, name, *args, **kwargs): |
2370 | @@ -294,9 +333,61 @@ |
2371 | class ICustomScoreSystem(zope.interface.Interface): |
2372 | """Marker interface for score systems created in the widget.""" |
2373 | |
2374 | + |
2375 | class IScoreSystemField(zope.schema.interfaces.IField): |
2376 | """A field that represents score system.""" |
2377 | |
2378 | + |
2379 | class ScoreSystemField(zope.schema.Field): |
2380 | """Score System Field.""" |
2381 | zope.interface.implements(IScoreSystemField) |
2382 | + |
2383 | + |
2384 | +class CustomScoreSystem(DiscreteValuesScoreSystem, Persistent): |
2385 | + """ScoreSystem class for custom (user-created) score systems""" |
2386 | + |
2387 | + implements(interfaces.ICustomScoreSystem) |
2388 | + |
2389 | + |
2390 | +class ScoreSystemsProxy(object): |
2391 | + """The Proxy class for adding/editing score systems""" |
2392 | + |
2393 | + implements(IScoreSystemsProxy) |
2394 | + adapts(ISchoolToolApplication) |
2395 | + |
2396 | + def __init__(self, app): |
2397 | + self.app = app |
2398 | + self.siteManager = app.getSiteManager() |
2399 | + self.__parent__ = app |
2400 | + self.__name__ = 'scoresystems' |
2401 | + |
2402 | + def getScoreSystems(self): |
2403 | + """Return list of tuples (name, scoresystem)""" |
2404 | + results = [] |
2405 | + for name, util in sorted(self.siteManager.getUtilitiesFor( |
2406 | + interfaces.ICustomScoreSystem)): |
2407 | + util = removeSecurityProxy(util) |
2408 | + if util.hidden: |
2409 | + continue |
2410 | + results.append((name, util)) |
2411 | + return results |
2412 | + |
2413 | + def addScoreSystem(self, scoresystem): |
2414 | + """Add scoresystem to app utilitiles""" |
2415 | + names = [name for name, util in self.getScoreSystems()] |
2416 | + name = scoresystem.title |
2417 | + n = name |
2418 | + i = 1 |
2419 | + while n in names: |
2420 | + n = name + u'-' + unicode(i) |
2421 | + i += 1 |
2422 | + self.siteManager.registerUtility(scoresystem, |
2423 | + interfaces.ICustomScoreSystem, name=n) |
2424 | + |
2425 | + def getScoreSystem(self, name): |
2426 | + """Get scoresystem from app utilitiles by the given name""" |
2427 | + for n, util in self.getScoreSystems(): |
2428 | + if n == name: |
2429 | + return removeSecurityProxy(util) |
2430 | + raise KeyError |
2431 | + |
2432 | |
2433 | === modified file 'src/schooltool/requirement/tests.py' |
2434 | --- src/schooltool/requirement/tests.py 2006-01-15 23:50:25 +0000 |
2435 | +++ src/schooltool/requirement/tests.py 2009-05-08 18:19:21 +0000 |
2436 | @@ -47,12 +47,14 @@ |
2437 | setUp=setUp, tearDown=tearDown, |
2438 | globs={'pprint': doctestunit.pprint}, |
2439 | optionflags=doctest.NORMALIZE_WHITESPACE| |
2440 | - doctest.ELLIPSIS), |
2441 | + doctest.ELLIPSIS| |
2442 | + doctest.REPORT_ONLY_FIRST_FAILURE), |
2443 | doctest.DocFileSuite('grades.txt', |
2444 | setUp=setUp, tearDown=tearDown, |
2445 | globs={'pprint': doctestunit.pprint}, |
2446 | optionflags=doctest.NORMALIZE_WHITESPACE| |
2447 | - doctest.ELLIPSIS), |
2448 | + doctest.ELLIPSIS| |
2449 | + doctest.REPORT_ONLY_FIRST_FAILURE), |
2450 | )) |
2451 | |
2452 | if __name__ == '__main__': |