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