Merge lp:~aelkner/schooltool/schooltool.gradebook_september_fixes into lp:~schooltool-owners/schooltool/schooltool.gradebook

Proposed by Alan Elkner
Status: Merged
Merge reported by: Gediminas Paulauskas
Merged at revision: not available
Proposed branch: lp:~aelkner/schooltool/schooltool.gradebook_september_fixes
Merge into: lp:~schooltool-owners/schooltool/schooltool.gradebook
Diff against target: 4609 lines
51 files modified
src/schooltool/gradebook/README.txt (+1/-1)
src/schooltool/gradebook/__init__.py (+28/-0)
src/schooltool/gradebook/activity.py (+50/-0)
src/schooltool/gradebook/browser/README.txt (+190/-18)
src/schooltool/gradebook/browser/absences_by_day_rml.pt (+100/-0)
src/schooltool/gradebook/browser/activity.py (+180/-8)
src/schooltool/gradebook/browser/add_edit_linked_column.pt (+68/-0)
src/schooltool/gradebook/browser/categories.pt (+7/-0)
src/schooltool/gradebook/browser/configure.zcml (+148/-14)
src/schooltool/gradebook/browser/deploy_report_worksheet.pt (+2/-1)
src/schooltool/gradebook/browser/failing_report_rml.pt (+116/-0)
src/schooltool/gradebook/browser/gradebook.css (+4/-0)
src/schooltool/gradebook/browser/gradebook.py (+172/-73)
src/schooltool/gradebook/browser/gradebook_column_preferences.pt (+30/-9)
src/schooltool/gradebook/browser/gradebook_grade_activity.pt (+2/-1)
src/schooltool/gradebook/browser/gradebook_overview.js.pt (+1/-1)
src/schooltool/gradebook/browser/gradebook_overview.pt (+15/-11)
src/schooltool/gradebook/browser/gradebook_startup.pt (+3/-3)
src/schooltool/gradebook/browser/layout_report_card.pt (+15/-12)
src/schooltool/gradebook/browser/mygrades.pt (+3/-3)
src/schooltool/gradebook/browser/no_current_term.pt (+1/-1)
src/schooltool/gradebook/browser/no_current_term.txt (+2/-1)
src/schooltool/gradebook/browser/pdf_views.py (+400/-30)
src/schooltool/gradebook/browser/report_card.py (+117/-17)
src/schooltool/gradebook/browser/report_card.txt (+74/-23)
src/schooltool/gradebook/browser/report_card_rml.pt (+4/-0)
src/schooltool/gradebook/browser/request_absences_by_day.pt (+42/-0)
src/schooltool/gradebook/browser/request_failing_report.pt (+83/-0)
src/schooltool/gradebook/browser/request_reports.pt (+17/-0)
src/schooltool/gradebook/browser/request_reports.py (+249/-0)
src/schooltool/gradebook/browser/section_absences_rml.pt (+98/-0)
src/schooltool/gradebook/browser/student_detail_rml.pt (+127/-0)
src/schooltool/gradebook/browser/student_gradebook.pt (+2/-2)
src/schooltool/gradebook/browser/weight_categories.pt (+22/-11)
src/schooltool/gradebook/browser/worksheet.py (+14/-1)
src/schooltool/gradebook/browser/worksheet_delete.pt (+6/-5)
src/schooltool/gradebook/browser/worksheet_overview.pt (+11/-3)
src/schooltool/gradebook/configure.zcml (+14/-0)
src/schooltool/gradebook/generations/__init__.py (+2/-2)
src/schooltool/gradebook/generations/evolve1.py (+2/-0)
src/schooltool/gradebook/generations/evolve2.py (+44/-0)
src/schooltool/gradebook/generations/tests/test_evolve2.py (+88/-0)
src/schooltool/gradebook/gradebook.py (+38/-26)
src/schooltool/gradebook/gradebook_init.py (+2/-0)
src/schooltool/gradebook/interfaces.py (+13/-3)
src/schooltool/requirement/README.txt (+17/-13)
src/schooltool/requirement/browser/README.txt (+5/-3)
src/schooltool/requirement/browser/scoresystem.py (+30/-13)
src/schooltool/requirement/browser/scoresystem_add.pt (+12/-4)
src/schooltool/requirement/browser/scoresystem_view.pt (+4/-0)
src/schooltool/requirement/scoresystem.py (+23/-23)
To merge this branch: bzr merge lp:~aelkner/schooltool/schooltool.gradebook_september_fixes
Reviewer Review Type Date Requested Status
Gediminas Paulauskas (community) Approve
Review via email: mp+11104@code.launchpad.net
To post a comment you must log in.
80. By Alan Elkner

protect evolve1 script from failing if there is no gradebook root yet

81. By Alan Elkner

fixed bug with deleting report sheet template activities, bug #417924

82. By Alan Elkner

Report Card Layout view re-wording of text and adding OK button, bug #400499

83. By Alan Elkner

fixed filename of report card pdf, bug #422915

84. By Alan Elkner

replaced Report Worksheet with Report Sheet in ui, bug #400402

85. By Alan Elkner

manage view for a worksheet has no activities message, bug #400406
also don't show delete button for no activities
added Worksheets link that was long overdue, and used in test

86. By Alan Elkner

changed Print Report Card links to Download Report Card, bug #400512

87. By Alan Elkner

changed Column Preferences link to Preferences, bug #427976

88. By Alan Elkner

report sheet activities now support ranged values, bug #417926

89. By Alan Elkner

added abbreviations column to discrete score systems, bug #409038

90. By Alan Elkner

added protection against duplicate scores and abbreviations in score systems

91. By Alan Elkner

added new linked column activities feature, bug #417918

92. By Alan Elkner

added absent and tardy columns to report card grid, bug #410005

93. By Alan Elkner

Added framework for the four new reports
This includes navigation and dummy reports, no real cells yet

94. By Alan Elkner

added date to absences by day report

95. By Alan Elkner

added request failing report view

96. By Alan Elkner

oops, forgot this file on last commit

97. By Alan Elkner

added request absences by day view

98. By Alan Elkner

made the sample data better

99. By Alan Elkner

added auto-create summary sheets feature, bug #427994

100. By Alan Elkner

string changes requested by Tom for the string freeze

101. By Alan Elkner

made sure all string constants in the templates are translatable

102. By Alan Elkner

fixed mixed strings in a couple templates

103. By Alan Elkner

replaced some sample data with real functionality in student detail report

Revision history for this message
Gediminas Paulauskas (menesis) wrote :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/schooltool/gradebook/README.txt'
2--- src/schooltool/gradebook/README.txt 2009-06-01 21:32:36 +0000
3+++ src/schooltool/gradebook/README.txt 2009-10-12 08:13:10 +0000
4@@ -375,7 +375,7 @@
5 a cell in the worksheet.
6
7 >>> gradebook.getEvaluation(paul, hw1)
8- <Evaluation for <Activity u'HW 1'>, value=10>
9+ (10, <RangedValuesScoreSystem None>)
10
11 We can get a student average for the worksheet, an integer percentage that
12 can later be used to formulate a letter grade.
13
14=== modified file 'src/schooltool/gradebook/__init__.py'
15--- src/schooltool/gradebook/__init__.py 2007-06-04 15:01:11 +0000
16+++ src/schooltool/gradebook/__init__.py 2009-10-12 08:13:10 +0000
17@@ -21,3 +21,31 @@
18
19 makeDecimalARock()
20 del makeDecimalARock
21+
22+def sliceString(source, start, end=None, startIndex=0, endIndex=0,
23+ includeEnd=False):
24+ index = -1 * len(start)
25+ while startIndex >= 0:
26+ index += len(start)
27+ index = source[index:].find(start)
28+ if index < 0:
29+ index = 0
30+ break
31+ startIndex -= 1
32+ if end is None:
33+ last = len(source)
34+ else:
35+ last = index - len(end)
36+ while endIndex >= 0:
37+ last += len(end)
38+ next = source[last:].find(end)
39+ if next < 0:
40+ last = len(source)
41+ break
42+ else:
43+ last += next
44+ endIndex -= 1
45+ if last < len(source) and includeEnd:
46+ last += len(end)
47+ return source[index:last]
48+
49
50=== modified file 'src/schooltool/gradebook/activity.py'
51--- src/schooltool/gradebook/activity.py 2009-04-09 00:41:49 +0000
52+++ src/schooltool/gradebook/activity.py 2009-10-12 08:13:10 +0000
53@@ -35,6 +35,7 @@
54 from zope.component import queryAdapter, getAdapters
55 from zope.schema.interfaces import IVocabularyFactory
56
57+from schooltool.app.interfaces import ISchoolToolApplication
58 from schooltool.common import SchoolToolMessage as _
59 from schooltool.requirement import requirement, scoresystem
60 from schooltool.gradebook import interfaces
61@@ -55,6 +56,45 @@
62 activities[name] = sheet1
63
64
65+def createSourceString(sourceObj):
66+ if interfaces.IActivity.providedBy(sourceObj):
67+ act_hash = unicode(hash(IKeyReference(sourceObj)))
68+ worksheet = sourceObj.__parent__
69+ else:
70+ act_hash = 'ave'
71+ worksheet = sourceObj
72+ section = worksheet.__parent__.__parent__
73+ sectionContainer = section.__parent__
74+ return '%s_%s_%s_%s' % (sectionContainer.__name__, section.__name__,
75+ unicode(hash(IKeyReference(worksheet))), act_hash)
76+
77+
78+def getSourceObj(source):
79+ scid, sid, ws_hash, act_hash = source.split('_')
80+
81+ app = ISchoolToolApplication(None)
82+ sectionContainer = app['schooltool.course.section'].get(scid, None)
83+ if sectionContainer is None:
84+ return None
85+
86+ section = sectionContainer.get(sid, None)
87+ if section is None:
88+ return None
89+
90+ for worksheet in interfaces.IActivities(section).values():
91+ if ws_hash == unicode(hash(IKeyReference(worksheet))):
92+ break
93+ else:
94+ return None
95+
96+ if act_hash == 'ave':
97+ return worksheet
98+ for activity in worksheet.values():
99+ if act_hash == unicode(hash(IKeyReference(activity))):
100+ return activity
101+ return None
102+
103+
104 class Activities(requirement.Requirement):
105 zope.interface.implements(interfaces.IActivities)
106
107@@ -243,3 +283,13 @@
108 def __call__(self, context):
109 section = context.context.__parent__.__parent__
110 return ExternalActivitiesSource(section)
111+
112+
113+class LinkedColumnActivity(Activity):
114+ zope.interface.implements(interfaces.ILinkedColumnActivity)
115+
116+ def __init__(self, title, category, label, source):
117+ super(LinkedColumnActivity, self).__init__(title, category, None, '',
118+ label)
119+ self.source = source
120+
121
122=== modified file 'src/schooltool/gradebook/browser/README.txt'
123--- src/schooltool/gradebook/browser/README.txt 2009-08-25 20:24:41 +0000
124+++ src/schooltool/gradebook/browser/README.txt 2009-10-12 08:13:10 +0000
125@@ -10,6 +10,10 @@
126 >>> from schooltool.app.browser.ftests import setup
127 >>> manager = setup.logIn('manager', 'schooltool')
128
129+Some imports:
130+
131+ >>> from schooltool.gradebook import sliceString
132+
133
134 Initial School Setup
135 --------------------
136@@ -75,6 +79,7 @@
137
138 >>> setup.addSchoolYear('2007', '2007-01-01', '2007-12-31')
139 >>> setup.addTerm('Winter', '2007-01-01', '2007-06-01', schoolyear='2007')
140+ >>> setup.addTerm('Fall', '2007-07-01', '2007-12-31', schoolyear='2007')
141
142 Next the administrator defines the courses that are available in the school.
143
144@@ -86,6 +91,13 @@
145 >>> manager.getControl('Add').click()
146 >>> manager.getLink('Physics I').click()
147
148+ >>> manager.getLink('2007').click()
149+ >>> manager.getLink('Courses').click()
150+ >>> manager.getLink('New Course').click()
151+ >>> manager.getControl('Title').value = 'English I'
152+ >>> manager.getControl('Add').click()
153+ >>> manager.getLink('English I').click()
154+
155 This completes the initial school setup.
156
157
158@@ -93,10 +105,11 @@
159 ----------
160
161 Every term, the administrators of a school are going to setup sections. So
162-let's add a section for our course:
163+let's add some sections:
164
165 >>> from schooltool.app.browser.ftests import setup
166 >>> setup.addSection('Physics I', '2007', 'Winter')
167+ >>> setup.addSection('English I', '2007', 'Fall')
168
169 But what would a section be without some students and a teacher?
170
171@@ -106,7 +119,7 @@
172 >>> addPerson('Claudia', 'Richter', 'claudia', 'pwd', browser=manager)
173 >>> addPerson('Stephan', 'Richter', 'stephan', 'pwd', browser=manager)
174
175-Now we can add those people to the section:
176+Now we can add those people to our sections:
177
178 >>> manager.getLink('2007').click()
179 >>> manager.getLink('Courses').click()
180@@ -125,6 +138,23 @@
181 >>> manager.getControl('Add').click()
182 >>> manager.getControl('OK').click()
183
184+ >>> manager.getLink('2007').click()
185+ >>> manager.getLink('Courses').click()
186+ >>> manager.getLink('English I').click()
187+ >>> manager.getLink('(1)').click()
188+
189+ >>> manager.getLink('edit individuals').click()
190+ >>> manager.getControl(name='add_item.paul').value = 'checked'
191+ >>> manager.getControl(name='add_item.tom').value = 'checked'
192+ >>> manager.getControl(name='add_item.claudia').value = 'checked'
193+ >>> manager.getControl('Add').click()
194+ >>> manager.getControl('OK').click()
195+
196+ >>> manager.getLink('edit instructors').click()
197+ >>> manager.getControl(name='add_item.stephan').value = 'checked'
198+ >>> manager.getControl('Add').click()
199+ >>> manager.getControl('OK').click()
200+
201
202 Instructor should be automatically capable of manipulating activities
203 and other section data.
204@@ -161,6 +191,15 @@
205 >>> 'Week 1' in stephan.contents
206 True
207
208+We'll note the message that appears for empty worksheets. Also, the fact that
209+there's no delete button.
210+
211+ >>> stephan.getLink('Week 1').click()
212+ >>> analyze.printQuery("id('content-body')/form/div[1]", stephan.contents)
213+ <div>This worksheet has no activities.</div>
214+ >>> analyze.printQuery("id('content-body')/form/div[3]", stephan.contents)
215+ >>> stephan.getLink('Worksheets').click()
216+
217 Then, we can use the 'New Worksheet' action link to create our second worksheet.
218
219 >>> stephan.getLink('New Worksheet').click()
220@@ -274,23 +313,26 @@
221 ... < stephan.contents.find('Final')
222 True
223
224-You can also delete activities that you have created:
225+We'll switch to the Fall term and add some activities to the English I section:
226
227+ >>> stephan.open('http://localhost/schoolyears/2007/winter/sections/1/activities/Worksheet-2/gradebook?currentTerm=Fall')
228 >>> stephan.getLink('New Activity').click()
229- >>> stephan.getControl('Title').value = 'HW 3'
230- >>> stephan.getControl('Description').value = 'Homework 3'
231+ >>> stephan.getControl('Title').value = 'Lab 1'
232+ >>> stephan.getControl('Description').value = 'Laboratory 1'
233 >>> stephan.getControl('Category').value = ['assignment']
234 >>> stephan.getControl('Add').click()
235- >>> stephan.getLink('Manage Activities').click()
236- >>> 'HW 3' in stephan.contents
237- True
238- >>> stephan.getControl(name='delete:list').value = ['Activity-3']
239- >>> stephan.getControl('Delete').click()
240- >>> 'HW 3' in stephan.contents
241- False
242+ >>> 'Lab 1' in stephan.contents
243+ True
244+ >>> stephan.getLink('New Activity').click()
245+ >>> stephan.getControl('Title').value = 'Final'
246+ >>> stephan.getControl('Description').value = 'Final Exam'
247+ >>> stephan.getControl('Category').value = ['exam']
248+ >>> stephan.getControl('Add').click()
249+ >>> 'Final' in stephan.contents
250+ True
251
252-Fianlly, let's change the current workskeet back to 'Week 1'. This setting
253-of current worksheet will be in effect for the gradebook as well.
254+Finally, we'll change the section back to the Winter Physics section and the
255+current workskeet back to 'Week 1'.
256
257 >>> stephan.open('http://localhost/schoolyears/2007/winter/sections/1/gradebook/')
258 >>> stephan.getLink('Week 1').click()
259@@ -639,15 +681,15 @@
260 >>> manager.getLink('Score Systems').click()
261 >>> manager.getLink('Add Score System').click()
262 >>> url = manager.url + '?form-submitted&UPDATE_SUBMIT&title=Good/Bad'
263- >>> url = url + '&displayed1=G&value1=1&percent1=60'
264- >>> url = url + '&displayed2=B&value2=0&percent2=0'
265+ >>> url = url + '&displayed1=G&abbr1=&value1=1&percent1=60'
266+ >>> url = url + '&displayed2=B&abbr2=&value2=0&percent2=0'
267 >>> manager.open(url)
268
269 We'll start by calling up the current column preferences and note that there
270 are none set yet.
271
272 >>> stephan.getLink('Return to Gradebook').click()
273- >>> stephan.getLink('Column Preferences').click()
274+ >>> stephan.getLink('Preferences').click()
275 >>> analyze.printQuery("id('content-body')/form/table//input", stephan.contents)
276 <input type="checkbox" name="hide_total" />
277 <input type="text" name="label_total" value="" />
278@@ -670,7 +712,7 @@
279 >>> url += '&scoresystem_average=goodbad'
280 >>> stephan.open(url)
281
282- >>> stephan.getLink('Column Preferences').click()
283+ >>> stephan.getLink('Preferences').click()
284 >>> analyze.printQuery("id('content-body')/form/table//input", stephan.contents)
285 <input type="checkbox" checked="checked" name="hide_total" />
286 <input type="text" name="label_total" value="Summe" />
287@@ -1058,3 +1100,133 @@
288 ...Tom...59.00...79%...44...15.00...
289 ...Paul...40...80%...40...
290
291+
292+Column Linking
293+--------------
294+
295+To add a spreadsheet feature we created LindedColumnActivity objects to allow
296+the user to pull in columns from other worksheets. These columns will not only
297+display the contents of the source column, but the values will be factored
298+into the average for the worksheet where the linked column activity lives.
299+
300+There are two types of linked activities, a link to an other worksheet's
301+activity, or a link to the average column of the worksheet. Activity links
302+will use the score system of the source activity whereas worksheet average
303+links will use an assumed 100 point system.
304+
305+We'll switch to the Fall term and enter some scores to the English I section:
306+
307+ >>> stephan.open('http://localhost/schoolyears/2007/winter/sections/1/activities/Worksheet-2/gradebook?currentTerm=Fall')
308+ >>> stephan.getLink('Lab1').click()
309+ >>> stephan.getControl('Cardune, Paul').value = u'89'
310+ >>> stephan.getControl('Hoffman, Tom').value = u'72'
311+ >>> stephan.getControl('Save').click()
312+
313+ >>> stephan.getLink('Final').click()
314+ >>> stephan.getControl('Cardune, Paul').value = u'99'
315+ >>> stephan.getControl('Hoffman, Tom').value = u'88'
316+ >>> stephan.getControl('Save').click()
317+
318+We'll test the totals and averages so that we can check the linked values below:
319+
320+ >>> results = analyze.queryHTML("id('content-body')//table//b", stephan.contents)
321+ >>> results = [result.strip() for result in results]
322+ >>> for result in results: print result
323+ <b>188</b>
324+ <b>94%</b>
325+ <b>160</b>
326+ <b>80%</b>
327+ <b>0</b>
328+ <b>0%</b>
329+
330+Now we'll return to the Winter Physics section and add our first linked column
331+to the Week 1 worksheet:
332+
333+ >>> stephan.open('http://localhost/schoolyears/2007/winter/sections/1/gradebook/')
334+ >>> stephan.getLink('Week 1').click()
335+ >>> stephan.getLink('New Linked Column').click()
336+
337+First we'll test the contents of the table of available activities and worksheet
338+averages that can be chosen as the link. We have to slice and dice the query
339+results becuase the ... feature is erratic:
340+
341+ >>> results = analyze.queryHTML("id('content-body')/form/table//td", stephan.contents)
342+ >>> slice_results = []
343+ >>> for low, high in [(0, 3), (4, 7), (8, 11), (12, 15), (16, 19), (20, 23)]:
344+ ... slice_results.extend(results[low:high])
345+ >>> for result in slice_results: print result
346+ <td class="cell padded odd">Winter</td>
347+ <td class="cell padded odd">Physics I</td>
348+ <td class="cell padded odd">Week 2</td>
349+ <td class="cell padded even"></td>
350+ <td class="cell padded even"></td>
351+ <td class="cell padded even"></td>
352+ <td class="cell padded odd"></td>
353+ <td class="cell padded odd"></td>
354+ <td class="cell padded odd"></td>
355+ <td class="cell padded even">Fall</td>
356+ <td class="cell padded even">English I</td>
357+ <td class="cell padded even">Sheet1</td>
358+ <td class="cell padded odd"></td>
359+ <td class="cell padded odd"></td>
360+ <td class="cell padded odd"></td>
361+ <td class="cell padded even"></td>
362+ <td class="cell padded even"></td>
363+ <td class="cell padded even"></td>
364+
365+ >>> results = analyze.queryHTML("id('content-body')/form/table//input", stephan.contents)
366+ >>> for result in results: print sliceString(result, 'value', '"', endIndex=1, includeEnd=True)
367+ value="HW 2"
368+ value="Final"
369+ value="Average"
370+ value="Lab 1"
371+ value="Final"
372+ value="Average"
373+
374+We'll add a link to HW 2 from Week 2, then the average of the worksheet from the
375+Fall English I section, Sheet1:
376+
377+ >>> stephan.getControl('HW 2').click()
378+ >>> stephan.getLink('New Linked Column').click()
379+ >>> stephan.getControl('Average', index=1).click()
380+
381+The gradebook now has two new columns whose values are pulled in from the
382+sources of the links. First we'll test the editable fields:
383+
384+ >>> results = analyze.queryHTML("id('content-body')//input", stephan.contents)
385+ >>> results = [result.strip() for result in results]
386+ >>> for result in results[7:-1]: print sliceString(result, 'value', '"', endIndex=1, includeEnd=True)
387+ value=""
388+ value="86"
389+ value="10.00"
390+ value="44"
391+ value=""
392+ value="15.00"
393+ value="40"
394+ value=""
395+ value=""
396+
397+Next we'll test the linked column data:
398+
399+ >>> results = analyze.queryHTML("id('content-body')//span", stephan.contents)
400+ >>> results = [result.strip() for result in results]
401+ >>> for result in results[7:]: print result
402+ <span>42</span>
403+ <span>0</span>
404+ <span>72</span>
405+ <span>80</span>
406+ <span>90</span>
407+ <span>94</span>
408+
409+Finally we'll test the totals and averages:
410+
411+ >>> results = analyze.queryHTML("id('content-body')//table//b", stephan.contents)
412+ >>> results = [result.strip() for result in results]
413+ >>> for result in results: print result
414+ <b>138.00</b>
415+ <b>62%</b>
416+ <b>211.00</b>
417+ <b>77%</b>
418+ <b>224</b>
419+ <b>90%</b>
420+
421
422=== added file 'src/schooltool/gradebook/browser/absences_by_day_rml.pt'
423--- src/schooltool/gradebook/browser/absences_by_day_rml.pt 1970-01-01 00:00:00 +0000
424+++ src/schooltool/gradebook/browser/absences_by_day_rml.pt 2009-10-12 08:13:10 +0000
425@@ -0,0 +1,100 @@
426+<?xml version="1.0" standalone="no" ?>
427+<!DOCTYPE document SYSTEM "rml_1_0.dtd" [
428+ <!ENTITY pound "&#xA3;">
429+ <!ENTITY nbsp "&#160;">
430+]>
431+
432+<document
433+ xmlns:tal="http://xml.zope.org/namespaces/tal"
434+ xmlns:metal="http://xml.zope.org/namespaces/metal"
435+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
436+ metal:use-macro="context/@@rml_macros/report"
437+ i18n:domain="schooltool">
438+
439+<metal:block fill-slot="page_templates">
440+ <tal:block content="structure view/use_template/default" />
441+</metal:block>
442+
443+<stylesheet>
444+ <metal:block fill-slot="extra_initialize">
445+ </metal:block>
446+ <metal:block fill-slot="stylesheet">
447+
448+ <paraStyle
449+ name="normal"
450+ fontName="Arial_Normal"
451+ fontSize="10"
452+ leading="12"/>
453+
454+ <paraStyle
455+ name="bold"
456+ fontName="Arial_Bold"
457+ fontSize="10"
458+ alignment="left"
459+ leading="12"/>
460+
461+ <paraStyle
462+ name="heading"
463+ fontName="Arial_Bold"
464+ fontSize="10"
465+ alignment="right"
466+ leading="12"/>
467+
468+ <paraStyle
469+ name="section_heading"
470+ fontName="Arial_Bold"
471+ fontSize="12"
472+ alignment="center"
473+ leading="12"/>
474+
475+ <blockTableStyle id="headings_table">
476+ <blockValign value="top" start="0,0" stop="0,-1"/>
477+ </blockTableStyle>
478+
479+ <blockTableStyle id="grid">
480+ <lineStyle kind="OUTLINE"
481+ colorName="black" thickness="0.25"
482+ start="0,0" stop="-1,-1" />
483+ <blockValign value="top" start="0,0" stop="0,-1"/>
484+ </blockTableStyle>
485+ </metal:block>
486+</stylesheet>
487+
488+<story metal:fill-slot="story">
489+ <para style="section_heading" tal:content="view/date_heading" />
490+ <spacer length="1cm" />
491+
492+ <blockTable style="headings_table" colWidths="8cm,4cm">
493+ <tr>
494+ <td />
495+ <td><para style="bold" tal:content="view/periods_heading" /></td>
496+ </tr>
497+ </blockTable>
498+ <spacer length=".2cm" />
499+
500+ <blockTable style="grid"
501+ tal:attributes="colWidths view/widths">
502+ <tr>
503+ <td><para style="bold" tal:content="view/name_heading" /></td>
504+ <td tal:repeat="period view/periods">
505+ <para style="bold" tal:content="period" />
506+ </td>
507+ </tr>
508+ </blockTable>
509+
510+ <blockTable style="headings_table"
511+ tal:attributes="colWidths view/widths">
512+ <tr tal:repeat="student view/students">
513+ <td><para style="bold" tal:content="student/name" /></td>
514+ <td tal:repeat="period student/periods">
515+ <para style="normal" tal:content="period" />
516+ </td>
517+ </tr>
518+ </blockTable>
519+
520+ <condPageBreak height="88cm"/>
521+
522+</story>
523+
524+</document>
525+
526
527=== modified file 'src/schooltool/gradebook/browser/activity.py'
528--- src/schooltool/gradebook/browser/activity.py 2009-08-25 20:24:41 +0000
529+++ src/schooltool/gradebook/browser/activity.py 2009-10-12 08:13:10 +0000
530@@ -26,10 +26,11 @@
531 import xlwt
532 from StringIO import StringIO
533
534-from zope.viewlet.viewlet import ViewletBase
535 from zope.app.container.interfaces import INameChooser
536 from zope.app.form.browser.editview import EditView
537+from zope.app.keyreference.interfaces import IKeyReference
538 from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
539+from zope.publisher.browser import BrowserView
540 from zope.schema import TextLine
541 from zope.security.checker import canWrite
542 from zope.security.interfaces import Unauthorized
543@@ -43,23 +44,27 @@
544 from zope.formlib import form
545 from zope import interface, schema
546 from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
547+from zope.viewlet.viewlet import ViewletBase
548
549 from z3c.form import form as z3cform
550 from z3c.form import field, button
551
552 from schooltool.app.interfaces import ISchoolToolApplication
553 from schooltool.app.browser import app
554+from schooltool.basicperson.interfaces import IDemographics
555 from schooltool.common import SchoolToolMessage as _
556+from schooltool.course.interfaces import ISection, ILearner, IInstructor
557+from schooltool.export import export
558 from schooltool.gradebook import interfaces, activity
559-from schooltool.gradebook.activity import Activity
560+from schooltool.gradebook.activity import createSourceString, getSourceObj
561+from schooltool.gradebook.activity import Activity, LinkedColumnActivity
562 from schooltool.gradebook.category import getCategories
563 from schooltool.person.interfaces import IPerson
564 from schooltool.gradebook.browser.gradebook import LinkedActivityGradesUpdater
565 from schooltool.requirement.interfaces import IRangedValuesScoreSystem
566 from schooltool.requirement.scoresystem import RangedValuesScoreSystem
567 from schooltool.requirement.scoresystem import UNSCORED
568-from schooltool.export import export
569-from schooltool.basicperson.interfaces import IDemographics
570+from schooltool.term.interfaces import ITerm
571
572
573 class ILinkedActivityFields(interface.Interface):
574@@ -418,10 +423,8 @@
575 export.Text(student.first_name),
576 export.Text(student.last_name)]
577 for activity in activities:
578- ev = gradebook.getEvaluation(student, activity)
579- if ev is not None and ev.value is not UNSCORED:
580- value = ev.value
581- else:
582+ value, ss = gradebook.getEvaluation(student, activity)
583+ if value is None:
584 value = ''
585 cells.append(export.Text(value))
586 for col, cell in enumerate(cells):
587@@ -441,3 +444,172 @@
588 data = datafile.getvalue()
589 self.setUpHeaders(data)
590 return data
591+
592+
593+class LinkedColumnBase(BrowserView):
594+ """Base class for add/edit linked column views"""
595+ def __init__(self, context, request):
596+ super(LinkedColumnBase, self).__init__(context, request)
597+ if interfaces.IWorksheet.providedBy(self.context):
598+ self.currentWorksheet = self.context
599+ else:
600+ self.currentWorksheet = self.context.__parent__
601+ self.person = IPerson(self.request.principal)
602+
603+ def title(self):
604+ if interfaces.IWorksheet.providedBy(self.context):
605+ return ''
606+ else:
607+ return self.context.title
608+
609+ def label(self):
610+ if interfaces.IWorksheet.providedBy(self.context):
611+ return ''
612+ else:
613+ return self.context.label
614+
615+ def getCategories(self):
616+ language = 'en' # XXX this need to be dynamic
617+ categories = getCategories(ISchoolToolApplication(None))
618+
619+ results = []
620+ for category in sorted(categories.getKeys()):
621+ result = {
622+ 'name': category,
623+ 'value': categories.getValue(category, language),
624+ }
625+ results.append(result)
626+ return results
627+
628+ def isLinked(self, activity):
629+ return interfaces.ILinkedColumnActivity.providedBy(activity)
630+
631+ def getRows(self):
632+ term_dict = {}
633+ for section in IInstructor(self.person).sections():
634+ term = ITerm(section)
635+ term_dict.setdefault(term, []).append(section)
636+ results = []
637+ for term in sorted(term_dict.keys(), key=lambda t: t.first):
638+ term_disp = term.title
639+ for section_index, section in enumerate(term_dict[term]):
640+ section_disp = list(section.courses)[0].title
641+ worksheets = interfaces.IActivities(section).values()
642+ worksheets = [worksheet for worksheet in worksheets
643+ if not worksheet.deployed
644+ and len(worksheet.values())
645+ and worksheet != self.currentWorksheet]
646+ for ws_index, worksheet in enumerate(worksheets):
647+ ws_disp = worksheet.title
648+ activities = [activity for activity in worksheet.values()
649+ if not self.isLinked(activity)]
650+ for act_index, activity in enumerate(activities):
651+ result = {
652+ 'term': term_disp,
653+ 'section': section_disp,
654+ 'worksheet': ws_disp,
655+ 'activity_name': createSourceString(activity),
656+ 'activity_value': activity.title,
657+ }
658+ results.append(result)
659+ term_disp = section_disp = ws_disp = ''
660+ if len(activities):
661+ result = {
662+ 'term': '',
663+ 'section': '',
664+ 'worksheet': '',
665+ 'activity_name': createSourceString(worksheet),
666+ 'activity_value': _('Average'),
667+ }
668+ results.append(result)
669+ return results
670+
671+ def getRequestSource(self):
672+ for key in self.request:
673+ parts = key.split('_')
674+ if len(parts) == 4:
675+ try:
676+ int(parts[2])
677+ return key
678+ except:
679+ pass
680+ return None
681+
682+ def buildUpdateTarget(self, target=None):
683+ title = self.request['title']
684+ label = self.request['label']
685+ category = self.request['category']
686+ source = self.getRequestSource()
687+ if not title:
688+ sourceObj = getSourceObj(source)
689+ if sourceObj is not None:
690+ title = sourceObj.title
691+ if target is None:
692+ return LinkedColumnActivity(title, category, label, source)
693+ else:
694+ target.title = title
695+ target.label = label
696+ target.category = category
697+ target.source = source
698+
699+
700+class AddLinkedColumnView(LinkedColumnBase):
701+ """View for adding a linked column to the gradebook"""
702+
703+ def viewTitle(self):
704+ return _('Add Linked Column')
705+
706+ def actionURL(self):
707+ return absoluteURL(self.context, self.request) + '/addLinkedColumn.html'
708+
709+ def nextURL(self):
710+ return absoluteURL(self.context, self.request)
711+
712+ def update(self):
713+ if 'form-submitted' not in self.request:
714+ return
715+ if 'CANCEL' in self.request:
716+ self.request.response.redirect(self.nextURL())
717+ else:
718+ activity = self.buildUpdateTarget()
719+ chooser = INameChooser(self.context)
720+ name = chooser.chooseName('', activity)
721+ self.context[name] = activity
722+ self.request.response.redirect(self.nextURL())
723+
724+
725+class EditLinkedColumnView(LinkedColumnBase):
726+ """View for editing a linked column in the gradebook"""
727+
728+ def viewTitle(self):
729+ sourceObj = getSourceObj(self.context.source)
730+ if sourceObj is None:
731+ details = ''
732+ else:
733+ if interfaces.IWorksheet.providedBy(sourceObj):
734+ act_disp = _('Average')
735+ worksheet = sourceObj
736+ else:
737+ act_disp = sourceObj.title
738+ worksheet = sourceObj.__parent__
739+ section = ISection(worksheet)
740+ term = ITerm(section)
741+ details = ' (%s - %s - %s - %s)' % (term.title,
742+ list(section.courses)[0].title, worksheet.title, act_disp)
743+ return _('Edit Linked Column') + details
744+
745+ def actionURL(self):
746+ return absoluteURL(self.context, self.request) + '/editLinkedColumn.html'
747+
748+ def nextURL(self):
749+ return absoluteURL(self.context.__parent__, self.request)
750+
751+ def update(self):
752+ if 'form-submitted' not in self.request:
753+ return
754+ if 'CANCEL' in self.request:
755+ self.request.response.redirect(self.nextURL())
756+ else:
757+ self.buildUpdateTarget(self.context)
758+ self.request.response.redirect(self.nextURL())
759+
760
761=== added file 'src/schooltool/gradebook/browser/add_edit_linked_column.pt'
762--- src/schooltool/gradebook/browser/add_edit_linked_column.pt 1970-01-01 00:00:00 +0000
763+++ src/schooltool/gradebook/browser/add_edit_linked_column.pt 2009-10-12 08:13:10 +0000
764@@ -0,0 +1,68 @@
765+<tal:define define="dummy view/update"/>
766+<html metal:use-macro="view/@@standard_macros/page" i18n:domain="schooltool">
767+<head>
768+ <title metal:fill-slot="title" tal:content="view/viewTitle" />
769+</head>
770+<body>
771+
772+<h1 metal:fill-slot="content-header" tal:content="view/viewTitle" />
773+
774+<metal:block metal:fill-slot="body">
775+ <form method="post"
776+ tal:attributes="action view/actionURL">
777+ <input type="hidden" name="form-submitted" value="" />
778+ <label for="title" i18n:translate="">Title</label>
779+ <input type="text" name="title"
780+ tal:attributes="value view/title" />
781+ <label for="label" i18n:translate="">Label</label>
782+ <input type="text" name="label"
783+ tal:attributes="value view/label" />
784+ <label for="category" i18n:translate="">Category</label>
785+ <select id="category" name="category">
786+ <tal:block repeat="category view/getCategories">
787+ <option tal:attributes="value category/name"
788+ tal:content="category/value" />
789+ </tal:block>
790+ </select>
791+ <br /><br />
792+ <table class="schooltool_gradebook">
793+ <tr>
794+ <th class="cell even" style="border-bottom: solid thin; padding: .4em">Term</th>
795+ <th class="cell even" style="border-bottom: solid thin; padding: .4em">Section</th>
796+ <th class="cell even" style="border-bottom: solid thin; padding: .4em">Worksheet</th>
797+ <th class="cell even" style="border-bottom: solid thin; padding: .4em">Activity</th>
798+ </tr>
799+ <tr tal:repeat="row view/getRows">
800+ <tal:if condition="repeat/row/odd">
801+ <td class="cell padded even" tal:content="row/term" />
802+ <td class="cell padded even" tal:content="row/section" />
803+ <td class="cell padded even" tal:content="row/worksheet" />
804+ <td class ="even" style="padding: .1em 0 .1em">
805+ <input type="submit"
806+ style="background: #83A67F; height: 1.7em; font-weight: bold;
807+ color: #FFFFFF"
808+ tal:attributes="name row/activity_name;value row/activity_value" />
809+ </td>
810+ </tal:if>
811+ <tal:if condition="repeat/row/even">
812+ <td class="cell padded odd" tal:content="row/term" />
813+ <td class="cell padded odd" tal:content="row/section" />
814+ <td class="cell padded odd" tal:content="row/worksheet" />
815+ <td class ="odd" style="padding: .1em 0 .1em">
816+ <input type="submit"
817+ style="background: #83A67F; height: 1.7em; font-weight: bold;
818+ color: #FFFFFF"
819+ tal:attributes="name row/activity_name;value row/activity_value" />
820+ </td>
821+ </tal:if>
822+ </tr>
823+ </table>
824+ <div class="controls">
825+ <tal:block metal:use-macro="view/@@standard_macros/cancel-button" />
826+ </div>
827+ </form>
828+
829+</metal:block>
830+</body>
831+</html>
832+
833
834=== modified file 'src/schooltool/gradebook/browser/categories.pt'
835--- src/schooltool/gradebook/browser/categories.pt 2009-04-17 13:19:19 +0000
836+++ src/schooltool/gradebook/browser/categories.pt 2009-10-12 08:13:10 +0000
837@@ -16,6 +16,13 @@
838 Manage Activity Categories
839 </h3>
840
841+ <p i18n:translate="">
842+ Categories are used in SchoolTool's gradebook to organize activities and to allow teachers to weight different types of activities differently to calculate grades. For example, the grade in a section might be comprised of 60% test scores, 20% homework and 20% class participation.
843+ </p>
844+ <p i18n:translate="">
845+ Currently, the list of available categories for all teachers is managed through this form. That is, if one or more teachers would like to use a category not in the current list, the site manager can add it here. Likewise, if some of the default categories are not relevant to your school, remove them below.
846+ </p>
847+
848 <div class="info" tal:condition="view/message" tal:content="view/message" />
849
850 <div class="row" tal:define="widget nocall:view/categories_widget">
851
852=== modified file 'src/schooltool/gradebook/browser/configure.zcml'
853--- src/schooltool/gradebook/browser/configure.zcml 2009-07-29 09:35:08 +0000
854+++ src/schooltool/gradebook/browser/configure.zcml 2009-10-12 08:13:10 +0000
855@@ -45,6 +45,13 @@
856 permission="schooltool.edit"
857 />
858
859+ <!-- Report sheet activity vocabulary -->
860+ <zope:utility
861+ provides="zope.schema.interfaces.IVocabularyFactory"
862+ component=".report_card.ReportScoreSystemsVocabulary"
863+ name="schooltool.gradebook.reportscoresystems"
864+ />
865+
866 <!-- Categories -->
867 <form
868 label="Activity Categories"
869@@ -123,7 +130,7 @@
870 <widget field="description" height="5" />
871 </addform>
872 <addform
873- label="Add a Report Worksheet"
874+ label="Add a Report Sheet"
875 name="addReportWorksheet.html"
876 schema="schooltool.gradebook.interfaces.IReportWorksheet"
877 fields="title"
878@@ -147,7 +154,7 @@
879 title="Edit"
880 />
881 <editform
882- label="Edit Report Worksheet"
883+ label="Edit Report Sheet"
884 name="edit.html"
885 for="schooltool.gradebook.interfaces.IReportWorksheet"
886 schema="schooltool.gradebook.interfaces.IReportWorksheet"
887@@ -246,6 +253,20 @@
888 for="..interfaces.ILinkedActivity"
889 name="edit.html"
890 />
891+ <page
892+ name="addLinkedColumn.html"
893+ for="schooltool.gradebook.interfaces.IWorksheet"
894+ class=".activity.AddLinkedColumnView"
895+ template="add_edit_linked_column.pt"
896+ permission="schooltool.edit"
897+ />
898+ <page
899+ name="editLinkedColumn.html"
900+ for="schooltool.gradebook.interfaces.ILinkedColumnActivity"
901+ class=".activity.EditLinkedColumnView"
902+ template="add_edit_linked_column.pt"
903+ permission="schooltool.edit"
904+ />
905
906 <!-- Menu items for ISection -->
907 <menuItem
908@@ -259,7 +280,7 @@
909 <!-- Menu items for IActivities -->
910 <menuItem
911 menu="schooltool_actions"
912- title="New Report Worksheet"
913+ title="New Report Sheet"
914 for="..interfaces.IGradebookTemplates"
915 action="+/addReportWorksheet.html"
916 permission="schooltool.view"
917@@ -303,11 +324,25 @@
918 />
919 <menuItem
920 menu="schooltool_actions"
921+ title="New Linked Column"
922+ for="..interfaces.IWorksheet"
923+ action="addLinkedColumn.html"
924+ permission="schooltool.view"
925+ />
926+ <menuItem
927+ menu="schooltool_actions"
928 title="Return to Gradebook"
929 for="..interfaces.IWorksheet"
930 action="gradebook"
931 permission="schooltool.view"
932 />
933+ <menuItem
934+ menu="schooltool_actions"
935+ title="Worksheets"
936+ for="..interfaces.IWorksheet"
937+ action="../index.html"
938+ permission="schooltool.view"
939+ />
940
941 <!-- Menu items for IGradebook -->
942 <menuItem
943@@ -340,6 +375,13 @@
944 />
945 <menuItem
946 menu="schooltool_actions"
947+ title="New Linked Column"
948+ for="..interfaces.IGradebook"
949+ action="../addLinkedColumn.html"
950+ permission="schooltool.view"
951+ />
952+ <menuItem
953+ menu="schooltool_actions"
954 title="Manage Activities"
955 for="..interfaces.IGradebook"
956 action="../manage.html"
957@@ -354,7 +396,7 @@
958 />
959 <menuItem
960 menu="schooltool_actions"
961- title="Column Preferences"
962+ title="Preferences"
963 for="..interfaces.IGradebook"
964 action="column_preferences.html"
965 permission="schooltool.view"
966@@ -469,7 +511,7 @@
967 <!-- Menu items for ITerm -->
968 <menuItem
969 menu="schooltool_actions"
970- title="Deploy Report Worksheet"
971+ title="Deploy Report Sheet"
972 for="schooltool.term.interfaces.ITerm"
973 action="deploy_report_worksheet.html"
974 permission="schooltool.edit"
975@@ -487,18 +529,25 @@
976 <!-- Menu items for ISchoolYear -->
977 <menuItem
978 menu="schooltool_actions"
979- title="Deploy Report Worksheet"
980+ title="Deploy Report Sheet"
981 for="schooltool.schoolyear.interfaces.ISchoolYear"
982 action="deploy_report_worksheet.html"
983 permission="schooltool.edit"
984 />
985 <menuItem
986 menu="schooltool_actions"
987- title="Layout Report Card"
988+ title="Report Card Layout"
989 for="schooltool.schoolyear.interfaces.ISchoolYear"
990 action="layout_report_card.html"
991 permission="schooltool.edit"
992 />
993+ <menuItem
994+ menu="schooltool_actions"
995+ title="Reports"
996+ for="schooltool.schoolyear.interfaces.ISchoolYear"
997+ action="report_pdfs.html"
998+ permission="schooltool.edit"
999+ />
1000
1001 <!-- Views for ISchoolYear -->
1002 <page
1003@@ -515,40 +564,125 @@
1004 template="layout_report_card.pt"
1005 permission="schooltool.edit"
1006 />
1007+ <page
1008+ name="report_pdfs.html"
1009+ for="schooltool.schoolyear.interfaces.ISchoolYear"
1010+ class=".request_reports.SchoolYearReportsView"
1011+ permission="schooltool.edit"
1012+ />
1013+ <page
1014+ name="request_failing_report.html"
1015+ for="schooltool.schoolyear.interfaces.ISchoolYear"
1016+ class=".request_reports.RequestFailingReportView"
1017+ template="request_failing_report.pt"
1018+ permission="schooltool.edit"
1019+ />
1020+ <page
1021+ name="failing_report.pdf"
1022+ for="schooltool.schoolyear.interfaces.ISchoolYear"
1023+ class=".pdf_views.FailingReportPDFView"
1024+ permission="schooltool.edit"
1025+ />
1026+ <page
1027+ name="request_absences_by_day.html"
1028+ for="schooltool.schoolyear.interfaces.ISchoolYear"
1029+ class=".request_reports.RequestAbsencesByDayView"
1030+ template="request_absences_by_day.pt"
1031+ permission="schooltool.edit"
1032+ />
1033+ <page
1034+ name="absences_by_day.pdf"
1035+ for="schooltool.schoolyear.interfaces.ISchoolYear"
1036+ class=".pdf_views.AbsencesByDayPDFView"
1037+ permission="schooltool.edit"
1038+ />
1039
1040 <!-- Menu items for IBasicPerson-->
1041 <menuItem
1042 menu="schooltool_actions"
1043- title="Print Report Card"
1044+ title="Reports"
1045 for="schooltool.person.interfaces.IPerson"
1046- action="print_report_card.html"
1047+ action="report_pdfs.html"
1048 permission="schooltool.edit"
1049 />
1050
1051 <!-- Views for IBasicPerson -->
1052 <page
1053- name="print_report_card.html"
1054+ name="report_pdfs.html"
1055+ for="schooltool.person.interfaces.IPerson"
1056+ class=".request_reports.StudentReportsView"
1057+ permission="schooltool.edit"
1058+ />
1059+ <page
1060+ name="report_card.pdf"
1061 for="schooltool.person.interfaces.IPerson"
1062 class=".pdf_views.StudentReportCardPDFView"
1063 permission="schooltool.edit"
1064 />
1065+ <page
1066+ name="student_detail.pdf"
1067+ for="schooltool.person.interfaces.IPerson"
1068+ class=".pdf_views.StudentDetailPDFView"
1069+ permission="schooltool.edit"
1070+ />
1071
1072 <!-- Menu items for IGroup -->
1073 <menuItem
1074 menu="schooltool_actions"
1075- title="Print Report Card"
1076+ title="Reports"
1077 for="schooltool.group.interfaces.IGroup"
1078- action="print_report_card.html"
1079+ action="report_pdfs.html"
1080 permission="schooltool.edit"
1081 />
1082
1083 <!-- Views for IGroup -->
1084 <page
1085- name="print_report_card.html"
1086+ name="report_pdfs.html"
1087+ for="schooltool.group.interfaces.IGroup"
1088+ class=".request_reports.GroupReportsView"
1089+ permission="schooltool.edit"
1090+ />
1091+ <page
1092+ name="report_card.pdf"
1093 for="schooltool.group.interfaces.IGroup"
1094 class=".pdf_views.GroupReportCardPDFView"
1095 permission="schooltool.edit"
1096 />
1097+ <page
1098+ name="student_detail.pdf"
1099+ for="schooltool.group.interfaces.IGroup"
1100+ class=".pdf_views.GroupDetailPDFView"
1101+ permission="schooltool.edit"
1102+ />
1103+
1104+ <!-- Menu items for ISection -->
1105+ <menuItem
1106+ menu="schooltool_actions"
1107+ title="Reports"
1108+ for="schooltool.course.interfaces.ISection"
1109+ action="report_pdfs.html"
1110+ permission="schooltool.edit"
1111+ />
1112+ <page
1113+ name="absences_by_day.pdf"
1114+ for="schooltool.course.interfaces.ISection"
1115+ class=".pdf_views.AbsencesByDayPDFView"
1116+ permission="schooltool.edit"
1117+ />
1118+
1119+ <!-- Views for ISection -->
1120+ <page
1121+ name="report_pdfs.html"
1122+ for="schooltool.course.interfaces.ISection"
1123+ class=".request_reports.SectionReportsView"
1124+ permission="schooltool.edit"
1125+ />
1126+ <page
1127+ name="section_absences.pdf"
1128+ for="schooltool.course.interfaces.ISection"
1129+ class=".pdf_views.SectionAbsencesPDFView"
1130+ permission="schooltool.edit"
1131+ />
1132
1133 <!-- Views for ISchoolToolApplication -->
1134 <page
1135@@ -560,7 +694,7 @@
1136 />
1137
1138 <zope:adapter
1139- factory=".report_card.ExistingScoresSystem" />
1140+ factory=".report_card.ReportScoresSystem" />
1141
1142 <!-- Handling sections that are added after deployment already made to a term -->
1143 <zope:subscriber
1144
1145=== modified file 'src/schooltool/gradebook/browser/deploy_report_worksheet.pt'
1146--- src/schooltool/gradebook/browser/deploy_report_worksheet.pt 2009-03-10 08:17:21 +0000
1147+++ src/schooltool/gradebook/browser/deploy_report_worksheet.pt 2009-10-12 08:13:10 +0000
1148@@ -22,7 +22,8 @@
1149 </select>
1150 <div class="controls">
1151 <tal:block metal:use-macro="view/@@standard_macros/cancel-button" />
1152- <input type="submit" class="button-ok" name="DEPLOY" value="Deploy" />
1153+ <input type="submit" class="button-ok" name="DEPLOY" value="Deploy"
1154+ i18n:attributes="value" />
1155 </div>
1156 </form>
1157
1158
1159=== added file 'src/schooltool/gradebook/browser/failing_report_rml.pt'
1160--- src/schooltool/gradebook/browser/failing_report_rml.pt 1970-01-01 00:00:00 +0000
1161+++ src/schooltool/gradebook/browser/failing_report_rml.pt 2009-10-12 08:13:10 +0000
1162@@ -0,0 +1,116 @@
1163+<?xml version="1.0" standalone="no" ?>
1164+<!DOCTYPE document SYSTEM "rml_1_0.dtd" [
1165+ <!ENTITY pound "&#xA3;">
1166+ <!ENTITY nbsp "&#160;">
1167+]>
1168+
1169+<document
1170+ xmlns:tal="http://xml.zope.org/namespaces/tal"
1171+ xmlns:metal="http://xml.zope.org/namespaces/metal"
1172+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
1173+ metal:use-macro="context/@@rml_macros/report"
1174+ i18n:domain="schooltool">
1175+
1176+<metal:block fill-slot="page_templates">
1177+ <tal:block content="structure view/use_template/default" />
1178+</metal:block>
1179+
1180+<stylesheet>
1181+ <metal:block fill-slot="extra_initialize">
1182+ </metal:block>
1183+ <metal:block fill-slot="stylesheet">
1184+
1185+ <paraStyle
1186+ name="normal"
1187+ fontName="Arial_Normal"
1188+ fontSize="10"
1189+ leading="12"/>
1190+
1191+ <paraStyle
1192+ name="bold"
1193+ fontName="Arial_Bold"
1194+ fontSize="10"
1195+ alignment="left"
1196+ leading="12"/>
1197+
1198+ <paraStyle
1199+ name="heading"
1200+ fontName="Arial_Bold"
1201+ fontSize="10"
1202+ alignment="right"
1203+ leading="12"/>
1204+
1205+ <paraStyle
1206+ name="section_heading"
1207+ fontName="Arial_Bold"
1208+ fontSize="12"
1209+ alignment="center"
1210+ leading="12"/>
1211+
1212+ <blockTableStyle id="headings_table">
1213+ <blockValign value="top" start="0,0" stop="0,-1"/>
1214+ </blockTableStyle>
1215+
1216+ <blockTableStyle id="grid">
1217+ <lineStyle kind="OUTLINE"
1218+ colorName="black" thickness="0.25"
1219+ start="0,0" stop="-1,-1" />
1220+ <blockValign value="top" start="0,0" stop="0,-1"/>
1221+ </blockTableStyle>
1222+ </metal:block>
1223+</stylesheet>
1224+
1225+<story metal:fill-slot="story">
1226+ <blockTable style="headings_table" colWidths="6cm,6cm"
1227+ alignment="center">
1228+ <tr>
1229+ <td><para style="heading" tal:content="view/worksheet_heading" /></td>
1230+ <td><para style="bold" tal:content="view/worksheet_value" /></td>
1231+ </tr>
1232+ <tr>
1233+ <td><para style="heading" tal:content="view/activity_heading" /></td>
1234+ <td><para style="bold" tal:content="view/activity_value" /></td>
1235+ </tr>
1236+ <tr>
1237+ <td><para style="heading" tal:content="view/score_heading" /></td>
1238+ <td><para style="bold" tal:content="view/score_value" /></td>
1239+ </tr>
1240+ </blockTable>
1241+
1242+ <spacer length=".5cm" />
1243+ <para style="bold" tal:content="view/heading_message" />
1244+
1245+ <spacer length=".5cm" />
1246+ <blockTable style="grid" colWidths="3cm,6cm,6cm,4cm"
1247+ alignment="left">
1248+ <tr>
1249+ <td><para style="bold" tal:content="view/name_heading" /></td>
1250+ <td><para style="bold" tal:content="view/course_heading" /></td>
1251+ <td><para style="bold" tal:content="view/teacher_heading" /></td>
1252+ <td><para style="bold" tal:content="view/grade_heading" /></td>
1253+ </tr>
1254+ </blockTable>
1255+ <spacer length=".5cm" />
1256+
1257+ <tal:block repeat="student view/students">
1258+
1259+ <para style="bold" tal:content="student/name" />
1260+
1261+ <blockTable style="headings_table" colWidths="3cm,6cm,6cm,4cm"
1262+ alignment="left">
1263+ <tr tal:repeat="row student/rows">
1264+ <td />
1265+ <td><para style="normal" tal:content="row/course" /></td>
1266+ <td><para style="normal" tal:content="row/teacher" /></td>
1267+ <td><para style="normal" tal:content="row/grade" /></td>
1268+ </tr>
1269+ </blockTable>
1270+ <spacer length=".5cm" />
1271+ </tal:block>
1272+
1273+ <condPageBreak height="88cm"/>
1274+
1275+</story>
1276+
1277+</document>
1278+
1279
1280=== modified file 'src/schooltool/gradebook/browser/gradebook.css'
1281--- src/schooltool/gradebook/browser/gradebook.css 2009-07-06 08:33:03 +0000
1282+++ src/schooltool/gradebook/browser/gradebook.css 2009-10-12 08:13:10 +0000
1283@@ -204,3 +204,7 @@
1284 padding-top: 0.5ex;
1285 }
1286
1287+.scoresystem_add_input {
1288+ width: 9em;
1289+}
1290+
1291
1292=== modified file 'src/schooltool/gradebook/browser/gradebook.py'
1293--- src/schooltool/gradebook/browser/gradebook.py 2009-08-26 02:49:24 +0000
1294+++ src/schooltool/gradebook/browser/gradebook.py 2009-10-12 08:13:10 +0000
1295@@ -25,6 +25,7 @@
1296 import datetime
1297 import decimal
1298
1299+from zope.app.container.interfaces import INameChooser
1300 from zope.app.keyreference.interfaces import IKeyReference
1301 from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
1302 from zope.component import queryUtility
1303@@ -46,6 +47,8 @@
1304 from schooltool.course.interfaces import ILearner, IInstructor
1305 from schooltool.gradebook import interfaces
1306 from schooltool.gradebook.activity import ensureAtLeastOneWorksheet
1307+from schooltool.gradebook.activity import createSourceString, getSourceObj
1308+from schooltool.gradebook.activity import Worksheet, LinkedColumnActivity
1309 from schooltool.gradebook.browser.report_utils import buildHTMLParagraphs
1310 from schooltool.person.interfaces import IPerson
1311 from schooltool.requirement.scoresystem import UNSCORED
1312@@ -63,6 +66,7 @@
1313 DISCRETE_SCORE_SYSTEM = 'd'
1314 RANGED_SCORE_SYSTEM = 'r'
1315 COMMENT_SCORE_SYSTEM = 'c'
1316+SUMMARY_TITLE = _('Summary')
1317
1318 column_keys = [('total', _("Total")), ('average', _("Ave."))]
1319
1320@@ -171,6 +175,8 @@
1321 gradebook = proxy.removeSecurityProxy(self.context)
1322 worksheet = gradebook.getCurrentWorksheet(person)
1323 for activity in gradebook.getWorksheetActivities(worksheet):
1324+ if interfaces.ILinkedColumnActivity.providedBy(activity):
1325+ continue
1326 ss = activity.scoresystem
1327 if IDiscreteValuesScoreSystem.providedBy(ss):
1328 result = [DISCRETE_SCORE_SYSTEM] + [score[0]
1329@@ -387,16 +393,16 @@
1330 self.message = _(
1331 'Invalid scores (highlighted in red) were not saved.')
1332 continue
1333- ev = gradebook.getEvaluation(student, activity)
1334+ value, ss = gradebook.getEvaluation(student, activity)
1335 # Delete the score
1336- if ev is not None and score is UNSCORED:
1337+ if value is not None and score is UNSCORED:
1338 self.context.removeEvaluation(student, activity)
1339 self.changed = True
1340 # Do nothing
1341- elif ev is None and score is UNSCORED:
1342+ elif value is None and score is UNSCORED:
1343 continue
1344 # Replace the score or add new one/
1345- elif ev is None or score != ev.value:
1346+ elif value is None or score != value:
1347 self.changed = True
1348 self.context.evaluate(
1349 student, activity, score, evaluator)
1350@@ -415,30 +421,65 @@
1351 flag, weeks = self.context.getDueDateFilter(self.person)
1352 return weeks
1353
1354+ def getActivityAttrs(self, activity):
1355+ shortTitle = activity.label
1356+ if shortTitle is None or len(shortTitle) == 0:
1357+ shortTitle = activity.title
1358+ shortTitle = shortTitle.replace(' ', '')
1359+ if len(shortTitle) > 5:
1360+ shortTitle = shortTitle[:5].strip()
1361+ longTitle = activity.title
1362+ if ICommentScoreSystem.providedBy(activity.scoresystem):
1363+ bestScore = ''
1364+ else:
1365+ bestScore = activity.scoresystem.getBestScore()
1366+ return shortTitle, longTitle, bestScore
1367+
1368 def activities(self):
1369 """Get a list of all activities."""
1370 self.person = IPerson(self.request.principal)
1371- result = []
1372+ results = []
1373 for activity in self.getFilteredActivities():
1374- shortTitle = activity.label
1375- if shortTitle is None or len(shortTitle) == 0:
1376- shortTitle = activity.title
1377- shortTitle = shortTitle.replace(' ', '')
1378- if len(shortTitle) > 5:
1379- shortTitle = shortTitle[:5].strip()
1380-
1381- if ICommentScoreSystem.providedBy(activity.scoresystem):
1382- bestScore = ''
1383+ if interfaces.ILinkedColumnActivity.providedBy(activity):
1384+ scorable = False
1385+ source = getSourceObj(activity.source)
1386+ if interfaces.IActivity.providedBy(source):
1387+ shortTitle, longTitle, bestScore = \
1388+ self.getActivityAttrs(source)
1389+ if source.label is not None and len(source.label):
1390+ shortTitle = source.label
1391+ if source.title is not None and len(source.title):
1392+ longTitle = source.title
1393+ elif interfaces.IWorksheet.providedBy(source):
1394+ shortTitle = source.title
1395+ if len(shortTitle) > 5:
1396+ shortTitle = shortTitle[:5].strip()
1397+ longTitle = source.title
1398+ bestScore = '100'
1399+ else:
1400+ shortTitle = longTitle = bestScore = ''
1401 else:
1402- bestScore = activity.scoresystem.getBestScore()
1403- result.append({'shortTitle': shortTitle,
1404- 'longTitle': activity.title,
1405- 'max': bestScore,
1406- 'hash': hash(IKeyReference(activity))})
1407-
1408- return result
1409+ scorable = not ICommentScoreSystem.providedBy(
1410+ activity.scoresystem)
1411+ shortTitle, longTitle, bestScore = \
1412+ self.getActivityAttrs(activity)
1413+ result = {
1414+ 'scorable': scorable,
1415+ 'shortTitle': shortTitle,
1416+ 'longTitle': longTitle,
1417+ 'max': bestScore,
1418+ 'hash': hash(IKeyReference(activity)),
1419+ }
1420+ results.append(result)
1421+ return results
1422+
1423+ def scorableActivities(self):
1424+ """Get a list of those activities that can be scored."""
1425+ return [result for result in self.activities() if result['scorable']]
1426
1427 def isFiltered(self, activity):
1428+ if interfaces.ILinkedColumnActivity.providedBy(activity):
1429+ return False
1430 flag, weeks = self.context.getDueDateFilter(self.person)
1431 if not flag:
1432 return False
1433@@ -450,6 +491,22 @@
1434 return[activity for activity in activities
1435 if not self.isFiltered(activity)]
1436
1437+ def getStudentActivityValue(self, student, activity):
1438+ gradebook = proxy.removeSecurityProxy(self.context)
1439+ value, ss = gradebook.getEvaluation(student, activity)
1440+ if value is None:
1441+ value = ''
1442+
1443+ act_hash = hash(IKeyReference(activity))
1444+ cell_name = '%s_%s' % (act_hash, student.username)
1445+ if cell_name in self.request:
1446+ value = self.request[cell_name]
1447+
1448+ if value and ICommentScoreSystem.providedBy(activity.scoresystem):
1449+ value = '...'
1450+
1451+ return value
1452+
1453 def table(self):
1454 """Generate the table of grades."""
1455 gradebook = proxy.removeSecurityProxy(self.context)
1456@@ -460,23 +517,12 @@
1457 for student in self.context.students:
1458 grades = []
1459 for act_hash, activity in activities:
1460- ev = gradebook.getEvaluation(student, activity)
1461- if ev is not None and ev.value is not UNSCORED:
1462- value = ev.value
1463- else:
1464- value = ''
1465-
1466- cell_name = '%s_%s' % (act_hash, student.username)
1467- if cell_name in self.request:
1468- value = self.request[cell_name]
1469-
1470- ss = activity.scoresystem
1471- if ICommentScoreSystem.providedBy(ss):
1472+ value = self.getStudentActivityValue(student, activity)
1473+ if interfaces.ILinkedColumnActivity.providedBy(activity):
1474 editable = False
1475- if value:
1476- value = '...'
1477 else:
1478- editable = True
1479+ editable = not ICommentScoreSystem.providedBy(
1480+ activity.scoresystem)
1481
1482 grade = {
1483 'activity': act_hash,
1484@@ -593,12 +639,12 @@
1485 def grades(self):
1486 gradebook = proxy.removeSecurityProxy(self.context)
1487 for student in self.context.students:
1488- ev = gradebook.getEvaluation(student, self.activity['obj'])
1489- value = self.request.get(student.username)
1490- if ev is not None and ev.value is not UNSCORED:
1491- value = value or ev.value
1492+ reqValue = self.request.get(student.username)
1493+ value, ss = gradebook.getEvaluation(student, self.activity['obj'])
1494+ if value is None:
1495+ value = reqValue or ''
1496 else:
1497- value = value or ''
1498+ value = reqValue or value
1499
1500 yield {'student': {'title': student.title, 'id': student.username},
1501 'value': value}
1502@@ -629,15 +675,15 @@
1503 'name': student.title})
1504 self.messages.append(message)
1505 continue
1506- ev = gradebook.getEvaluation(student, activity)
1507+ value, ss = gradebook.getEvaluation(student, activity)
1508 # Delete the score
1509- if ev is not None and score is UNSCORED:
1510+ if value is not None and score is UNSCORED:
1511 self.context.removeEvaluation(student, activity)
1512 # Do nothing
1513- elif ev is None and score is UNSCORED:
1514+ elif value is None and score is UNSCORED:
1515 continue
1516 # Replace the score or add new one/
1517- elif ev is None or score != ev.value:
1518+ elif value is None or score != value:
1519 self.context.evaluate(
1520 student, activity, score, evaluator)
1521
1522@@ -653,17 +699,6 @@
1523 return (0, 0)
1524
1525
1526-def getEvaluationDiscreteValue(ev):
1527- ss = ev.requirement.scoresystem
1528- if IDiscreteValuesScoreSystem.providedBy(ss):
1529- val = ss.getNumericalValue(ev.value)
1530- if val is not None:
1531- return val
1532- elif IRangedValuesScoreSystem.providedBy(ss):
1533- return ev.value
1534- return 0
1535-
1536-
1537 class MyGradesView(SectionFinder):
1538 """Student view of own grades."""
1539
1540@@ -693,19 +728,20 @@
1541 count = 0
1542 for activity in self.context.getCurrentActivities(self.person):
1543 activity = proxy.removeSecurityProxy(activity)
1544- ev = proxy.removeSecurityProxy(
1545- self.context.getEvaluation(self.person, activity))
1546+ value, ss = self.context.getEvaluation(self.person, activity)
1547
1548- if ev is not None and ev.value is not UNSCORED:
1549- ss = ev.requirement.scoresystem
1550+ if value is not None:
1551 if IValuesScoreSystem.providedBy(ss):
1552- grade = '%s / %s' % (ev.value, ss.getBestScore())
1553+ grade = '%s / %s' % (value, ss.getBestScore())
1554 s_min, s_max = getScoreSystemDiscreteValues(ss)
1555- value = getEvaluationDiscreteValue(ev)
1556+ if IDiscreteValuesScoreSystem.providedBy(ss):
1557+ value = ss.getNumericalValue(value)
1558+ if value is None:
1559+ value = 0
1560 total += value - s_min
1561 count += s_max - s_min
1562 else:
1563- grade = ev.value
1564+ grade = value
1565 else:
1566 grade = None
1567
1568@@ -754,6 +790,65 @@
1569 class GradebookColumnPreferences(BrowserView):
1570 """A view for editing a teacher's gradebook column preferences."""
1571
1572+ def worksheets(self):
1573+ results = []
1574+ gradebook = proxy.removeSecurityProxy(self.context)
1575+ for worksheet in gradebook.context.__parent__.values():
1576+ if worksheet.deployed:
1577+ continue
1578+ results.append(worksheet)
1579+ return results
1580+
1581+ def addSummary(self):
1582+ gradebook = proxy.removeSecurityProxy(self.context)
1583+ worksheets = gradebook.context.__parent__
1584+
1585+ overwrite = self.request.get('overwrite', '') == 'on'
1586+ if overwrite:
1587+ currentWorksheets = []
1588+ for worksheet in worksheets.values():
1589+ if worksheet.deployed:
1590+ continue
1591+ if worksheet.title == SUMMARY_TITLE:
1592+ while len(worksheet.values()):
1593+ del worksheet[worksheet.values()[0].__name__]
1594+ summary = worksheet
1595+ else:
1596+ currentWorksheets.append(worksheet)
1597+ next = SUMMARY_TITLE
1598+ else:
1599+ next = self.nextSummaryTitle()
1600+ currentWorksheets = self.worksheets()
1601+ summary = Worksheet(next)
1602+ chooser = INameChooser(worksheets)
1603+ name = chooser.chooseName('', summary)
1604+ worksheets[name] = summary
1605+
1606+ for worksheet in currentWorksheets:
1607+ if worksheet.title.startswith(SUMMARY_TITLE):
1608+ continue
1609+ activity = LinkedColumnActivity(worksheet.title, u'assignment',
1610+ '', createSourceString(worksheet))
1611+ chooser = INameChooser(summary)
1612+ name = chooser.chooseName('', activity)
1613+ summary[name] = activity
1614+
1615+ def nextSummaryTitle(self):
1616+ index = 1
1617+ next = SUMMARY_TITLE
1618+ while True:
1619+ for worksheet in self.worksheets():
1620+ if worksheet.title == next:
1621+ break
1622+ else:
1623+ break
1624+ index += 1
1625+ next = SUMMARY_TITLE + str(index)
1626+ return next
1627+
1628+ def summaryFound(self):
1629+ return self.nextSummaryTitle() != SUMMARY_TITLE
1630+
1631 def update(self):
1632 self.person = IPerson(self.request.principal)
1633 gradebook = proxy.removeSecurityProxy(self.context)
1634@@ -774,7 +869,10 @@
1635 prefs['scoresystem'] = self.request['scoresystem_' + key]
1636 gradebook.setColumnPreferences(self.person, columnPreferences)
1637
1638- if 'CANCEL' in self.request or 'UPDATE_SUBMIT' in self.request:
1639+ if 'ADD_SUMMARY' in self.request:
1640+ self.addSummary()
1641+
1642+ if 'form-submitted' in self.request:
1643 self.request.response.redirect('index.html')
1644
1645 @property
1646@@ -960,20 +1058,21 @@
1647 self.person = IPerson(self.request.principal)
1648 gradebook = proxy.removeSecurityProxy(self.context.gradebook)
1649
1650- self.student = '%s %s' % (self.context.student.first_name,
1651- self.context.student.last_name)
1652- self.worksheet = gradebook.context.title
1653- self.section = '%s - %s' % (list(gradebook.section.courses)[0].title,
1654- gradebook.section.title)
1655+ mapping = {
1656+ 'worksheet': gradebook.context.title,
1657+ 'student': '%s %s' % (self.context.student.first_name,
1658+ self.context.student.last_name),
1659+ 'section': '%s - %s' % (list(gradebook.section.courses)[0].title,
1660+ gradebook.section.title),
1661+ }
1662+ self.title = _('$worksheet for $student in $section', mapping=mapping)
1663
1664 self.blocks = []
1665 activities = [activity for activity in gradebook.context.values()
1666 if not self.isFiltered(activity)]
1667 for activity in activities:
1668- ev = gradebook.getEvaluation(self.context.student, activity)
1669- if ev is not None and ev.value is not UNSCORED:
1670- value = ev.value
1671- else:
1672+ value, ss = gradebook.getEvaluation(self.context.student, activity)
1673+ if value is None:
1674 value = ''
1675 if ICommentScoreSystem.providedBy(activity.scoresystem):
1676 block = {
1677
1678=== modified file 'src/schooltool/gradebook/browser/gradebook_column_preferences.pt'
1679--- src/schooltool/gradebook/browser/gradebook_column_preferences.pt 2009-05-14 10:35:19 +0000
1680+++ src/schooltool/gradebook/browser/gradebook_column_preferences.pt 2009-10-12 08:13:10 +0000
1681@@ -5,20 +5,20 @@
1682 </head>
1683 <body>
1684
1685-<h1 metal:fill-slot="content-header"
1686- i18n:translate="">Set Column Preferences</h1>
1687-
1688 <metal:block metal:fill-slot="body">
1689- <form method="post"
1690+ <form method="post" class="addscoresystem"
1691 tal:attributes="action string:${context/@@absolute_url}/column_preferences.html">
1692 <input type="hidden" name="form-submitted" value="" />
1693
1694- <table class="schooltool_gradebook">
1695+ <h3 i18n:translate="">Set Column Preferences</h3>
1696+ <br />
1697+
1698+ <table class="schooltool_gradebook" style="margin-left: 1em">
1699 <tr>
1700- <th class="cell header fully_padded">Column Type</th>
1701- <th class="cell header fully_padded">Hide?</th>
1702- <th class="cell header fully_padded">Label</th>
1703- <th class="cell header fully_padded">Score System</th>
1704+ <th class="cell header fully_padded" i18n:translate="">Column Type</th>
1705+ <th class="cell header fully_padded" i18n:translate="">Hide?</th>
1706+ <th class="cell header fully_padded" i18n:translate="">Label</th>
1707+ <th class="cell header fully_padded" i18n:translate="">Score System</th>
1708 </tr>
1709
1710 <tal:block repeat="column view/columns">
1711@@ -59,6 +59,27 @@
1712 <input type="submit" class="button-ok" name="UPDATE_SUBMIT" value="Update" />
1713 <input type="submit" class="button-cancel" name="CANCEL" value="Cancel" />
1714 </div>
1715+
1716+ <fieldset>
1717+ <legend><b i18n:translate="">Summary</b><legend>
1718+ <p i18n:translate="">
1719+ You can add a summary worksheet to each of your gradebooks that automatically includes a column for the average of all existing worksheets.
1720+ </p>
1721+ <p i18n:translate="">
1722+ For example, if you have three worksheets in a section, "A," "B," and "C," and click "Create Summary Worksheets," an additional sheet, entitled "Summary" will be created, which includes the average scores from "A," "B" and "C" in the first three columns.
1723+ </p>
1724+ <tal:block tal:condition="view/summaryFound">
1725+ <input type="checkbox" name="overwrite">
1726+ <span i18n:translate="">Overwrite existing worksheets titled "Summary".</span>
1727+ </tal:block>
1728+
1729+ <br />
1730+ <div class="controls">
1731+ <input type="submit" class="button-ok" name="ADD_SUMMARY" value="Create Summary Worksheets"
1732+ i18n:attributes="value" />
1733+ </div>
1734+ </fieldset>
1735+
1736 </form>
1737
1738 </metal:block>
1739
1740=== modified file 'src/schooltool/gradebook/browser/gradebook_grade_activity.pt'
1741--- src/schooltool/gradebook/browser/gradebook_grade_activity.pt 2009-07-06 03:19:53 +0000
1742+++ src/schooltool/gradebook/browser/gradebook_grade_activity.pt 2009-10-12 08:13:10 +0000
1743@@ -95,7 +95,8 @@
1744 </table>
1745
1746 <div class="controls">
1747- <input type="submit" class="button-ok" name="UPDATE_SUBMIT" value="Save" />
1748+ <input type="submit" class="button-ok" name="UPDATE_SUBMIT" value="Save"
1749+ i18n:attributes="value" />
1750 <tal:block metal:use-macro="view/@@standard_macros/cancel-button" />
1751 </div>
1752
1753
1754=== modified file 'src/schooltool/gradebook/browser/gradebook_overview.js.pt'
1755--- src/schooltool/gradebook/browser/gradebook_overview.js.pt 2009-07-10 22:27:33 +0000
1756+++ src/schooltool/gradebook/browser/gradebook_overview.js.pt 2009-10-12 08:13:10 +0000
1757@@ -6,7 +6,7 @@
1758 students[<tal:block replace="repeat/row/index"/>] = '<tal:block replace="python: view.breakJSString(row['student']['id'])"/>';
1759 </tal:loop>
1760
1761-var numactivities = <tal:block replace="python: len(view.activities())"/>
1762+var numactivities = <tal:block replace="python: len(view.scorableActivities())"/>
1763 var activities = new Array(numactivities);
1764 <tal:loop repeat="activity view/activities">
1765 activities[<tal:block replace="repeat/activity/index"/>] = '<tal:block replace="activity/hash"/>';
1766
1767=== modified file 'src/schooltool/gradebook/browser/gradebook_overview.pt'
1768--- src/schooltool/gradebook/browser/gradebook_overview.pt 2009-07-14 01:24:47 +0000
1769+++ src/schooltool/gradebook/browser/gradebook_overview.pt 2009-10-12 08:13:10 +0000
1770@@ -13,7 +13,7 @@
1771 <body>
1772 <div metal:fill-slot="body">
1773
1774- <h1 style="color: red" id="javascript-warning">
1775+ <h1 style="color: red" id="javascript-warning" i18n:translate="">
1776 Your browser either has Javascript turned off, or it is not supported.
1777 Javascript is <b>required</b> for the gradebook.
1778 Please enable Javascript or use a browser with Javascript.
1779@@ -27,7 +27,7 @@
1780 tal:attributes="action string:${context/@@absolute_url}">
1781
1782 <tal:if condition="python:not list(view.getSections())">
1783- <b>Term:</b>
1784+ <b i18n:translate="">Term:</b>
1785 <span tal:content="view/getCurrentTerm" />
1786 </tal:if>
1787 <tal:if condition="python:list(view.getTerms())">
1788@@ -49,7 +49,7 @@
1789
1790 <span>&nbsp;&nbsp;&nbsp;&nbsp;</span>
1791 <tal:if condition="python:not list(view.getSections())">
1792- <b>Section:</b>
1793+ <b i18n:translate="">Section:</b>
1794 <span tal:content="view/getCurrentSection" />
1795 </tal:if>
1796 <tal:if condition="python:list(view.getSections())">
1797@@ -74,7 +74,7 @@
1798 tal:condition="view/getDueDateFilter" />
1799 <input type="checkbox" name="due_date:list"
1800 tal:condition="not: view/getDueDateFilter" />
1801- <span>show only activities due in past</span>
1802+ <span i18n:translate="">show only activities due in past</span>
1803 <select name="num_weeks">
1804 <tal:block repeat="weeks view/weeksChoices">
1805 <option value="1"
1806@@ -87,7 +87,7 @@
1807 tal:content="weeks" />
1808 </tal:block>
1809 </select>
1810- <span>weeks</span>
1811+ <span i18n:translate="">weeks</span>
1812 <br /><br />
1813
1814 <div class="message" style="color:red"
1815@@ -135,12 +135,14 @@
1816 onmouseout string:restoreDescription()">
1817 <div class="padded">
1818 <a href=""
1819+ tal:condition="activity/scorable"
1820 tal:attributes="href
1821 string:gradeActivity.html?activity=${activity/hash};
1822 title activity/longTitle;"
1823- tal:content="activity/shortTitle">
1824- HW 1
1825- </a>
1826+ tal:content="activity/shortTitle" />
1827+ <span
1828+ tal:condition="not: activity/scorable"
1829+ tal:content="activity/shortTitle" />
1830 </div>
1831 <a href="?sort_by="
1832 tal:attributes="href
1833@@ -159,6 +161,7 @@
1834 tal:attributes="id string:fd_${activity/hash}_cell">
1835 <input type="text" size="4" class="data fd"
1836 onkeydown="spreadsheetBehaviour(event)"
1837+ tal:condition="activity/scorable"
1838 tal:attributes="id string:fd_${activity/hash};
1839 onkeyup string:checkFillDown('${activity/hash}');
1840 onfocus string:handleCellFocus(this, '${activity/hash}')">
1841@@ -166,6 +169,7 @@
1842 style="display: none"
1843 class="fdbtn"
1844 value="Apply"
1845+ tal:condition="activity/scorable"
1846 tal:attributes="id string:fdbtn_${activity/hash};
1847 onclick string:performFillDown('${activity/hash}')">
1848 </td>
1849@@ -196,7 +200,7 @@
1850 </td>
1851 <td class="cell even" tal:repeat="grade row/grades"
1852 tal:attributes="id string:${grade/activity}_${row/student/id}_cell">
1853- <b tal:condition="not: grade/editable"
1854+ <span tal:condition="not: grade/editable"
1855 tal:content="grade/value" />
1856 <input class="data" type="text" name="" value="" size="4"
1857 tal:condition="grade/editable"
1858@@ -232,7 +236,7 @@
1859 </td>
1860 <td class="cell odd" tal:repeat="grade row/grades"
1861 tal:attributes="id string:${grade/activity}_${row/student/id}_cell">
1862- <b tal:condition="not: grade/editable"
1863+ <span tal:condition="not: grade/editable"
1864 tal:content="grade/value" />
1865 <input class="data" type="text" name="" value="" size="4"
1866 tal:condition="grade/editable"
1867@@ -249,7 +253,7 @@
1868
1869 <div class="controls">
1870 <input type="submit" class="button-ok" name="UPDATE_SUBMIT" value="Save"
1871- onclick="setNotEdited()"/>
1872+ onclick="setNotEdited()" i18n:attributes="value" />
1873 </div>
1874
1875 </form>
1876
1877=== modified file 'src/schooltool/gradebook/browser/gradebook_startup.pt'
1878--- src/schooltool/gradebook/browser/gradebook_startup.pt 2008-04-11 17:31:55 +0000
1879+++ src/schooltool/gradebook/browser/gradebook_startup.pt 2009-10-12 08:13:10 +0000
1880@@ -10,14 +10,14 @@
1881 <div metal:fill-slot="body">
1882
1883 <tal:block condition="python:not view.sectionsTaught and not view.sectionsAttended">
1884- <h1>You do not teach or attend any classes.</h1>
1885+ <h1 i18n:translate="">You do not teach or attend any classes.</h1>
1886 </tal:block>
1887 <tal:block condition="view/sectionsTaught">
1888- <a class="navigation_header"
1889+ <a class="navigation_header" i18n:translate=""
1890 tal:attributes="href view/gradebookURL">Classes you teach</a>
1891 </tal:block>
1892 <tal:block condition="view/sectionsAttended">
1893- <a class="navigation_header"
1894+ <a class="navigation_header" i18n:translate=""
1895 tal:attributes="href view/mygradesURL">Classes you attend</a>
1896 </tal:block>
1897
1898
1899=== modified file 'src/schooltool/gradebook/browser/layout_report_card.pt'
1900--- src/schooltool/gradebook/browser/layout_report_card.pt 2009-08-11 04:06:14 +0000
1901+++ src/schooltool/gradebook/browser/layout_report_card.pt 2009-10-12 08:13:10 +0000
1902@@ -10,16 +10,16 @@
1903 tal:attributes="action string:${context/@@absolute_url}/layout_report_card.html">
1904 <input type="hidden" name="form-submitted" value="" />
1905
1906- <h3>Layout Report Card</h3>
1907+ <h3 i18n:translate="">Report Card Layout</h3>
1908
1909 <fieldset>
1910- <legend><b>Grid Columns</b><legend>
1911+ <legend><b i18n:translate="">Grid Columns</b><legend>
1912 <table>
1913 <tr>
1914 <td></td>
1915- <td align="center">Source</td>
1916- <td align="center">Heading</td>
1917- <td>Remove?</td>
1918+ <td align="center" i18n:translate="">Source</td>
1919+ <td align="center" i18n:translate="">Heading</td>
1920+ <td i18n:translate="">Remove?</td>
1921 </tr>
1922
1923 <tal:block repeat="column view/columns">
1924@@ -58,7 +58,7 @@
1925 </td>
1926 <td>
1927 <select id="new_source" name="new_source">
1928- <option selected value="">Choose a column to add</option>
1929+ <option selected value="" i18n:translate="">Choose a column to add</option>
1930 <tal:block repeat="choice view/column_choices">
1931 <option tal:attributes="value choice/value"
1932 tal:content="choice/name" />
1933@@ -74,13 +74,13 @@
1934 </fieldset>
1935
1936 <fieldset>
1937- <legend><b>Outline Activities</b><legend>
1938+ <legend><b i18n:translate="">Outline Activities</b><legend>
1939 <table>
1940 <tr>
1941 <td></td>
1942- <td align="center">Source</td>
1943- <td align="center">Heading</td>
1944- <td>Remove?</td>
1945+ <td align="center" i18n:translate="">Source</td>
1946+ <td align="center" i18n:translate="">Heading</td>
1947+ <td i18n:translate="">Remove?</td>
1948 </tr>
1949
1950 <tal:block repeat="activity view/outline_activities">
1951@@ -119,7 +119,7 @@
1952 </td>
1953 <td>
1954 <select id="new_activity_source" name="new_activity_source">
1955- <option selected value="">Choose an activity to add</option>
1956+ <option selected value="" i18n:translate="">Choose an activity to add</option>
1957 <tal:block repeat="choice view/activity_choices">
1958 <option tal:attributes="value choice/value"
1959 tal:content="choice/name" />
1960@@ -135,7 +135,10 @@
1961 </fieldset>
1962
1963 <div class="controls">
1964- <input type="submit" class="button-ok" name="Update" value="Update" />
1965+ <input type="submit" class="button-ok" name="Update" value="Save"
1966+ i18n:attributes="value" />
1967+ <input type="submit" class="button-ok" name="OK" value="OK"
1968+ i18n:attributes="value" />
1969 </div>
1970 </form>
1971
1972
1973=== modified file 'src/schooltool/gradebook/browser/mygrades.pt'
1974--- src/schooltool/gradebook/browser/mygrades.pt 2009-06-19 09:39:10 +0000
1975+++ src/schooltool/gradebook/browser/mygrades.pt 2009-10-12 08:13:12 +0000
1976@@ -12,7 +12,7 @@
1977 <form method="post"
1978 tal:attributes="action string:${context/@@absolute_url}">
1979 <tal:if condition="python:not list(view.getSections())">
1980- <b>Term:</b>
1981+ <b i18n:translate="">Term:</b>
1982 <span tal:content="view/getCurrentTerm" />
1983 </tal:if>
1984 <tal:if condition="python:list(view.getTerms())">
1985@@ -34,7 +34,7 @@
1986
1987 <span>&nbsp;&nbsp;&nbsp;&nbsp;</span>
1988 <tal:if condition="python:not list(view.getSections())">
1989- <b>Section:</b>
1990+ <b i18n:translate="">Section:</b>
1991 <span tal:content="view/getCurrentSection" />
1992 </tal:if>
1993 <tal:if condition="python:list(view.getSections())">
1994@@ -76,7 +76,7 @@
1995 <td colspan="2" class="gradebook_description">
1996 <div tal:condition="view/average"
1997 tal:content="string: ${view/average_label}: ${view/average}">Average</div>
1998- <div tal:condition="not: view/average">Nothing Graded</div>
1999+ <div tal:condition="not: view/average" i18n:translate="">Nothing Graded</div>
2000 </td>
2001 </tr>
2002 <tr>
2003
2004=== modified file 'src/schooltool/gradebook/browser/no_current_term.pt'
2005--- src/schooltool/gradebook/browser/no_current_term.pt 2009-06-08 01:43:13 +0000
2006+++ src/schooltool/gradebook/browser/no_current_term.pt 2009-10-12 08:13:12 +0000
2007@@ -8,7 +8,7 @@
2008 </head>
2009 <body>
2010 <div metal:fill-slot="body">
2011- <h1>The operation you attempted cannot be completed because there are curently
2012+ <h1 i18n:translate="">The operation you attempted cannot be completed because there are curently
2013 no terms set up in your SchoolTool instance. Please have a user with
2014 administation access set up at least one term and try the operation again.</h1>
2015 </div>
2016
2017=== modified file 'src/schooltool/gradebook/browser/no_current_term.txt'
2018--- src/schooltool/gradebook/browser/no_current_term.txt 2009-06-25 05:00:11 +0000
2019+++ src/schooltool/gradebook/browser/no_current_term.txt 2009-10-12 08:13:12 +0000
2020@@ -19,7 +19,8 @@
2021 >>> manager.getLink('Manage').click()
2022 >>> manager.getLink('Persons').click()
2023 >>> manager.getLink('One').click()
2024- >>> manager.getLink('Print Report Card').click()
2025+ >>> manager.getLink('Reports').click()
2026+ >>> manager.getLink('Download Report Card').click()
2027 >>> manager.url
2028 'http://localhost/no_current_term.html'
2029
2030
2031=== modified file 'src/schooltool/gradebook/browser/pdf_views.py'
2032--- src/schooltool/gradebook/browser/pdf_views.py 2009-08-11 04:06:14 +0000
2033+++ src/schooltool/gradebook/browser/pdf_views.py 2009-10-12 08:13:12 +0000
2034@@ -20,6 +20,8 @@
2035 PDF Views
2036 """
2037
2038+from datetime import datetime
2039+
2040 from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
2041 from zope.component import getUtility
2042 from zope.traversing.browser.absoluteurl import absoluteURL
2043@@ -28,8 +30,11 @@
2044 from schooltool.app.browser.report import ReportPDFView
2045 from schooltool.common import SchoolToolMessage as _
2046 from schooltool.course.interfaces import ILearner
2047+from schooltool.gradebook.browser.report_card import (ABSENT_HEADING,
2048+ TARDY_HEADING, ABSENT_KEY, TARDY_KEY)
2049 from schooltool.gradebook.browser.report_utils import buildHTMLParagraphs
2050 from schooltool.gradebook.interfaces import IGradebookRoot, IActivities
2051+from schooltool.lyceum.journal.interfaces import ISectionJournalData
2052 from schooltool.requirement.interfaces import IEvaluations
2053 from schooltool.requirement.scoresystem import UNSCORED
2054 from schooltool.schoolyear.interfaces import ISchoolYear
2055@@ -37,20 +42,47 @@
2056
2057
2058 class BasePDFView(ReportPDFView):
2059- """The report card (PDF) base class"""
2060-
2061- template=ViewPageTemplateFile('report_card_rml.pt')
2062-
2063- def __call__(self):
2064- """Make sure there is a current term."""
2065- current_term = getUtility(IDateManager).current_term
2066- if current_term is None:
2067+ """A base class for all PDF views"""
2068+
2069+ def noCurrentTerm(self):
2070+ self.current_term = getUtility(IDateManager).current_term
2071+ if self.current_term is None:
2072 next_url = absoluteURL(ISchoolToolApplication(None), self.request)
2073 next_url += '/no_current_term.html'
2074 self.request.response.redirect(next_url)
2075+ return True
2076+ self.schoolyear = ISchoolYear(self.current_term)
2077+ return False
2078+
2079+
2080+class BaseStudentPDFView(BasePDFView):
2081+ """A base class for all student PDF views"""
2082+
2083+
2084+class BaseReportCardPDFView(BaseStudentPDFView):
2085+ """The report card (PDF) base class"""
2086+
2087+ template=ViewPageTemplateFile('report_card_rml.pt')
2088+
2089+ def __call__(self):
2090+ """Make sure there is a current term."""
2091+ if self.noCurrentTerm():
2092 return
2093- self.schoolyear = ISchoolYear(current_term)
2094- return super(BasePDFView, self).__call__()
2095+ return super(BaseReportCardPDFView, self).__call__()
2096+
2097+ def isJournalSource(self, layout):
2098+ return layout.source in [ABSENT_KEY, TARDY_KEY]
2099+
2100+ def getJournalScore(self, student, section, layout):
2101+ jd = ISectionJournalData(section)
2102+ result = 0
2103+ for meeting in jd.recordedMeetings(student):
2104+ grade = jd.getGrade(student, meeting)
2105+ if grade == 'n' and layout.source == ABSENT_KEY:
2106+ result += 1
2107+ if grade == 'p' and layout.source == TARDY_KEY:
2108+ result += 1
2109+ return result or None
2110
2111 def getActivity(self, section, layout):
2112 termName, worksheetName, activityName = layout.source.split('|')
2113@@ -60,6 +92,10 @@
2114 return None
2115
2116 def getLayoutActivityHeading(self, layout, truncate=True):
2117+ if layout.source == ABSENT_KEY:
2118+ return ABSENT_HEADING
2119+ if layout.source == TARDY_KEY:
2120+ return TARDY_HEADING
2121 termName, worksheetName, activityName = layout.source.split('|')
2122 root = IGradebookRoot(ISchoolToolApplication(None))
2123 heading = root.deployed[worksheetName][activityName].title
2124@@ -117,7 +153,6 @@
2125 else:
2126 layouts = []
2127
2128- evaluations = IEvaluations(student)
2129 courses = []
2130 for section in sections:
2131 course = tuple(section.courses)
2132@@ -125,15 +160,24 @@
2133 courses.append(course)
2134
2135 scores = {}
2136+ evaluations = IEvaluations(student)
2137 for layout in layouts:
2138 byCourse = {}
2139 for section in sections:
2140- activity = self.getActivity(section, layout)
2141- if activity is None:
2142- continue
2143- score = evaluations.get(activity, None)
2144- if score is not None and score.value is not UNSCORED:
2145- byCourse[course] = unicode(score.value)
2146+ course = tuple(section.courses)
2147+ if self.isJournalSource(layout):
2148+ score = self.getJournalScore(student, section, layout)
2149+ if score is not None:
2150+ if course in byCourse:
2151+ score += int(byCourse[course])
2152+ byCourse[course] = unicode(score)
2153+ else:
2154+ activity = self.getActivity(section, layout)
2155+ if activity is None:
2156+ continue
2157+ score = evaluations.get(activity, None)
2158+ if score is not None and score.value is not UNSCORED:
2159+ byCourse[course] = unicode(score.value)
2160 if len(byCourse):
2161 scores[layout.source] = byCourse
2162
2163@@ -159,7 +203,7 @@
2164
2165 return {
2166 'headings': headings,
2167- 'widths': '8.2cm' + ',1.2cm' * len(scoredLayouts),
2168+ 'widths': '8.2cm' + ',1.6cm' * len(scoredLayouts),
2169 'rows': rows,
2170 }
2171
2172@@ -216,16 +260,342 @@
2173 return section_list
2174
2175
2176-class StudentReportCardPDFView(BasePDFView):
2177- """A view for printing a report card for a student"""
2178-
2179- def collectStudents(self):
2180- return [self.context]
2181-
2182-
2183-class GroupReportCardPDFView(BasePDFView):
2184- """A view for printing a report card for each person in a group"""
2185-
2186- def collectStudents(self):
2187- return list(self.context.members)
2188+class StudentReportCardPDFView(BaseReportCardPDFView):
2189+ """A view for printing a report card for a student"""
2190+
2191+ def collectStudents(self):
2192+ return [self.context]
2193+
2194+
2195+class GroupReportCardPDFView(BaseReportCardPDFView):
2196+ """A view for printing a report card for each person in a group"""
2197+
2198+ def collectStudents(self):
2199+ return list(self.context.members)
2200+
2201+
2202+class BaseStudentDetailPDFView(BaseStudentPDFView):
2203+ """The report card (PDF) base class"""
2204+
2205+ template=ViewPageTemplateFile('student_detail_rml.pt')
2206+
2207+ def __call__(self):
2208+ """Make sure there is a current term."""
2209+ if self.noCurrentTerm():
2210+ return
2211+ return super(BaseStudentDetailPDFView, self).__call__()
2212+
2213+ @property
2214+ def title(self):
2215+ return _('Detailed Student Report') + ': ' + self.schoolyear.title
2216+
2217+ @property
2218+ def grades_heading(self):
2219+ return _('Grade Detail')
2220+
2221+ @property
2222+ def course_heading(self):
2223+ return _('Courses')
2224+
2225+ @property
2226+ def attendance_heading(self):
2227+ return _('Attendance Detail')
2228+
2229+ @property
2230+ def date_heading(self):
2231+ return _('Dates')
2232+
2233+ @property
2234+ def name_heading(self):
2235+ return _('Student Name')
2236+
2237+ @property
2238+ def userid_heading(self):
2239+ return _('User Id')
2240+
2241+ def getGradesColumns(self):
2242+ return ['Q1', 'Q2', 'Q3', 'Q4']
2243+
2244+ def getAttendanceColumns(self):
2245+ return ['1', '2', '3', '4', '5', '6', '7', '8']
2246+
2247+ def grades(self, student):
2248+ columns = self.getGradesColumns()
2249+ widths = '4cm' + ',1cm' * len(columns)
2250+ rows = []
2251+ for sdf in range(2):
2252+ row = {
2253+ 'title': 'English I',
2254+ 'scores': ['A', '', 'C', ''],
2255+ }
2256+ rows.append(row)
2257+ return {
2258+ 'widths': widths,
2259+ 'headings': columns,
2260+ 'rows': rows,
2261+ }
2262+
2263+ def attendance(self, student):
2264+ columns = self.getAttendanceColumns()
2265+ widths = '4cm' + ',1cm' * len(columns)
2266+ rows = []
2267+ for sdf in range(2):
2268+ row = {
2269+ 'title': '9/27/09',
2270+ 'scores': ['', 'A', '', 'T', '', '', '', ''],
2271+ }
2272+ rows.append(row)
2273+ return {
2274+ 'widths': widths,
2275+ 'headings': columns,
2276+ 'rows': rows,
2277+ }
2278+
2279+ def students(self):
2280+ results = []
2281+ for student in self.collectStudents():
2282+ name = u'%s %s' % (student.first_name, student.last_name)
2283+ result = {
2284+ 'name': name,
2285+ 'userid': student.username,
2286+ 'grades': self.grades(student),
2287+ 'attendance': self.attendance(student),
2288+ }
2289+ results.append(result)
2290+ return results
2291+
2292+
2293+class StudentDetailPDFView(BaseStudentDetailPDFView):
2294+ """A view for printing a report card for a student"""
2295+
2296+ def collectStudents(self):
2297+ return [self.context]
2298+
2299+
2300+class GroupDetailPDFView(BaseStudentDetailPDFView):
2301+ """A view for printing a report card for each person in a group"""
2302+
2303+ def collectStudents(self):
2304+ return list(self.context.members)
2305+
2306+
2307+class FailingReportPDFView(BasePDFView):
2308+ """A view for printing a report of all the students failing an activity"""
2309+
2310+ template=ViewPageTemplateFile('failing_report_rml.pt')
2311+
2312+ def __call__(self):
2313+ self.schoolyear = self.context
2314+ self.activity = self.getActivity()
2315+ return super(FailingReportPDFView, self).__call__()
2316+
2317+ def getActivity(self):
2318+ source = self.request.get('activity', None)
2319+ if source is None:
2320+ return None
2321+ termName, worksheetName, activityName = source.split('|')
2322+ root = IGradebookRoot(ISchoolToolApplication(None))
2323+ return root.deployed[worksheetName][activityName]
2324+
2325+ @property
2326+ def title(self):
2327+ return _('Failures by Term Report') + ': ' + self.schoolyear.title
2328+
2329+ @property
2330+ def worksheet_heading(self):
2331+ return _('Report Sheet:')
2332+
2333+ @property
2334+ def worksheet_value(self):
2335+ return self.activity.__parent__.title
2336+
2337+ @property
2338+ def activity_heading(self):
2339+ return _('Activity:')
2340+
2341+ @property
2342+ def activity_value(self):
2343+ return self.activity.title
2344+
2345+ @property
2346+ def score_heading(self):
2347+ return _('Passing Score:')
2348+
2349+ @property
2350+ def score_value(self):
2351+ return self.request.get('min', '')
2352+
2353+ @property
2354+ def heading_message(self):
2355+ return _('The following students are at risk of failing the following courses:')
2356+
2357+ @property
2358+ def name_heading(self):
2359+ return _('Student')
2360+
2361+ @property
2362+ def course_heading(self):
2363+ return _('Course')
2364+
2365+ @property
2366+ def teacher_heading(self):
2367+ return _('Teacher(s)')
2368+
2369+ @property
2370+ def grade_heading(self):
2371+ return _('Grade')
2372+
2373+ def students(self):
2374+ return [
2375+ {
2376+ 'name': 'Alan Elkner',
2377+ 'rows': [
2378+ {
2379+ 'course': 'English I',
2380+ 'teacher': 'Tom Hoffman',
2381+ 'grade': 'D',
2382+ },
2383+ {
2384+ 'course': 'Algebra II',
2385+ 'teacher': 'Jeff Elkner',
2386+ 'grade': 'F',
2387+ },
2388+ ],
2389+ },
2390+ {
2391+ 'name': 'Jeff Elkner',
2392+ 'rows': [
2393+ {
2394+ 'course': 'English I',
2395+ 'teacher': 'Tom Hoffman',
2396+ 'grade': 'F',
2397+ },
2398+ ],
2399+ },
2400+ ]
2401+
2402+
2403+class AbsencesByDayPDFView(BasePDFView):
2404+ """A view for printing a report with those students absent on a given day"""
2405+
2406+ template=ViewPageTemplateFile('absences_by_day_rml.pt')
2407+
2408+ def __call__(self):
2409+ self.schoolyear = self.context
2410+ return super(AbsencesByDayPDFView, self).__call__()
2411+
2412+ @property
2413+ def title(self):
2414+ return _('Absences By Day Report')
2415+
2416+ def getDay(self):
2417+ day = self.request.get('day', None)
2418+ if day is None:
2419+ return datetime.date(datetime.now())
2420+ try:
2421+ year, month, day = [int(part) for part in day.split('-')]
2422+ return datetime.date(datetime(year, month, day))
2423+ except:
2424+ return None
2425+
2426+ @property
2427+ def date_heading(self):
2428+ day = self.getDay()
2429+ if day is None:
2430+ return ''
2431+ else:
2432+ return day.strftime('%A %B %0d, %Y')
2433+
2434+ @property
2435+ def periods_heading(self):
2436+ return _('Period Number')
2437+
2438+ @property
2439+ def name_heading(self):
2440+ return _('Student')
2441+
2442+ @property
2443+ def widths(self):
2444+ return '8cm' + ',1cm' * 8
2445+
2446+ @property
2447+ def periods(self):
2448+ return ['1', '2', '3', '4', '5', '6', '7', '8']
2449+
2450+ @property
2451+ def students(self):
2452+ return [
2453+ {
2454+ 'name': 'Alan Elkner',
2455+ 'periods': ['A', 'T', '', 'T', '', '', '', ''],
2456+ },
2457+ {
2458+ 'name': 'Tom Hoffman',
2459+ 'periods': ['T', '', '', 'T', 'A', '', '', ''],
2460+ },
2461+ ]
2462+
2463+
2464+class SectionAbsencesPDFView(BasePDFView):
2465+ """A view for printing a report with absences for a given section"""
2466+
2467+ template=ViewPageTemplateFile('section_absences_rml.pt')
2468+
2469+ def __call__(self):
2470+ self.section = self.context
2471+ return super(SectionAbsencesPDFView, self).__call__()
2472+
2473+ @property
2474+ def title(self):
2475+ return _('Absences by Section Report')
2476+
2477+ @property
2478+ def course_heading(self):
2479+ return _('Course')
2480+
2481+ @property
2482+ def course(self):
2483+ return ', '.join([course.title for course in self.section.courses])
2484+
2485+ @property
2486+ def teacher_heading(self):
2487+ return _('Teacher')
2488+
2489+ @property
2490+ def teacher(self):
2491+ return ', '.join(['%s %s' % (teacher.first_name, teacher.last_name)
2492+ for teacher in self.section.instructors])
2493+
2494+ @property
2495+ def student_heading(self):
2496+ return _('Student')
2497+
2498+ @property
2499+ def absences_heading(self):
2500+ return _('Absences')
2501+
2502+ @property
2503+ def tardies_heading(self):
2504+ return _('Tardies')
2505+
2506+ @property
2507+ def total_heading(self):
2508+ return _('Total')
2509+
2510+ @property
2511+ def students(self):
2512+ return [
2513+ {
2514+ 'name': 'Alan Elkner',
2515+ 'absences': '5',
2516+ 'tardies': '4',
2517+ 'total': '9',
2518+ },
2519+ {
2520+ 'name': 'Tom Hoffman',
2521+ 'absences': '',
2522+ 'tardies': '3',
2523+ 'total': '3',
2524+ },
2525+ ]
2526
2527
2528=== modified file 'src/schooltool/gradebook/browser/report_card.py'
2529--- src/schooltool/gradebook/browser/report_card.py 2009-08-11 04:06:14 +0000
2530+++ src/schooltool/gradebook/browser/report_card.py 2009-10-12 08:13:12 +0000
2531@@ -20,11 +20,12 @@
2532 Report Card Views
2533 """
2534
2535+from zope.app.component.vocabulary import UtilityVocabulary, UtilityTerm
2536 from zope.app.container.interfaces import INameChooser
2537 from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
2538 from zope.component import adapts, queryUtility
2539 from zope.interface import Interface, implements
2540-from zope.schema import Choice
2541+from zope.schema import Choice, Int
2542 from zope.security.checker import canWrite
2543 from zope.security.interfaces import Unauthorized
2544 from zope.traversing.api import getName
2545@@ -46,7 +47,14 @@
2546 from schooltool.gradebook.gradebook_init import ReportLayout, ReportColumn
2547 from schooltool.gradebook.gradebook_init import OutlineActivity
2548 from schooltool.requirement.interfaces import ICommentScoreSystem
2549+from schooltool.requirement.interfaces import IScoreSystem
2550+from schooltool.requirement.interfaces import IRangedValuesScoreSystem
2551+from schooltool.requirement.scoresystem import RangedValuesScoreSystem
2552
2553+ABSENT_HEADING = _('Absent')
2554+TARDY_HEADING = _('Tardy')
2555+ABSENT_KEY = 'absent'
2556+TARDY_KEY = 'tardy'
2557
2558 def copyActivities(sourceWorksheet, destWorksheet):
2559 """Copy the activities from the source worksheet to the destination."""
2560@@ -102,30 +110,94 @@
2561 self.context.changePosition(name, new_pos-1)
2562
2563
2564-class IExistingScoreSystem(Interface):
2565+def ReportScoreSystemsVocabulary(context):
2566+ vocab = UtilityVocabulary(context, interface=IScoreSystem)
2567+ rangedTerm = UtilityTerm('ranged', _('-- Use range below --'))
2568+ vocab._terms[rangedTerm.token] = rangedTerm
2569+ return vocab
2570+
2571+
2572+class IReportScoreSystem(Interface):
2573 """A schema used to choose an existing score system."""
2574
2575 scoresystem = Choice(
2576- title=_('Existing Score System'),
2577- vocabulary='schooltool.requirement.scoresystems',
2578+ title=_('Score System'),
2579+ description=_('Choose an existing score system or use range below'),
2580+ vocabulary='schooltool.gradebook.reportscoresystems',
2581 required=True)
2582
2583-
2584-class ExistingScoresSystem(object):
2585- implements(IExistingScoreSystem)
2586+ min = Int(
2587+ title=_("Minimum"),
2588+ description=_("Lowest integer score value possible"),
2589+ min=0,
2590+ required=False)
2591+
2592+ max = Int(
2593+ title=_("Maximum"),
2594+ description=_("Highest integer score value possible"),
2595+ min=0,
2596+ required=False)
2597+
2598+
2599+class ReportScoresSystem(object):
2600+ implements(IReportScoreSystem)
2601 adapts(IReportActivity)
2602
2603 def __init__(self, context):
2604 self.__dict__['context'] = context
2605
2606+ def isRanged(self, ss):
2607+ return (IRangedValuesScoreSystem.providedBy(ss) and
2608+ ss.title == 'generated')
2609+
2610+ def getValue(self, name, default):
2611+ if default is None:
2612+ default = 0
2613+ value = self.__dict__.get(name, default)
2614+ if value is None:
2615+ return default
2616+ return value
2617+
2618 def __setattr__(self, name, value):
2619+ self.__dict__[name] = value
2620+ ss = self.context.scoresystem
2621+
2622 if name == 'scoresystem':
2623- self.context.scoresystem = value
2624- else:
2625- setattr(self.context, name, value)
2626+ if value == 'ranged':
2627+ if self.isRanged(ss):
2628+ minimum = self.getValue('min', ss.min)
2629+ maximum = self.getValue('max', ss.max)
2630+ else:
2631+ minimum = self.getValue('min', 0)
2632+ maximum = self.getValue('max', 100)
2633+ self.context.scoresystem = RangedValuesScoreSystem(u'generated',
2634+ min=minimum, max=maximum)
2635+ else:
2636+ self.context.scoresystem = value
2637+
2638+ else: # min, max
2639+ if self.isRanged(ss):
2640+ minimum = self.getValue('min', ss.min)
2641+ maximum = self.getValue('max', ss.max)
2642+ self.context.scoresystem = RangedValuesScoreSystem(u'generated',
2643+ min=minimum, max=maximum)
2644
2645 def __getattr__(self, name):
2646- return getattr(self.context, name)
2647+ ss = self.context.scoresystem
2648+ if ss is None:
2649+ return None
2650+ rv = None
2651+ if self.isRanged(ss):
2652+ if name == 'scoresystem':
2653+ rv = 'ranged'
2654+ elif name == 'min':
2655+ rv = ss.min
2656+ elif name == 'max':
2657+ rv = ss.max
2658+ else:
2659+ if name == 'scoresystem':
2660+ rv = ss
2661+ return rv
2662
2663
2664 class ReportActivityAddView(form.AddForm):
2665@@ -135,7 +207,7 @@
2666
2667 fields = field.Fields(IReportActivity)
2668 fields = fields.select('title', 'label', 'description')
2669- fields += field.Fields(IExistingScoreSystem)
2670+ fields += field.Fields(IReportScoreSystem)
2671
2672 def updateActions(self):
2673 super(ReportActivityAddView, self).updateActions()
2674@@ -160,8 +232,19 @@
2675
2676 def create(self, data):
2677 categories = getCategories(ISchoolToolApplication(None))
2678+ if data['scoresystem'] == 'ranged':
2679+ minimum = data['min']
2680+ if minimum is None:
2681+ minimum = 0
2682+ maximum = data['max']
2683+ if maximum is None:
2684+ maximum = 100
2685+ scoresystem = RangedValuesScoreSystem(u'generated',
2686+ min=minimum, max=maximum)
2687+ else:
2688+ scoresystem = data['scoresystem']
2689 activity = ReportActivity(data['title'], categories.getDefaultKey(),
2690- data['scoresystem'], data['description'],
2691+ scoresystem, data['description'],
2692 data['label'])
2693 return activity
2694
2695@@ -183,7 +266,7 @@
2696
2697 fields = field.Fields(IReportActivity)
2698 fields = fields.select('title', 'label', 'description')
2699- fields += field.Fields(IExistingScoreSystem)
2700+ fields += field.Fields(IReportScoreSystem)
2701
2702 @button.buttonAndHandler(_("Cancel"))
2703 def handle_cancel_action(self, action):
2704@@ -310,13 +393,13 @@
2705
2706 @property
2707 def column_choices(self):
2708- return self.choices()
2709+ return self.choices(no_journal=False)
2710
2711 @property
2712 def activity_choices(self):
2713 return self.choices(no_comment=False)
2714
2715- def choices(self, no_comment=True):
2716+ def choices(self, no_comment=True, no_journal=True):
2717 """Get a list of the possible choices for layout activities."""
2718 results = []
2719 root = IGradebookRoot(ISchoolToolApplication(None))
2720@@ -338,10 +421,21 @@
2721 'value': value,
2722 }
2723 results.append(result)
2724+ if not no_journal:
2725+ result = {
2726+ 'name': ABSENT_HEADING,
2727+ 'value': ABSENT_KEY,
2728+ }
2729+ results.append(result)
2730+ result = {
2731+ 'name': TARDY_HEADING,
2732+ 'value': TARDY_KEY,
2733+ }
2734+ results.append(result)
2735 return results
2736
2737 def update(self):
2738- if 'Update' in self.request:
2739+ if 'Update' in self.request or 'OK' in self.request:
2740 columns = self.updatedColumns()
2741 outline_activities = self.updatedOutlineActivities()
2742
2743@@ -355,6 +449,9 @@
2744 layout.columns = columns
2745 layout.outline_activities = outline_activities
2746
2747+ if 'OK' in self.request:
2748+ self.request.response.redirect(self.nextURL())
2749+
2750 def updatedColumns(self):
2751 columns = []
2752 index = 1
2753@@ -397,6 +494,9 @@
2754 activities.append(column)
2755 return activities
2756
2757+ def nextURL(self):
2758+ return absoluteURL(self.context, self.request)
2759+
2760
2761 def handleSectionAdded(event):
2762 """Make sure the same worksheets are deployed to newly added sections."""
2763
2764=== modified file 'src/schooltool/gradebook/browser/report_card.txt'
2765--- src/schooltool/gradebook/browser/report_card.txt 2009-08-05 05:42:54 +0000
2766+++ src/schooltool/gradebook/browser/report_card.txt 2009-10-12 08:13:12 +0000
2767@@ -29,7 +29,7 @@
2768
2769 We'll add one.
2770
2771- >>> manager.getLink('New Report Worksheet').click()
2772+ >>> manager.getLink('New Report Sheet').click()
2773 >>> manager.getControl('Title').value = 'Test'
2774 >>> manager.getControl('Add').click()
2775
2776@@ -43,12 +43,11 @@
2777 >>> manager.getLink('Test').click()
2778 >>> analyze.printQuery("id('content-body')//a", manager.contents)
2779
2780-To add a report activity to it, we'll click on the supplied link. We need only
2781-fill in the title as the other required fields default to the first value
2782-in the vocabulary.
2783+To add a report activity to it, we'll click on the supplied link.
2784
2785 >>> manager.getLink('New Report Activity').click()
2786 >>> manager.getControl('Title').value = 'Activity 1'
2787+ >>> manager.getControl('Score System').value = ['100 Points']
2788 >>> manager.getControl('Add').click()
2789
2790 Now we see that the new report activity appears in the list.
2791@@ -57,28 +56,68 @@
2792 <a href="http://localhost/schooltool.gradebook/templates/ReportWorksheet/ReportActivity">Activity 1</a>
2793
2794 Next, we'll click on the newly added activity link to edit its score system.
2795-We see that, when it was added, it was defaulted to the first existing score
2796-system in the list.
2797+We see that it is currently set to the 100 points score system we selected
2798+when we added it.
2799
2800 >>> manager.getLink('Activity 1').click()
2801- >>> manager.getControl('Existing Score System').value
2802+ >>> manager.getControl('Score System').value
2803 ['100 Points']
2804
2805 We'll change it, apply the change, and revisit the activity to see that the
2806 change took.
2807
2808- >>> manager.getControl('Existing Score System').value = ['Letter Grade']
2809+ >>> manager.getControl('Score System').value = ['Letter Grade']
2810 >>> manager.getControl('Apply').click()
2811 >>> manager.getLink('Activity 1').click()
2812- >>> manager.getControl('Existing Score System').value
2813+ >>> manager.getControl('Score System').value
2814 ['Letter Grade']
2815
2816+Now we'll change it to use a dynamically generated ranged values score system.
2817+The min and max fields don't need to be filled in as they defualt to 0 and 100.
2818+
2819+ >>> manager.getControl('Score System').value = ['-- Use range below --']
2820+ >>> manager.getControl('Apply').click()
2821+ >>> manager.getLink('Activity 1').click()
2822+ >>> manager.getControl('Score System').value
2823+ ['-- Use range below --']
2824+ >>> manager.getControl('Minimum').value
2825+ '0'
2826+ >>> manager.getControl('Maximum').value
2827+ '100'
2828+
2829+Next we'll simply change the ranges and verify.
2830+
2831+ >>> manager.getControl('Minimum').value = '1'
2832+ >>> manager.getControl('Maximum').value = '5'
2833+ >>> manager.getControl('Apply').click()
2834+ >>> manager.getLink('Activity 1').click()
2835+ >>> manager.getControl('Score System').value
2836+ ['-- Use range below --']
2837+ >>> manager.getControl('Minimum').value
2838+ '1'
2839+ >>> manager.getControl('Maximum').value
2840+ '5'
2841+
2842+Finally, we'll make sure we can change it back to an existing score system.
2843+We'll note that, in doing so, the min and max fields are cleared out. They
2844+are only relavent when '-- Use range below --' is chosen as the score system.
2845+
2846+ >>> manager.getControl('Score System').value = ['100 Points']
2847+ >>> manager.getControl('Apply').click()
2848+ >>> manager.getLink('Activity 1').click()
2849+ >>> manager.getControl('Score System').value
2850+ ['100 Points']
2851+ >>> manager.getControl('Minimum').value
2852+ ''
2853+ >>> manager.getControl('Maximum').value
2854+ ''
2855+
2856 Later we will want to test the handling of deployment of multiple templates
2857 to a term, so here we'll set up a second template.
2858
2859 >>> manager.getLink('Manage').click()
2860 >>> manager.getLink('Report Sheet Templates').click()
2861- >>> manager.getLink('New Report Worksheet').click()
2862+ >>> manager.getLink('New Report Sheet').click()
2863 >>> manager.getControl('Title').value = 'Test2'
2864 >>> manager.getControl('Add').click()
2865 >>> manager.getLink('Test2').click()
2866@@ -110,12 +149,12 @@
2867 ... members=['Student1','Student2'])
2868
2869 We'll navigate to the 'Spring' term where we just created the two sections,
2870-and we'll click on the 'Deploy Report Worksheet' link.
2871+and we'll click on the 'Deploy Report Sheet' link.
2872
2873 >>> manager.getLink('2005-2006').click()
2874 >>> manager.getLink('Spring').click()
2875 >>> term_url = manager.url
2876- >>> manager.getLink('Deploy Report Worksheet').click()
2877+ >>> manager.getLink('Deploy Report Sheet').click()
2878
2879 Hitting the 'Cancel' button brings us back to the term view.
2880
2881@@ -127,12 +166,12 @@
2882 also bring us back to the term view. First we'll deploy the first template,
2883 then the second.
2884
2885- >>> manager.getLink('Deploy Report Worksheet').click()
2886+ >>> manager.getLink('Deploy Report Sheet').click()
2887 >>> manager.getControl('Template').value = ['ReportWorksheet']
2888 >>> manager.getControl('Deploy').click()
2889 >>> manager.url == term_url
2890 True
2891- >>> manager.getLink('Deploy Report Worksheet').click()
2892+ >>> manager.getLink('Deploy Report Sheet').click()
2893 >>> manager.getControl('Template').value = ['ReportWorksheet-2']
2894 >>> manager.getControl('Deploy').click()
2895
2896@@ -264,7 +303,7 @@
2897
2898 >>> manager.getLink('Manage').click()
2899 >>> manager.getLink('Report Sheet Templates').click()
2900- >>> manager.getLink('New Report Worksheet').click()
2901+ >>> manager.getLink('New Report Sheet').click()
2902 >>> manager.getControl('Title').value = 'Test3'
2903 >>> manager.getControl('Add').click()
2904 >>> manager.getLink('Test3').click()
2905@@ -275,7 +314,7 @@
2906 >>> manager.getLink('2005-2006').click()
2907 >>> manager.getLink('Fall').click()
2908 >>> term_url = manager.url
2909- >>> manager.getLink('Deploy Report Worksheet').click()
2910+ >>> manager.getLink('Deploy Report Sheet').click()
2911 >>> manager.getControl('Template').value = ['ReportWorksheet-3']
2912 >>> manager.getControl('Deploy').click()
2913
2914@@ -284,7 +323,7 @@
2915 we have.
2916
2917 >>> manager.getLink('2005-2006').click()
2918- >>> manager.getLink('Layout Report Card').click()
2919+ >>> manager.getLink('Report Card Layout').click()
2920
2921 The view starts off with no columns set up. The 'New Column' select control is
2922 the only list box present.
2923@@ -294,11 +333,13 @@
2924 <option value="fall|2005-2006_fall|ReportActivity">Fall - Test3 - Activity 3</option>
2925 <option value="spring|2005-2006_spring|ReportActivity">Spring - Test - Activity 1</option>
2926 <option value="spring|2005-2006_spring-2|ReportActivity">Spring - Test2 - Activity 2</option>
2927+ <option value="absent">Absent</option>
2928+ <option value="tardy">Tardy</option>
2929
2930 We'll add a first column to the layout.
2931
2932 >>> manager.getControl('New Column').value = ['fall|2005-2006_fall|ReportActivity']
2933- >>> manager.getControl('Update').click()
2934+ >>> manager.getControl('Save').click()
2935
2936 Now the view has the first column and the select control below it to add
2937 another column.
2938@@ -307,41 +348,51 @@
2939 <option selected="selected" value="fall|2005-2006_fall|ReportActivity">Fall - Test3 - Activity 3</option>
2940 <option value="spring|2005-2006_spring|ReportActivity">Spring - Test - Activity 1</option>
2941 <option value="spring|2005-2006_spring-2|ReportActivity">Spring - Test2 - Activity 2</option>
2942+ <option value="absent">Absent</option>
2943+ <option value="tardy">Tardy</option>
2944
2945 >>> analyze.printQuery("id('content-body')/form/fieldset[1]//table//tr[3]/td[2]//option", manager.contents)
2946 <option selected="selected" value="">Choose a column to add</option>
2947 <option value="fall|2005-2006_fall|ReportActivity">Fall - Test3 - Activity 3</option>
2948 <option value="spring|2005-2006_spring|ReportActivity">Spring - Test - Activity 1</option>
2949 <option value="spring|2005-2006_spring-2|ReportActivity">Spring - Test2 - Activity 2</option>
2950+ <option value="absent">Absent</option>
2951+ <option value="tardy">Tardy</option>
2952
2953 We can change the first column to another activity. We'll see that the change
2954 has taken effect.
2955
2956 >>> manager.getControl('Column1').value = ['spring|2005-2006_spring|ReportActivity']
2957- >>> manager.getControl('Update').click()
2958+ >>> manager.getControl('Save').click()
2959
2960 >>> analyze.printQuery("id('content-body')/form/fieldset[1]//table//tr[2]/td[2]//option", manager.contents)
2961 <option value="fall|2005-2006_fall|ReportActivity">Fall - Test3 - Activity 3</option>
2962 <option selected="selected" value="spring|2005-2006_spring|ReportActivity">Spring - Test - Activity 1</option>
2963 <option value="spring|2005-2006_spring-2|ReportActivity">Spring - Test2 - Activity 2</option>
2964+ <option value="absent">Absent</option>
2965+ <option value="tardy">Tardy</option>
2966
2967 >>> analyze.printQuery("id('content-body')/form/fieldset[1]//table//tr[3]/td[2]//option", manager.contents)
2968 <option selected="selected" value="">Choose a column to add</option>
2969 <option value="fall|2005-2006_fall|ReportActivity">Fall - Test3 - Activity 3</option>
2970 <option value="spring|2005-2006_spring|ReportActivity">Spring - Test - Activity 1</option>
2971 <option value="spring|2005-2006_spring-2|ReportActivity">Spring - Test2 - Activity 2</option>
2972+ <option value="absent">Absent</option>
2973+ <option value="tardy">Tardy</option>
2974
2975 If we want, we can delete the column. We'll see that the view no longer has
2976 any columns.
2977
2978 >>> manager.getControl(name='delete:list').value = ['Column1']
2979- >>> manager.getControl('Update').click()
2980+ >>> manager.getControl('Save').click()
2981
2982 >>> analyze.printQuery("id('content-body')/form/fieldset[1]//table//tr[2]/td[2]//option", manager.contents)
2983 <option selected="selected" value="">Choose a column to add</option>
2984 <option value="fall|2005-2006_fall|ReportActivity">Fall - Test3 - Activity 3</option>
2985 <option value="spring|2005-2006_spring|ReportActivity">Spring - Test - Activity 1</option>
2986 <option value="spring|2005-2006_spring-2|ReportActivity">Spring - Test2 - Activity 2</option>
2987+ <option value="absent">Absent</option>
2988+ <option value="tardy">Tardy</option>
2989
2990 Next, we'll repeat the same set of tests for the outline activities. For
2991 starters, we see only choices for outline activities to add.
2992@@ -355,7 +406,7 @@
2993 We'll add a first activity to the layout.
2994
2995 >>> manager.getControl('New Activity').value = ['fall|2005-2006_fall|ReportActivity']
2996- >>> manager.getControl('Update').click()
2997+ >>> manager.getControl('Save').click()
2998
2999 Now the view has the first activity and the select control below it to add
3000 another activity.
3001@@ -375,7 +426,7 @@
3002 has taken effect.
3003
3004 >>> manager.getControl('Activity1').value = ['spring|2005-2006_spring|ReportActivity']
3005- >>> manager.getControl('Update').click()
3006+ >>> manager.getControl('Save').click()
3007
3008 >>> analyze.printQuery("id('content-body')/form/fieldset[2]//table//tr[2]/td[2]//option", manager.contents)
3009 <option value="fall|2005-2006_fall|ReportActivity">Fall - Test3 - Activity 3</option>
3010@@ -392,7 +443,7 @@
3011 any activities.
3012
3013 >>> manager.getControl(name='delete:list').value = ['Activity1']
3014- >>> manager.getControl('Update').click()
3015+ >>> manager.getControl('Save').click()
3016
3017 >>> analyze.printQuery("id('content-body')/form/fieldset[2]//table//tr[2]/td[2]//option", manager.contents)
3018 <option selected="selected" value="">Choose an activity to add</option>
3019
3020=== modified file 'src/schooltool/gradebook/browser/report_card_rml.pt'
3021--- src/schooltool/gradebook/browser/report_card_rml.pt 2009-08-11 04:06:14 +0000
3022+++ src/schooltool/gradebook/browser/report_card_rml.pt 2009-10-12 08:13:12 +0000
3023@@ -11,6 +11,10 @@
3024 metal:use-macro="context/@@rml_macros/report"
3025 i18n:domain="schooltool">
3026
3027+<metal:block fill-slot="page_templates">
3028+ <tal:block content="structure view/use_template/default" />
3029+</metal:block>
3030+
3031 <stylesheet>
3032 <metal:block fill-slot="extra_initialize">
3033 </metal:block>
3034
3035=== added file 'src/schooltool/gradebook/browser/request_absences_by_day.pt'
3036--- src/schooltool/gradebook/browser/request_absences_by_day.pt 1970-01-01 00:00:00 +0000
3037+++ src/schooltool/gradebook/browser/request_absences_by_day.pt 2009-10-12 08:13:12 +0000
3038@@ -0,0 +1,42 @@
3039+<tal:define define="dummy view/update"/>
3040+<html metal:use-macro="view/@@standard_macros/page" i18n:domain="schooltool">
3041+<head>
3042+ <title metal:fill-slot="title" tal:content="view/title" />
3043+</head>
3044+<body>
3045+
3046+<metal:block metal:fill-slot="body">
3047+ <form method="post" class="addscoresystem"
3048+ tal:attributes="action string:${context/@@absolute_url}/request_absences_by_day.html">
3049+ <input type="hidden" name="form-submitted" value="" />
3050+ <h3 tal:content="view/title" />
3051+ <br />
3052+
3053+ <tal:block condition="view/message">
3054+ <span class="message" style="color: red"
3055+ tal:content="view/message" />
3056+ <br /><br />
3057+ </tal:block>
3058+
3059+ <table>
3060+ <tr>
3061+ <td>
3062+ <label for="day" i18n:translate="">Date</label>
3063+ </td>
3064+ <td>
3065+ <input type="text" id="day" name="day"
3066+ tal:attributes="value view/currentDay">
3067+ </td>
3068+ </tr>
3069+ </table>
3070+ <div class="controls">
3071+ <tal:block metal:use-macro="view/@@standard_macros/cancel-button" />
3072+ <input type="submit" class="button-ok" name="DOWNLOAD" value="Download"
3073+ i18n:attributes="value" />
3074+ </div>
3075+ </form>
3076+
3077+</metal:block>
3078+</body>
3079+</html>
3080+
3081
3082=== added file 'src/schooltool/gradebook/browser/request_failing_report.pt'
3083--- src/schooltool/gradebook/browser/request_failing_report.pt 1970-01-01 00:00:00 +0000
3084+++ src/schooltool/gradebook/browser/request_failing_report.pt 2009-10-12 08:13:12 +0000
3085@@ -0,0 +1,83 @@
3086+<tal:define define="dummy view/update"/>
3087+<html metal:use-macro="view/@@standard_macros/page" i18n:domain="schooltool">
3088+<head>
3089+ <title metal:fill-slot="title" tal:content="view/title" />
3090+</head>
3091+<body>
3092+
3093+<metal:block metal:fill-slot="body">
3094+ <tal:block condition="view/message">
3095+ <span class="message" style="color: red"
3096+ tal:content="view/message" />
3097+ <br /><br />
3098+ </tal:block>
3099+
3100+ <form method="post" class="addscoresystem"
3101+ tal:attributes="action string:${context/@@absolute_url}/request_failing_report.html">
3102+ <input type="hidden" name="form-submitted" value="" />
3103+
3104+ <h3 tal:content="view/title" />
3105+
3106+ <p i18n:translate="">
3107+ This report is a list of students achieving below a minimum score on a selected activity from a report sheet.
3108+ </p>
3109+ <p i18n:translate="">
3110+ For example, say the term is "Fall 2011," and you have deployed a report sheet entitled "Semester Grades,"
3111+ which includes an activity "First Quarter," using a grading scale where a "3" or higher is passing.
3112+ To create a report of students failing the first quarter grade, you would select the report activity "Fall 2011 - Semester
3113+ Grades - First Quarter" and choose "3" as the minimum passing grade.
3114+ And click "Download."
3115+ </p>
3116+
3117+ <table style="margin-left: 1em">
3118+ <tr>
3119+ <td>
3120+ <label for="source" i18n:translate="">Report Activity</label>
3121+ </td>
3122+ <td>
3123+ <select id="source" name="source" onchange="this.form.submit()">
3124+ <tal:block repeat="choice view/choices">
3125+ <option tal:condition="python: choice['value'] == view.current_source()"
3126+ selected
3127+ tal:attributes="value choice/value"
3128+ tal:content="choice/name" />
3129+ <option tal:condition="python: choice['value'] != view.current_source()"
3130+ tal:attributes="value choice/value"
3131+ tal:content="choice/name" />
3132+ </tal:block>
3133+ </select>
3134+ </td>
3135+ </tr>
3136+ <tr>
3137+ <td>
3138+ <label for="score" i18n:translate="">Minimum Passing Score</label>
3139+ </td>
3140+ <td>
3141+ <select id="score" name="score"
3142+ tal:condition="python: len(view.scores()) > 0">
3143+ <tal:block repeat="score view/scores">
3144+ <option tal:condition="python: score['value'] == ''"
3145+ selected
3146+ tal:attributes="value score/value"
3147+ tal:content="score/name" />
3148+ <option tal:condition="python: score['value'] != ''"
3149+ tal:attributes="value score/value"
3150+ tal:content="score/name" />
3151+ </tal:block>
3152+ </select>
3153+ <input type="text" id="score" name="score"
3154+ tal:condition="python: len(view.scores()) == 0">
3155+ </td>
3156+ </tr>
3157+ </table>
3158+ <div class="controls">
3159+ <tal:block metal:use-macro="view/@@standard_macros/cancel-button" />
3160+ <input type="submit" class="button-ok" name="DOWNLOAD" value="Download"
3161+ i18n:attributes="value" />
3162+ </div>
3163+ </form>
3164+
3165+</metal:block>
3166+</body>
3167+</html>
3168+
3169
3170=== added file 'src/schooltool/gradebook/browser/request_reports.pt'
3171--- src/schooltool/gradebook/browser/request_reports.pt 1970-01-01 00:00:00 +0000
3172+++ src/schooltool/gradebook/browser/request_reports.pt 2009-10-12 08:13:12 +0000
3173@@ -0,0 +1,17 @@
3174+<html metal:use-macro="context/@@standard_macros/page"
3175+ i18n:domain="schooltool">
3176+<head>
3177+ <title metal:fill-slot="title" tal:content="view/title" />
3178+</head>
3179+<body>
3180+<div metal:fill-slot="body">
3181+ <h1 style="font-size: 180%" tal:content="view/title" />
3182+ <br />
3183+ <tal:block repeat="link view/links">
3184+ <a class="navigation_header"
3185+ tal:attributes="href link/url"
3186+ tal:content="link/content" />
3187+ </tal:block>
3188+</div>
3189+</body>
3190+</html>
3191
3192=== added file 'src/schooltool/gradebook/browser/request_reports.py'
3193--- src/schooltool/gradebook/browser/request_reports.py 1970-01-01 00:00:00 +0000
3194+++ src/schooltool/gradebook/browser/request_reports.py 2009-10-12 08:13:12 +0000
3195@@ -0,0 +1,249 @@
3196+#
3197+# SchoolTool - common information systems platform for school administration
3198+# Copyright (c) 2005 Shuttleworth Foundation
3199+#
3200+# This program is free software; you can redistribute it and/or modify
3201+# it under the terms of the GNU General Public License as published by
3202+# the Free Software Foundation; either version 2 of the License, or
3203+# (at your option) any later version.
3204+#
3205+# This program is distributed in the hope that it will be useful,
3206+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3207+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3208+# GNU General Public License for more details.
3209+#
3210+# You should have received a copy of the GNU General Public License
3211+# along with this program; if not, write to the Free Software
3212+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
3213+#
3214+"""
3215+Request PDF Views
3216+"""
3217+
3218+from datetime import datetime
3219+
3220+from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
3221+from zope.publisher.browser import BrowserView
3222+from zope.traversing.browser.absoluteurl import absoluteURL
3223+
3224+from schooltool.app.interfaces import ISchoolToolApplication
3225+from schooltool.common import SchoolToolMessage as _
3226+
3227+from schooltool.gradebook.interfaces import IGradebookRoot
3228+from schooltool.requirement.interfaces import ICommentScoreSystem
3229+from schooltool.requirement.interfaces import IDiscreteValuesScoreSystem
3230+
3231+
3232+class BaseView(BrowserView):
3233+ """Base class for all request report views"""
3234+
3235+ template=ViewPageTemplateFile('request_reports.pt')
3236+
3237+ def __call__(self):
3238+ return self.template()
3239+
3240+
3241+class StudentReportsView(BaseView):
3242+
3243+ def title(self):
3244+ return _('Student Reports')
3245+
3246+ def links(self):
3247+ url = absoluteURL(self.context, self.request)
3248+ results = [
3249+ {
3250+ 'url': url + '/report_card.pdf',
3251+ 'content': _('Download Report Card'),
3252+ },
3253+ {
3254+ 'url': url + '/student_detail.pdf',
3255+ 'content': _('Download Student Detail Report'),
3256+ },
3257+ ]
3258+ return results
3259+
3260+
3261+class GroupReportsView(BaseView):
3262+
3263+ def title(self):
3264+ return _('Group Reports')
3265+
3266+ def links(self):
3267+ url = absoluteURL(self.context, self.request)
3268+ results = [
3269+ {
3270+ 'url': url + '/report_card.pdf',
3271+ 'content': _('Download Report Card'),
3272+ },
3273+ {
3274+ 'url': url + '/student_detail.pdf',
3275+ 'content': _('Download Detailed Student Report'),
3276+ },
3277+ ]
3278+ return results
3279+
3280+
3281+class SchoolYearReportsView(BaseView):
3282+
3283+ def title(self):
3284+ return _('School Year Reports')
3285+
3286+ def links(self):
3287+ url = absoluteURL(self.context, self.request)
3288+ results = [
3289+ {
3290+ 'url': url + '/request_failing_report.html',
3291+ 'content': _('Failures by Term'),
3292+ },
3293+ {
3294+ 'url': url + '/request_absences_by_day.html',
3295+ 'content': _('Absences By Day'),
3296+ },
3297+ ]
3298+ return results
3299+
3300+
3301+class SectionReportsView(BaseView):
3302+
3303+ def title(self):
3304+ return _('Section Reports')
3305+
3306+ def links(self):
3307+ url = absoluteURL(self.context, self.request)
3308+ results = [
3309+ {
3310+ 'url': url + '/section_absences.pdf',
3311+ 'content': _('Download Absences by Section Report'),
3312+ },
3313+ ]
3314+ return results
3315+
3316+
3317+class RequestFailingReportView(BrowserView):
3318+
3319+ def title(self):
3320+ return _('Request Failing Report')
3321+
3322+ def current_source(self):
3323+ if 'source' in self.request:
3324+ return self.request['source']
3325+ return ''
3326+
3327+ def getScoreSystem(self, source):
3328+ termName, worksheetName, activityName = source.split('|')
3329+ root = IGradebookRoot(ISchoolToolApplication(None))
3330+ return root.deployed[worksheetName][activityName].scoresystem
3331+
3332+ def choices(self):
3333+ """Get a list of the possible choices for report activities."""
3334+ result = {
3335+ 'name': _('Choose a report activity'),
3336+ 'value': '',
3337+ }
3338+ results = [result]
3339+ root = IGradebookRoot(ISchoolToolApplication(None))
3340+ for term in self.context.values():
3341+ deployedKey = '%s_%s' % (self.context.__name__, term.__name__)
3342+ for key in root.deployed:
3343+ if key.startswith(deployedKey):
3344+ deployedWorksheet = root.deployed[key]
3345+ for activity in deployedWorksheet.values():
3346+ if ICommentScoreSystem.providedBy(activity.scoresystem):
3347+ continue
3348+ name = '%s - %s - %s' % (term.title,
3349+ deployedWorksheet.title, activity.title)
3350+ value = '%s|%s|%s' % (term.__name__,
3351+ deployedWorksheet.__name__, activity.__name__)
3352+ result = {
3353+ 'name': name,
3354+ 'value': value,
3355+ }
3356+ results.append(result)
3357+ return results
3358+
3359+ def scores(self):
3360+ results = []
3361+ current = self.current_source()
3362+ if current:
3363+ ss = self.getScoreSystem(current)
3364+ if IDiscreteValuesScoreSystem.providedBy(ss):
3365+ result = {
3366+ 'name': _('Choose a minimum passing score'),
3367+ 'value': '',
3368+ }
3369+ results.append(result)
3370+ for score in ss.scores:
3371+ result = {
3372+ 'name': score[0],
3373+ 'value': score[0],
3374+ }
3375+ results.append(result)
3376+ return results
3377+
3378+ def getErrorMessage(self):
3379+ return _('You must specify both a report activity and a minimum passing score.')
3380+
3381+ def update(self):
3382+ self.message = ''
3383+ if 'form-submitted' in self.request:
3384+ if 'CANCEL' in self.request:
3385+ self.request.response.redirect(self.nextURL())
3386+ elif 'DOWNLOAD' in self.request:
3387+ if not (self.request['source'] and self.request['score']):
3388+ self.message = self.getErrorMessage()
3389+ else:
3390+ url = '%s?activity=%s&min=%s' % (self.reportURL(),
3391+ self.request['source'], self.request['score'])
3392+ self.request.response.redirect(url)
3393+
3394+ def reportURL(self):
3395+ return absoluteURL(self.context, self.request) + '/failing_report.pdf'
3396+
3397+ def nextURL(self):
3398+ return absoluteURL(self.context, self.request) + '/report_pdfs.html'
3399+
3400+
3401+class RequestAbsencesByDayView(BrowserView):
3402+
3403+ def title(self):
3404+ return _('Request Absences By Day Report')
3405+
3406+ def currentDay(self):
3407+ day = self.request.get('day', None)
3408+ if day is None:
3409+ tod = datetime.now()
3410+ return '%d-%02d-%02d' % (tod.year, tod.month, tod.day)
3411+ else:
3412+ return day
3413+
3414+ def isValidDate(self):
3415+ day = self.currentDay()
3416+ try:
3417+ year, month, day = [int(part) for part in day.split('-')]
3418+ except:
3419+ return False
3420+ date = datetime.date(datetime(year, month, day))
3421+ return self.context.first <= date <= self.context.last
3422+
3423+ def getErrorMessage(self):
3424+ return _('You must specify a valid date within the school year.')
3425+
3426+ def update(self):
3427+ self.message = ''
3428+ if 'form-submitted' in self.request:
3429+ if 'CANCEL' in self.request:
3430+ self.request.response.redirect(self.nextURL())
3431+ elif 'DOWNLOAD' in self.request:
3432+ if not self.isValidDate():
3433+ self.message = self.getErrorMessage()
3434+ else:
3435+ url = '%s?day=%s' % (self.reportURL(),
3436+ self.request['day'])
3437+ self.request.response.redirect(url)
3438+
3439+ def reportURL(self):
3440+ return absoluteURL(self.context, self.request) + '/absences_by_day.pdf'
3441+
3442+ def nextURL(self):
3443+ return absoluteURL(self.context, self.request) + '/report_pdfs.html'
3444+
3445
3446=== added file 'src/schooltool/gradebook/browser/section_absences_rml.pt'
3447--- src/schooltool/gradebook/browser/section_absences_rml.pt 1970-01-01 00:00:00 +0000
3448+++ src/schooltool/gradebook/browser/section_absences_rml.pt 2009-10-12 08:13:12 +0000
3449@@ -0,0 +1,98 @@
3450+<?xml version="1.0" standalone="no" ?>
3451+<!DOCTYPE document SYSTEM "rml_1_0.dtd" [
3452+ <!ENTITY pound "&#xA3;">
3453+ <!ENTITY nbsp "&#160;">
3454+]>
3455+
3456+<document
3457+ xmlns:tal="http://xml.zope.org/namespaces/tal"
3458+ xmlns:metal="http://xml.zope.org/namespaces/metal"
3459+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
3460+ metal:use-macro="context/@@rml_macros/report"
3461+ i18n:domain="schooltool">
3462+
3463+<metal:block fill-slot="page_templates">
3464+ <tal:block content="structure view/use_template/default" />
3465+</metal:block>
3466+
3467+<stylesheet>
3468+ <metal:block fill-slot="extra_initialize">
3469+ </metal:block>
3470+ <metal:block fill-slot="stylesheet">
3471+
3472+ <paraStyle
3473+ name="normal"
3474+ fontName="Arial_Normal"
3475+ fontSize="10"
3476+ leading="12"/>
3477+
3478+ <paraStyle
3479+ name="bold"
3480+ fontName="Arial_Bold"
3481+ fontSize="10"
3482+ alignment="left"
3483+ leading="12"/>
3484+
3485+ <paraStyle
3486+ name="heading"
3487+ fontName="Arial_Bold"
3488+ fontSize="10"
3489+ alignment="right"
3490+ leading="12"/>
3491+
3492+ <paraStyle
3493+ name="section_heading"
3494+ fontName="Arial_Bold"
3495+ fontSize="12"
3496+ alignment="center"
3497+ leading="12"/>
3498+
3499+ <blockTableStyle id="headings_table">
3500+ <blockValign value="top" start="0,0" stop="0,-1"/>
3501+ </blockTableStyle>
3502+
3503+ <blockTableStyle id="grid">
3504+ <lineStyle kind="OUTLINE"
3505+ colorName="black" thickness="0.25"
3506+ start="0,0" stop="-1,-1" />
3507+ <blockValign value="top" start="0,0" stop="0,-1"/>
3508+ </blockTableStyle>
3509+ </metal:block>
3510+</stylesheet>
3511+
3512+<story metal:fill-slot="story">
3513+ <blockTable style="headings_table" colWidths="4cm,4cm,4cm,4cm">
3514+ <tr>
3515+ <td><para style="heading" tal:content="view/course_heading" /></td>
3516+ <td><para style="normal" tal:content="view/course" /></td>
3517+ <td><para style="heading" tal:content="view/teacher_heading" /></td>
3518+ <td><para style="normal" tal:content="view/teacher" /></td>
3519+ </tr>
3520+ </blockTable>
3521+ <spacer length=".5cm" />
3522+
3523+ <blockTable style="grid" colWidths="7.5cm,2.5cm,2.5cm,2.5cm">
3524+ <tr>
3525+ <td><para style="bold" tal:content="view/student_heading" /></td>
3526+ <td><para style="bold" tal:content="view/absences_heading" /></td>
3527+ <td><para style="bold" tal:content="view/tardies_heading" /></td>
3528+ <td><para style="bold" tal:content="view/total_heading" /></td>
3529+ </tr>
3530+ </blockTable>
3531+ <spacer length=".2cm" />
3532+
3533+ <blockTable style="headings_table" colWidths="7.5cm,2.5cm,2.5cm,2.5cm">
3534+ <tr tal:repeat="student view/students">
3535+ <td><para style="normal" tal:content="student/name" /></td>
3536+ <td><para style="normal" tal:content="student/absences" /></td>
3537+ <td><para style="normal" tal:content="student/tardies" /></td>
3538+ <td><para style="normal" tal:content="student/total" /></td>
3539+ </tr>
3540+ </blockTable>
3541+
3542+ <condPageBreak height="88cm"/>
3543+
3544+</story>
3545+
3546+</document>
3547+
3548
3549=== added file 'src/schooltool/gradebook/browser/student_detail_rml.pt'
3550--- src/schooltool/gradebook/browser/student_detail_rml.pt 1970-01-01 00:00:00 +0000
3551+++ src/schooltool/gradebook/browser/student_detail_rml.pt 2009-10-12 08:13:12 +0000
3552@@ -0,0 +1,127 @@
3553+<?xml version="1.0" standalone="no" ?>
3554+<!DOCTYPE document SYSTEM "rml_1_0.dtd" [
3555+ <!ENTITY pound "&#xA3;">
3556+ <!ENTITY nbsp "&#160;">
3557+]>
3558+
3559+<document
3560+ xmlns:tal="http://xml.zope.org/namespaces/tal"
3561+ xmlns:metal="http://xml.zope.org/namespaces/metal"
3562+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
3563+ metal:use-macro="context/@@rml_macros/report"
3564+ i18n:domain="schooltool">
3565+
3566+<metal:block fill-slot="page_templates">
3567+ <tal:block content="structure view/use_template/default" />
3568+</metal:block>
3569+
3570+<stylesheet>
3571+ <metal:block fill-slot="extra_initialize">
3572+ </metal:block>
3573+ <metal:block fill-slot="stylesheet">
3574+
3575+ <paraStyle
3576+ name="normal"
3577+ fontName="Arial_Normal"
3578+ fontSize="10"
3579+ leading="12"/>
3580+
3581+ <paraStyle
3582+ name="bold"
3583+ fontName="Arial_Bold"
3584+ fontSize="10"
3585+ leading="12"/>
3586+
3587+ <paraStyle
3588+ name="heading"
3589+ fontName="Arial_Bold"
3590+ fontSize="10"
3591+ alignment="right"
3592+ leading="12"/>
3593+
3594+ <paraStyle
3595+ name="section_heading"
3596+ fontName="Arial_Bold"
3597+ fontSize="12"
3598+ alignment="center"
3599+ leading="12"/>
3600+
3601+ <blockTableStyle id="headings_table">
3602+ <blockValign value="top" start="0,0" stop="0,-1"/>
3603+ </blockTableStyle>
3604+
3605+ <blockTableStyle id="grid">
3606+ <lineStyle kind="OUTLINE"
3607+ colorName="black" thickness="0.25"
3608+ start="0,0" stop="-1,-1" />
3609+ <lineStyle kind="INNERGRID"
3610+ colorName="black" thickness="0.25"
3611+ start="0,0" stop="-1,-1" />
3612+ <blockValign value="top" start="0,0" stop="0,-1"/>
3613+ </blockTableStyle>
3614+ </metal:block>
3615+</stylesheet>
3616+
3617+<story metal:fill-slot="story">
3618+
3619+ <tal:block repeat="student view/students">
3620+ <tal:block define="grades student/grades; attendance student/attendance">
3621+ <blockTable style="headings_table" colWidths="4cm,4cm,4cm,4cm">
3622+ <tr>
3623+ <td><para style="heading" tal:content="view/name_heading" /></td>
3624+ <td><para style="normal" tal:content="student/name" /></td>
3625+ <td><para style="heading" tal:content="view/userid_heading" /></td>
3626+ <td><para style="normal" tal:content="student/userid" /></td>
3627+ </tr>
3628+ </blockTable>
3629+ <spacer length=".5cm" />
3630+
3631+ <para style="section_heading" tal:content="view/grades_heading" />
3632+ <spacer length=".5cm" />
3633+
3634+ <blockTable style="grid"
3635+ tal:attributes="colWidths grades/widths">
3636+ <tr>
3637+ <td><para style="bold" tal:content="view/course_heading" /></td>
3638+ <td tal:repeat="heading grades/headings">
3639+ <para style="bold" tal:content="heading" />
3640+ </td>
3641+ </tr>
3642+
3643+ <tr tal:repeat="row grades/rows">
3644+ <td><para style="normal" tal:content="row/title" /></td>
3645+ <td tal:repeat="score row/scores">
3646+ <para style="normal" tal:content="score" />
3647+ </td>
3648+ </tr>
3649+ </blockTable>
3650+ <spacer length=".5cm" />
3651+
3652+ <para style="section_heading" tal:content="view/attendance_heading" />
3653+ <spacer length=".5cm" />
3654+
3655+ <blockTable style="grid"
3656+ tal:attributes="colWidths attendance/widths">
3657+ <tr>
3658+ <td><para style="bold" tal:content="view/date_heading" /></td>
3659+ <td tal:repeat="heading attendance/headings">
3660+ <para style="bold" tal:content="heading" />
3661+ </td>
3662+ </tr>
3663+
3664+ <tr tal:repeat="row attendance/rows">
3665+ <td><para style="normal" tal:content="row/title" /></td>
3666+ <td tal:repeat="score row/scores">
3667+ <para style="normal" tal:content="score" />
3668+ </td>
3669+ </tr>
3670+ </blockTable>
3671+
3672+ <condPageBreak height="88cm"/>
3673+ </tal:block>
3674+ </tal:block>
3675+
3676+</story>
3677+
3678+</document>
3679+
3680
3681=== modified file 'src/schooltool/gradebook/browser/student_gradebook.pt'
3682--- src/schooltool/gradebook/browser/student_gradebook.pt 2009-07-29 09:35:08 +0000
3683+++ src/schooltool/gradebook/browser/student_gradebook.pt 2009-10-12 08:13:12 +0000
3684@@ -1,10 +1,10 @@
3685 <html metal:use-macro="view/@@standard_macros/page" i18n:domain="schooltool">
3686 <head>
3687- <title metal:fill-slot="title" tal:content="string: ${view/worksheet} for ${view/student} in ${view/section}" />
3688+ <title metal:fill-slot="title" tal:content="view/title" />
3689 </head>
3690 <body>
3691 <h1 metal:fill-slot="content-header" i18n:translate="">
3692- <span tal:replace="string: ${view/worksheet} for ${view/student} in ${view/section}" />
3693+ <span tal:replace="view/title" />
3694 </h1>
3695
3696 <metal:block metal:fill-slot="body">
3697
3698=== modified file 'src/schooltool/gradebook/browser/weight_categories.pt'
3699--- src/schooltool/gradebook/browser/weight_categories.pt 2008-10-12 02:23:54 +0000
3700+++ src/schooltool/gradebook/browser/weight_categories.pt 2009-10-12 08:13:12 +0000
3701@@ -8,18 +8,29 @@
3702 </head>
3703 <body>
3704 <div metal:fill-slot="body">
3705- <h1 tal:content="string: Category weights for worksheet ${context/title}" />
3706-
3707- <div class="message" style="color:red; padding:1em"
3708- tal:condition="view/message"
3709- tal:content="view/message">
3710- Message
3711- </div>
3712- <br />
3713-
3714- <form method="post"
3715+ <form method="post" class="addscoresystem"
3716 tal:attributes="action string:${context/@@absolute_url}/weights.html">
3717- <table>
3718+ <h3 tal:content="string: Category weights for worksheet ${context/title}" />
3719+ <br />
3720+
3721+ <p i18n:translate="">
3722+ This form allows you to change the weighting of categories when calculating average scores within a worksheet.
3723+ </p>
3724+ <p i18n:translate="">
3725+ Enter numeric values for each relevant category. The total of all values must be exactly 100.
3726+ </p>
3727+ <p i18n:translate="">
3728+ For example, to weight exams as 60% of the grade, homework as 20% and quizzes as 20%, enter "60" next to "Exam," 20" next to "Homework" and "20" next to "quiz."
3729+ </p>
3730+
3731+ <div class="message" style="color:red; padding:1em"
3732+ tal:condition="view/message"
3733+ tal:content="view/message">
3734+ Message
3735+ </div>
3736+ <br />
3737+
3738+ <table style="margin-left: 1em">
3739 <tr tal:repeat="row view/rows">
3740 <td>
3741 <label tal:attributes="for row/category"
3742
3743=== modified file 'src/schooltool/gradebook/browser/worksheet.py'
3744--- src/schooltool/gradebook/browser/worksheet.py 2009-03-06 03:22:18 +0000
3745+++ src/schooltool/gradebook/browser/worksheet.py 2009-10-12 08:13:12 +0000
3746@@ -33,6 +33,8 @@
3747 from schooltool.gradebook.browser.activity import BaseEditView
3748 from schooltool.person.interfaces import IPerson
3749
3750+from schooltool.common import SchoolToolMessage as _
3751+
3752
3753 class WorksheetGradebookView(BrowserView):
3754 """A view that redirects from the worksheet to its gradebook."""
3755@@ -53,14 +55,21 @@
3756
3757 def activities(self):
3758 pos = 0
3759+ results = []
3760 for activity in list(self.context.values()):
3761 pos += 1
3762+ url = absoluteURL(activity, self.request)
3763+ if interfaces.ILinkedColumnActivity.providedBy(activity):
3764+ url += '/editLinkedColumn.html'
3765 yield {'name': getName(activity),
3766 'title': activity.title,
3767- 'url': absoluteURL(activity, self.request),
3768+ 'url': url,
3769 'pos': pos,
3770 'deployed': self.context.deployed}
3771
3772+ def isTemplate(self):
3773+ return interfaces.IReportWorksheet.providedBy(self.context)
3774+
3775 def canModify(self):
3776 return canWrite(self.context, 'title')
3777
3778@@ -89,6 +98,10 @@
3779 if new_pos != old_pos:
3780 self.context.changePosition(name, new_pos-1)
3781
3782+ @property
3783+ def noActivitiesMessage(self):
3784+ return _('This worksheet has no activities.')
3785+
3786
3787 class WorksheetAddView(app.BaseAddView):
3788 """A view for adding a worksheet."""
3789
3790=== modified file 'src/schooltool/gradebook/browser/worksheet_delete.pt'
3791--- src/schooltool/gradebook/browser/worksheet_delete.pt 2008-06-20 10:21:46 +0000
3792+++ src/schooltool/gradebook/browser/worksheet_delete.pt 2009-10-12 08:13:12 +0000
3793@@ -5,19 +5,20 @@
3794 </head>
3795 <body>
3796
3797-<h1 metal:fill-slot="content-header"
3798- tal:content="string:Delete Worksheet '${context/title}'">Delete Worksheet</h1>
3799+<h1 metal:fill-slot="content-header" tal:content="view/title" />
3800
3801 <metal:block metal:fill-slot="body">
3802 <div style="padding-bottom: 1em">
3803- <p>Are you sure you want to delete this workskeet together with all its activities?</p>
3804+ <p i18n:translate="">Are you sure you want to delete this workskeet together with all its activities?</p>
3805 </div>
3806
3807 <form method="post"
3808 tal:attributes="action string:${context/@@absolute_url}/delete.html">
3809 <div class="controls">
3810- <input type="submit" class="button-ok" name="DELETE" value="Delete"/>
3811- <input type="submit" class="button-cancel" name="CANCEL" value="Cancel"/>
3812+ <input type="submit" class="button-ok" name="DELETE" value="Delete"
3813+ i18n:attributes="value" />
3814+ <input type="submit" class="button-cancel" name="CANCEL" value="Cancel"
3815+ i18n:attributes="value" />
3816 </div>
3817 </form>
3818
3819
3820=== modified file 'src/schooltool/gradebook/browser/worksheet_overview.pt'
3821--- src/schooltool/gradebook/browser/worksheet_overview.pt 2009-03-10 08:33:59 +0000
3822+++ src/schooltool/gradebook/browser/worksheet_overview.pt 2009-10-12 08:13:12 +0000
3823@@ -11,9 +11,15 @@
3824 <metal:block metal:fill-slot="body"
3825 tal:define="activities view/activities">
3826
3827+ <p tal:condition="view/isTemplate" i18n:translate="">
3828+ A report sheet template defines the activities to be included in a report sheet deployed to each section in a term or year.
3829+ </p>
3830+
3831 <form method="post"
3832- tal:attributes="action string:${context/@@absolute_url}/manage.html">
3833+ tal:attributes="action string:${context/@@absolute_url}">
3834 <input type="hidden" name="form-submitted" value="" />
3835+ <div tal:condition="python: len(list(context.values())) == 0"
3836+ tal:content="view/noActivitiesMessage" />
3837 <div tal:repeat="activity activities">
3838 <select name=""
3839 onchange="this.form.submit()"
3840@@ -41,8 +47,10 @@
3841
3842 </div>
3843 <div class="controls" tal:condition="view/canModify">
3844- <tal:block tal:condition="view/canModify"
3845- metal:use-macro="view/@@standard_macros/delete-button" />
3846+ <tal:block tal:condition="python: len(list(context.values())) > 0">
3847+ <tal:block tal:condition="view/canModify"
3848+ metal:use-macro="view/@@standard_macros/delete-button" />
3849+ </tal:block>
3850 </div>
3851 </form>
3852 </metal:block>
3853
3854=== modified file 'src/schooltool/gradebook/configure.zcml'
3855--- src/schooltool/gradebook/configure.zcml 2009-07-13 09:19:00 +0000
3856+++ src/schooltool/gradebook/configure.zcml 2009-10-12 08:13:10 +0000
3857@@ -161,6 +161,20 @@
3858 set_schema=".interfaces.ILinkedActivity"
3859 />
3860 </class>
3861+ <class class=".activity.LinkedColumnActivity">
3862+ <allow interface="zope.interface.common.mapping.IReadMapping" />
3863+ <require
3864+ permission="schooltool.view"
3865+ attributes="keys __iter__ values items __len__
3866+ label due_date source title description category
3867+ scoresystem"
3868+ />
3869+ <require
3870+ permission="schooltool.edit"
3871+ interface="zope.interface.common.mapping.IWriteMapping"
3872+ set_schema=".interfaces.ILinkedColumnActivity"
3873+ />
3874+ </class>
3875
3876 <!-- External Activities Vocabulary -->
3877
3878
3879=== modified file 'src/schooltool/gradebook/generations/__init__.py'
3880--- src/schooltool/gradebook/generations/__init__.py 2009-06-05 00:11:04 +0000
3881+++ src/schooltool/gradebook/generations/__init__.py 2009-10-12 08:13:12 +0000
3882@@ -23,6 +23,6 @@
3883 from zope.app.generations.generations import SchemaManager
3884
3885 schemaManager = SchemaManager(
3886- minimum_generation=1,
3887- generation=1,
3888+ minimum_generation=2,
3889+ generation=2,
3890 package_name='schooltool.gradebook.generations')
3891
3892=== modified file 'src/schooltool/gradebook/generations/evolve1.py'
3893--- src/schooltool/gradebook/generations/evolve1.py 2009-07-13 00:12:34 +0000
3894+++ src/schooltool/gradebook/generations/evolve1.py 2009-10-12 08:13:12 +0000
3895@@ -70,6 +70,8 @@
3896 updateObjActivities(section, IActivities(section), ss, custom_ss)
3897
3898 root = IGradebookRoot(app)
3899+ if root is None:
3900+ return
3901 updateObjActivities(root, root.templates, ss, custom_ss)
3902 updateObjActivities(root, root.deployed, ss, custom_ss)
3903
3904
3905=== added file 'src/schooltool/gradebook/generations/evolve2.py'
3906--- src/schooltool/gradebook/generations/evolve2.py 1970-01-01 00:00:00 +0000
3907+++ src/schooltool/gradebook/generations/evolve2.py 2009-10-12 08:13:12 +0000
3908@@ -0,0 +1,44 @@
3909+#
3910+# SchoolTool - common information systems platform for school administration
3911+# Copyright (c) 2008 Shuttleworth Foundation
3912+#
3913+# This program is free software; you can redistribute it and/or modify
3914+# it under the terms of the GNU General Public License as published by
3915+# the Free Software Foundation; either version 2 of the License, or
3916+# (at your option) any later version.
3917+#
3918+# This program is distributed in the hope that it will be useful,
3919+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3920+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3921+# GNU General Public License for more details.
3922+#
3923+# You should have received a copy of the GNU General Public License
3924+# along with this program; if not, write to the Free Software
3925+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
3926+#
3927+"""
3928+Evolve database to generation 1.
3929+
3930+Moves hard-coded score system utilities to the app site manager.
3931+"""
3932+
3933+from zope.app.zopeappgenerations import getRootFolder
3934+from zope.security.proxy import removeSecurityProxy
3935+
3936+from schooltool.app.interfaces import ISchoolToolApplication
3937+from schooltool.requirement.interfaces import ICustomScoreSystem
3938+from schooltool.requirement.scoresystem import CustomScoreSystem
3939+
3940+
3941+def evolve(context):
3942+ """Adds abbreviation column to all custom score systems"""
3943+
3944+ app = getRootFolder(context)
3945+ sm = app.getSiteManager()
3946+ for name, util in sorted(sm.getUtilitiesFor(ICustomScoreSystem)):
3947+ util = removeSecurityProxy(util)
3948+ new_scores = []
3949+ for score, value, percent in util.scores:
3950+ new_scores.append([score, '', value, percent])
3951+ util.scores = new_scores
3952+
3953
3954=== added file 'src/schooltool/gradebook/generations/tests/test_evolve2.py'
3955--- src/schooltool/gradebook/generations/tests/test_evolve2.py 1970-01-01 00:00:00 +0000
3956+++ src/schooltool/gradebook/generations/tests/test_evolve2.py 2009-10-12 08:13:12 +0000
3957@@ -0,0 +1,88 @@
3958+# coding=UTF8
3959+#
3960+# SchoolTool - common information systems platform for school administration
3961+# Copyright (c) 2008 Shuttleworth Foundation
3962+#
3963+# This program is free software; you can redistribute it and/or modify
3964+# it under the terms of the GNU General Public License as published by
3965+# the Free Software Foundation; either version 2 of the License, or
3966+# (at your option) any later version.
3967+#
3968+# This program is distributed in the hope that it will be useful,
3969+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3970+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3971+# GNU General Public License for more details.
3972+#
3973+# You should have received a copy of the GNU General Public License
3974+# along with this program; if not, write to the Free Software
3975+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
3976+#
3977+"""
3978+Unit tests for schooltool.gradebook.generations.evolve2
3979+"""
3980+
3981+import unittest
3982+
3983+from zope.app.zopeappgenerations import getRootFolder
3984+from zope.interface import implements
3985+from zope.testing import doctest
3986+
3987+from schooltool.app.interfaces import ISchoolToolApplication
3988+from schooltool.gradebook.generations.tests import ContextStub
3989+from schooltool.gradebook.generations.tests import provideAdapters
3990+from schooltool.gradebook.generations.evolve2 import evolve
3991+from schooltool.requirement.interfaces import ICustomScoreSystem
3992+
3993+
3994+class CustomScoreSystemStub(object):
3995+ implements(ICustomScoreSystem)
3996+
3997+ scores = [('A', 0, 0), ('B', 0, 0)]
3998+ title = 'stub'
3999+ hidden = False
4000+
4001+
4002+def doctest_evolve2():
4003+ """Evolution to generation 2.
4004+
4005+ First, we'll set up the app object:
4006+
4007+ >>> provideAdapters()
4008+ >>> context = ContextStub()
4009+ >>> app = getRootFolder(context)
4010+
4011+ >>> from zope.app.component.site import LocalSiteManager
4012+ >>> app.setSiteManager(LocalSiteManager(app))
4013+
4014+ We'll set up our test with data that will be effected by running the
4015+ evolve script:
4016+
4017+ >>> from schooltool.requirement.interfaces import IScoreSystemsProxy
4018+ >>> proxy = IScoreSystemsProxy(app)
4019+ >>> custom_ss = CustomScoreSystemStub()
4020+ >>> proxy.addScoreSystem(custom_ss)
4021+
4022+ Finally, we'll run the evolve script, testing the effected values before and
4023+ after:
4024+
4025+ >>> custom_ss.scores
4026+ [('A', 0, 0), ('B', 0, 0)]
4027+
4028+ >>> evolve(context)
4029+
4030+ >>> custom_ss.scores
4031+ [['A', '', 0, 0], ['B', '', 0, 0]]
4032+ """
4033+
4034+
4035+def test_suite():
4036+ return unittest.TestSuite([
4037+ doctest.DocTestSuite(optionflags=doctest.ELLIPSIS
4038+ | doctest.NORMALIZE_WHITESPACE
4039+ | doctest.REPORT_NDIFF
4040+ | doctest.REPORT_ONLY_FIRST_FAILURE),
4041+ ])
4042+
4043+if __name__ == '__main__':
4044+ unittest.main(defaultTest='test_suite')
4045+
4046
4047=== modified file 'src/schooltool/gradebook/gradebook.py'
4048--- src/schooltool/gradebook/gradebook.py 2009-06-29 07:43:38 +0000
4049+++ src/schooltool/gradebook/gradebook.py 2009-10-12 08:13:10 +0000
4050@@ -46,18 +46,19 @@
4051 from schooltool.securitypolicy.crowds import AdministratorsCrowd
4052
4053 from schooltool.gradebook import interfaces
4054-from schooltool.gradebook.activity import Activities
4055+from schooltool.gradebook.activity import getSourceObj, Activities
4056 from schooltool.gradebook.activity import ensureAtLeastOneWorksheet
4057 from schooltool.requirement.scoresystem import UNSCORED, ScoreValidationError
4058 from schooltool.requirement.interfaces import IDiscreteValuesScoreSystem
4059 from schooltool.requirement.interfaces import IRangedValuesScoreSystem
4060+from schooltool.requirement.scoresystem import RangedValuesScoreSystem
4061 from schooltool.common import SchoolToolMessage as _
4062
4063 GRADEBOOK_SORTING_KEY = 'schooltool.gradebook.sorting'
4064 CURRENT_WORKSHEET_KEY = 'schooltool.gradebook.currentworksheet'
4065 DUE_DATE_FILTER_KEY = 'schooltool.gradebook.duedatefilter'
4066 COLUMN_PREFERENCES_KEY = 'schooltool.gradebook.columnpreferences'
4067-
4068+
4069
4070 class WorksheetGradebookTraverser(object):
4071 '''Traverser that goes from a worksheet to the gradebook'''
4072@@ -159,12 +160,28 @@
4073 return True
4074 return False
4075
4076- def getEvaluation(self, student, activity, default=None):
4077+ def getEvaluation(self, student, activity):
4078 """See interfaces.IGradebook"""
4079 student = self._checkStudent(student)
4080 activity = self._checkActivity(activity)
4081 evaluations = requirement.interfaces.IEvaluations(student)
4082- return evaluations.get(activity, default)
4083+ ev, value, ss = None, None, None
4084+ if interfaces.ILinkedColumnActivity.providedBy(activity):
4085+ sourceObj = getSourceObj(activity.source)
4086+ if interfaces.IActivity.providedBy(sourceObj):
4087+ ev = evaluations.get(sourceObj, None)
4088+ ss = sourceObj.scoresystem
4089+ elif interfaces.IWorksheet.providedBy(sourceObj):
4090+ gb = interfaces.IGradebook(sourceObj)
4091+ if student in gb.students:
4092+ total, value = gb.getWorksheetTotalAverage(sourceObj, student)
4093+ ss = RangedValuesScoreSystem()
4094+ else:
4095+ ev = evaluations.get(activity, None)
4096+ ss = activity.scoresystem
4097+ if ev is not None and ev.value is not UNSCORED:
4098+ value = ev.value
4099+ return value, ss
4100
4101 def evaluate(self, student, activity, score, evaluator=None):
4102 """See interfaces.IGradebook"""
4103@@ -197,9 +214,9 @@
4104 if weights:
4105 adjusted_weights = {}
4106 for activity in self.getWorksheetActivities(worksheet):
4107- ev = self.getEvaluation(student, activity)
4108+ value, ss = self.getEvaluation(student, activity)
4109 category = activity.category
4110- if ev is not None and ev.value is not UNSCORED:
4111+ if value is not None:
4112 if category in weights:
4113 adjusted_weights[category] = weights[category]
4114 total_percentage = 0
4115@@ -212,17 +229,15 @@
4116 average_totals = {}
4117 average_counts = {}
4118 for activity in self.getWorksheetActivities(worksheet):
4119- ev = self.getEvaluation(student, activity)
4120- if ev is not None and ev.value is not UNSCORED:
4121- ss = ev.requirement.scoresystem
4122+ value, ss = self.getEvaluation(student, activity)
4123+ if value is not None:
4124 if IDiscreteValuesScoreSystem.providedBy(ss):
4125- minimum = ss.scores[-1][1]
4126- maximum = ss.scores[0][1]
4127- value = ss.getNumericalValue(ev.value)
4128+ minimum = ss.scores[-1][2]
4129+ maximum = ss.scores[0][2]
4130+ value = ss.getNumericalValue(value)
4131 elif IRangedValuesScoreSystem.providedBy(ss):
4132 minimum = ss.min
4133 maximum = ss.max
4134- value = ev.value
4135 else:
4136 continue
4137 totals.setdefault(activity.category, Decimal(0))
4138@@ -244,17 +259,15 @@
4139 total = 0
4140 count = 0
4141 for activity in self.getWorksheetActivities(worksheet):
4142- ev = self.getEvaluation(student, activity)
4143- if ev is not None and ev.value is not UNSCORED:
4144- ss = ev.requirement.scoresystem
4145+ value, ss = self.getEvaluation(student, activity)
4146+ if value is not None:
4147 if IDiscreteValuesScoreSystem.providedBy(ss):
4148- minimum = ss.scores[-1][1]
4149- maximum = ss.scores[0][1]
4150- value = ss.getNumericalValue(ev.value)
4151+ minimum = ss.scores[-1][2]
4152+ maximum = ss.scores[0][2]
4153+ value = ss.getNumericalValue(value)
4154 elif IRangedValuesScoreSystem.providedBy(ss):
4155 minimum = ss.min
4156 maximum = ss.max
4157- value = ev.value
4158 else:
4159 continue
4160 total += value - minimum
4161@@ -432,12 +445,11 @@
4162
4163 def __getattr__(self, name):
4164 activity = self.context.activities[name]
4165- ev = self.context.gradebook.getEvaluation(self.context.student,
4166- activity)
4167- if ev is not None and ev.value is not UNSCORED:
4168- return ev.value
4169- else:
4170- return ''
4171+ value, ss = self.context.gradebook.getEvaluation(self.context.student,
4172+ activity)
4173+ if value is None:
4174+ value = ''
4175+ return value
4176
4177
4178 def getWorksheetSection(worksheet):
4179
4180=== modified file 'src/schooltool/gradebook/gradebook_init.py'
4181--- src/schooltool/gradebook/gradebook_init.py 2009-08-14 09:53:09 +0000
4182+++ src/schooltool/gradebook/gradebook_init.py 2009-10-12 08:13:10 +0000
4183@@ -139,6 +139,8 @@
4184
4185
4186 def getGradebookRoot(app):
4187+ if GRADEBOOK_ROOT_KEY not in app:
4188+ return None
4189 return app[GRADEBOOK_ROOT_KEY]
4190
4191
4192
4193=== modified file 'src/schooltool/gradebook/interfaces.py'
4194--- src/schooltool/gradebook/interfaces.py 2009-08-05 05:42:54 +0000
4195+++ src/schooltool/gradebook/interfaces.py 2009-10-12 08:13:10 +0000
4196@@ -210,7 +210,7 @@
4197 def hasEvaluation(student, activity):
4198 """Check whether an evaluation exists for a student-activity pair."""
4199
4200- def getEvaluation(student, activity, default=None):
4201+ def getEvaluation(student, activity):
4202 """Get the evaluation of a student for a given activity."""
4203
4204 def getCurrentEvaluationsForStudent(student):
4205@@ -304,7 +304,7 @@
4206 title=_('Worksheets'),
4207 description=_('Worksheets in this gradebook.'))
4208
4209- def getEvaluation(student, activity, default=None):
4210+ def getEvaluation(student, activity):
4211 """Get the evaluation of a student for a given activity."""
4212
4213 def getCurrentWorksheet():
4214@@ -329,7 +329,7 @@
4215 title=_(u"External Activity ID"),
4216 description=_(u"A unique identifier for the external activity"),
4217 required=True)
4218-
4219+
4220 points = zope.schema.Int(
4221 title=_(u"Points"),
4222 description=_(u"Points value to calculate the activity grade"),
4223@@ -403,3 +403,13 @@
4224
4225 def __eq__(another):
4226 """Compare equality with other external activities"""
4227+
4228+
4229+class ILinkedColumnActivity(IActivity):
4230+ """An activity that can be linked to an external activity"""
4231+
4232+ source = zope.schema.TextLine(
4233+ title=_(u"Linked Column Activity Source"),
4234+ description=_(u"A text string that specifies the source of the column"),
4235+ required=True)
4236+
4237
4238=== modified file 'src/schooltool/requirement/README.txt'
4239--- src/schooltool/requirement/README.txt 2009-06-25 05:00:11 +0000
4240+++ src/schooltool/requirement/README.txt 2009-10-12 08:13:12 +0000
4241@@ -179,8 +179,9 @@
4242 >>> from decimal import Decimal
4243 >>> check = scoresystem.DiscreteValuesScoreSystem(
4244 ... u'Check', u'Check-mark score system',
4245- ... [('+', Decimal(1), Decimal(80)), ('v', Decimal(0), Decimal(60)),
4246- ... ('-', Decimal(-1), Decimal(0))])
4247+ ... [('+', '', Decimal(1), Decimal(80)),
4248+ ... ('v', '', Decimal(0), Decimal(60)),
4249+ ... ('-', '', Decimal(-1), Decimal(0))])
4250
4251 The first and second arguments of the constructor are the title and
4252 description. The third argument is a list that really represents a mapping
4253@@ -248,9 +249,9 @@
4254 >>> from schooltool.requirement import scoresystem
4255 >>> check = scoresystem.DiscreteValuesScoreSystem(
4256 ... u'Check', u'Check-mark score system',
4257- ... [('+', Decimal(1), Decimal(80)),
4258- ... ('v', Decimal(0), Decimal(60)),
4259- ... ('-', Decimal(-1), Decimal(0))],
4260+ ... [('+', '', Decimal(1), Decimal(80)),
4261+ ... ('v', '', Decimal(0), Decimal(60)),
4262+ ... ('-', '', Decimal(-1), Decimal(0))],
4263 ... minPassingScore='v')
4264 >>> check
4265 <DiscreteValuesScoreSystem u'Check'>
4266@@ -280,8 +281,9 @@
4267 >>> from schooltool.requirement import scoresystem
4268 >>> check = scoresystem.DiscreteValuesScoreSystem(
4269 ... u'Check', u'Check-mark score system',
4270- ... [('+', Decimal(1), Decimal(80)), ('v', Decimal(0), Decimal(60)),
4271- ... ('-', Decimal(-1)), Decimal(0)],
4272+ ... [('+', '', Decimal(1), Decimal(80)),
4273+ ... ('v', '', Decimal(0), Decimal(60)),
4274+ ... ('-', '', Decimal(-1)), Decimal(0)],
4275 ... bestScore='+', minPassingScore='v')
4276
4277 >>> check.getBestScore()
4278@@ -299,8 +301,8 @@
4279 >>> scoresystem.PassFail.title
4280 u'Pass/Fail'
4281 >>> scoresystem.PassFail.scores
4282- [(u'Pass', Decimal("1"), Decimal("60")),
4283- (u'Fail', Decimal("0"), Decimal("0"))]
4284+ [(u'Pass', u'', Decimal("1"), Decimal("60")),
4285+ (u'Fail', u'', Decimal("0"), Decimal("0"))]
4286 >>> scoresystem.PassFail.isValidScore('Pass')
4287 True
4288 >>> scoresystem.PassFail.isPassingScore('Pass')
4289@@ -327,9 +329,11 @@
4290 >>> scoresystem.AmericanLetterScoreSystem.title
4291 u'Letter Grade'
4292 >>> scoresystem.AmericanLetterScoreSystem.scores
4293- [('A', Decimal("4"), Decimal("90")), ('B', Decimal("3"), Decimal("80")),
4294- ('C', Decimal("2"), Decimal("70")), ('D', Decimal("1"), Decimal("60")),
4295- ('F', Decimal("0"), Decimal("0"))]
4296+ [('A', u'', Decimal("4"), Decimal("90")),
4297+ ('B', u'', Decimal("3"), Decimal("80")),
4298+ ('C', u'', Decimal("2"), Decimal("70")),
4299+ ('D', u'', Decimal("1"), Decimal("60")),
4300+ ('F', u'', Decimal("0"), Decimal("0"))]
4301 >>> scoresystem.AmericanLetterScoreSystem.isValidScore('C')
4302 True
4303 >>> scoresystem.AmericanLetterScoreSystem.isValidScore('E')
4304@@ -357,7 +361,7 @@
4305 'ExtendedAmericanLetterScoreSystem'
4306 >>> scoresystem.ExtendedAmericanLetterScoreSystem.title
4307 u'Extended Letter Grade'
4308- >>> [s for s, v, p in scoresystem.ExtendedAmericanLetterScoreSystem.scores]
4309+ >>> [s for s, a, v, p in scoresystem.ExtendedAmericanLetterScoreSystem.scores]
4310 ['A+', 'A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'D-', 'F']
4311 >>> scoresystem.ExtendedAmericanLetterScoreSystem.isValidScore('B-')
4312 True
4313
4314=== modified file 'src/schooltool/requirement/browser/README.txt'
4315--- src/schooltool/requirement/browser/README.txt 2009-07-08 20:23:33 +0000
4316+++ src/schooltool/requirement/browser/README.txt 2009-10-12 08:13:12 +0000
4317@@ -95,11 +95,11 @@
4318
4319 We'll send the form values necessary to add a score system called 'Good/Bad'.
4320
4321- >>> url = update_url + '&title=Good/Bad&displayed1=G&value1=1&percent1=60'
4322- >>> url = url + '&displayed2=B&value2=0&percent2=0'
4323+ >>> url = update_url + '&title=Good/Bad&displayed1=G&abbr1=&value1=1&percent1=60'
4324+ >>> url = url + '&displayed2=B&abbr2=&value2=0&percent2=0'
4325 >>> manager.open(url)
4326
4327-Now we see the two score systems in the list.
4328+Now we see the new score system in the list.
4329
4330 >>> analyze.printQuery("id('content-body')/form//a", manager.contents)
4331 <a href="http://localhost/scoresystems/view.html?name=extended-letter-grade">Extended Letter Grade</a>
4332@@ -124,9 +124,11 @@
4333 >>> manager.getLink('Good/Bad').click()
4334 >>> analyze.printQuery("id('content-body')/table//span", manager.contents)
4335 <span>G</span>
4336+ <span></span>
4337 <span>1</span>
4338 <span>60</span>
4339 <span>B</span>
4340+ <span></span>
4341 <span>0</span>
4342 <span>0</span>
4343
4344
4345=== modified file 'src/schooltool/requirement/browser/scoresystem.py'
4346--- src/schooltool/requirement/browser/scoresystem.py 2009-07-08 20:23:33 +0000
4347+++ src/schooltool/requirement/browser/scoresystem.py 2009-10-12 08:13:12 +0000
4348@@ -43,9 +43,11 @@
4349 NO_NEGATIVE_PERCENTS = _('All percentages must be non-negative.')
4350 NO_PERCENTS_OVER_100 = _('Percentages cannot be greater than 100.')
4351 MUST_HAVE_AT_LEAST_2_SCORES = _('A score system must have at least two scores.')
4352-VALUES_MUST_DESCEND = _('Score values must go in descending order.')
4353-PERCENTS_MUST_DESCEND = _('Score percentages must go in descending order.')
4354-LAST_PERCENT_NOT_ZERO = _('The last percentage must be zero.')
4355+VALUES_MUST_DESCEND = _('Score values must go in descending order.')
4356+PERCENTS_MUST_DESCEND = _('Score percentages must go in descending order.')
4357+LAST_PERCENT_NOT_ZERO = _('The last percentage must be zero.')
4358+DUPLICATE_DISPLAYED = _('Duplicate scores are not allowed.')
4359+DUPLICATE_ABBREVIATION = _('Duplicate abbreviations are not allowed.')
4360
4361
4362 def escName(name):
4363@@ -104,17 +106,19 @@
4364 def scores(self):
4365 rownum = 1
4366 results = []
4367- for displayed, value, percent in self.getRequestScores():
4368- results.append(self.buildScoreRow(rownum, displayed, value,
4369+ for displayed, abbr, value, percent in self.getRequestScores():
4370+ results.append(self.buildScoreRow(rownum, displayed, abbr, value,
4371 percent))
4372 rownum += 1
4373- results.append(self.buildScoreRow(rownum, '', '', ''))
4374+ results.append(self.buildScoreRow(rownum, '', '', '', ''))
4375 return results
4376
4377- def buildScoreRow(self, rownum, displayed, value, percent):
4378+ def buildScoreRow(self, rownum, displayed, abbr, value, percent):
4379 return {
4380 'displayed_name': 'displayed' + unicode(rownum),
4381 'displayed_value': displayed,
4382+ 'abbr_name': 'abbr' + unicode(rownum),
4383+ 'abbr_value': abbr,
4384 'value_name': 'value' + unicode(rownum),
4385 'value_value': value,
4386 'percent_name': 'percent' + unicode(rownum),
4387@@ -127,6 +131,7 @@
4388 while True:
4389 rownum += 1
4390 displayed_name = 'displayed' + unicode(rownum)
4391+ abbr_name = 'abbr' + unicode(rownum)
4392 value_name = 'value' + unicode(rownum)
4393 percent_name = 'percent' + unicode(rownum)
4394 if displayed_name not in self.request:
4395@@ -134,6 +139,7 @@
4396 if not len(self.request[displayed_name]):
4397 continue
4398 result = (self.request[displayed_name],
4399+ self.request[abbr_name],
4400 self.request[value_name],
4401 self.request[percent_name])
4402 results.append(result)
4403@@ -145,7 +151,7 @@
4404 return self.setMessage(MISSING_TITLE)
4405
4406 scores = []
4407- for displayed, value, percent in self.getRequestScores():
4408+ for displayed, abbr, value, percent in self.getRequestScores():
4409 try:
4410 decimal_value = Decimal(value)
4411 except:
4412@@ -160,7 +166,7 @@
4413 return self.setMessage(NO_NEGATIVE_PERCENTS)
4414 if decimal_percent > 100:
4415 return self.setMessage(NO_PERCENTS_OVER_100)
4416- scores.append([displayed, decimal_value, decimal_percent])
4417+ scores.append([displayed, abbr, decimal_value, decimal_percent])
4418
4419 self.validTitle = title
4420 self.validScores = scores
4421@@ -171,15 +177,25 @@
4422 return self.setMessage(MUST_HAVE_AT_LEAST_2_SCORES)
4423
4424 last_value, last_percent = None, None
4425- for displayed, value, percent in self.validScores:
4426+ disp_list, abbr_list = [], []
4427+ for displayed, abbr, value, percent in self.validScores:
4428 if last_value is not None:
4429 if value >= last_value:
4430 return self.setMessage(VALUES_MUST_DESCEND)
4431 if last_percent is not None:
4432 if percent >= last_percent:
4433 return self.setMessage(PERCENTS_MUST_DESCEND)
4434+ for d in disp_list:
4435+ if d.lower() == displayed.lower():
4436+ return self.setMessage(DUPLICATE_DISPLAYED)
4437+ if abbr:
4438+ for a in abbr_list:
4439+ if a.lower() == abbr.lower():
4440+ return self.setMessage(DUPLICATE_ABBREVIATION)
4441 last_value = value
4442 last_percent = percent
4443+ disp_list.append(displayed)
4444+ abbr_list.append(abbr)
4445
4446 if last_percent <> 0:
4447 return self.setMessage(LAST_PERCENT_NOT_ZERO)
4448@@ -208,12 +224,13 @@
4449
4450 def scores(self):
4451 target = self.getScoreSystem()
4452- return [self.buildScoreRow(displayed, value, percent)
4453- for displayed, value, percent in target.scores]
4454+ return [self.buildScoreRow(displayed, abbr, value, percent)
4455+ for displayed, abbr, value, percent in target.scores]
4456
4457- def buildScoreRow(self, displayed, value, percent):
4458+ def buildScoreRow(self, displayed, abbr, value, percent):
4459 return {
4460 'displayed_value': displayed,
4461+ 'abbr_value': abbr,
4462 'value_value': value,
4463 'percent_value': percent,
4464 }
4465
4466=== modified file 'src/schooltool/requirement/browser/scoresystem_add.pt'
4467--- src/schooltool/requirement/browser/scoresystem_add.pt 2009-07-06 02:59:50 +0000
4468+++ src/schooltool/requirement/browser/scoresystem_add.pt 2009-10-12 08:13:12 +0000
4469@@ -33,6 +33,9 @@
4470 Each score is also associated with a minimum percentage necessary to achieve the corresponding grade. For example, 95% and above is an "A," 87% and up is a "B," etc.
4471 </p>
4472 <p>
4473+ Enter an abbreviation for a score only if the score has more than four characters and you wish to create an alternative abbreviation for the gradebook.
4474+ </p>
4475+ <p>
4476 Start with the highest score. Click "Add a lower score" to add scores until you have created a score with a minimum percentage of 0.
4477 </p>
4478 <fieldset>
4479@@ -45,22 +48,27 @@
4480 <table class="schooltool_gradebook">
4481 <tr>
4482 <th class="cell header fully_padded">Score</th>
4483+ <th class="cell header fully_padded">Abbreviation</th>
4484 <th class="cell header fully_padded">Point Value</th>
4485- <th class="cell header fully_padded">Minimum Percentage</th>
4486+ <th class="cell header fully_padded">Low Percentage</th>
4487 </tr>
4488
4489 <tal:block repeat="score view/scores">
4490 <tr class="bordered">
4491 <td class="cell fully_padded">
4492- <input type="text"
4493+ <input type="text" class="scoresystem_add_input"
4494 tal:attributes="name score/displayed_name; value score/displayed_value" />
4495 </td>
4496 <td class="cell fully_padded">
4497- <input type="text"
4498+ <input type="text" class="scoresystem_add_input"
4499+ tal:attributes="name score/abbr_name; value score/abbr_value" />
4500+ </td>
4501+ <td class="cell fully_padded">
4502+ <input type="text" class="scoresystem_add_input"
4503 tal:attributes="name score/value_name; value score/value_value" />
4504 </td>
4505 <td class="cell fully_padded">
4506- <input type="text"
4507+ <input type="text" class="scoresystem_add_input"
4508 tal:attributes="name score/percent_name; value score/percent_value" />
4509 </td>
4510 </tr>
4511
4512=== modified file 'src/schooltool/requirement/browser/scoresystem_view.pt'
4513--- src/schooltool/requirement/browser/scoresystem_view.pt 2009-07-08 20:23:33 +0000
4514+++ src/schooltool/requirement/browser/scoresystem_view.pt 2009-10-12 08:13:12 +0000
4515@@ -16,6 +16,7 @@
4516 <table class="schooltool_gradebook">
4517 <tr>
4518 <th class="cell header fully_padded">Score Displayed</th>
4519+ <th class="cell header fully_padded">Abbreviation</th>
4520 <th class="cell header fully_padded">Score Value</th>
4521 <th class="cell header fully_padded">Low Percentage</th>
4522 </tr>
4523@@ -26,6 +27,9 @@
4524 <span tal:content="score/displayed_value" />
4525 </td>
4526 <td class="cell fully_padded">
4527+ <span tal:content="score/abbr_value" />
4528+ </td>
4529+ <td class="cell fully_padded">
4530 <span tal:content="score/value_value" />
4531 </td>
4532 <td class="cell fully_padded">
4533
4534=== modified file 'src/schooltool/requirement/scoresystem.py'
4535--- src/schooltool/requirement/scoresystem.py 2009-06-29 07:43:38 +0000
4536+++ src/schooltool/requirement/scoresystem.py 2009-10-12 08:13:12 +0000
4537@@ -204,14 +204,14 @@
4538 def getFractionalValue(self, score):
4539 """See interfaces.IScoreSystem"""
4540 # get maximum and minimum score to determine the range
4541- maximum = self.scores[0][1]
4542- minimum = self.scores[-1][1]
4543+ maximum = self.scores[0][2]
4544+ minimum = self.scores[-1][2]
4545 # normalized numerical score
4546 value = self.getNumericalValue(score) - minimum
4547 return value / (maximum - minimum)
4548
4549 def scoresDict(self):
4550- scores = [(score, value) for score, value, percent in self.scores]
4551+ scores = [(score, value) for score, abbr, value, percent in self.scores]
4552 return dict(scores)
4553
4554 class GlobalDiscreteValuesScoreSystem(DiscreteValuesScoreSystem):
4555@@ -227,36 +227,36 @@
4556 PassFail = GlobalDiscreteValuesScoreSystem(
4557 'PassFail',
4558 u'Pass/Fail', u'Pass or Fail score system.',
4559- [(u'Pass', Decimal(1), Decimal(60)),
4560- (u'Fail', Decimal(0), Decimal(0))],
4561+ [(u'Pass', u'', Decimal(1), Decimal(60)),
4562+ (u'Fail', u'', Decimal(0), Decimal(0))],
4563 u'Pass', u'Pass')
4564
4565 AmericanLetterScoreSystem = GlobalDiscreteValuesScoreSystem(
4566 'AmericanLetterScoreSystem',
4567 u'Letter Grade', u'American Letter Grade',
4568- [('A', Decimal(4), Decimal(90)),
4569- ('B', Decimal(3), Decimal(80)),
4570- ('C', Decimal(2), Decimal(70)),
4571- ('D', Decimal(1), Decimal(60)),
4572- ('F', Decimal(0), Decimal(0))],
4573+ [('A', u'', Decimal(4), Decimal(90)),
4574+ ('B', u'', Decimal(3), Decimal(80)),
4575+ ('C', u'', Decimal(2), Decimal(70)),
4576+ ('D', u'', Decimal(1), Decimal(60)),
4577+ ('F', u'', Decimal(0), Decimal(0))],
4578 'A', 'D')
4579
4580 ExtendedAmericanLetterScoreSystem = GlobalDiscreteValuesScoreSystem(
4581 'ExtendedAmericanLetterScoreSystem',
4582 u'Extended Letter Grade', u'American Extended Letter Grade',
4583- [('A+', Decimal('4.0'), Decimal(98)),
4584- ('A', Decimal('4.0'), Decimal(93)),
4585- ('A-', Decimal('3.7'), Decimal(90)),
4586- ('B+', Decimal('3.3'), Decimal(88)),
4587- ('B', Decimal('3.0'), Decimal(83)),
4588- ('B-', Decimal('2.7'), Decimal(80)),
4589- ('C+', Decimal('2.3'), Decimal(78)),
4590- ('C', Decimal('2.0'), Decimal(73)),
4591- ('C-', Decimal('1.7'), Decimal(70)),
4592- ('D+', Decimal('1.3'), Decimal(68)),
4593- ('D', Decimal('1.0'), Decimal(63)),
4594- ('D-', Decimal('0.7'), Decimal(60)),
4595- ('F', Decimal('0.0'), Decimal(0))],
4596+ [('A+', u'', Decimal('4.0'), Decimal(98)),
4597+ ('A', u'', Decimal('4.0'), Decimal(93)),
4598+ ('A-', u'', Decimal('3.7'), Decimal(90)),
4599+ ('B+', u'', Decimal('3.3'), Decimal(88)),
4600+ ('B', u'', Decimal('3.0'), Decimal(83)),
4601+ ('B-', u'', Decimal('2.7'), Decimal(80)),
4602+ ('C+', u'', Decimal('2.3'), Decimal(78)),
4603+ ('C', u'', Decimal('2.0'), Decimal(73)),
4604+ ('C-', u'', Decimal('1.7'), Decimal(70)),
4605+ ('D+', u'', Decimal('1.3'), Decimal(68)),
4606+ ('D', u'', Decimal('1.0'), Decimal(63)),
4607+ ('D-', u'', Decimal('0.7'), Decimal(60)),
4608+ ('F', u'', Decimal('0.0'), Decimal(0))],
4609 'A+', 'D-')
4610
4611

Subscribers

People subscribed via source and target branches