Merge lp:~aelkner/schooltool/schooltool.gradebook_column_preferences into lp:~schooltool-owners/schooltool/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
Reviewer Review Type Date Requested Status
Justas Sadzevičius (community) Approve
Review via email: mp+7191@code.launchpad.net
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
=== modified file 'src/schooltool/gradebook/browser/README.txt'
--- src/schooltool/gradebook/browser/README.txt 2009-05-08 01:30:55 +0000
+++ src/schooltool/gradebook/browser/README.txt 2009-06-01 21:32:36 +0000
@@ -7,8 +7,8 @@
7SchoolTool setup is the configuration of the categories. So let's log in as a7SchoolTool setup is the configuration of the categories. So let's log in as a
8manager:8manager:
99
10 >>> from schooltool.app.browser.ftests import setup10 >>> from schooltool.app.browser.ftests import setup
11 >>> manager = setup.logInManager()11 >>> manager = setup.logIn('manager', 'schooltool')
1212
1313
14Initial School Setup14Initial School Setup
@@ -557,6 +557,74 @@
557 '62'557 '62'
558558
559559
560Column Preferences
561------------------
562
563Teachers may want to hide or change the label of the summary columns or, in
564the case of the average column, they may want to choose a score system to
565be used in converting the average to a discrete value. To support this, we
566provide the column preferences view.
567
568First we will add a custom score system which we will use as a column
569preference.
570
571 >>> manager.getLink('Manage').click()
572 >>> manager.getLink('Score Systems').click()
573 >>> manager.getLink('Add Score System').click()
574 >>> url = manager.url + '?form-submitted&SAVE&title=Good/Bad'
575 >>> url = url + '&displayed1=G&value1=1&percent1=60'
576 >>> url = url + '&displayed2=B&value2=0&percent2=0'
577 >>> manager.open(url)
578
579We'll start by calling up the current column preferences and note that there
580are none set yet.
581
582 >>> stephan.getLink('Return to Gradebook').click()
583 >>> stephan.getLink('Column Preferences').click()
584 >>> analyze.printQuery("id('content-body')/form/table//input", stephan.contents)
585 <input type="checkbox" name="hide_total" />
586 <input type="text" name="label_total" value="" />
587 <input type="checkbox" name="hide_average" />
588 <input type="text" name="label_average" value="" />
589
590 >>> analyze.printQuery("id('content-body')/form/table//option", stephan.contents)
591 <option selected="selected" value="">-- No score system --</option>
592 <option value="extended-letter-grade">Extended Letter Grade</option>
593 <option value="goodbad">Good/Bad</option>
594 <option value="letter-grade">Letter Grade</option>
595 <option value="passfail">Pass/Fail</option>
596
597Now we'll set all of the preferences to something and test that the changes
598were saved.
599
600 >>> url = stephan.url + '?form-submitted&UPDATE_SUBMIT'
601 >>> url += '&hide_total=on&label_total=Summe'
602 >>> url += '&hide_average=on&label_average=Durchschnitt'
603 >>> url += '&scoresystem_average=goodbad'
604 >>> stephan.open(url)
605
606 >>> stephan.getLink('Column Preferences').click()
607 >>> analyze.printQuery("id('content-body')/form/table//input", stephan.contents)
608 <input type="checkbox" checked="checked" name="hide_total" />
609 <input type="text" name="label_total" value="Summe" />
610 <input type="checkbox" checked="checked" name="hide_average" />
611 <input type="text" name="label_average" value="Durchschnitt" />
612
613 >>> analyze.printQuery("id('content-body')/form/table//option", stephan.contents)
614 <option value="">-- No score system --</option>
615 <option value="extended-letter-grade">Extended Letter Grade</option>
616 <option selected="selected" value="goodbad">Good/Bad</option>
617 <option value="letter-grade">Letter Grade</option>
618 <option value="passfail">Pass/Fail</option>
619
620Finally, we will reset the preferences to none so that the rest of the tests
621pass.
622
623 >>> url = stephan.url + '?form-submitted&UPDATE_SUBMIT'
624 >>> url += '&scoresystem_average='
625 >>> stephan.open(url)
626
627
560My Grades628My Grades
561---------629---------
562630
@@ -575,7 +643,7 @@
575 True643 True
576 >>> 'HW 2' in claudia.contents or 'Final' in claudia.contents644 >>> 'HW 2' in claudia.contents or 'Final' in claudia.contents
577 False645 False
578 >>> claudia.contents.find('Current Grade: 86%') \646 >>> claudia.contents.find('Ave.: 86%') \
579 ... < claudia.contents.find('HW 1') \647 ... < claudia.contents.find('HW 1') \
580 ... < claudia.contents.find('Quiz') \648 ... < claudia.contents.find('Quiz') \
581 ... < claudia.contents.find('86 / 100')649 ... < claudia.contents.find('86 / 100')
@@ -737,12 +805,12 @@
737 >>> claudia.open('http://localhost/schoolyears/2007/winter/sections/1/mygrades')805 >>> claudia.open('http://localhost/schoolyears/2007/winter/sections/1/mygrades')
738 >>> print claudia.contents806 >>> print claudia.contents
739 <BLANKLINE>807 <BLANKLINE>
740 ... Current Grade: 86%...808 ... Ave.: 86%...
741 >>> tom = setup.logIn('tom', 'pwd')809 >>> tom = setup.logIn('tom', 'pwd')
742 >>> tom.open('http://localhost/schoolyears/2007/winter/sections/1/mygrades')810 >>> tom.open('http://localhost/schoolyears/2007/winter/sections/1/mygrades')
743 >>> print tom.contents811 >>> print tom.contents
744 <BLANKLINE>812 <BLANKLINE>
745 ...Current Grade: 88%...813 ...Ave.: 88%...
746814
747Students should not be able to view a teacher's gradebook.815Students should not be able to view a teacher's gradebook.
748816
749817
=== modified file 'src/schooltool/gradebook/browser/configure.zcml'
--- src/schooltool/gradebook/browser/configure.zcml 2009-05-08 01:30:55 +0000
+++ src/schooltool/gradebook/browser/configure.zcml 2009-06-08 01:43:13 +0000
@@ -352,6 +352,13 @@
352 action="../weights.html"352 action="../weights.html"
353 permission="schooltool.view"353 permission="schooltool.view"
354 />354 />
355 <menuItem
356 menu="schooltool_actions"
357 title="Column Preferences"
358 for="..interfaces.IGradebook"
359 action="column_preferences.html"
360 permission="schooltool.view"
361 />
355362
356 <!-- Special navagation viewlet for update linked activity grades action -->363 <!-- Special navagation viewlet for update linked activity grades action -->
357 <navigationViewlet364 <navigationViewlet
@@ -420,9 +427,15 @@
420 class=".gradebook.UpdateLinkedActivityGrades"427 class=".gradebook.UpdateLinkedActivityGrades"
421 permission="schooltool.edit"428 permission="schooltool.edit"
422 />429 />
430 <page
431 name="column_preferences.html"
432 for="..interfaces.IGradebook"
433 class=".gradebook.GradebookColumnPreferences"
434 template="gradebook_column_preferences.pt"
435 permission="schooltool.view"
436 />
423437
424 <!-- Terms -->438 <!-- Terms -->
425
426 <zope:adapter439 <zope:adapter
427 for="schooltool.gradebook.interfaces.IExternalActivitiesSource440 for="schooltool.gradebook.interfaces.IExternalActivitiesSource
428 zope.publisher.interfaces.browser.IBrowserRequest"441 zope.publisher.interfaces.browser.IBrowserRequest"
@@ -488,7 +501,7 @@
488 permission="schooltool.edit"501 permission="schooltool.edit"
489 />502 />
490503
491 <!-- Views for IBasicPerson-->504 <!-- Views for IBasicPerson -->
492 <page505 <page
493 name="print_report_card.html"506 name="print_report_card.html"
494 for="schooltool.person.interfaces.IPerson"507 for="schooltool.person.interfaces.IPerson"
@@ -513,6 +526,15 @@
513 permission="schooltool.edit"526 permission="schooltool.edit"
514 />527 />
515528
529 <!-- Views for ISchoolToolApplication -->
530 <page
531 name="no_current_term.html"
532 for="schooltool.app.interfaces.ISchoolToolApplication"
533 class=".gradebook.NoCurrentTerm"
534 template="no_current_term.pt"
535 permission="schooltool.view"
536 />
537
516 <zope:adapter538 <zope:adapter
517 factory=".report_card.ExistingScoresSystem" />539 factory=".report_card.ExistingScoresSystem" />
518540
519541
=== modified file 'src/schooltool/gradebook/browser/gradebook.css'
--- src/schooltool/gradebook/browser/gradebook.css 2009-05-08 01:30:55 +0000
+++ src/schooltool/gradebook/browser/gradebook.css 2009-06-01 21:32:36 +0000
@@ -148,3 +148,7 @@
148 border: solid 1px black;148 border: solid 1px black;
149}149}
150150
151.bold {
152 font-weight: bold;
153}
154
151155
=== modified file 'src/schooltool/gradebook/browser/gradebook.py'
--- src/schooltool/gradebook/browser/gradebook.py 2009-05-11 13:16:00 +0000
+++ src/schooltool/gradebook/browser/gradebook.py 2009-06-08 01:43:13 +0000
@@ -20,38 +20,71 @@
20Gradebook Views20Gradebook Views
21"""21"""
2222
23from schooltool.course.interfaces import ILearner
24from schooltool.course.interfaces import IInstructor
25__docformat__ = 'reStructuredText'23__docformat__ = 'reStructuredText'
26import zope.schema24
25import datetime
26import decimal
27
28from zope.app.keyreference.interfaces import IKeyReference
29from zope.component import queryUtility
30from zope.publisher.browser import BrowserView
31from zope.schema import ValidationError
32from zope.schema.interfaces import IVocabularyFactory
27from zope.security import proxy33from zope.security import proxy
34from zope.traversing.api import getName
28from zope.traversing.browser.absoluteurl import absoluteURL35from zope.traversing.browser.absoluteurl import absoluteURL
29from zope.app.keyreference.interfaces import IKeyReference
30from zope.viewlet import viewlet36from zope.viewlet import viewlet
31from zope.traversing.api import getName
32from zope.publisher.browser import BrowserView
3337
34from schooltool.app import app38from schooltool.app import app
35from schooltool.app.interfaces import ISchoolToolApplication39from schooltool.app.interfaces import ISchoolToolApplication
36from schooltool.course.interfaces import ISection40from schooltool.course.interfaces import ISection
41from schooltool.course.interfaces import ILearner, IInstructor
37from schooltool.gradebook import interfaces42from schooltool.gradebook import interfaces
38from schooltool.gradebook.activity import ensureAtLeastOneWorksheet43from schooltool.gradebook.activity import ensureAtLeastOneWorksheet
39from schooltool.person.interfaces import IPerson44from schooltool.person.interfaces import IPerson
40from schooltool.requirement.scoresystem import UNSCORED45from schooltool.requirement.scoresystem import UNSCORED
41from schooltool.common import SchoolToolMessage as _
42from schooltool.requirement.interfaces import IValuesScoreSystem46from schooltool.requirement.interfaces import IValuesScoreSystem
43from schooltool.requirement.interfaces import IDiscreteValuesScoreSystem47from schooltool.requirement.interfaces import IDiscreteValuesScoreSystem
44from schooltool.requirement.interfaces import IRangedValuesScoreSystem48from schooltool.requirement.interfaces import IRangedValuesScoreSystem
45from schooltool.term.interfaces import ITerm49from schooltool.term.interfaces import ITerm
4650
47import datetime51from schooltool.common import SchoolToolMessage as _
48import decimal52
4953
50GradebookCSSViewlet = viewlet.CSSViewlet("gradebook.css")54GradebookCSSViewlet = viewlet.CSSViewlet("gradebook.css")
5155
52DISCRETE_SCORE_SYSTEM = 'd'56DISCRETE_SCORE_SYSTEM = 'd'
53RANGED_SCORE_SYSTEM = 'r'57RANGED_SCORE_SYSTEM = 'r'
5458
59column_keys = [('total', _("Total")), ('average', _("Ave."))]
60
61
62def escName(name):
63 """converts title-based scoresystem name to querystring format"""
64 chars = [c for c in name.lower() if c.isalnum() or c == ' ']
65 return u''.join(chars).replace(' ', '-')
66
67
68def getScoreSystemFromEscName(name):
69 """converts escaped scoresystem title to scoresystem"""
70 factory = queryUtility(IVocabularyFactory,
71 'schooltool.requirement.discretescoresystems')
72 vocab = factory(None)
73 for term in vocab:
74 if name == escName(term.token):
75 return term.value
76 return None
77
78
79def convertAverage(average, scoresystem):
80 """converts average to display value of the given scoresystem"""
81 if scoresystem is None:
82 return '%s%%' % average
83 for score in scoresystem.scores:
84 if average >= score[2]:
85 return score[0]
86 raise ValueError
87
5588
56class GradebookStartup(object):89class GradebookStartup(object):
57 """A view for entry into into the gradebook or mygrades views."""90 """A view for entry into into the gradebook or mygrades views."""
@@ -241,6 +274,37 @@
241 return True274 return True
242 return False275 return False
243276
277 def processColumnPreferences(self):
278 gradebook = proxy.removeSecurityProxy(self.context)
279 if self.isTeacher:
280 person = self.person
281 else:
282 section = ISection(gradebook)
283 instructors = list(section.instructors)
284 if len(instructors) == 0:
285 return {}
286 person = instructors[0]
287 columnPreferences = gradebook.getColumnPreferences(person)
288 column_keys_dict = dict(column_keys)
289 prefs = columnPreferences.get('total', {})
290 self.total_hide = prefs.get('hide', False)
291 self.total_label = prefs.get('label', '')
292 if len(self.total_label) == 0:
293 self.total_label = column_keys_dict['total']
294 prefs = columnPreferences.get('average', {})
295 self.average_hide = prefs.get('hide', False)
296 self.average_label = prefs.get('label', '')
297 if len(self.average_label) == 0:
298 self.average_label = column_keys_dict['average']
299 self.average_scoresystem = getScoreSystemFromEscName(
300 prefs.get('scoresystem', ''))
301 self.apply_all_colspan = 1
302 if not self.total_hide:
303 self.apply_all_colspan += 1
304 if not self.average_hide:
305 self.apply_all_colspan += 1
306
307
244class GradebookOverview(SectionFinder):308class GradebookOverview(SectionFinder):
245 """Gradebook Overview/Table"""309 """Gradebook Overview/Table"""
246310
@@ -255,6 +319,9 @@
255 worksheet = gradebook.context319 worksheet = gradebook.context
256 gradebook.setCurrentWorksheet(self.person, worksheet)320 gradebook.setCurrentWorksheet(self.person, worksheet)
257321
322 """Retrieve column preferences."""
323 self.processColumnPreferences()
324
258 """Retrieve sorting information and store changes of it."""325 """Retrieve sorting information and store changes of it."""
259 if 'sort_by' in self.request:326 if 'sort_by' in self.request:
260 sort_by = self.request['sort_by']327 sort_by = self.request['sort_by']
@@ -297,7 +364,7 @@
297 try:364 try:
298 score = activity.scoresystem.fromUnicode(365 score = activity.scoresystem.fromUnicode(
299 self.request[cell_name])366 self.request[cell_name])
300 except (zope.schema.ValidationError, ValueError):367 except (ValidationError, ValueError):
301 self.message = _(368 self.message = _(
302 'The grade $value for activity $name is not valid.',369 'The grade $value for activity $name is not valid.',
303 mapping={'value': self.request[cell_name],370 mapping={'value': self.request[cell_name],
@@ -387,6 +454,8 @@
387 total, average = gradebook.getWorksheetTotalAverage(worksheet,454 total, average = gradebook.getWorksheetTotalAverage(worksheet,
388 student)455 student)
389456
457 average = convertAverage(average, self.average_scoresystem)
458
390 rows.append(459 rows.append(
391 {'student': {'title': student.title, 460 {'student': {'title': student.title,
392 'id': student.username,461 'id': student.username,
@@ -518,7 +587,7 @@
518 try:587 try:
519 score = activity.scoresystem.fromUnicode(588 score = activity.scoresystem.fromUnicode(
520 self.request[id])589 self.request[id])
521 except (zope.schema.ValidationError, ValueError):590 except (ValidationError, ValueError):
522 self.message = _(591 self.message = _(
523 'The grade $value for $name is not valid.',592 'The grade $value for $name is not valid.',
524 mapping={'value': self.request[id],593 mapping={'value': self.request[id],
@@ -579,6 +648,9 @@
579 if self.handleSectionChange():648 if self.handleSectionChange():
580 return649 return
581650
651 """Retrieve column preferences."""
652 self.processColumnPreferences()
653
582 self.table = []654 self.table = []
583 total = 0655 total = 0
584 count = 0656 count = 0
@@ -602,8 +674,10 @@
602674
603 self.table.append({'activity': activity.title,675 self.table.append({'activity': activity.title,
604 'grade': grade})676 'grade': grade})
677
605 if count:678 if count:
606 self.average = int((float(100 * total) / float(count)) + 0.5)679 average = int((float(100 * total) / float(count)) + 0.5)
680 self.average = convertAverage(average, self.average_scoresystem)
607 else:681 else:
608 self.average = None682 self.average = None
609683
@@ -638,3 +712,80 @@
638 next_url = absoluteURL(self.context.__parent__, self.request) + \712 next_url = absoluteURL(self.context.__parent__, self.request) + \
639 '/gradebook'713 '/gradebook'
640 self.request.response.redirect(next_url)714 self.request.response.redirect(next_url)
715
716
717class GradebookColumnPreferences(BrowserView):
718 """A view for editing a teacher's gradebook column preferences."""
719
720 def update(self):
721 self.person = IPerson(self.request.principal)
722 gradebook = proxy.removeSecurityProxy(self.context)
723
724 if 'UPDATE_SUBMIT' in self.request:
725 columnPreferences = gradebook.getColumnPreferences(self.person)
726 for key, name in column_keys:
727 prefs = columnPreferences.setdefault(key, {})
728 if 'hide_' + key in self.request:
729 prefs['hide'] = True
730 else:
731 prefs['hide'] = False
732 if 'label_' + key in self.request:
733 prefs['label'] = self.request['label_' + key]
734 else:
735 prefs['label'] = ''
736 if key != 'total':
737 prefs['scoresystem'] = self.request['scoresystem_' + key]
738 gradebook.setColumnPreferences(self.person, columnPreferences)
739
740 if 'CANCEL' in self.request or 'UPDATE_SUBMIT' in self.request:
741 self.request.response.redirect('index.html')
742
743 @property
744 def columns(self):
745 self.person = IPerson(self.request.principal)
746 gradebook = proxy.removeSecurityProxy(self.context)
747 results = []
748 columnPreferences = gradebook.getColumnPreferences(self.person)
749 for key, name in column_keys:
750 prefs = columnPreferences.get(key, {})
751 hide = prefs.get('hide', False)
752 label = prefs.get('label', '')
753 scoresystem = prefs.get('scoresystem', '')
754 result = {
755 'name': name,
756 'hide_name': 'hide_' + key,
757 'hide_value': hide,
758 'label_name': 'label_' + key,
759 'label_value': label,
760 'scoresystem_name': 'scoresystem_' + key,
761 'scoresystem_value': scoresystem,
762 }
763 results.append(result)
764 return results
765
766 @property
767 def scoresystems(self):
768 factory = queryUtility(IVocabularyFactory,
769 'schooltool.requirement.discretescoresystems')
770 vocab = factory(None)
771 result = {
772 'name': _('-- No score system --'),
773 'value': '',
774 }
775 results = [result]
776 for term in vocab:
777 result = {
778 'name': term.token,
779 'value': escName(term.token),
780 }
781 results.append(result)
782 return results
783
784
785class NoCurrentTerm(BrowserView):
786 """A view for informing the user of the need to set up a schoolyear
787 and at least one term."""
788
789 def update(self):
790 pass
791
641792
=== added file 'src/schooltool/gradebook/browser/gradebook_column_preferences.pt'
--- src/schooltool/gradebook/browser/gradebook_column_preferences.pt 1970-01-01 00:00:00 +0000
+++ src/schooltool/gradebook/browser/gradebook_column_preferences.pt 2009-05-14 10:35:19 +0000
@@ -0,0 +1,67 @@
1<tal:define define="dummy view/update"/>
2<html metal:use-macro="view/@@standard_macros/page" i18n:domain="schooltool">
3<head>
4 <title metal:fill-slot="title" i18n:translate="">Set Column Preferences</title>
5</head>
6<body>
7
8<h1 metal:fill-slot="content-header"
9 i18n:translate="">Set Column Preferences</h1>
10
11<metal:block metal:fill-slot="body">
12 <form method="post"
13 tal:attributes="action string:${context/@@absolute_url}/column_preferences.html">
14 <input type="hidden" name="form-submitted" value="" />
15
16 <table class="schooltool_gradebook">
17 <tr>
18 <th class="cell header fully_padded">Column Type</th>
19 <th class="cell header fully_padded">Hide?</th>
20 <th class="cell header fully_padded">Label</th>
21 <th class="cell header fully_padded">Score System</th>
22 </tr>
23
24 <tal:block repeat="column view/columns">
25 <tr class="bordered">
26 <td class="cell fully_padded">
27 <div tal:content="column/name" />
28 </td>
29 <td class="cell fully_padded">
30 <input type="checkbox"
31 tal:attributes="name column/hide_name; checked column/hide_value" />
32 </td>
33 <td class="cell fully_padded">
34 <input type="text"
35 tal:attributes="name column/label_name; value column/label_value" />
36 </td>
37 <td class="cell fully_padded">
38 <tal:block condition="python: column['hide_name'] != 'hide_total'">
39 <select tal:attributes="id column/scoresystem_name; name column/scoresystem_name">
40 <tal:block repeat="scoresystem view/scoresystems">
41 <option tal:condition="python: scoresystem['value'] == column['scoresystem_value']"
42 selected
43 tal:attributes="value scoresystem/value"
44 tal:content="scoresystem/name" />
45 <option tal:condition="python: scoresystem['value'] != column['scoresystem_value']"
46 tal:attributes="value scoresystem/value"
47 tal:content="scoresystem/name" />
48 </tal:block>
49 </select>
50 </tal:block>
51 </td>
52 </tr>
53 </tal:block>
54
55 </table>
56 <div style="height: 11px;"></div>
57
58 <div class="controls">
59 <input type="submit" class="button-ok" name="UPDATE_SUBMIT" value="Update" />
60 <input type="submit" class="button-cancel" name="CANCEL" value="Cancel" />
61 </div>
62 </form>
63
64</metal:block>
65</body>
66</html>
67
068
=== modified file 'src/schooltool/gradebook/browser/gradebook_overview.js.pt'
--- src/schooltool/gradebook/browser/gradebook_overview.js.pt 2009-04-17 04:22:56 +0000
+++ src/schooltool/gradebook/browser/gradebook_overview.js.pt 2009-04-30 19:20:05 +0000
@@ -160,7 +160,6 @@
160 }160 }
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)
162 {162 {
163 document.getElementById(name).select();
164 return true;163 return true;
165 }164 }
166165
167166
=== modified file 'src/schooltool/gradebook/browser/gradebook_overview.pt'
--- src/schooltool/gradebook/browser/gradebook_overview.pt 2009-04-29 00:39:08 +0000
+++ src/schooltool/gradebook/browser/gradebook_overview.pt 2009-05-15 06:01:23 +0000
@@ -114,7 +114,7 @@
114 <table class="schooltool_gradebook">114 <table class="schooltool_gradebook">
115 <tr tal:condition="python:len(view.descriptions) > 0">115 <tr tal:condition="python:len(view.descriptions) > 0">
116 <td id="description" class="description"116 <td id="description" class="description"
117 tal:attributes="colspan python:len(view.descriptions) + 3" />117 tal:attributes="colspan python:len(view.descriptions) + view.apply_all_colspan" />
118 </tr>118 </tr>
119119
120 <tr>120 <tr>
@@ -122,11 +122,11 @@
122 <div i18n:translate="">Name</div>122 <div i18n:translate="">Name</div>
123 <a href="?sort_by=student" i18n:translate="">(sort)</a>123 <a href="?sort_by=student" i18n:translate="">(sort)</a>
124 </th>124 </th>
125 <th class="cell header">125 <th tal:condition="not: view/total_hide" class="cell padded header">
126 <div i18n:translate="">Total</div>126 <div tal:content="view/total_label" />
127 </th>127 </th>
128 <th class="cell header">128 <th tal:condition="not: view/average_hide" class="cell padded header">
129 <div i18n:translate="">Ave.</div>129 <div tal:content="view/average_label" />
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>
131 </th>131 </th>
132 <th class="cell title header" tal:repeat="activity view/activities">132 <th class="cell title header" tal:repeat="activity view/activities">
@@ -152,7 +152,8 @@
152 </tr>152 </tr>
153153
154 <tr>154 <tr>
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"
156 class="cell padded fd_cell empty" i18n:translate="">Apply a grade for all students:</td>
156 <td class="cell padded fd_cell" tal:repeat="activity view/activities"157 <td class="cell padded fd_cell" tal:repeat="activity view/activities"
157 tal:attributes="id string:fd_${activity/hash}_cell">158 tal:attributes="id string:fd_${activity/hash}_cell">
158 <input type="text" size="4" class="data fd"159 <input type="text" size="4" class="data fd"
@@ -178,14 +179,14 @@
178 Tom Hoffman179 Tom Hoffman
179 </a>180 </a>
180 </td>181 </td>
181 <td class="cell padded even">182 <td tal:condition="not: view/total_hide" class="cell padded even">
182 <tal:if condition="row/total|nothing">183 <tal:if condition="row/total|nothing">
183 <b><span tal:replace="row/total" /></b>184 <b><span tal:replace="row/total" /></b>
184 </tal:if>185 </tal:if>
185 </td>186 </td>
186 <td class="cell padded even">187 <td tal:condition="not: view/average_hide" class="cell padded even">
187 <tal:if condition="row/average|nothing">188 <tal:if condition="row/average|nothing">
188 <b><span tal:replace="row/average" />%</b>189 <b><span tal:replace="row/average" /></b>
189 </tal:if>190 </tal:if>
190 </td>191 </td>
191 <td class="cell even" tal:repeat="grade row/grades"192 <td class="cell even" tal:repeat="grade row/grades"
@@ -207,14 +208,14 @@
207 Tom Hoffman208 Tom Hoffman
208 </a>209 </a>
209 </td>210 </td>
210 <td class="cell padded odd">211 <td tal:condition="not: view/total_hide" class="cell padded odd">
211 <tal:if condition="row/total|nothing">212 <tal:if condition="row/total|nothing">
212 <b><span tal:replace="row/total" /></b>213 <b><span tal:replace="row/total" /></b>
213 </tal:if>214 </tal:if>
214 </td>215 </td>
215 <td class="cell padded odd">216 <td tal:condition="not: view/average_hide" class="cell padded odd">
216 <tal:if condition="row/average|nothing">217 <tal:if condition="row/average|nothing">
217 <b><span tal:replace="row/average" />%</b>218 <b><span tal:replace="row/average" /></b>
218 </tal:if>219 </tal:if>
219 </td>220 </td>
220 <td class="cell odd" tal:repeat="grade row/grades"221 <td class="cell odd" tal:repeat="grade row/grades"
221222
=== modified file 'src/schooltool/gradebook/browser/mygrades.pt'
--- src/schooltool/gradebook/browser/mygrades.pt 2009-04-29 00:39:08 +0000
+++ src/schooltool/gradebook/browser/mygrades.pt 2009-05-15 06:01:23 +0000
@@ -72,10 +72,10 @@
72 </table>72 </table>
7373
74 <table class="schooltool_gradebook">74 <table class="schooltool_gradebook">
75 <tr>75 <tr tal:condition="not: view/average_hide">
76 <td colspan="2" class="description">76 <td colspan="2" class="description">
77 <div tal:condition="view/average"77 <div tal:condition="view/average"
78 tal:content="string: Current Grade: ${view/average}%">Average</div>78 tal:content="string: ${view/average_label}: ${view/average}">Average</div>
79 <div tal:condition="not: view/average">Nothing Graded</div>79 <div tal:condition="not: view/average">Nothing Graded</div>
80 </td>80 </td>
81 </tr>81 </tr>
8282
=== added file 'src/schooltool/gradebook/browser/no_current_term.pt'
--- src/schooltool/gradebook/browser/no_current_term.pt 1970-01-01 00:00:00 +0000
+++ src/schooltool/gradebook/browser/no_current_term.pt 2009-06-08 01:43:13 +0000
@@ -0,0 +1,16 @@
1<tal:tag condition="view/update" />
2<html metal:use-macro="context/@@standard_macros/page"
3 i18n:domain="schooltool">
4<head>
5 <title metal:fill-slot="title" i18n:translate="">
6 No Current Term
7 </title>
8</head>
9<body>
10<div metal:fill-slot="body">
11 <h1>The operation you attempted cannot be completed because there are curently
12 no terms set up in your SchoolTool instance. Please have a user with
13 administation access set up at least one term and try the operation again.</h1>
14</div>
15</body>
16</html>
017
=== added file 'src/schooltool/gradebook/browser/no_current_term.txt'
--- src/schooltool/gradebook/browser/no_current_term.txt 1970-01-01 00:00:00 +0000
+++ src/schooltool/gradebook/browser/no_current_term.txt 2009-06-08 01:43:13 +0000
@@ -0,0 +1,25 @@
1===============
2No Current Term
3===============
4
5If a schooltool operation relies on there being at least one term set up, and
6the administator has not gotten around to doing that, we need to fail
7gracefully. That means giving the user an error message rather than crashing.
8
9Let's log in as manager and create a student.
10
11 >>> from schooltool.app.browser.ftests import setup
12 >>> manager = Browser('manager', 'schooltool')
13 >>> setup.addPerson('Student One', 'student1', 'pwd')
14
15We'll navigate to the student and request a report card, an operation that is
16impossible without there being at least one term set up. We'll test that the
17user gets redirected to the error page.
18
19 >>> manager.getLink('Manage').click()
20 >>> manager.getLink('Persons').click()
21 >>> manager.getLink('Student One').click()
22 >>> manager.getLink('Print Report Card').click()
23 >>> manager.url
24 'http://localhost/no_current_term.html'
25
026
=== modified file 'src/schooltool/gradebook/browser/pdf_views.py'
--- src/schooltool/gradebook/browser/pdf_views.py 2009-05-11 13:08:19 +0000
+++ src/schooltool/gradebook/browser/pdf_views.py 2009-06-08 01:43:13 +0000
@@ -35,6 +35,7 @@
35from zope.component import getUtility, queryAdapter35from zope.component import getUtility, queryAdapter
36from zope.i18n import translate36from zope.i18n import translate
37from zope.publisher.browser import BrowserView37from zope.publisher.browser import BrowserView
38from zope.traversing.browser.absoluteurl import absoluteURL
3839
39from schooltool.app.interfaces import ISchoolToolApplication40from schooltool.app.interfaces import ISchoolToolApplication
40from schooltool.app.browser import pdfcal41from schooltool.app.browser import pdfcal
@@ -256,6 +257,13 @@
256257
257 def __call__(self):258 def __call__(self):
258 """Return the PDF of a report card for each student."""259 """Return the PDF of a report card for each student."""
260 current_term = getUtility(IDateManager).current_term
261 if current_term is None:
262 next_url = absoluteURL(ISchoolToolApplication(None), self.request)
263 next_url += '/no_current_term.html'
264 self.request.response.redirect(next_url)
265 return
266
259 if self.pdf_support_disabled:267 if self.pdf_support_disabled:
260 return translate(self.pdf_disabled_text, context=self.request)268 return translate(self.pdf_disabled_text, context=self.request)
261269
262270
=== modified file 'src/schooltool/gradebook/configure.zcml'
--- src/schooltool/gradebook/configure.zcml 2009-04-17 13:26:14 +0000
+++ src/schooltool/gradebook/configure.zcml 2009-06-05 00:11:04 +0000
@@ -284,4 +284,11 @@
284 <adapter factory=".gradebook_init.GradebookAppStartup"284 <adapter factory=".gradebook_init.GradebookAppStartup"
285 name="gradebook_startup_init" />285 name="gradebook_startup_init" />
286286
287 <!-- generations -->
288 <utility
289 name="schooltool.gradebook"
290 provides="zope.app.generations.interfaces.ISchemaManager"
291 component=".generations.schemaManager"
292 />
293
287</configure>294</configure>
288295
=== added directory 'src/schooltool/gradebook/generations'
=== added file 'src/schooltool/gradebook/generations/__init__.py'
--- src/schooltool/gradebook/generations/__init__.py 1970-01-01 00:00:00 +0000
+++ src/schooltool/gradebook/generations/__init__.py 2009-06-05 00:11:04 +0000
@@ -0,0 +1,28 @@
1#
2# SchoolTool - common information systems platform for school administration
3# Copyright (c) 2008 Shuttleworth Foundation
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18#
19"""
20Generations for database version upgrades.
21"""
22
23from zope.app.generations.generations import SchemaManager
24
25schemaManager = SchemaManager(
26 minimum_generation=1,
27 generation=1,
28 package_name='schooltool.gradebook.generations')
029
=== added file 'src/schooltool/gradebook/generations/evolve1.py'
--- src/schooltool/gradebook/generations/evolve1.py 1970-01-01 00:00:00 +0000
+++ src/schooltool/gradebook/generations/evolve1.py 2009-06-05 00:11:04 +0000
@@ -0,0 +1,89 @@
1#
2# SchoolTool - common information systems platform for school administration
3# Copyright (c) 2008 Shuttleworth Foundation
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18#
19"""
20Evolve database to generation 1.
21
22Moves hard-coded score system utilities to the app site manager.
23"""
24
25from zope.app.zopeappgenerations import getRootFolder
26
27from schooltool.app.interfaces import ISchoolToolApplication
28from schooltool.course.interfaces import ISectionContainer
29from schooltool.gradebook.interfaces import IActivities, IGradebookRoot
30from schooltool.person.interfaces import IPersonContainer
31from schooltool.schoolyear.interfaces import ISchoolYearContainer
32from schooltool.requirement.interfaces import IScoreSystemsProxy
33from schooltool.requirement.interfaces import IDiscreteValuesScoreSystem
34from schooltool.requirement.interfaces import IEvaluations
35from schooltool.requirement.scoresystem import CustomScoreSystem, PassFail
36from schooltool.requirement.scoresystem import AmericanLetterScoreSystem
37from schooltool.requirement.scoresystem import ExtendedAmericanLetterScoreSystem
38
39
40def updateEvaluations(app, ss, custom_ss):
41 """Update all evaluations using the hard-coded score system to use the
42 newly created custom score system"""
43
44 for person in app['persons'].values():
45 evaluations = IEvaluations(person)
46 for evaluation in evaluations.values():
47 if evaluation.scoreSystem == ss:
48 evaluation.scoreSystem = custom_ss
49 person._p_changed = True
50
51
52def updateObjActivities(obj, activities, ss, custom_ss):
53 """Update the obj activities using the hard-coded score system to use the
54 newly created custom score system"""
55
56 for worksheet in activities.values():
57 for activity in worksheet.values():
58 if activity.scoresystem == ss:
59 activity.scoresystem = custom_ss
60 obj._p_changed = True
61
62
63def updateAllActivities(app, ss, custom_ss):
64 """Update all activities using the hard-coded score system to use the
65 newly created custom score system"""
66
67 for sections in app['schooltool.course.section'].values():
68 for section in sections.values():
69 updateObjActivities(section, IActivities(section), ss, custom_ss)
70
71 root = IGradebookRoot(app)
72 updateObjActivities(root, root.templates, ss, custom_ss)
73 updateObjActivities(root, root.deployed, ss, custom_ss)
74
75
76def evolve(context):
77 """Migrates hard-coded discrete values score systems found in
78 schooltool.requirement.scoresystem to the app site manager"""
79
80 app = getRootFolder(context)
81 ssProxy = IScoreSystemsProxy(app)
82 for ss in [PassFail, AmericanLetterScoreSystem,
83 ExtendedAmericanLetterScoreSystem]:
84 custom_ss = CustomScoreSystem(ss.title, ss.description, ss.scores,
85 ss._bestScore, ss._minPassingScore)
86 ssProxy.addScoreSystem(custom_ss)
87 updateEvaluations(app, ss, custom_ss)
88 updateAllActivities(app, ss, custom_ss)
89
090
=== added file 'src/schooltool/gradebook/generations/install.py'
--- src/schooltool/gradebook/generations/install.py 1970-01-01 00:00:00 +0000
+++ src/schooltool/gradebook/generations/install.py 2009-06-05 00:11:04 +0000
@@ -0,0 +1,31 @@
1#
2# SchoolTool - common information systems platform for school administration
3# Copyright (c) 2008 Shuttleworth Foundation
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18#
19"""
20Initial deployment of generations to schooltool.gradebook.
21
22Manually evolves to generation 1. Done to avoid double deployment when
23introducing generations.
24"""
25
26import evolve1
27
28
29def evolve(context):
30 evolve1.evolve(context)
31
032
=== added directory 'src/schooltool/gradebook/generations/tests'
=== added file 'src/schooltool/gradebook/generations/tests/__init__.py'
--- src/schooltool/gradebook/generations/tests/__init__.py 1970-01-01 00:00:00 +0000
+++ src/schooltool/gradebook/generations/tests/__init__.py 2009-06-05 00:11:04 +0000
@@ -0,0 +1,72 @@
1"""
2Tests for generation scripts.
3"""
4
5from persistent.interfaces import IPersistent
6
7from zope.app.keyreference.interfaces import IKeyReference
8from zope.app.publication.zopepublication import ZopePublication
9from zope.app.testing.setup import setUpAnnotations
10from zope.component import provideAdapter
11from zope.interface import implements
12
13from schooltool.app.app import SchoolToolApplication
14from schooltool.app.interfaces import ISchoolToolApplication
15from schooltool.course.interfaces import ISection
16from schooltool.gradebook.activity import getSectionActivities
17from schooltool.gradebook.interfaces import IGradebookRoot, IActivities
18from schooltool.gradebook.gradebook_init import getGradebookRoot
19from schooltool.requirement.evaluation import getEvaluations
20from schooltool.requirement.interfaces import IEvaluations
21from schooltool.requirement.interfaces import IHaveEvaluations
22from schooltool.requirement.scoresystem import ScoreSystemsProxy
23
24
25class ContextStub(object):
26 """Stub for the context argument passed to evolve scripts.
27
28 >>> from zope.app.zopeappgenerations import getRootFolder
29 >>> context = ContextStub()
30 >>> getRootFolder(context) is context.root_folder
31 True
32 """
33
34 class ConnectionStub(object):
35 def __init__(self, root_folder):
36 self.root_folder = root_folder
37 def root(self):
38 return {ZopePublication.root_name: self.root_folder}
39
40 def __init__(self):
41 self.root_folder = SchoolToolApplication()
42 self.connection = self.ConnectionStub(self.root_folder)
43
44
45_d = {}
46
47class StupidKeyReference(object):
48 implements(IKeyReference)
49 key_type_id = 'StupidKeyReference'
50 def __init__(self, ob):
51 global _d
52 self.id = id(ob)
53 _d[self.id] = ob
54 def __call__(self):
55 return _d[self.id]
56 def __hash__(self):
57 return self.id
58 def __cmp__(self, other):
59 return cmp(hash(self), hash(other))
60
61
62def provideAdapters():
63 setUpAnnotations()
64 provideAdapter(StupidKeyReference, [IPersistent], IKeyReference)
65 provideAdapter(ScoreSystemsProxy)
66 provideAdapter(getGradebookRoot, adapts=(ISchoolToolApplication,),
67 provides=IGradebookRoot)
68 provideAdapter(getSectionActivities, adapts=(ISection,),
69 provides=IActivities)
70 provideAdapter(getEvaluations, adapts=(IHaveEvaluations,),
71 provides=IEvaluations)
72
073
=== added file 'src/schooltool/gradebook/generations/tests/test_evolve1.py'
--- src/schooltool/gradebook/generations/tests/test_evolve1.py 1970-01-01 00:00:00 +0000
+++ src/schooltool/gradebook/generations/tests/test_evolve1.py 2009-06-05 00:11:04 +0000
@@ -0,0 +1,140 @@
1# coding=UTF8
2#
3# SchoolTool - common information systems platform for school administration
4# Copyright (c) 2008 Shuttleworth Foundation
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19#
20"""
21Unit tests for schooltool.gradebook.generations.evolve1
22"""
23
24import unittest
25
26from zope.app.zopeappgenerations import getRootFolder
27from zope.testing import doctest
28
29from schooltool.app.interfaces import ISchoolToolApplication
30from schooltool.gradebook.generations.tests import ContextStub
31from schooltool.gradebook.generations.tests import provideAdapters
32from schooltool.gradebook.generations.evolve1 import evolve
33from schooltool.requirement.interfaces import IHaveEvaluations
34
35
36def doctest_evolve1():
37 """Evolution to generation 1.
38
39 First, we'll set up the app object:
40
41 >>> provideAdapters()
42 >>> context = ContextStub()
43 >>> app = getRootFolder(context)
44
45 >>> from zope.app.component.site import LocalSiteManager
46 >>> app.setSiteManager(LocalSiteManager(app))
47
48 We'll set up our test with data that will be effected by running the
49 evolve script:
50
51 >>> from schooltool.person.person import PersonContainer, Person
52 >>> app['persons'] = PersonContainer()
53 >>> student = Person('student')
54 >>> app['persons']['student'] = student
55
56 >>> from schooltool.course.section import SectionContainerContainer
57 >>> from schooltool.course.section import SectionContainer, Section
58 >>> app['schooltool.course.section'] = SectionContainerContainer()
59 >>> app['schooltool.course.section']['1'] = SectionContainer()
60 >>> section = Section('section')
61 >>> app['schooltool.course.section']['1']['1'] = section
62
63 >>> from schooltool.gradebook.gradebook_init import setUpGradebookRoot
64 >>> from schooltool.gradebook.interfaces import IGradebookRoot
65 >>> setUpGradebookRoot(app)
66 >>> root = IGradebookRoot(app)
67
68 >>> from schooltool.requirement.scoresystem import PassFail
69 >>> from schooltool.requirement.scoresystem import AmericanLetterScoreSystem
70 >>> from schooltool.gradebook.activity import ReportWorksheet
71 >>> from schooltool.gradebook.activity import ReportActivity
72 >>> root.templates['1'] = temp_ws = ReportWorksheet('1')
73 >>> temp_ws['1'] = temp_act = ReportActivity('1', None, PassFail)
74 >>> root.deployed['1'] = dep_ws = ReportWorksheet('1')
75 >>> dep_ws['1'] = dep_act = ReportActivity('1', None, AmericanLetterScoreSystem)
76
77 >>> from schooltool.gradebook.interfaces import IActivities
78 >>> activities = IActivities(section)
79
80 >>> from schooltool.requirement.scoresystem import ExtendedAmericanLetterScoreSystem
81 >>> from schooltool.gradebook.activity import Worksheet, Activity
82 >>> activities['1'] = worksheet = Worksheet('1')
83 >>> worksheet['1'] = activity = Activity('1', None, ExtendedAmericanLetterScoreSystem)
84
85 >>> from schooltool.requirement.interfaces import IHaveEvaluations
86 >>> from schooltool.requirement.interfaces import IEvaluations
87 >>> from zope.interface import alsoProvides
88 >>> alsoProvides(student, IHaveEvaluations)
89 >>> evaluations = IEvaluations(student)
90
91 >>> from schooltool.requirement.evaluation import Evaluation
92 >>> ev = Evaluation(activity, ExtendedAmericanLetterScoreSystem, 'A+', None)
93 >>> evaluations.addEvaluation(ev)
94
95 Finally, we'll run the evolve script, testing the effected values before and
96 after:
97
98 >>> from schooltool.requirement.interfaces import IScoreSystemsProxy
99 >>> proxy = IScoreSystemsProxy(app)
100 >>> proxy.getScoreSystems()
101 []
102
103 >>> temp_act.scoresystem
104 <GlobalDiscreteValuesScoreSystem u'Pass/Fail'>
105 >>> dep_act.scoresystem
106 <GlobalDiscreteValuesScoreSystem u'Letter Grade'>
107 >>> activity.scoresystem
108 <GlobalDiscreteValuesScoreSystem u'Extended Letter Grade'>
109 >>> ev.scoreSystem
110 <GlobalDiscreteValuesScoreSystem u'Extended Letter Grade'>
111
112 >>> evolve(context)
113
114 >>> proxy.getScoreSystems()
115 [(u'Extended Letter Grade', <CustomScoreSystem u'Extended Letter Grade'>),
116 (u'Letter Grade', <CustomScoreSystem u'Letter Grade'>),
117 (u'Pass/Fail', <CustomScoreSystem u'Pass/Fail'>)]
118
119 >>> temp_act.scoresystem
120 <CustomScoreSystem u'Pass/Fail'>
121 >>> dep_act.scoresystem
122 <CustomScoreSystem u'Letter Grade'>
123 >>> activity.scoresystem
124 <CustomScoreSystem u'Extended Letter Grade'>
125 >>> ev.scoreSystem
126 <CustomScoreSystem u'Extended Letter Grade'>
127 """
128
129
130def test_suite():
131 return unittest.TestSuite([
132 doctest.DocTestSuite(optionflags=doctest.ELLIPSIS
133 | doctest.NORMALIZE_WHITESPACE
134 | doctest.REPORT_NDIFF
135 | doctest.REPORT_ONLY_FIRST_FAILURE),
136 ])
137
138if __name__ == '__main__':
139 unittest.main(defaultTest='test_suite')
140
0141
=== modified file 'src/schooltool/gradebook/gradebook.py'
--- src/schooltool/gradebook/gradebook.py 2009-05-08 22:20:00 +0000
+++ src/schooltool/gradebook/gradebook.py 2009-06-01 21:32:36 +0000
@@ -23,7 +23,7 @@
23from zope.component import adapts, queryMultiAdapter23from zope.component import adapts, queryMultiAdapter
24from schooltool.app.interfaces import ISchoolToolApplication24from schooltool.app.interfaces import ISchoolToolApplication
25__docformat__ = 'reStructuredText'25__docformat__ = 'reStructuredText'
26import persistent.dict26from persistent.dict import PersistentDict
2727
28from decimal import Decimal28from decimal import Decimal
2929
@@ -53,6 +53,7 @@
53GRADEBOOK_SORTING_KEY = 'schooltool.gradebook.sorting'53GRADEBOOK_SORTING_KEY = 'schooltool.gradebook.sorting'
54CURRENT_WORKSHEET_KEY = 'schooltool.gradebook.currentworksheet'54CURRENT_WORKSHEET_KEY = 'schooltool.gradebook.currentworksheet'
55DUE_DATE_FILTER_KEY = 'schooltool.gradebook.duedatefilter'55DUE_DATE_FILTER_KEY = 'schooltool.gradebook.duedatefilter'
56COLUMN_PREFERENCES_KEY = 'schooltool.gradebook.columnpreferences'
56 57
5758
58class WorksheetGradebookTraverser(object):59class WorksheetGradebookTraverser(object):
@@ -235,7 +236,7 @@
235 person = proxy.removeSecurityProxy(person)236 person = proxy.removeSecurityProxy(person)
236 ann = annotation.interfaces.IAnnotations(person)237 ann = annotation.interfaces.IAnnotations(person)
237 if CURRENT_WORKSHEET_KEY not in ann:238 if CURRENT_WORKSHEET_KEY not in ann:
238 ann[CURRENT_WORKSHEET_KEY] = persistent.dict.PersistentDict()239 ann[CURRENT_WORKSHEET_KEY] = PersistentDict()
239 if self.worksheets:240 if self.worksheets:
240 default = self.worksheets[0]241 default = self.worksheets[0]
241 else:242 else:
@@ -248,7 +249,7 @@
248 worksheet = proxy.removeSecurityProxy(worksheet)249 worksheet = proxy.removeSecurityProxy(worksheet)
249 ann = annotation.interfaces.IAnnotations(person)250 ann = annotation.interfaces.IAnnotations(person)
250 if CURRENT_WORKSHEET_KEY not in ann:251 if CURRENT_WORKSHEET_KEY not in ann:
251 ann[CURRENT_WORKSHEET_KEY] = persistent.dict.PersistentDict()252 ann[CURRENT_WORKSHEET_KEY] = PersistentDict()
252 section_id = hash(IKeyReference(self.section))253 section_id = hash(IKeyReference(self.section))
253 ann[CURRENT_WORKSHEET_KEY][section_id] = worksheet254 ann[CURRENT_WORKSHEET_KEY][section_id] = worksheet
254255
@@ -264,6 +265,18 @@
264 ann = annotation.interfaces.IAnnotations(person)265 ann = annotation.interfaces.IAnnotations(person)
265 ann[DUE_DATE_FILTER_KEY] = (flag, weeks)266 ann[DUE_DATE_FILTER_KEY] = (flag, weeks)
266267
268 def getColumnPreferences(self, person):
269 person = proxy.removeSecurityProxy(person)
270 ann = annotation.interfaces.IAnnotations(person)
271 if COLUMN_PREFERENCES_KEY not in ann:
272 return PersistentDict()
273 return ann[COLUMN_PREFERENCES_KEY]
274
275 def setColumnPreferences(self, person, columnPreferences):
276 person = proxy.removeSecurityProxy(person)
277 ann = annotation.interfaces.IAnnotations(person)
278 ann[COLUMN_PREFERENCES_KEY] = PersistentDict(columnPreferences)
279
267 def getCurrentActivities(self, person):280 def getCurrentActivities(self, person):
268 worksheet = self.getCurrentWorksheet(person)281 worksheet = self.getCurrentWorksheet(person)
269 return self.getWorksheetActivities(worksheet)282 return self.getWorksheetActivities(worksheet)
@@ -297,7 +310,7 @@
297 person = proxy.removeSecurityProxy(person)310 person = proxy.removeSecurityProxy(person)
298 ann = annotation.interfaces.IAnnotations(person)311 ann = annotation.interfaces.IAnnotations(person)
299 if GRADEBOOK_SORTING_KEY not in ann:312 if GRADEBOOK_SORTING_KEY not in ann:
300 ann[GRADEBOOK_SORTING_KEY] = persistent.dict.PersistentDict()313 ann[GRADEBOOK_SORTING_KEY] = PersistentDict()
301 section_id = hash(IKeyReference(self.section))314 section_id = hash(IKeyReference(self.section))
302 return ann[GRADEBOOK_SORTING_KEY].get(section_id, ('student', False))315 return ann[GRADEBOOK_SORTING_KEY].get(section_id, ('student', False))
303316
@@ -305,7 +318,7 @@
305 person = proxy.removeSecurityProxy(person)318 person = proxy.removeSecurityProxy(person)
306 ann = annotation.interfaces.IAnnotations(person)319 ann = annotation.interfaces.IAnnotations(person)
307 if GRADEBOOK_SORTING_KEY not in ann:320 if GRADEBOOK_SORTING_KEY not in ann:
308 ann[GRADEBOOK_SORTING_KEY] = persistent.dict.PersistentDict()321 ann[GRADEBOOK_SORTING_KEY] = PersistentDict()
309 section_id = hash(IKeyReference(self.section))322 section_id = hash(IKeyReference(self.section))
310 ann[GRADEBOOK_SORTING_KEY][section_id] = value323 ann[GRADEBOOK_SORTING_KEY][section_id] = value
311324
312325
=== modified file 'src/schooltool/gradebook/interfaces.py'
--- src/schooltool/gradebook/interfaces.py 2009-05-08 01:30:55 +0000
+++ src/schooltool/gradebook/interfaces.py 2009-06-01 21:32:36 +0000
@@ -236,6 +236,12 @@
236 def setDueDateFilter(person, flag, weeks):236 def setDueDateFilter(person, flag, weeks):
237 """Set the user's current due date filter setting."""237 """Set the user's current due date filter setting."""
238238
239 def getColumnPreferences(person):
240 """Get the user's column preferences."""
241
242 def setColumnPreferences(columnPreferences):
243 """Set the user's column preferences."""
244
239 def getCurrentActivities(person):245 def getCurrentActivities(person):
240 """Get the activities for the user's currently active worksheet."""246 """Get the activities for the user's currently active worksheet."""
241247
242248
=== modified file 'src/schooltool/requirement/README.txt'
--- src/schooltool/requirement/README.txt 2008-09-25 03:01:46 +0000
+++ src/schooltool/requirement/README.txt 2009-05-14 08:34:27 +0000
@@ -179,7 +179,8 @@
179 >>> from decimal import Decimal179 >>> from decimal import Decimal
180 >>> check = scoresystem.DiscreteValuesScoreSystem(180 >>> check = scoresystem.DiscreteValuesScoreSystem(
181 ... u'Check', u'Check-mark score system',181 ... u'Check', u'Check-mark score system',
182 ... [('+', Decimal(1)), ('v', Decimal(0)), ('-', Decimal(-1))])182 ... [('+', Decimal(1), Decimal(80)), ('v', Decimal(0), Decimal(60)),
183 ... ('-', Decimal(-1), Decimal(0))])
183184
184The first and second arguments of the constructor are the title and185The first and second arguments of the constructor are the title and
185description. The third argument is a list that really represents a mapping186description. The third argument is a list that really represents a mapping
@@ -247,9 +248,9 @@
247 >>> from schooltool.requirement import scoresystem248 >>> from schooltool.requirement import scoresystem
248 >>> check = scoresystem.DiscreteValuesScoreSystem(249 >>> check = scoresystem.DiscreteValuesScoreSystem(
249 ... u'Check', u'Check-mark score system',250 ... u'Check', u'Check-mark score system',
250 ... [('+', Decimal(1)),251 ... [('+', Decimal(1), Decimal(80)),
251 ... ('v', Decimal(0)),252 ... ('v', Decimal(0), Decimal(60)),
252 ... ('-', Decimal(-1))],253 ... ('-', Decimal(-1), Decimal(0))],
253 ... minPassingScore='v')254 ... minPassingScore='v')
254 >>> check255 >>> check
255 <DiscreteValuesScoreSystem u'Check'>256 <DiscreteValuesScoreSystem u'Check'>
@@ -279,7 +280,8 @@
279 >>> from schooltool.requirement import scoresystem280 >>> from schooltool.requirement import scoresystem
280 >>> check = scoresystem.DiscreteValuesScoreSystem(281 >>> check = scoresystem.DiscreteValuesScoreSystem(
281 ... u'Check', u'Check-mark score system',282 ... u'Check', u'Check-mark score system',
282 ... [('+', Decimal(1)), ('v', Decimal(0)), ('-', Decimal(-1))],283 ... [('+', Decimal(1), Decimal(80)), ('v', Decimal(0), Decimal(60)),
284 ... ('-', Decimal(-1)), Decimal(0)],
283 ... bestScore='+', minPassingScore='v')285 ... bestScore='+', minPassingScore='v')
284286
285 >>> check.getBestScore()287 >>> check.getBestScore()
@@ -297,7 +299,8 @@
297 >>> scoresystem.PassFail.title299 >>> scoresystem.PassFail.title
298 u'Pass/Fail'300 u'Pass/Fail'
299 >>> scoresystem.PassFail.scores301 >>> scoresystem.PassFail.scores
300 [(u'Pass', Decimal("1")), (u'Fail', Decimal("0"))]302 [(u'Pass', Decimal("1"), Decimal("60")),
303 (u'Fail', Decimal("0"), Decimal("0"))]
301 >>> scoresystem.PassFail.isValidScore('Pass')304 >>> scoresystem.PassFail.isValidScore('Pass')
302 True305 True
303 >>> scoresystem.PassFail.isPassingScore('Pass')306 >>> scoresystem.PassFail.isPassingScore('Pass')
@@ -324,8 +327,9 @@
324 >>> scoresystem.AmericanLetterScoreSystem.title327 >>> scoresystem.AmericanLetterScoreSystem.title
325 u'Letter Grade'328 u'Letter Grade'
326 >>> scoresystem.AmericanLetterScoreSystem.scores329 >>> scoresystem.AmericanLetterScoreSystem.scores
327 [('A', Decimal("4")), ('B', Decimal("3")), ('C', Decimal("2")),330 [('A', Decimal("4"), Decimal("90")), ('B', Decimal("3"), Decimal("80")),
328 ('D', Decimal("1")), ('F', Decimal("0"))]331 ('C', Decimal("2"), Decimal("70")), ('D', Decimal("1"), Decimal("60")),
332 ('F', Decimal("0"), Decimal("0"))]
329 >>> scoresystem.AmericanLetterScoreSystem.isValidScore('C')333 >>> scoresystem.AmericanLetterScoreSystem.isValidScore('C')
330 True334 True
331 >>> scoresystem.AmericanLetterScoreSystem.isValidScore('E')335 >>> scoresystem.AmericanLetterScoreSystem.isValidScore('E')
@@ -353,7 +357,7 @@
353 'ExtendedAmericanLetterScoreSystem'357 'ExtendedAmericanLetterScoreSystem'
354 >>> scoresystem.ExtendedAmericanLetterScoreSystem.title358 >>> scoresystem.ExtendedAmericanLetterScoreSystem.title
355 u'Extended Letter Grade'359 u'Extended Letter Grade'
356 >>> [s for s, v in scoresystem.ExtendedAmericanLetterScoreSystem.scores]360 >>> [s for s, v, p in scoresystem.ExtendedAmericanLetterScoreSystem.scores]
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']
358 >>> scoresystem.ExtendedAmericanLetterScoreSystem.isValidScore('B-')362 >>> scoresystem.ExtendedAmericanLetterScoreSystem.isValidScore('B-')
359 True363 True
@@ -842,6 +846,126 @@
842 >>> len(evals)846 >>> len(evals)
843 2847 2
844848
849
850The ScoresSystemsProxy
851~~~~~~~~~~~~~~~~~~~~~~
852
853Score systems are utilities that are pre-created in code (eventually we will
854migrate these to the application's site manager) and user created ones that
855we add to the site manager by way of the ScoresSystemsProxy class.
856
857First we'll set up the site and initialize the app object.
858
859 >>> from schooltool.testing import setup
860 >>> app = setup.setUpSchoolToolSite()
861
862The ScoresSystemsProxy class is itself an adapter of the app object.
863
864 >>> from schooltool.app.interfaces import ISchoolToolApplication
865 >>> from schooltool.requirement.interfaces import IScoreSystemsProxy
866 >>> from schooltool.requirement.scoresystem import ScoreSystemsProxy
867 >>> from zope.component import provideAdapter
868 >>> provideAdapter(ScoreSystemsProxy)
869 >>> ssProxy = IScoreSystemsProxy(app)
870 >>> ssProxy
871 <schooltool.requirement.scoresystem.ScoreSystemsProxy object at ...>
872
873At fist, there are no scoresystems registered with the app.
874
875 >>> ssProxy.getScoreSystems()
876 []
877
878We'll create a couple of custom score systems and add them to the proxy.
879
880 >>> from schooltool.requirement.scoresystem import CustomScoreSystem
881 >>> custom1 = CustomScoreSystem('Custom 1')
882 >>> ssProxy.addScoreSystem( custom1)
883 >>> custom2 = CustomScoreSystem('Custom 2')
884 >>> ssProxy.addScoreSystem(custom2)
885
886Now, when we ask the proxy for what score systems it has, it will return both
887of the newly added ones.
888
889 >>> ssProxy.getScoreSystems()
890 [(u'Custom 1', <CustomScoreSystem 'Custom 1'>),
891 (u'Custom 2', <CustomScoreSystem 'Custom 2'>)]
892
893We can get one of them by name.
894
895 >>> ssProxy.getScoreSystem('Custom 1')
896 <CustomScoreSystem 'Custom 1'>
897
898
899Score System Vocabularies
900-------------------------
901
902Score System vocabularies are used to provide a pulldown for fields
903requiring scoresystem input. Let's register the utilities.
904
905 >>> from zope.component import provideUtility
906 >>> from schooltool.requirement import scoresystem
907
908First, the discrete values score systems.
909
910 >>> zope.component.provideUtility(
911 ... scoresystem.PassFail, interfaces.IDiscreteValuesScoreSystem,
912 ... u'Pass/Fail')
913 >>> zope.component.provideUtility(
914 ... scoresystem.AmericanLetterScoreSystem, interfaces.IDiscreteValuesScoreSystem,
915 ... u'Letter Grade')
916 >>> zope.component.provideUtility(
917 ... scoresystem.ExtendedAmericanLetterScoreSystem, interfaces.IDiscreteValuesScoreSystem,
918 ... u'Extended Letter Grade')
919
920Secondly, the ranged values score systems.
921
922 >>> zope.component.provideUtility(
923 ... scoresystem.PercentScoreSystem, interfaces.IScoreSystem,
924 ... u'Percent')
925 >>> zope.component.provideUtility(
926 ... scoresystem.HundredPointsScoreSystem, interfaces.IScoreSystem,
927 ... u'100 Points')
928
929Finally, the vocabularies.
930
931 >>> from zope.schema.vocabulary import getVocabularyRegistry
932 >>> getVocabularyRegistry().register(
933 ... 'schooltool.requirement.scoresystems',
934 ... scoresystem.ScoreSystemsVocabulary)
935 >>> getVocabularyRegistry().register(
936 ... 'schooltool.requirement.discretescoresystems',
937 ... scoresystem.DiscreteScoreSystemsVocabulary)
938
939Now, when we access the vocabularies as the application views will, we can test
940that they deliver the desired list of utilities.
941
942First the discrete values score systems vocabulary, used when only discrete
943values score systems are valid, like when converting an average into a discrete
944grade. Note the presence of the custom score systems we just created.
945
946 >>> from zope.app.component.vocabulary import UtilityVocabulary
947 >>> vocab = UtilityVocabulary(None, interface=interfaces.IDiscreteValuesScoreSystem)
948 >>> for v in vocab: print v
949 <UtilityTerm Custom 1, instance of CustomScoreSystem>
950 <UtilityTerm Custom 2, instance of CustomScoreSystem>
951 <UtilityTerm Extended Letter Grade, instance of GlobalDiscreteValuesScoreSystem>
952 <UtilityTerm Letter Grade, instance of GlobalDiscreteValuesScoreSystem>
953 <UtilityTerm Pass/Fail, instance of GlobalDiscreteValuesScoreSystem>
954
955Secondly, we have the general score systems vocabulary, returning all score
956systems registered. This is used for report sheet activities.
957
958 >>> vocab = UtilityVocabulary(None, interface=interfaces.IScoreSystem)
959 >>> for v in vocab: print v
960 <UtilityTerm 100 Points, instance of GlobalRangedValuesScoreSystem>
961 <UtilityTerm Custom 1, instance of CustomScoreSystem>
962 <UtilityTerm Custom 2, instance of CustomScoreSystem>
963 <UtilityTerm Extended Letter Grade, instance of GlobalDiscreteValuesScoreSystem>
964 <UtilityTerm Letter Grade, instance of GlobalDiscreteValuesScoreSystem>
965 <UtilityTerm Pass/Fail, instance of GlobalDiscreteValuesScoreSystem>
966 <UtilityTerm Percent, instance of GlobalRangedValuesScoreSystem>
967
968
845Epilogue969Epilogue
846--------970--------
847971
848972
=== modified file 'src/schooltool/requirement/browser/README.txt'
--- src/schooltool/requirement/browser/README.txt 2008-04-17 17:22:08 +0000
+++ src/schooltool/requirement/browser/README.txt 2009-06-05 00:11:04 +0000
@@ -64,3 +64,69 @@
64 >>> browser.getControl('1').click()64 >>> browser.getControl('1').click()
65 >>> 'Be kind to your fellow students.' not in browser.contents65 >>> 'Be kind to your fellow students.' not in browser.contents
66 True66 True
67
68
69Score System Management
70-----------------------
71
72Score Systems can be created by the user to supplement the ones that are
73already delivered with schooltool. To do so, as manager, the user goes
74to the 'Manage' tab and clicks on the 'Score Systems' link.
75
76 >>> from schooltool.app.browser.ftests import setup
77 >>> manager = setup.logIn('manager', 'schooltool')
78 >>> manager.getLink('Manage').click()
79 >>> manager.getLink('Score Systems').click()
80
81We see the score systems that come with schooltool.
82
83 >>> analyze.printQuery("id('content-body')/form//a", manager.contents)
84 <a href="http://localhost/scoresystems/view.html?name=extended-letter-grade">Extended Letter Grade</a>
85 <a href="http://localhost/scoresystems/view.html?name=letter-grade">Letter Grade</a>
86 <a href="http://localhost/scoresystems/view.html?name=passfail">Pass/Fail</a>
87
88To add a new score system, the user clicks 'Add Score System'.
89
90 >>> manager.getLink('Add Score System').click()
91 >>> base_url = manager.url + '?form-submitted'
92 >>> cancel_url = base_url + '&CANCEL'
93 >>> update_url = base_url + '&UPDATE_SUBMIT'
94 >>> save_url = base_url + '&SAVE'
95
96We'll send the form values necessary to add a score system called 'Good/Bad'.
97
98 >>> url = save_url + '&title=Good/Bad&displayed1=G&value1=1&percent1=60'
99 >>> url = url + '&displayed2=B&value2=0&percent2=0'
100 >>> manager.open(url)
101
102Now we see the two score systems in the list.
103
104 >>> analyze.printQuery("id('content-body')/form//a", manager.contents)
105 <a href="http://localhost/scoresystems/view.html?name=extended-letter-grade">Extended Letter Grade</a>
106 <a href="http://localhost/scoresystems/view.html?name=goodbad">Good/Bad</a>
107 <a href="http://localhost/scoresystems/view.html?name=letter-grade">Letter Grade</a>
108 <a href="http://localhost/scoresystems/view.html?name=passfail">Pass/Fail</a>
109
110Let's hide the 'Pass/Fail' one.
111
112 >>> hide_url = manager.url + '?form-submitted&hide_passfail'
113 >>> manager.open(hide_url)
114
115Now we won't see it in the list.
116
117 >>> analyze.printQuery("id('content-body')/form//a", manager.contents)
118 <a href="http://localhost/scoresystems/view.html?name=extended-letter-grade">Extended Letter Grade</a>
119 <a href="http://localhost/scoresystems/view.html?name=goodbad">Good/Bad</a>
120 <a href="http://localhost/scoresystems/view.html?name=letter-grade">Letter Grade</a>
121
122Let's click on 'Good/Bad' and test it's view.
123
124 >>> manager.getLink('Good/Bad').click()
125 >>> analyze.printQuery("id('content-body')/table//span", manager.contents)
126 <span>G</span>
127 <span>1</span>
128 <span>60</span>
129 <span>B</span>
130 <span>0</span>
131 <span>0</span>
132
67133
=== modified file 'src/schooltool/requirement/browser/configure.zcml'
--- src/schooltool/requirement/browser/configure.zcml 2008-03-20 20:43:52 +0000
+++ src/schooltool/requirement/browser/configure.zcml 2009-05-08 18:19:21 +0000
@@ -3,8 +3,54 @@
3 xmlns:zope="http://namespaces.zope.org/zope"3 xmlns:zope="http://namespaces.zope.org/zope"
4 i18n_domain="schooltool">4 i18n_domain="schooltool">
55
6 <!-- Manage Tab -->
7 <configure package="schooltool.skin">
8 <navigationViewlet
9 name="scoresystems"
10 for="*"
11 manager="schooltool.app.browser.interfaces.IManageMenuViewletManager"
12 template="templates/navigationViewlet.pt"
13 class="schooltool.skin.skin.NavigationViewlet"
14 permission="schooltool.edit"
15 link="scoresystems"
16 title="Score Systems"
17 order="220"
18 />
19 </configure>
20
21 <!-- Menu items for IScoreSystemsProxy -->
22 <menuItem
23 menu="schooltool_actions"
24 title="Add Score System"
25 for="schooltool.requirement.interfaces.IScoreSystemsProxy"
26 action="add.html"
27 permission="schooltool.edit"
28 />
29
30 <!-- Score System Views -->
31 <page
32 name="index.html"
33 for="schooltool.requirement.interfaces.IScoreSystemsProxy"
34 class=".scoresystem.ScoreSystemsView"
35 template="scoresystems_overview.pt"
36 permission="schooltool.edit"
37 />
38 <page
39 name="add.html"
40 for="schooltool.requirement.interfaces.IScoreSystemsProxy"
41 class=".scoresystem.ScoreSystemAddView"
42 template="scoresystem_add.pt"
43 permission="schooltool.edit"
44 />
45 <page
46 name="view.html"
47 for="schooltool.requirement.interfaces.IScoreSystemsProxy"
48 class=".scoresystem.ScoreSystemViewView"
49 template="scoresystem_view.pt"
50 permission="schooltool.edit"
51 />
52
6 <!-- Score system widget registration -->53 <!-- Score system widget registration -->
7
8 <zope:view54 <zope:view
9 type="zope.publisher.interfaces.browser.IBrowserRequest"55 type="zope.publisher.interfaces.browser.IBrowserRequest"
10 for="schooltool.requirement.scoresystem.IScoreSystemField"56 for="schooltool.requirement.scoresystem.IScoreSystemField"
@@ -12,7 +58,6 @@
12 factory=".scoresystem.ScoreSystemWidget"58 factory=".scoresystem.ScoreSystemWidget"
13 permission="zope.Public"59 permission="zope.Public"
14 />60 />
15
16 <page61 <page
17 name="index.html"62 name="index.html"
18 for="schooltool.requirement.interfaces.IRequirement"63 for="schooltool.requirement.interfaces.IRequirement"
@@ -22,14 +67,12 @@
22 menu="zmi_views"67 menu="zmi_views"
23 title="View"68 title="View"
24 />69 />
25
26 <page70 <page
27 name="treenode"71 name="treenode"
28 for="schooltool.requirement.interfaces.IRequirement"72 for="schooltool.requirement.interfaces.IRequirement"
29 template="treenode.pt"73 template="treenode.pt"
30 permission="schooltool.view"74 permission="schooltool.view"
31 />75 />
32
33 <containerViews76 <containerViews
34 for="..interfaces.IRequirement"77 for="..interfaces.IRequirement"
35 contents="zope.ManageContent"78 contents="zope.ManageContent"
3679
=== modified file 'src/schooltool/requirement/browser/ftesting.zcml'
--- src/schooltool/requirement/browser/ftesting.zcml 2008-10-22 15:50:26 +0000
+++ src/schooltool/requirement/browser/ftesting.zcml 2009-05-08 18:19:21 +0000
@@ -9,6 +9,7 @@
9 <include package="schooltool.schoolyear" />9 <include package="schooltool.schoolyear" />
10 <include package="schooltool.timetable" />10 <include package="schooltool.timetable" />
11 <include package="schooltool.requirement" />11 <include package="schooltool.requirement" />
12 <include package="schooltool.gradebook" />
1213
13 <configure14 <configure
14 package="schooltool.skin"15 package="schooltool.skin"
1516
=== modified file 'src/schooltool/requirement/browser/scoresystem.py'
--- src/schooltool/requirement/browser/scoresystem.py 2008-04-16 18:57:28 +0000
+++ src/schooltool/requirement/browser/scoresystem.py 2009-05-14 08:34:27 +0000
@@ -21,14 +21,216 @@
21$Id$21$Id$
22"""22"""
23__docformat__ = 'reStructuredText'23__docformat__ = 'reStructuredText'
24
25from decimal import Decimal
26
24import zope.interface27import zope.interface
25import zope.schema28import zope.schema
26from zope.app import form29from zope.app import form
27from zope.app.pagetemplate import ViewPageTemplateFile30from zope.app.pagetemplate import ViewPageTemplateFile
31from zope.publisher.browser import BrowserView
32from zope.security.proxy import removeSecurityProxy
33from zope.traversing.browser.absoluteurl import absoluteURL
2834
29from schooltool.common import SchoolToolMessage as _35from schooltool.common import SchoolToolMessage as _
30from schooltool.requirement import interfaces, scoresystem36from schooltool.requirement import interfaces, scoresystem
3137
38
39MISSING_TITLE = _('The Title field must not be empty.')
40VALUE_NOT_NUMERIC = _('Value field must contain a valid number.')
41PERCENT_NOT_NUMERIC = _('Percent field must contain a valid number.')
42NO_NEGATIVE_VALUES = _('All values must be non-negative.')
43NO_NEGATIVE_PERCENTS = _('All percentages must be non-negative.')
44NO_PERCENTS_OVER_100 = _('Percentages cannot be greater than 100.')
45MUST_HAVE_AT_LEAST_2_SCORES = _('A score system must have at least two scores.')
46VALUES_MUST_DESCEND = _('Score values must go in descending order.')
47PERCENTS_MUST_DESCEND = _('Score percentages must go in descending order.')
48LAST_PERCENT_NOT_ZERO = _('The last percentage must be zero.')
49
50
51def escName(name):
52 """converts title-based scoresystem name to querystring format"""
53 chars = [c for c in name.lower() if c.isalnum() or c == ' ']
54 return u''.join(chars).replace(' ', '-')
55
56
57class ScoreSystemsView(BrowserView):
58 """A view for maintaining user-created scoresystem utilities"""
59
60 def update(self):
61 if 'form-submitted' in self.request:
62 for name, ss in self.context.getScoreSystems():
63 ss = removeSecurityProxy(ss)
64 if 'hide_' + escName(name) in self.request:
65 ss.hidden = True
66
67 def scoresystems(self):
68 url = absoluteURL(self.context, self.request) + '/view.html'
69 results = []
70 for name, ss in self.context.getScoreSystems():
71 ss = removeSecurityProxy(ss)
72 result = {
73 'title': ss.title,
74 'url': '%s?name=%s' % (url, escName(name)),
75 'hide_name': 'hide_' + escName(name),
76 }
77 results.append(result)
78 return results
79
80
81class ScoreSystemAddView(BrowserView):
82 """A view for adding a user-created scoresystem utility"""
83
84 def update(self):
85 self.message = ''
86
87 if 'form-submitted' in self.request:
88 if 'CANCEL' in self.request:
89 self.request.response.redirect(self.nextURL())
90
91 if not self.validateForm():
92 return
93 if 'SAVE' in self.request:
94 if not self.validateScores():
95 return
96 target = scoresystem.CustomScoreSystem()
97 self.updateScoreSystem(target)
98 self.context.addScoreSystem(target)
99 self.request.response.redirect(self.nextURL())
100
101 def nextURL(self):
102 return absoluteURL(self.context, self.request)
103
104 def scores(self):
105 rownum = 1
106 results = []
107 for displayed, value, percent in self.getRequestScores():
108 results.append(self.buildScoreRow(rownum, displayed, value,
109 percent))
110 rownum += 1
111 results.append(self.buildScoreRow(rownum, '', '', ''))
112 return results
113
114 def buildScoreRow(self, rownum, displayed, value, percent):
115 return {
116 'displayed_name': 'displayed' + unicode(rownum),
117 'displayed_value': displayed,
118 'value_name': 'value' + unicode(rownum),
119 'value_value': value,
120 'percent_name': 'percent' + unicode(rownum),
121 'percent_value': percent,
122 }
123
124 def getRequestScores(self):
125 rownum = 0
126 results = []
127 while True:
128 rownum += 1
129 displayed_name = 'displayed' + unicode(rownum)
130 value_name = 'value' + unicode(rownum)
131 percent_name = 'percent' + unicode(rownum)
132 if displayed_name not in self.request:
133 break
134 if not len(self.request[displayed_name]):
135 continue
136 result = (self.request[displayed_name],
137 self.request[value_name],
138 self.request[percent_name])
139 results.append(result)
140 return results
141
142 def validateForm(self):
143 title = self.request['title']
144 if not len(title):
145 return self.setMessage(MISSING_TITLE)
146
147 scores = []
148 for displayed, value, percent in self.getRequestScores():
149 try:
150 decimal_value = Decimal(value)
151 except:
152 return self.setMessage(VALUE_NOT_NUMERIC)
153 if decimal_value < 0:
154 return self.setMessage(NO_NEGATIVE_VALUES)
155 try:
156 decimal_percent = Decimal(percent)
157 except:
158 return self.setMessage(PERCENT_NOT_NUMERIC)
159 if decimal_percent < 0:
160 return self.setMessage(NO_NEGATIVE_PERCENTS)
161 if decimal_percent > 100:
162 return self.setMessage(NO_PERCENTS_OVER_100)
163 scores.append([displayed, decimal_value, decimal_percent])
164
165 self.validTitle = title
166 self.validScores = scores
167 return True
168
169 def validateScores(self):
170 if len(self.validScores) < 2:
171 return self.setMessage(MUST_HAVE_AT_LEAST_2_SCORES)
172
173 last_value, last_percent = None, None
174 for displayed, value, percent in self.validScores:
175 if last_value is not None:
176 if value >= last_value:
177 return self.setMessage(VALUES_MUST_DESCEND)
178 if last_percent is not None:
179 if percent >= last_percent:
180 return self.setMessage(PERCENTS_MUST_DESCEND)
181 last_value = value
182 last_percent = percent
183
184 if last_percent <> 0:
185 return self.setMessage(LAST_PERCENT_NOT_ZERO)
186
187 return True
188
189 def setMessage(self, message):
190 self.message = message
191 return False
192
193 def updateScoreSystem(self, target):
194 target.title = self.validTitle
195 target.scores = self.validScores
196 target._bestScore = target.scores[0][1]
197
198 @property
199 def title_value(self):
200 if 'form-submitted' in self.request:
201 return self.request['title']
202 else:
203 return ''
204
205
206class ScoreSystemViewView(BrowserView):
207 """A view for viewing a user-created scoresystem utility"""
208
209 def scores(self):
210 target = self.getScoreSystem()
211 return [self.buildScoreRow(displayed, value, percent)
212 for displayed, value, percent in target.scores]
213
214 def buildScoreRow(self, displayed, value, percent):
215 return {
216 'displayed_value': displayed,
217 'value_value': value,
218 'percent_value': percent,
219 }
220
221 @property
222 def title_value(self):
223 target = self.getScoreSystem()
224 return target.title
225
226 def getScoreSystem(self):
227 name = self.request['QUERY_STRING'].split('=')[1]
228 for n, ss in self.context.getScoreSystems():
229 if escName(n) == name:
230 return removeSecurityProxy(ss)
231 raise KeyError
232
233
32class IWidgetData(interfaces.IRangedValuesScoreSystem):234class IWidgetData(interfaces.IRangedValuesScoreSystem):
33 """A schema used to generate the score system widget."""235 """A schema used to generate the score system widget."""
34236
35237
=== added file 'src/schooltool/requirement/browser/scoresystem_add.pt'
--- src/schooltool/requirement/browser/scoresystem_add.pt 1970-01-01 00:00:00 +0000
+++ src/schooltool/requirement/browser/scoresystem_add.pt 2009-05-08 18:19:21 +0000
@@ -0,0 +1,65 @@
1<tal:define define="dummy view/update"/>
2<html metal:use-macro="view/@@standard_macros/page" i18n:domain="schooltool">
3<head>
4 <title metal:fill-slot="title" i18n:translate="">Add Score System</title>
5</head>
6<body>
7
8<h1 metal:fill-slot="content-header"
9 i18n:translate="">Add Score System</h1>
10
11<metal:block metal:fill-slot="body">
12
13 <div class="message" style="color:red; padding:1em"
14 tal:condition="view/message"
15 tal:content="view/message">
16 Message
17 </div>
18
19
20 <form method="post"
21 tal:attributes="action string:${context/@@absolute_url}/add.html">
22 <input type="hidden" name="form-submitted" value="" />
23
24 <label for="title" class="bold padded" i18n:translate="">Title</label>
25 <input type="text" name="title" id="title"
26 tal:attributes="value view/title_value" />
27 <span>&nbsp&nbsp</span>
28 <input type="submit" class="button-ok" name="SAVE" value="Submit" />
29 <div style="height: 31px;"></div>
30
31 <table class="schooltool_gradebook">
32 <tr>
33 <th class="cell header fully_padded">Score Displayed</th>
34 <th class="cell header fully_padded">Score Value</th>
35 <th class="cell header fully_padded">Low Persntage</th>
36 </tr>
37
38 <tal:block repeat="score view/scores">
39 <tr class="bordered">
40 <td class="cell fully_padded">
41 <input type="text"
42 tal:attributes="name score/displayed_name; value score/displayed_value" />
43 </td>
44 <td class="cell fully_padded">
45 <input type="text"
46 tal:attributes="name score/value_name; value score/value_value" />
47 </td>
48 <td class="cell fully_padded">
49 <input type="text"
50 tal:attributes="name score/percent_name; value score/percent_value" />
51 </td>
52 </tr>
53 </tal:block>
54
55 </table>
56 <div style="height: 11px;"></div>
57
58 <div class="controls">
59 <input type="submit" class="button-ok" name="UPDATE_SUBMIT" value="Update" />
60 <input type="submit" class="button-cancel" name="CANCEL" value="Cancel" />
61 </div>
62 </form>
63</metal:block>
64</body>
65</html>
066
=== added file 'src/schooltool/requirement/browser/scoresystem_view.pt'
--- src/schooltool/requirement/browser/scoresystem_view.pt 1970-01-01 00:00:00 +0000
+++ src/schooltool/requirement/browser/scoresystem_view.pt 2009-06-05 00:11:04 +0000
@@ -0,0 +1,41 @@
1<html metal:use-macro="view/@@standard_macros/page" i18n:domain="schooltool">
2<head>
3 <title metal:fill-slot="title" i18n:translate="">View Score System</title>
4</head>
5<body>
6
7<h1 metal:fill-slot="content-header"
8 i18n:translate="">View Score System</h1>
9
10<metal:block metal:fill-slot="body">
11
12 <b>Title:</b>
13 <span tal:content="view/title_value" />
14 <div style="height: 31px;"></div>
15
16 <table class="schooltool_gradebook">
17 <tr>
18 <th class="cell header fully_padded">Score Displayed</th>
19 <th class="cell header fully_padded">Score Value</th>
20 <th class="cell header fully_padded">Low Percentage</th>
21 </tr>
22
23 <tal:block repeat="score view/scores">
24 <tr class="bordered">
25 <td class="cell fully_padded">
26 <span tal:content="score/displayed_value" />
27 </td>
28 <td class="cell fully_padded">
29 <span tal:content="score/value_value" />
30 </td>
31 <td class="cell fully_padded">
32 <span tal:content="score/percent_value" />
33 </td>
34 </tr>
35 </tal:block>
36
37 </table>
38
39</metal:block>
40</body>
41</html>
042
=== added file 'src/schooltool/requirement/browser/scoresystems_overview.pt'
--- src/schooltool/requirement/browser/scoresystems_overview.pt 1970-01-01 00:00:00 +0000
+++ src/schooltool/requirement/browser/scoresystems_overview.pt 2009-05-08 18:19:21 +0000
@@ -0,0 +1,44 @@
1<tal:define define="dummy view/update"/>
2<html metal:use-macro="view/@@standard_macros/page" i18n:domain="schooltool">
3<head>
4 <title metal:fill-slot="title" i18n:translate="">Score Systems</title>
5</head>
6<body>
7
8<h1 metal:fill-slot="content-header"
9 i18n:translate="">Score Systems</h1>
10
11<metal:block metal:fill-slot="body"
12 tal:define="scoresystems view/scoresystems">
13
14 <form method="post"
15 tal:attributes="action string:${context/@@absolute_url}/index.html">
16 <input type="hidden" name="form-submitted" value="" />
17
18 <table class="schooltool_gradebook">
19 <tr>
20 <th class="cell header fully_padded">Hide?</th>
21 <th class="cell header fully_padded">Score System</th>
22 </tr>
23
24 <tr class="bordered"
25 tal:repeat="scoresystem scoresystems">
26 <td class="cell fully_padded">
27 <input type="checkbox"
28 tal:attributes="name scoresystem/hide_name;" />
29 </td>
30 <td class="cell fully_padded">
31 <a tal:content="scoresystem/title"
32 tal:attributes="href scoresystem/url">Score System</a>
33 </td>
34 </tr>
35 </table>
36
37 <div class="controls">
38 <input type="submit" class="button-ok" name="UPDATE_SUBMIT" value="Update" />
39 </div>
40
41 </form>
42</metal:block>
43</body>
44</html>
045
=== modified file 'src/schooltool/requirement/browser/tests.py'
--- src/schooltool/requirement/browser/tests.py 2008-03-19 04:22:15 +0000
+++ src/schooltool/requirement/browser/tests.py 2009-05-08 18:19:21 +0000
@@ -243,12 +243,14 @@
243 return unittest.TestSuite((243 return unittest.TestSuite((
244 doctest.DocTestSuite(setUp=setUp, tearDown=tearDown,244 doctest.DocTestSuite(setUp=setUp, tearDown=tearDown,
245 optionflags=doctest.ELLIPSIS|245 optionflags=doctest.ELLIPSIS|
246 doctest.REPORT_NDIFF),246 doctest.REPORT_NDIFF|
247 doctest.REPORT_ONLY_FIRST_FAILURE),
247 doctest.DocFileSuite('scoresystem.txt',248 doctest.DocFileSuite('scoresystem.txt',
248 setUp=setUp, tearDown=tearDown,249 setUp=setUp, tearDown=tearDown,
249 globs={'pprint': doctestunit.pprint},250 globs={'pprint': doctestunit.pprint},
250 optionflags=doctest.NORMALIZE_WHITESPACE|251 optionflags=doctest.NORMALIZE_WHITESPACE|
251 doctest.ELLIPSIS),252 doctest.ELLIPSIS|
253 doctest.REPORT_ONLY_FIRST_FAILURE),
252 ))254 ))
253255
254256
255257
=== modified file 'src/schooltool/requirement/configure.zcml'
--- src/schooltool/requirement/configure.zcml 2008-09-14 22:04:05 +0000
+++ src/schooltool/requirement/configure.zcml 2009-06-05 00:11:04 +0000
@@ -10,29 +10,26 @@
10 factory=".requirement.getRequirement"10 factory=".requirement.getRequirement"
11 trusted="true"11 trusted="true"
12 />12 />
13
14 <adapter13 <adapter
15 for=".interfaces.IHaveEvaluations"14 for=".interfaces.IHaveEvaluations"
16 provides=".interfaces.IEvaluations"15 provides=".interfaces.IEvaluations"
17 factory=".evaluation.getEvaluations"16 factory=".evaluation.getEvaluations"
18 trusted="true"17 trusted="true"
19 />18 />
20
21 <view19 <view
22 name="requirement" type="*"20 name="requirement" type="*"
23 provides="zope.traversing.interfaces.ITraversable"21 provides="zope.traversing.interfaces.ITraversable"
24 for="schooltool.requirement.interfaces.IHaveRequirement"22 for=".interfaces.IHaveRequirement"
25 factory=".requirement.requirementNamespace"23 factory=".requirement.requirementNamespace"
26 />24 />
27 <adapter25 <adapter
28 name="requirement"26 name="requirement"
29 provides="zope.traversing.interfaces.ITraversable"27 provides="zope.traversing.interfaces.ITraversable"
30 for="schooltool.requirement.interfaces.IHaveRequirement"28 for=".interfaces.IHaveRequirement"
31 factory=".requirement.requirementNamespace"29 factory=".requirement.requirementNamespace"
32 />30 />
3331
34 <!-- Requirement Content -->32 <!-- Requirement Content -->
35
36 <class class=".requirement.Requirement">33 <class class=".requirement.Requirement">
37 <allow interface="zope.app.container.interfaces.ISimpleReadContainer" />34 <allow interface="zope.app.container.interfaces.ISimpleReadContainer" />
38 <require35 <require
@@ -50,7 +47,6 @@
50 </class>47 </class>
5148
52 <!-- Scoresystem Content -->49 <!-- Scoresystem Content -->
53
54 <class class=".scoresystem.DiscreteValuesScoreSystem">50 <class class=".scoresystem.DiscreteValuesScoreSystem">
55 <require51 <require
56 permission="zope.View"52 permission="zope.View"
@@ -61,7 +57,6 @@
61 set_schema=".interfaces.IScoreSystem"57 set_schema=".interfaces.IScoreSystem"
62 />58 />
63 </class>59 </class>
64
65 <class class=".scoresystem.GlobalDiscreteValuesScoreSystem">60 <class class=".scoresystem.GlobalDiscreteValuesScoreSystem">
66 <require61 <require
67 permission="zope.View"62 permission="zope.View"
@@ -72,7 +67,6 @@
72 set_schema=".interfaces.IScoreSystem"67 set_schema=".interfaces.IScoreSystem"
73 />68 />
74 </class>69 </class>
75
76 <class class=".scoresystem.RangedValuesScoreSystem">70 <class class=".scoresystem.RangedValuesScoreSystem">
77 <require71 <require
78 permission="zope.View"72 permission="zope.View"
@@ -83,7 +77,6 @@
83 set_schema=".interfaces.IScoreSystem"77 set_schema=".interfaces.IScoreSystem"
84 />78 />
85 </class>79 </class>
86
87 <class class=".scoresystem.GlobalRangedValuesScoreSystem">80 <class class=".scoresystem.GlobalRangedValuesScoreSystem">
88 <require81 <require
89 permission="zope.View"82 permission="zope.View"
@@ -94,55 +87,46 @@
94 set_schema=".interfaces.IScoreSystem"87 set_schema=".interfaces.IScoreSystem"
95 />88 />
96 </class>89 </class>
90 <class class=".scoresystem.CustomScoreSystem">
91 <require
92 permission="zope.View"
93 interface=".interfaces.ICustomScoreSystem"
94 />
95 <require
96 permission="schooltool.edit"
97 set_schema=".interfaces.ICustomScoreSystem"
98 />
99 </class>
97100
98 <!-- Score System registrations -->101 <!-- Score System registrations -->
99
100 <utility102 <utility
101 provides="zope.schema.interfaces.IVocabularyFactory"103 provides="zope.schema.interfaces.IVocabularyFactory"
102 component="schooltool.requirement.scoresystem.ScoreSystemsVocabulary"104 component=".scoresystem.ScoreSystemsVocabulary"
103 name="schooltool.requirement.scoresystems"105 name="schooltool.requirement.scoresystems"
104 />106 />
105107 <utility
106 <utility108 provides="zope.schema.interfaces.IVocabularyFactory"
107 provides="schooltool.requirement.interfaces.IScoreSystem"109 component=".scoresystem.DiscreteScoreSystemsVocabulary"
108 component="schooltool.requirement.scoresystem.PassFail"110 name="schooltool.requirement.discretescoresystems"
109 name="Pass/Fail"111 />
110 />112 <utility
111113 provides="schooltool.requirement.interfaces.IScoreSystem"
112 <utility114 component=".scoresystem.PercentScoreSystem"
113 provides="schooltool.requirement.interfaces.IScoreSystem"
114 component="schooltool.requirement.scoresystem.AmericanLetterScoreSystem"
115 name="Letter Grade"
116 />
117
118 <utility
119 provides="schooltool.requirement.interfaces.IScoreSystem"
120 component="
121 schooltool.requirement.scoresystem.ExtendedAmericanLetterScoreSystem"
122 name="Extended Letter Grade"
123 />
124
125 <utility
126 provides="schooltool.requirement.interfaces.IScoreSystem"
127 component="schooltool.requirement.scoresystem.PercentScoreSystem"
128 name="Percent"115 name="Percent"
129 />116 />
130
131 <utility117 <utility
132 provides="schooltool.requirement.interfaces.IScoreSystem"118 provides="schooltool.requirement.interfaces.IScoreSystem"
133 component="schooltool.requirement.scoresystem.HundredPointsScoreSystem"119 component=".scoresystem.HundredPointsScoreSystem"
134 name="100 Points"120 name="100 Points"
135 />121 />
136122
137 <!-- Evaluations Content -->123 <!-- Evaluations Content -->
138
139 <class class=".evaluation.Evaluations">124 <class class=".evaluation.Evaluations">
140 <require125 <require
141 permission="schooltool.edit"126 permission="schooltool.edit"
142 interface=".interfaces.IEvaluations"127 interface=".interfaces.IEvaluations"
143 />128 />
144 </class>129 </class>
145
146 <class class=".evaluation.Evaluation">130 <class class=".evaluation.Evaluation">
147 <require131 <require
148 permission="schooltool.view"132 permission="schooltool.view"
@@ -155,24 +139,42 @@
155 </class>139 </class>
156140
157 <!-- These declarations should go somewhere else eventually -->141 <!-- These declarations should go somewhere else eventually -->
158
159 <class class="schooltool.app.app.SchoolToolApplication">142 <class class="schooltool.app.app.SchoolToolApplication">
160 <implements143 <implements
161 interface="schooltool.requirement.interfaces.IHaveRequirement" />144 interface=".interfaces.IHaveRequirement" />
162 </class>145 </class>
163
164 <class class="schooltool.course.course.Course">146 <class class="schooltool.course.course.Course">
165 <implements interface=".interfaces.IHaveRequirement" />147 <implements interface=".interfaces.IHaveRequirement" />
166 </class>148 </class>
167
168 <class class="schooltool.course.section.Section">149 <class class="schooltool.course.section.Section">
169 <implements interface=".interfaces.IHaveRequirement" />150 <implements interface=".interfaces.IHaveRequirement" />
170 </class>151 </class>
171
172 <class class="schooltool.person.person.Person">152 <class class="schooltool.person.person.Person">
173 <implements interface=".interfaces.IHaveEvaluations" />153 <implements interface=".interfaces.IHaveEvaluations" />
174 </class>154 </class>
175155
156 <!-- ScoreSystemsProxy adapter -->
157 <class class=".scoresystem.ScoreSystemsProxy">
158 <require
159 permission="schooltool.edit"
160 interface=".interfaces.IScoreSystemsProxy" />
161 </class>
162 <adapter
163 for="schooltool.app.app.SchoolToolApplication"
164 provides=".interfaces.IScoreSystemsProxy"
165 factory=".scoresystem.ScoreSystemsProxy"
166 trusted="true"
167 />
168
169 <!-- Pluggable traverser plugins for HTTP paths -->
170 <adapterTraverserPlugin
171 for="schooltool.app.interfaces.ISchoolToolApplication"
172 layer="zope.publisher.interfaces.http.IHTTPRequest"
173 name="scoresystems"
174 adapter=".interfaces.IScoreSystemsProxy"
175 permission="schooltool.edit"
176 />
177
176 <include package=".browser" />178 <include package=".browser" />
177179
178</configure>180</configure>
179181
=== modified file 'src/schooltool/requirement/interfaces.py'
--- src/schooltool/requirement/interfaces.py 2009-04-29 00:26:02 +0000
+++ src/schooltool/requirement/interfaces.py 2009-05-08 18:19:21 +0000
@@ -116,13 +116,22 @@
116class IDiscreteValuesScoreSystem(IValuesScoreSystem):116class IDiscreteValuesScoreSystem(IValuesScoreSystem):
117 """A score system that consists of discrete values."""117 """A score system that consists of discrete values."""
118118
119 hidden = zope.schema.Bool(
120 title=u"Hidden Score System",
121 required=False
122 )
123
119 scores = zope.schema.List(124 scores = zope.schema.List(
120 title=u'Scores',125 title=u'Scores',
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).',
122 value_type=zope.schema.Tuple(),127 value_type=zope.schema.Tuple(),
123 required=True)128 required=True)
124129
125130
131class ICustomScoreSystem(IDiscreteValuesScoreSystem):
132 """A user-created score system that consists of discrete values."""
133
134
126class IRangedValuesScoreSystem(IValuesScoreSystem):135class IRangedValuesScoreSystem(IValuesScoreSystem):
127 """A score system that allows for a randge of values."""136 """A score system that allows for a randge of values."""
128137
@@ -232,3 +241,16 @@
232 name that the original ``IEvaluations`` object had.241 name that the original ``IEvaluations`` object had.
233 """242 """
234243
244
245class IScoreSystemsProxy(zope.interface.Interface):
246 """The Proxy class for adding/editing score systems"""
247
248 def getScoreSystems():
249 """Return list of tuples (name, scoresystem)"""
250
251 def addScoreSystem(scoresystem):
252 """Add scoresystem to app utilitiles"""
253
254 def getScoreSystem(name):
255 """Get scoresystem from app utilitiles by the given name"""
256
235257
=== modified file 'src/schooltool/requirement/scoresystem.py'
--- src/schooltool/requirement/scoresystem.py 2008-09-25 03:01:46 +0000
+++ src/schooltool/requirement/scoresystem.py 2009-05-14 08:34:27 +0000
@@ -22,20 +22,34 @@
22"""22"""
23__docformat__ = 'restructuredtext'23__docformat__ = 'restructuredtext'
2424
25from decimal import Decimal
26
27from persistent import Persistent
28
29from zope.app.component.vocabulary import UtilityVocabulary
30from zope.component import adapts, queryMultiAdapter
31from zope.interface import implements
25import zope.interface32import zope.interface
26import zope.schema33import zope.schema
27import zope.security.checker34import zope.security.checker
28from zope.app.component.vocabulary import UtilityVocabulary35from zope.security.proxy import removeSecurityProxy
2936
30from decimal import Decimal37from schooltool.app.interfaces import ISchoolToolApplication
31
32from schooltool.requirement import interfaces38from schooltool.requirement import interfaces
39from schooltool.requirement.interfaces import IDiscreteValuesScoreSystem
40from schooltool.requirement.interfaces import IScoreSystemsProxy
3341
3442
35def ScoreSystemsVocabulary(context):43def ScoreSystemsVocabulary(context):
36 return UtilityVocabulary(context,44 return UtilityVocabulary(context,
37 interface=interfaces.IScoreSystem)45 interface=interfaces.IScoreSystem)
3846
47
48def DiscreteScoreSystemsVocabulary(context):
49 return UtilityVocabulary(context,
50 interface=interfaces.IDiscreteValuesScoreSystem)
51
52
39class UNSCORED(object):53class UNSCORED(object):
40 """This object behaves like a string.54 """This object behaves like a string.
4155
@@ -49,9 +63,11 @@
49 def __repr__(self):63 def __repr__(self):
50 return 'UNSCORED'64 return 'UNSCORED'
5165
66
52zope.security.checker.BasicTypes[UNSCORED] = zope.security.checker.NoProxy67zope.security.checker.BasicTypes[UNSCORED] = zope.security.checker.NoProxy
53UNSCORED = UNSCORED()68UNSCORED = UNSCORED()
5469
70
55class AbstractScoreSystem(object):71class AbstractScoreSystem(object):
56 zope.interface.implements(interfaces.IScoreSystem)72 zope.interface.implements(interfaces.IScoreSystem)
5773
@@ -88,6 +104,7 @@
88 def __reduce__(self):104 def __reduce__(self):
89 return 'CommentScoreSystem'105 return 'CommentScoreSystem'
90106
107
91# Singelton108# Singelton
92CommentScoreSystem = CommentScoreSystem(109CommentScoreSystem = CommentScoreSystem(
93 u'Comments', u'Scores are commentary text.')110 u'Comments', u'Scores are commentary text.')
@@ -126,6 +143,7 @@
126 scores = None143 scores = None
127 _minPassingScore = None144 _minPassingScore = None
128 _bestScore = None145 _bestScore = None
146 hidden = False
129147
130 def __init__(self, title=None, description=None,148 def __init__(self, title=None, description=None,
131 scores=None, bestScore=None, minPassingScore=None):149 scores=None, bestScore=None, minPassingScore=None):
@@ -141,12 +159,12 @@
141 return None159 return None
142 if self._minPassingScore is None:160 if self._minPassingScore is None:
143 return None161 return None
144 scores = dict(self.scores)162 scores = self.scoresDict()
145 return scores[score] >= scores[self._minPassingScore]163 return scores[score] >= scores[self._minPassingScore]
146164
147 def isValidScore(self, score):165 def isValidScore(self, score):
148 """See interfaces.IScoreSystem"""166 """See interfaces.IScoreSystem"""
149 scores = dict(self.scores).keys()167 scores = self.scoresDict().keys()
150 return score in scores + [UNSCORED]168 return score in scores + [UNSCORED]
151169
152 def getBestScore(self):170 def getBestScore(self):
@@ -167,7 +185,7 @@
167 """See interfaces.IScoreSystem"""185 """See interfaces.IScoreSystem"""
168 if score is UNSCORED:186 if score is UNSCORED:
169 return None187 return None
170 scores = dict(self.scores)188 scores = self.scoresDict()
171 return scores[score]189 return scores[score]
172190
173 def getFractionalValue(self, score):191 def getFractionalValue(self, score):
@@ -179,6 +197,10 @@
179 value = self.getNumericalValue(score) - minimum197 value = self.getNumericalValue(score) - minimum
180 return value / (maximum - minimum)198 return value / (maximum - minimum)
181199
200 def scoresDict(self):
201 scores = [(score, value) for score, value, percent in self.scores]
202 return dict(scores)
203
182class GlobalDiscreteValuesScoreSystem(DiscreteValuesScoreSystem):204class GlobalDiscreteValuesScoreSystem(DiscreteValuesScoreSystem):
183205
184 def __init__(self, name, *args, **kwargs):206 def __init__(self, name, *args, **kwargs):
@@ -188,25 +210,41 @@
188 def __reduce__(self):210 def __reduce__(self):
189 return self.__name__211 return self.__name__
190212
213
191PassFail = GlobalDiscreteValuesScoreSystem(214PassFail = GlobalDiscreteValuesScoreSystem(
192 'PassFail',215 'PassFail',
193 u'Pass/Fail', u'Pass or Fail score system.',216 u'Pass/Fail', u'Pass or Fail score system.',
194 [(u'Pass', Decimal(1)), (u'Fail', Decimal(0))], u'Pass', u'Pass')217 [(u'Pass', Decimal(1), Decimal(60)),
218 (u'Fail', Decimal(0), Decimal(0))],
219 u'Pass', u'Pass')
195220
196AmericanLetterScoreSystem = GlobalDiscreteValuesScoreSystem(221AmericanLetterScoreSystem = GlobalDiscreteValuesScoreSystem(
197 'AmericanLetterScoreSystem',222 'AmericanLetterScoreSystem',
198 u'Letter Grade', u'American Letter Grade',223 u'Letter Grade', u'American Letter Grade',
199 [('A', Decimal(4)), ('B', Decimal(3)), ('C', Decimal(2)),224 [('A', Decimal(4), Decimal(90)),
200 ('D', Decimal(1)), ('F', Decimal(0))], 'A', 'D')225 ('B', Decimal(3), Decimal(80)),
226 ('C', Decimal(2), Decimal(70)),
227 ('D', Decimal(1), Decimal(60)),
228 ('F', Decimal(0), Decimal(0))],
229 'A', 'D')
201230
202ExtendedAmericanLetterScoreSystem = GlobalDiscreteValuesScoreSystem(231ExtendedAmericanLetterScoreSystem = GlobalDiscreteValuesScoreSystem(
203 'ExtendedAmericanLetterScoreSystem',232 'ExtendedAmericanLetterScoreSystem',
204 u'Extended Letter Grade', u'American Extended Letter Grade',233 u'Extended Letter Grade', u'American Extended Letter Grade',
205 [('A+', Decimal('4.0')), ('A', Decimal('4.0')), ('A-', Decimal('3.7')),234 [('A+', Decimal('4.0'), Decimal(98)),
206 ('B+', Decimal('3.3')), ('B', Decimal('3.0')), ('B-', Decimal('2.7')),235 ('A', Decimal('4.0'), Decimal(93)),
207 ('C+', Decimal('2.3')), ('C', Decimal('2.0')), ('C-', Decimal('1.7')),236 ('A-', Decimal('3.7'), Decimal(90)),
208 ('D+', Decimal('1.3')), ('D', Decimal('1.0')), ('D-', Decimal('0.7')),237 ('B+', Decimal('3.3'), Decimal(88)),
209 ('F', Decimal('0.0'))], 'A+', 'D-')238 ('B', Decimal('3.0'), Decimal(83)),
239 ('B-', Decimal('2.7'), Decimal(80)),
240 ('C+', Decimal('2.3'), Decimal(78)),
241 ('C', Decimal('2.0'), Decimal(73)),
242 ('C-', Decimal('1.7'), Decimal(70)),
243 ('D+', Decimal('1.3'), Decimal(68)),
244 ('D', Decimal('1.0'), Decimal(63)),
245 ('D-', Decimal('0.7'), Decimal(60)),
246 ('F', Decimal('0.0'), Decimal(0))],
247 'A+', 'D-')
210248
211249
212class RangedValuesScoreSystem(AbstractValuesScoreSystem):250class RangedValuesScoreSystem(AbstractValuesScoreSystem):
@@ -271,6 +309,7 @@
271 value = self.getNumericalValue(score) - self.min309 value = self.getNumericalValue(score) - self.min
272 return value / (self.max - self.min)310 return value / (self.max - self.min)
273311
312
274class GlobalRangedValuesScoreSystem(RangedValuesScoreSystem):313class GlobalRangedValuesScoreSystem(RangedValuesScoreSystem):
275314
276 def __init__(self, name, *args, **kwargs):315 def __init__(self, name, *args, **kwargs):
@@ -294,9 +333,61 @@
294class ICustomScoreSystem(zope.interface.Interface):333class ICustomScoreSystem(zope.interface.Interface):
295 """Marker interface for score systems created in the widget."""334 """Marker interface for score systems created in the widget."""
296335
336
297class IScoreSystemField(zope.schema.interfaces.IField):337class IScoreSystemField(zope.schema.interfaces.IField):
298 """A field that represents score system."""338 """A field that represents score system."""
299339
340
300class ScoreSystemField(zope.schema.Field):341class ScoreSystemField(zope.schema.Field):
301 """Score System Field."""342 """Score System Field."""
302 zope.interface.implements(IScoreSystemField)343 zope.interface.implements(IScoreSystemField)
344
345
346class CustomScoreSystem(DiscreteValuesScoreSystem, Persistent):
347 """ScoreSystem class for custom (user-created) score systems"""
348
349 implements(interfaces.ICustomScoreSystem)
350
351
352class ScoreSystemsProxy(object):
353 """The Proxy class for adding/editing score systems"""
354
355 implements(IScoreSystemsProxy)
356 adapts(ISchoolToolApplication)
357
358 def __init__(self, app):
359 self.app = app
360 self.siteManager = app.getSiteManager()
361 self.__parent__ = app
362 self.__name__ = 'scoresystems'
363
364 def getScoreSystems(self):
365 """Return list of tuples (name, scoresystem)"""
366 results = []
367 for name, util in sorted(self.siteManager.getUtilitiesFor(
368 interfaces.ICustomScoreSystem)):
369 util = removeSecurityProxy(util)
370 if util.hidden:
371 continue
372 results.append((name, util))
373 return results
374
375 def addScoreSystem(self, scoresystem):
376 """Add scoresystem to app utilitiles"""
377 names = [name for name, util in self.getScoreSystems()]
378 name = scoresystem.title
379 n = name
380 i = 1
381 while n in names:
382 n = name + u'-' + unicode(i)
383 i += 1
384 self.siteManager.registerUtility(scoresystem,
385 interfaces.ICustomScoreSystem, name=n)
386
387 def getScoreSystem(self, name):
388 """Get scoresystem from app utilitiles by the given name"""
389 for n, util in self.getScoreSystems():
390 if n == name:
391 return removeSecurityProxy(util)
392 raise KeyError
393
303394
=== modified file 'src/schooltool/requirement/tests.py'
--- src/schooltool/requirement/tests.py 2006-01-15 23:50:25 +0000
+++ src/schooltool/requirement/tests.py 2009-05-08 18:19:21 +0000
@@ -47,12 +47,14 @@
47 setUp=setUp, tearDown=tearDown,47 setUp=setUp, tearDown=tearDown,
48 globs={'pprint': doctestunit.pprint},48 globs={'pprint': doctestunit.pprint},
49 optionflags=doctest.NORMALIZE_WHITESPACE|49 optionflags=doctest.NORMALIZE_WHITESPACE|
50 doctest.ELLIPSIS),50 doctest.ELLIPSIS|
51 doctest.REPORT_ONLY_FIRST_FAILURE),
51 doctest.DocFileSuite('grades.txt',52 doctest.DocFileSuite('grades.txt',
52 setUp=setUp, tearDown=tearDown,53 setUp=setUp, tearDown=tearDown,
53 globs={'pprint': doctestunit.pprint},54 globs={'pprint': doctestunit.pprint},
54 optionflags=doctest.NORMALIZE_WHITESPACE|55 optionflags=doctest.NORMALIZE_WHITESPACE|
55 doctest.ELLIPSIS),56 doctest.ELLIPSIS|
57 doctest.REPORT_ONLY_FIRST_FAILURE),
56 ))58 ))
5759
58if __name__ == '__main__':60if __name__ == '__main__':

Subscribers

People subscribed via source and target branches