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

Proposed by Alan Elkner
Status: Merged
Merge reported by: Justas Sadzevičius
Merged at revision: not available
Proposed branch: lp:~aelkner/schooltool/schooltool.gradebook_migrate_sla
Merge into: lp:~schooltool-owners/schooltool/schooltool.gradebook
Diff against target: None lines
To merge this branch: bzr merge lp:~aelkner/schooltool/schooltool.gradebook_migrate_sla
Reviewer Review Type Date Requested Status
Justas Sadzevičius (community) Approve
Review via email: mp+8248@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Justas Sadzevičius (justas.sadzevicius) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/schooltool/gradebook/browser/README.txt'
2--- src/schooltool/gradebook/browser/README.txt 2009-06-01 21:32:36 +0000
3+++ src/schooltool/gradebook/browser/README.txt 2009-07-06 03:19:53 +0000
4@@ -89,11 +89,11 @@
5
6 But what would a section be without some students and a teacher?
7
8- >>> from schooltool.app.browser.ftests.setup import addPerson
9- >>> addPerson('Paul Cardune', 'paul', 'pwd')
10- >>> addPerson('Tom Hoffman', 'tom', 'pwd')
11- >>> addPerson('Claudia Richter', 'claudia', 'pwd')
12- >>> addPerson('Stephan Richter', 'stephan', 'pwd')
13+ >>> from schooltool.basicperson.browser.ftests.setup import addPerson
14+ >>> addPerson('Paul', 'Cardune', 'paul', 'pwd', browser=manager)
15+ >>> addPerson('Tom', 'Hoffman', 'tom', 'pwd', browser=manager)
16+ >>> addPerson('Claudia', 'Richter', 'claudia', 'pwd', browser=manager)
17+ >>> addPerson('Stephan', 'Richter', 'stephan', 'pwd', browser=manager)
18
19 Now we can add those people to the section:
20
21@@ -103,17 +103,14 @@
22 >>> manager.getLink('(1)').click()
23
24 >>> manager.getLink('edit individuals').click()
25- >>> manager.getControl('Paul Cardune').click()
26- >>> manager.getControl('Tom Hoffman').click()
27- >>> manager.getControl('Claudia Richter').click()
28+ >>> manager.getControl(name='add_item.paul').value = 'checked'
29+ >>> manager.getControl(name='add_item.tom').value = 'checked'
30+ >>> manager.getControl(name='add_item.claudia').value = 'checked'
31 >>> manager.getControl('Add').click()
32 >>> manager.getControl('OK').click()
33
34- >>> 'Paul Cardune' in manager.contents
35- True
36-
37 >>> manager.getLink('edit instructors').click()
38- >>> manager.getControl('Stephan Richter').click()
39+ >>> manager.getControl(name='add_item.stephan').value = 'checked'
40 >>> manager.getControl('Add').click()
41 >>> manager.getControl('OK').click()
42
43@@ -177,9 +174,9 @@
44 ...New Activity...
45 ...Manage Activities...
46 ...Physics I (1)...
47- ...Claudia...
48- ...Paul...
49- ...Tom...
50+ ...Cardune, Paul...
51+ ...Hoffman, Tom...
52+ ...Richter, Claudia...
53 >>> '<span style="font-weight: bold;">Week 1</span>' in stephan.contents
54 True
55
56@@ -312,7 +309,7 @@
57 >>> txt = contents[index:]
58 >>> cell_name = txt[:txt.find('"')]
59 >>> stephan.getControl(name=cell_name).value = '56'
60- >>> stephan.getControl('Update').click()
61+ >>> stephan.getControl('Save').click()
62
63 We should see the score reflected in the spreadsheet.
64
65@@ -322,28 +319,28 @@
66 If we enter an invalid score, we will get an error message.
67
68 >>> stephan.getControl(name=cell_name).value = 'Bad'
69- >>> stephan.getControl('Update').click()
70+ >>> stephan.getControl('Save').click()
71 >>> 'The grade Bad for activity HW 1 is not valid.' in stephan.contents
72 True
73
74 We can change the score and see the change reflected in the spreadsheet.
75
76 >>> stephan.getControl(name=cell_name).value = '88'
77- >>> stephan.getControl('Update').click()
78+ >>> stephan.getControl('Save').click()
79 >>> stephan.getControl(name=cell_name).value
80 '88'
81
82 Finally, we can remove the score by clearing out the cell.
83
84 >>> stephan.getControl(name=cell_name).value = ''
85- >>> stephan.getControl('Update').click()
86+ >>> stephan.getControl('Save').click()
87 >>> stephan.getControl(name=cell_name).value
88 ''
89
90 We need to put the score back for future tests to pass.
91
92 >>> stephan.getControl(name=cell_name).value = '36'
93- >>> stephan.getControl('Update').click()
94+ >>> stephan.getControl('Save').click()
95
96
97 Entering Scores for a Column (Activity)
98@@ -357,13 +354,13 @@
99 Now we just enter the grades. Since Claudia has already a grade, we only need
100 to grade Paul and Tom:
101
102- >>> stephan.getControl('Paul Cardune').value = u'-1'
103- >>> stephan.getControl('Tom Hoffman').value = u'42'
104- >>> stephan.getControl('Update').click()
105+ >>> stephan.getControl('Cardune, Paul').value = u'-1'
106+ >>> stephan.getControl('Hoffman, Tom').value = u'42'
107+ >>> stephan.getControl('Save').click()
108
109 Again, we entered an invalid value, this time for Paul:
110
111- >>> 'The grade -1 for Paul Cardune is not valid.' in stephan.contents
112+ >>> 'The grade -1 for Cardune, Paul is not valid.' in stephan.contents
113 True
114
115 Also note that all the other entered values should be retained:
116@@ -374,8 +371,8 @@
117 True
118 >>> 'value="36"' in stephan.contents
119 True
120- >>> stephan.getControl('Paul Cardune').value = u'40'
121- >>> stephan.getControl('Update').click()
122+ >>> stephan.getControl('Cardune, Paul').value = u'40'
123+ >>> stephan.getControl('Save').click()
124
125 The screen will return to the grade overview, where the grades are now
126 visible:
127@@ -390,16 +387,16 @@
128 Now let's enter again and change a grade:
129
130 >>> stephan.getLink('HW1').click()
131- >>> stephan.getControl('Claudia Richter').value = u'48'
132- >>> stephan.getControl('Update').click()
133+ >>> stephan.getControl('Richter, Claudia').value = u'48'
134+ >>> stephan.getControl('Save').click()
135 >>> 'value="48"' in stephan.contents
136 True
137
138 When you want to delete an evaluation altogether, simply blank the value:
139
140 >>> stephan.getLink('HW1').click()
141- >>> stephan.getControl('Claudia Richter').value = u''
142- >>> stephan.getControl('Update').click()
143+ >>> stephan.getControl('Richter, Claudia').value = u''
144+ >>> stephan.getControl('Save').click()
145 >>> 'value="98"' in stephan.contents
146 False
147
148@@ -415,10 +412,10 @@
149
150 >>> stephan.getLink('Week 2').click()
151 >>> stephan.getLink('HW2').click()
152- >>> stephan.getControl('Paul Cardune').value = u'90'
153- >>> stephan.getControl('Tom Hoffman').value = u'72'
154- >>> stephan.getControl('Claudia Richter').value = u'42'
155- >>> stephan.getControl('Update').click()
156+ >>> stephan.getControl('Cardune, Paul').value = u'90'
157+ >>> stephan.getControl('Hoffman, Tom').value = u'72'
158+ >>> stephan.getControl('Richter, Claudia').value = u'42'
159+ >>> stephan.getControl('Save').click()
160
161 We'll set the current worksheet back to week 1 for the rest of the tests.
162
163@@ -427,12 +424,70 @@
164 We need to set Claudia's Quiz score to 86 to replace tests that we deleted.
165
166 >>> stephan.getLink('Quiz').click()
167- >>> stephan.getControl('Claudia Richter').value = u'86'
168- >>> stephan.getControl('Update').click()
169+ >>> stephan.getControl('Richter, Claudia').value = u'86'
170+ >>> stephan.getControl('Save').click()
171 >>> stephan.url
172 'http://localhost/schoolyears/2007/winter/sections/1/activities/Worksheet/gradebook/index.html'
173
174
175+Entering Scores for a Row (Student)
176+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
177+
178+With the introduction of the comment score system, we need to provide the user
179+with a way of entering comments into the gradebook. Since comments have
180+arbitrary length, we need a special view for entering them. We will provide
181+a row view (by student) for this purpose. When the user clicks on a student,
182+they will see a form with one field for each activity, comments having the
183+fckEditor widget, the rest of the score system types just having a TextLine
184+widget.
185+
186+Additionally, the user will have two special buttons, 'Previous' and 'Next',
187+which allows them to go from student to student without having to return to
188+the gradebook spreadsheet each time. 'Apply' and 'Cancel' return to the
189+spreadsheet as one would expect.
190+
191+We'll start by clicking on Paul and testing the contents of the form.
192+Since Paul is the first student in the list of students, there will be
193+no 'Previous' button.
194+
195+ >>> stephan.getLink('Cardune, Paul').click()
196+ >>> analyze.printQuery("id('form')/div[1]/h3", stephan.contents)
197+ <h3>Enter grades for Cardune, Paul</h3>
198+ >>> analyze.printQuery("id('form')/div[2]//input", stephan.contents)
199+ <input type="submit" id="form-buttons-apply" name="form.buttons.apply" class="submit-widget button-field button-ok" value="Apply" />
200+ <input type="submit" id="form-buttons-next" name="form.buttons.next" class="submit-widget button-field button-ok" value="Next" />
201+ <input type="submit" id="form-buttons-cancel" name="form.buttons.cancel" class="submit-widget button-field button-cancel" value="Cancel" />
202+
203+When we click on the 'Next' button it takes us to the middle student, Tom.
204+Here we will see both a 'Previous' and a 'Next' button.
205+
206+ >>> stephan.getControl('Next').click()
207+ >>> analyze.printQuery("id('form')/div[1]/h3", stephan.contents)
208+ <h3>Enter grades for Hoffman, Tom</h3>
209+ >>> analyze.printQuery("id('form')/div[2]//input", stephan.contents)
210+ <input type="submit" id="form-buttons-apply" name="form.buttons.apply" class="submit-widget button-field button-ok" value="Apply" />
211+ <input type="submit" id="form-buttons-previous" name="form.buttons.previous" class="submit-widget button-field button-ok" value="Previous" />
212+ <input type="submit" id="form-buttons-next" name="form.buttons.next" class="submit-widget button-field button-ok" value="Next" />
213+ <input type="submit" id="form-buttons-cancel" name="form.buttons.cancel" class="submit-widget button-field button-cancel" value="Cancel" />
214+
215+When we click on the 'Next' button it takes us to the last student, Claudia.
216+Here we will see no 'Next' button.
217+
218+ >>> stephan.getControl('Next').click()
219+ >>> analyze.printQuery("id('form')/div[1]/h3", stephan.contents)
220+ <h3>Enter grades for Richter, Claudia</h3>
221+ >>> analyze.printQuery("id('form')/div[2]//input", stephan.contents)
222+ <input type="submit" id="form-buttons-apply" name="form.buttons.apply" class="submit-widget button-field button-ok" value="Apply" />
223+ <input type="submit" id="form-buttons-previous" name="form.buttons.previous" class="submit-widget button-field button-ok" value="Previous" />
224+ <input type="submit" id="form-buttons-cancel" name="form.buttons.cancel" class="submit-widget button-field button-cancel" value="Cancel" />
225+
226+Hitting the 'Cancel' button takes the user back to the gradebook.
227+
228+ >>> stephan.getControl('Cancel').click()
229+ >>> analyze.printQuery("id('content-body')/div/form/span[3]", stephan.contents)
230+ <span>show only activities due in past</span>
231+
232+
233 Sorting
234 ~~~~~~~
235
236@@ -440,9 +495,9 @@
237 descending and ascending fashion. By default the student's name is sorted
238 alphabetically:
239
240- >>> stephan.contents.find('Claudia') \
241- ... < stephan.contents.find('Paul') \
242- ... < stephan.contents.find('Tom')
243+ >>> stephan.contents.find('Carduner, Paul') \
244+ ... < stephan.contents.find('Hoffman, Tom') \
245+ ... < stephan.contents.find('Richter, Claudia')
246 True
247
248 Then we want to sort by grade in Homework 1, so we should have:
249@@ -474,18 +529,18 @@
250 >>> stephan.getLink('Summary').click()
251 >>> print stephan.contents
252 <BLANKLINE>
253- ...Claudia Richter...
254- ...86</td>...
255- ...42</td>...
256- ...C</td>...
257- ...Paul Cardune...
258+ ...Paul...
259 ...80</td>...
260 ...90</td>...
261 ...A</td>...
262- ...Tom Hoffman...
263+ ...Tom...
264 ...84</td>...
265 ...72</td>...
266 ...B</td>...
267+ ...Claudia...
268+ ...86</td>...
269+ ...42</td>...
270+ ...C</td>...
271 >>> stephan.open(stephan.url[:stephan.url.rfind('/')])
272
273
274@@ -537,11 +592,11 @@
275 >>> stephan.getControl('Update').click()
276 >>> print stephan.contents
277 <BLANKLINE>
278- ...Claudia Richter...
279+ ...Claudia...
280 ...86%</b>...
281- ...Tom Hoffman...
282+ ...Tom...
283 ...84%</b>...
284- ...Paul Cardune...
285+ ...Paul...
286 ...80%</b>...
287
288 Finally, we'll test hitting the 'Cancel' button. It should return to the
289@@ -571,7 +626,7 @@
290 >>> manager.getLink('Manage').click()
291 >>> manager.getLink('Score Systems').click()
292 >>> manager.getLink('Add Score System').click()
293- >>> url = manager.url + '?form-submitted&SAVE&title=Good/Bad'
294+ >>> url = manager.url + '?form-submitted&UPDATE_SUBMIT&title=Good/Bad'
295 >>> url = url + '&displayed1=G&value1=1&percent1=60'
296 >>> url = url + '&displayed2=B&value2=0&percent2=0'
297 >>> manager.open(url)
298@@ -680,22 +735,15 @@
299 >>> manager.getLink('(2)').click()
300
301 >>> manager.getLink('edit individuals').click()
302- >>> manager.getControl('Stephan Richter').click()
303+ >>> manager.getControl(name='add_item.stephan').value = 'checked'
304 >>> manager.getControl('Add').click()
305 >>> manager.getControl('OK').click()
306
307 >>> manager.getLink('edit instructors').click()
308- >>> manager.getControl('Tom Hoffman').click()
309+ >>> manager.getControl(name='add_item.tom').value = 'checked'
310 >>> manager.getControl('Add').click()
311 >>> manager.getControl('OK').click()
312
313- >>> print manager.contents
314- <BLANKLINE>
315- ...Instructors...
316- ...Tom Hoffman...
317- ...Students...
318- ...Stephan Richter...
319-
320 We'll have Tom set up a worksheet.
321
322 >>> tom = setup.logIn('tom', 'pwd')
323@@ -779,7 +827,7 @@
324 >>> manager.open('http://localhost/schoolyears/2007/winter/sections/1/gradebook')
325 >>> manager.getLink('HW1').click()
326 >>> manager.getControl(name='tom').value = '45'
327- >>> manager.getControl('Update').click()
328+ >>> manager.getControl('Save').click()
329
330 Let's set the setting back to cover our tracks:
331
332@@ -797,7 +845,7 @@
333 ...Physics I (1)...
334 >>> stephan.getLink('HW1').click()
335 >>> stephan.getControl(name='tom').value = '44'
336- >>> stephan.getControl('Update').click()
337+ >>> stephan.getControl('Save').click()
338
339 Students won't be able to see each other's grade's because the mygrades view
340 uses the request's principal to determine which grades to display.
341@@ -825,6 +873,11 @@
342
343 Gradebook's worksheets can be exported to a XLS file:
344
345+ >>> stephan.getLink('Export XLS').click()
346+ >>> stephan.headers.get('Content-Type')
347+ 'application/excel'
348+ >>> stephan.open('http://localhost/gradebook.html')
349+ >>> stephan.getLink('Classes you teach').click()
350 >>> stephan.getLink('Worksheets').click()
351 >>> stephan.getLink('Export XLS').click()
352 >>> stephan.headers.get('Content-Type')
353@@ -878,9 +931,9 @@
354 >>> stephan.contents
355 <BLANKLINE>
356 ...Name...Total...Ave...HW 1...Quiz...
357- ...Claudia Richter...<b>86</b>...<b>86%</b>...86...
358- ...Tom Hoffman...<b>44</b>...<b>88%</b>...44...
359- ...Paul Cardune...<b>40</b>...<b>80%</b>...40...
360+ ...Claudia...<b>86</b>...<b>86%</b>...86...
361+ ...Tom...<b>44</b>...<b>88%</b>...44...
362+ ...Paul...<b>40</b>...<b>80%</b>...40...
363
364 This state should change after we update the grades from external
365 activities. Remember that the gradebook has weighting defined?:
366@@ -930,9 +983,9 @@
367 >>> stephan.contents
368 <BLANKLINE>
369 ...Name...Total...Ave...HW 1...Quiz...Hardware...
370- ...Claudia Richter...92.00...69%...86...6.00...
371- ...Tom Hoffman...53.00...82%...44...9.00...
372- ...Paul Cardune...40...80%...40...
373+ ...Claudia...92.00...69%...86...6.00...
374+ ...Tom...53.00...82%...44...9.00...
375+ ...Paul...40...80%...40...
376
377 Let's edit the external activity. The form doesn't allow to edit the
378 score system. The edit view also shows an 'Update Grades' button to
379@@ -982,7 +1035,7 @@
380 >>> stephan.contents
381 <BLANKLINE>
382 ...Name...Total...Ave...HW 1...Quiz...Hardware As...
383- ...Claudia Richter...96.00...69%...86...10.00...
384- ...Tom Hoffman...59.00...79%...44...15.00...
385- ...Paul Cardune...40...80%...40...
386+ ...Claudia...96.00...69%...86...10.00...
387+ ...Tom...59.00...79%...44...15.00...
388+ ...Paul...40...80%...40...
389
390
391=== modified file 'src/schooltool/gradebook/browser/activity.py'
392--- src/schooltool/gradebook/browser/activity.py 2009-05-05 00:01:47 +0000
393+++ src/schooltool/gradebook/browser/activity.py 2009-06-28 00:30:06 +0000
394@@ -59,6 +59,7 @@
395 from schooltool.requirement.scoresystem import RangedValuesScoreSystem
396 from schooltool.requirement.scoresystem import UNSCORED
397 from schooltool.export import export
398+from schooltool.basicperson.interfaces import IDemographics
399
400
401 class ILinkedActivityFields(interface.Interface):
402@@ -399,8 +400,9 @@
403 def print_headers(self, ws, worksheet):
404 gradebook = interfaces.IGradebook(worksheet)
405 activities = gradebook.getWorksheetActivities(worksheet)
406- headers = [export.Header('Name')] + \
407- [export.Header(activity.title) for activity in activities]
408+ header_labels = ['ID', 'First name', 'Last name']
409+ header_labels.extend([activity.title for activity in activities])
410+ headers = [export.Header(label) for label in header_labels]
411 for col, header in enumerate(headers):
412 self.write(ws, 0, col, header.data, **header.style)
413
414@@ -408,8 +410,12 @@
415 gradebook = interfaces.IGradebook(worksheet)
416 activities = gradebook.getWorksheetActivities(worksheet)
417 starting_row = 1
418- for row, student in enumerate(gradebook.students):
419- cells = [export.Text(student.title)]
420+ students = sorted(gradebook.students,
421+ key=lambda x:IDemographics(x).get('ID', ''))
422+ for row, student in enumerate(students):
423+ cells = [export.Text(IDemographics(student).get('ID', '')),
424+ export.Text(student.first_name),
425+ export.Text(student.last_name)]
426 for activity in activities:
427 ev = gradebook.getEvaluation(student, activity)
428 if ev is not None and ev.value is not UNSCORED:
429
430=== modified file 'src/schooltool/gradebook/browser/configure.zcml'
431--- src/schooltool/gradebook/browser/configure.zcml 2009-06-08 01:43:13 +0000
432+++ src/schooltool/gradebook/browser/configure.zcml 2009-06-28 00:30:06 +0000
433@@ -360,6 +360,14 @@
434 permission="schooltool.view"
435 />
436
437+ <menuItem
438+ menu="schooltool_actions"
439+ title="Export XLS"
440+ for="..interfaces.IGradebook"
441+ action="../../export.xls"
442+ permission="schooltool.view"
443+ />
444+
445 <!-- Special navagation viewlet for update linked activity grades action -->
446 <navigationViewlet
447 name="update_grades"
448@@ -435,6 +443,15 @@
449 permission="schooltool.view"
450 />
451
452+ <!-- Student Gradebook -->
453+ <page
454+ name="index.html"
455+ for="..interfaces.IStudentGradebook"
456+ class=".gradebook.GradeStudent"
457+ template="grade_student.pt"
458+ permission="schooltool.edit"
459+ />
460+
461 <!-- Terms -->
462 <zope:adapter
463 for="schooltool.gradebook.interfaces.IExternalActivitiesSource
464
465=== modified file 'src/schooltool/gradebook/browser/ftesting.zcml'
466--- src/schooltool/gradebook/browser/ftesting.zcml 2009-02-08 21:41:09 +0000
467+++ src/schooltool/gradebook/browser/ftesting.zcml 2009-06-25 05:00:11 +0000
468@@ -4,6 +4,7 @@
469
470 <include package="schooltool.common" />
471
472+ <include package="schooltool.basicperson" />
473 <include package="schooltool.course" />
474 <include package="schooltool.term" />
475 <include package="schooltool.schoolyear" />
476@@ -33,6 +34,15 @@
477 name="samplesource"
478 />
479
480- <browser:defaultSkin name="SchoolTool" />
481+ <interface
482+ interface="schooltool.basicperson.ftesting.IBasicPersonFtestingSkin"
483+ type="zope.publisher.interfaces.browser.IBrowserSkinType"
484+ name="BasicPerson"
485+ />
486+
487+ <!-- Provide local overrides of standard configurations-->
488+ <includeOverrides package="schooltool.basicperson" file="overrides.zcml" />
489+
490+ <browser:defaultSkin name="BasicPerson" />
491
492 </configure>
493
494=== added file 'src/schooltool/gradebook/browser/grade_student.pt'
495--- src/schooltool/gradebook/browser/grade_student.pt 1970-01-01 00:00:00 +0000
496+++ src/schooltool/gradebook/browser/grade_student.pt 2009-06-25 05:00:11 +0000
497@@ -0,0 +1,8 @@
498+<html metal:use-macro="view/@@standard_macros/page" i18n:domain="schooltool">
499+ <body>
500+ <metal:nothing metal:fill-slot="content-header" />
501+ <metal:block metal:fill-slot="body">
502+ <div metal:use-macro="macro:form" />
503+ </metal:block>
504+ </body>
505+</html>
506
507=== modified file 'src/schooltool/gradebook/browser/gradebook.css'
508--- src/schooltool/gradebook/browser/gradebook.css 2009-06-01 21:32:36 +0000
509+++ src/schooltool/gradebook/browser/gradebook.css 2009-07-06 02:39:56 +0000
510@@ -152,3 +152,55 @@
511 font-weight: bold;
512 }
513
514+FORM.addscoresystem, div.addscoresystem {
515+ width: 48em;
516+ margin: auto;
517+}
518+
519+FORM.addscoresystem FIELDSET, div.addscoresystem FIELDSET {
520+ margin: 1em;
521+}
522+
523+FORM.addscoresystem h3, div.addscoresystem h3 {
524+ margin: 0;
525+ padding: 0.25em;
526+}
527+
528+FORM.addscoresystem .controls, div.addscoresystem .controls {
529+ margin: 0.5em 1em 1em 1em;
530+}
531+
532+FORM.addscoresystem, div.addscoresystem, FORM div.oneline-form{
533+ background: #eae8e3;
534+ border: 1px #bab5ab solid;
535+}
536+
537+FORM.addscoresystem h3, div.addscoresystem h3 {
538+ background: #bab5ab;
539+}
540+
541+FORM.addscoresystem FIELDSET, div.addscoresystem FIELDSET {
542+ border: 1px #bab5ab solid;
543+ /* Mozilla's default padding for fieldsets -- makes it nice in MSIE too */
544+ padding: 0.35em 0.625em 0.75em 0.625em;
545+}
546+
547+FORM.addscoresystem p, div.addscoresystem p {
548+ margin: 0.5em;
549+}
550+
551+FORM.addscoresystem p.hint, div.addscoresystem p.hint {
552+ font-size: 80%;
553+ font-style: italic;
554+ color: #bab5ab;
555+}
556+
557+FORM.addscoresystem .controls INPUT, div.addscoresystem .controls INPUT {
558+ background: #fff;
559+}
560+
561+FORM.addscoresystem DIV.label, div.addscoresystem DIV.label {
562+ width: 10em;
563+ padding-top: 0.5ex;
564+}
565+
566
567=== modified file 'src/schooltool/gradebook/browser/gradebook.py'
568--- src/schooltool/gradebook/browser/gradebook.py 2009-06-08 01:43:13 +0000
569+++ src/schooltool/gradebook/browser/gradebook.py 2009-07-06 01:26:11 +0000
570@@ -26,15 +26,20 @@
571 import decimal
572
573 from zope.app.keyreference.interfaces import IKeyReference
574+from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
575 from zope.component import queryUtility
576+from zope.html.field import HtmlFragment
577 from zope.publisher.browser import BrowserView
578-from zope.schema import ValidationError
579+from zope.schema import ValidationError, Text, TextLine
580 from zope.schema.interfaces import IVocabularyFactory
581 from zope.security import proxy
582 from zope.traversing.api import getName
583 from zope.traversing.browser.absoluteurl import absoluteURL
584 from zope.viewlet import viewlet
585
586+from z3c.form import form as z3cform
587+from z3c.form import field, button
588+
589 from schooltool.app import app
590 from schooltool.app.interfaces import ISchoolToolApplication
591 from schooltool.course.interfaces import ISection
592@@ -43,6 +48,7 @@
593 from schooltool.gradebook.activity import ensureAtLeastOneWorksheet
594 from schooltool.person.interfaces import IPerson
595 from schooltool.requirement.scoresystem import UNSCORED
596+from schooltool.requirement.interfaces import ICommentScoreSystem
597 from schooltool.requirement.interfaces import IValuesScoreSystem
598 from schooltool.requirement.interfaces import IDiscreteValuesScoreSystem
599 from schooltool.requirement.interfaces import IRangedValuesScoreSystem
600@@ -55,6 +61,7 @@
601
602 DISCRETE_SCORE_SYSTEM = 'd'
603 RANGED_SCORE_SYSTEM = 'r'
604+COMMENT_SCORE_SYSTEM = 'c'
605
606 column_keys = [('total', _("Total")), ('average', _("Ave."))]
607
608@@ -158,8 +165,10 @@
609 if IDiscreteValuesScoreSystem.providedBy(ss):
610 result = [DISCRETE_SCORE_SYSTEM] + [score[0]
611 for score in ss.scores]
612- else:
613+ elif IRangedValuesScoreSystem.providedBy(ss):
614 result = [RANGED_SCORE_SYSTEM, ss.min, ss.max]
615+ else:
616+ result = [COMMENT_SCORE_SYSTEM]
617 resultStr = ', '.join(["'%s'" % unicode(value)
618 for value in result])
619 results[hash(IKeyReference(activity))] = resultStr
620@@ -313,7 +322,7 @@
621 def update(self):
622 self.person = IPerson(self.request.principal)
623 gradebook = proxy.removeSecurityProxy(self.context)
624- self.message = ''
625+ self.messages = []
626
627 """Make sure the current worksheet matches the current url"""
628 worksheet = gradebook.context
629@@ -365,11 +374,12 @@
630 score = activity.scoresystem.fromUnicode(
631 self.request[cell_name])
632 except (ValidationError, ValueError):
633- self.message = _(
634+ message = _(
635 'The grade $value for activity $name is not valid.',
636 mapping={'value': self.request[cell_name],
637 'name': activity.title})
638- return
639+ self.messages.append(message)
640+ continue
641 ev = gradebook.getEvaluation(student, activity)
642 # Delete the score
643 if ev is not None and score is UNSCORED:
644@@ -410,9 +420,13 @@
645 if len(shortTitle) > 5:
646 shortTitle = shortTitle[:5].strip()
647
648+ if ICommentScoreSystem.providedBy(activity.scoresystem):
649+ bestScore = ''
650+ else:
651+ bestScore = activity.scoresystem.getBestScore()
652 result.append({'shortTitle': shortTitle,
653 'longTitle': activity.title,
654- 'max': activity.scoresystem.getBestScore(),
655+ 'max': bestScore,
656 'hash': hash(IKeyReference(activity))})
657
658 return result
659@@ -449,7 +463,20 @@
660 if cell_name in self.request:
661 value = self.request[cell_name]
662
663- grades.append({'activity': act_hash, 'value': value})
664+ ss = activity.scoresystem
665+ if ICommentScoreSystem.providedBy(ss):
666+ editable = False
667+ if value:
668+ value = '...'
669+ else:
670+ editable = True
671+
672+ grade = {
673+ 'activity': act_hash,
674+ 'editable': editable,
675+ 'value': value
676+ }
677+ grades.append(grade)
678
679 total, average = gradebook.getWorksheetTotalAverage(worksheet,
680 student)
681@@ -459,7 +486,8 @@
682 rows.append(
683 {'student': {'title': student.title,
684 'id': student.username,
685- 'url': absoluteURL(student, self.request),
686+ 'url': absoluteURL(self.context, self.request) +
687+ ('/%s' % student.username)
688 },
689 'grades': grades, 'total': unicode(total),
690 'average': unicode(average)
691@@ -543,8 +571,6 @@
692 class GradeActivity(object):
693 """Grading a single activity"""
694
695- message = ''
696-
697 @property
698 def activity(self):
699 act_hash = int(self.request['activity'])
700@@ -570,6 +596,7 @@
701 'value': value}
702
703 def update(self):
704+ self.messages = []
705 if 'CANCEL' in self.request:
706 self.request.response.redirect('index.html')
707
708@@ -588,11 +615,12 @@
709 score = activity.scoresystem.fromUnicode(
710 self.request[id])
711 except (ValidationError, ValueError):
712- self.message = _(
713+ message = _(
714 'The grade $value for $name is not valid.',
715 mapping={'value': self.request[id],
716 'name': student.title})
717- return
718+ self.messages.append(message)
719+ continue
720 ev = gradebook.getEvaluation(student, activity)
721 # Delete the score
722 if ev is not None and score is UNSCORED:
723@@ -605,7 +633,8 @@
724 self.context.evaluate(
725 student, activity, score, evaluator)
726
727- self.request.response.redirect('index.html')
728+ if not len(self.messages):
729+ self.request.response.redirect('index.html')
730
731
732 def getScoreSystemDiscreteValues(ss):
733@@ -789,3 +818,123 @@
734 def update(self):
735 pass
736
737+
738+class GradeStudent(z3cform.EditForm):
739+ """Edit form for a student's grades."""
740+ z3cform.extends(z3cform.EditForm)
741+ template = ViewPageTemplateFile('grade_student.pt')
742+
743+ def __init__(self, context, request):
744+ super(GradeStudent, self).__init__(context, request)
745+
746+ def update(self):
747+ self.person = IPerson(self.request.principal)
748+ for index, activity in enumerate(self.getFilteredActivities()):
749+ if ICommentScoreSystem.providedBy(activity.scoresystem):
750+ field_cls = HtmlFragment
751+ else:
752+ field_cls = TextLine
753+ newSchemaFld = field_cls(
754+ title=activity.title,
755+ description=activity.description,
756+ constraint=activity.scoresystem.fromUnicode,
757+ required=False)
758+ newSchemaFld.__name__ = str(hash(IKeyReference(activity)))
759+ newSchemaFld.interface = interfaces.IStudentGradebookForm
760+ newFormFld = field.Field(newSchemaFld)
761+ self.fields += field.Fields(newFormFld)
762+ super(GradeStudent, self).update()
763+
764+ @button.buttonAndHandler(_("Previous"))
765+ def handle_previous_action(self, action):
766+ if self.applyData():
767+ return
768+ prev, next = self.prevNextStudent()
769+ if prev is not None:
770+ url = '%s/%s' % (self.nextURL(), prev.username)
771+ self.request.response.redirect(url)
772+
773+ @button.buttonAndHandler(_("Next"))
774+ def handle_next_action(self, action):
775+ if self.applyData():
776+ return
777+ prev, next = self.prevNextStudent()
778+ if next is not None:
779+ url = '%s/%s' % (self.nextURL(), next.username)
780+ self.request.response.redirect(url)
781+
782+ @button.buttonAndHandler(_("Cancel"))
783+ def handle_cancel_action(self, action):
784+ self.request.response.redirect(self.nextURL())
785+
786+ def applyData(self):
787+ data, errors = self.extractData()
788+ if errors:
789+ self.status = self.formErrorsMessage
790+ return True
791+ changes = self.applyChanges(data)
792+ if changes:
793+ self.status = self.successMessage
794+ else:
795+ self.status = self.noChangesMessage
796+ return False
797+
798+ def updateActions(self):
799+ super(GradeStudent, self).updateActions()
800+ self.actions['apply'].addClass('button-ok')
801+ self.actions['previous'].addClass('button-ok')
802+ self.actions['next'].addClass('button-ok')
803+ self.actions['cancel'].addClass('button-cancel')
804+
805+ prev, next = self.prevNextStudent()
806+ if prev is None:
807+ del self.actions['previous']
808+ if next is None:
809+ del self.actions['next']
810+
811+ def applyChanges(self, data):
812+ super(GradeStudent, self).applyChanges(data)
813+ self.request.response.redirect(self.nextURL())
814+
815+ def prevNextStudent(self):
816+ gradebook = proxy.removeSecurityProxy(self.context.gradebook)
817+ section = ISection(gradebook)
818+ student = self.context.student
819+
820+ prev, next = None, None
821+ members = [member for name, member in
822+ sorted([(m.last_name + m.first_name, m) for m in section.members])]
823+ if len(members) < 2:
824+ return prev, next
825+ for index, member in enumerate(members):
826+ if member == student:
827+ if index == 0:
828+ next = members[1]
829+ elif index == len(members) - 1:
830+ prev = members[-2]
831+ else:
832+ prev = members[index - 1]
833+ next = members[index + 1]
834+ break
835+ return prev, next
836+
837+ def isFiltered(self, activity):
838+ flag, weeks = self.context.gradebook.getDueDateFilter(self.person)
839+ if not flag:
840+ return False
841+ cutoff = datetime.date.today() - datetime.timedelta(7 * int(weeks))
842+ return activity.due_date < cutoff
843+
844+ def getFilteredActivities(self):
845+ activities = self.context.gradebook.getCurrentActivities(self.person)
846+ return[activity for activity in activities
847+ if not self.isFiltered(activity)]
848+
849+ @property
850+ def label(self):
851+ return _(u'Enter grades for ${fullname}',
852+ mapping={'fullname': self.context.student.title})
853+
854+ def nextURL(self):
855+ return absoluteURL(self.context.gradebook, self.request)
856+
857
858=== modified file 'src/schooltool/gradebook/browser/gradebook_grade_activity.pt'
859--- src/schooltool/gradebook/browser/gradebook_grade_activity.pt 2009-04-03 20:34:21 +0000
860+++ src/schooltool/gradebook/browser/gradebook_grade_activity.pt 2009-07-06 03:19:53 +0000
861@@ -19,11 +19,12 @@
862 </h3>
863 <br />
864
865- <div class="message"
866- tal:condition="view/message"
867- tal:content="view/message">
868+ <div class="message" style="color:red"
869+ tal:repeat="message view/messages"
870+ tal:content="message">
871 Message
872 </div>
873+ <br />
874
875 <form tal:attributes="action request/URL"
876 method="post" enctype="multipart/form-data">
877@@ -94,7 +95,7 @@
878 </table>
879
880 <div class="controls">
881- <tal:block metal:use-macro="view/@@standard_macros/update-button" />
882+ <input type="submit" class="button-ok" name="UPDATE_SUBMIT" value="Save" />
883 <tal:block metal:use-macro="view/@@standard_macros/cancel-button" />
884 </div>
885
886
887=== modified file 'src/schooltool/gradebook/browser/gradebook_overview.js.pt'
888--- src/schooltool/gradebook/browser/gradebook_overview.js.pt 2009-04-30 19:20:05 +0000
889+++ src/schooltool/gradebook/browser/gradebook_overview.js.pt 2009-07-06 00:18:14 +0000
890@@ -1,8 +1,9 @@
891+<tal:tag condition="view/update" />
892 /* This is the Javascript to be included when rendering the gradebook overview. */
893 var numstudents = <tal:block replace="python: len(view.students)"/>
894 var students = new Array(numstudents);
895-<tal:loop repeat="student view/students">
896- students[<tal:block replace="repeat/student/index"/>] = '<tal:block replace="python: view.breakJSString(student.username)"/>';
897+<tal:loop repeat="row view/table">
898+ students[<tal:block replace="repeat/row/index"/>] = '<tal:block replace="python: view.breakJSString(row['student']['id'])"/>';
899 </tal:loop>
900
901 var numactivities = <tal:block replace="python: len(view.activities())"/>
902@@ -167,6 +168,11 @@
903 var element = document.getElementById(name);
904 var elementCell = document.getElementById(name+'_cell');
905 var value = element.value;
906+
907+
908+
909+
910+
911 changeBackgroundColor(name+'_cell', 'default_bg');
912
913 if (value == '')
914
915=== modified file 'src/schooltool/gradebook/browser/gradebook_overview.pt'
916--- src/schooltool/gradebook/browser/gradebook_overview.pt 2009-05-15 06:01:23 +0000
917+++ src/schooltool/gradebook/browser/gradebook_overview.pt 2009-07-06 03:19:53 +0000
918@@ -90,11 +90,12 @@
919 <span>weeks</span>
920 <br /><br />
921
922- <div class="message" style="color:red; padding:1em"
923- tal:condition="view/message"
924- tal:content="view/message">
925+ <div class="message" style="color:red"
926+ tal:repeat="message view/messages"
927+ tal:content="message">
928 Message
929 </div>
930+ <br />
931
932 <table class="schooltool_gradebook">
933 <tr>
934@@ -191,7 +192,10 @@
935 </td>
936 <td class="cell even" tal:repeat="grade row/grades"
937 tal:attributes="id string:${grade/activity}_${row/student/id}_cell">
938+ <b tal:condition="not: grade/editable"
939+ tal:content="grade/value" />
940 <input class="data" type="text" name="" value="" size="4"
941+ tal:condition="grade/editable"
942 onkeydown="spreadsheetBehaviour(event)"
943 tal:attributes="name string:${grade/activity}_${row/student/id};
944 id string:${grade/activity}_${row/student/id};
945@@ -220,7 +224,10 @@
946 </td>
947 <td class="cell odd" tal:repeat="grade row/grades"
948 tal:attributes="id string:${grade/activity}_${row/student/id}_cell">
949+ <b tal:condition="not: grade/editable"
950+ tal:content="grade/value" />
951 <input class="data" type="text" name="" value="" size="4"
952+ tal:condition="grade/editable"
953 onkeydown="spreadsheetBehaviour(event)"
954 tal:attributes="name string:${grade/activity}_${row/student/id};
955 id string:${grade/activity}_${row/student/id};
956@@ -233,7 +240,7 @@
957 </table>
958
959 <div class="controls">
960- <input type="submit" class="button-ok" name="UPDATE_SUBMIT" value="Update"
961+ <input type="submit" class="button-ok" name="UPDATE_SUBMIT" value="Save"
962 onclick="setNotEdited()"/>
963 </div>
964
965
966=== modified file 'src/schooltool/gradebook/browser/no_current_term.txt'
967--- src/schooltool/gradebook/browser/no_current_term.txt 2009-06-08 01:43:13 +0000
968+++ src/schooltool/gradebook/browser/no_current_term.txt 2009-06-25 05:00:11 +0000
969@@ -8,9 +8,9 @@
970
971 Let's log in as manager and create a student.
972
973- >>> from schooltool.app.browser.ftests import setup
974+ >>> from schooltool.basicperson.browser.ftests import setup
975 >>> manager = Browser('manager', 'schooltool')
976- >>> setup.addPerson('Student One', 'student1', 'pwd')
977+ >>> setup.addPerson('Student', 'One', 'student1', 'pwd')
978
979 We'll navigate to the student and request a report card, an operation that is
980 impossible without there being at least one term set up. We'll test that the
981@@ -18,7 +18,7 @@
982
983 >>> manager.getLink('Manage').click()
984 >>> manager.getLink('Persons').click()
985- >>> manager.getLink('Student One').click()
986+ >>> manager.getLink('One').click()
987 >>> manager.getLink('Print Report Card').click()
988 >>> manager.url
989 'http://localhost/no_current_term.html'
990
991=== modified file 'src/schooltool/gradebook/browser/report_card.txt'
992--- src/schooltool/gradebook/browser/report_card.txt 2009-04-28 23:46:54 +0000
993+++ src/schooltool/gradebook/browser/report_card.txt 2009-06-25 05:00:11 +0000
994@@ -95,18 +95,19 @@
995 terms with some sections. Of course, we'll need a teacher and some students
996 in order to do that.
997
998- >>> setup.addPerson('Teacher One', 'teacher1', 'pwd')
999- >>> setup.addPerson('Student One', 'student1', 'pwd')
1000- >>> setup.addPerson('Student Two', 'student2', 'pwd')
1001+ >>> from schooltool.basicperson.browser.ftests.setup import addPerson
1002+ >>> addPerson('Teacher1', 'One', 'teacher1', 'pwd')
1003+ >>> addPerson('Student1', 'One', 'student1', 'pwd')
1004+ >>> addPerson('Student2', 'Two', 'student2', 'pwd')
1005 >>> setup.addCourse('CompSci I', '2005-2006')
1006 >>> setup.addSection('CompSci I', '2005-2006', 'Spring',
1007 ... title='Morning CompSci I',
1008- ... instructors=['Teacher One'],
1009- ... members=['Student One','Student Two'])
1010+ ... instructors=['Teacher1'],
1011+ ... members=['Student1','Student2'])
1012 >>> setup.addSection('CompSci I', '2005-2006', 'Spring',
1013 ... title='Afternoon CompSci I',
1014- ... instructors=['Teacher One'],
1015- ... members=['Student One','Student Two'])
1016+ ... instructors=['Teacher1'],
1017+ ... members=['Student1','Student2'])
1018
1019 We'll navigate to the 'Spring' term where we just created the two sections,
1020 and we'll click on the 'Deploy Report Worksheet' link.
1021@@ -214,8 +215,8 @@
1022
1023 >>> setup.addSection('CompSci I', '2005-2006', 'Spring',
1024 ... title='Evening CompSci I',
1025- ... instructors=['Teacher One'],
1026- ... members=['Student One','Student Two'])
1027+ ... instructors=['Teacher1'],
1028+ ... members=['Student1','Student2'])
1029
1030 >>> manager.open(term_url)
1031 >>> manager.getLink('Sections').click()
1032@@ -257,8 +258,8 @@
1033
1034 >>> setup.addSection('CompSci I', '2005-2006', 'Fall',
1035 ... title='Evening CompSci I',
1036- ... instructors=['Teacher One'],
1037- ... members=['Student One','Student Two'])
1038+ ... instructors=['Teacher1'],
1039+ ... members=['Student1','Student2'])
1040
1041 >>> manager.getLink('Manage').click()
1042 >>> manager.getLink('Report Sheet Templates').click()
1043
1044=== modified file 'src/schooltool/gradebook/configure.zcml'
1045--- src/schooltool/gradebook/configure.zcml 2009-06-05 00:11:04 +0000
1046+++ src/schooltool/gradebook/configure.zcml 2009-06-25 05:00:11 +0000
1047@@ -189,6 +189,23 @@
1048 trusted="true"
1049 />
1050
1051+ <!-- Student Gradebook Adapter -->
1052+ <class class=".gradebook.StudentGradebook">
1053+ <require
1054+ permission="schooltool.view"
1055+ interface=".interfaces.IStudentGradebook" />
1056+ </class>
1057+ <adapter
1058+ factory=".gradebook.StudentGradebook"
1059+ trusted="true"
1060+ />
1061+
1062+
1063+ <!-- Student Gradebook Form Adapter -->
1064+ <adapter
1065+ factory=".gradebook.StudentGradebookFormAdapter"
1066+ />
1067+
1068 <!-- MyGrades Adapter -->
1069 <class class=".gradebook.MyGrades">
1070 <require
1071@@ -250,6 +267,14 @@
1072 provides="zope.publisher.interfaces.IPublishTraverse"
1073 factory=".gradebook.WorksheetGradebookTraverser" />
1074
1075+ <!-- special traversal adapter for traversing from a section gradebook
1076+ to a student gradebook -->
1077+ <adapter
1078+ for=".interfaces.IGradebook
1079+ zope.publisher.interfaces.http.IHTTPRequest"
1080+ provides="zope.publisher.interfaces.IPublishTraverse"
1081+ factory=".gradebook.StudentGradebookTraverser" />
1082+
1083 <!-- apidoc help setup -->
1084 <configure
1085 xmlns:zcml="http://namespaces.zope.org/zcml"
1086
1087=== modified file 'src/schooltool/gradebook/gradebook.py'
1088--- src/schooltool/gradebook/gradebook.py 2009-06-01 21:32:36 +0000
1089+++ src/schooltool/gradebook/gradebook.py 2009-06-29 07:43:38 +0000
1090@@ -30,11 +30,14 @@
1091 from zope.security import proxy
1092 from zope import annotation
1093 from zope.app.keyreference.interfaces import IKeyReference
1094+from zope.component import adapts, queryMultiAdapter, getMultiAdapter
1095 from zope.interface import implements
1096 from zope.location.location import LocationProxy
1097 from zope.publisher.interfaces import IPublishTraverse
1098+from zope.security.proxy import removeSecurityProxy
1099
1100 from schooltool import course, requirement
1101+from schooltool.basicperson.interfaces import IBasicPerson
1102 from schooltool.traverser import traverser
1103 from schooltool.securitypolicy.crowds import ConfigurableCrowd
1104 from schooltool.securitypolicy.crowds import AggregateCrowd
1105@@ -45,7 +48,7 @@
1106 from schooltool.gradebook import interfaces
1107 from schooltool.gradebook.activity import Activities
1108 from schooltool.gradebook.activity import ensureAtLeastOneWorksheet
1109-from schooltool.requirement.scoresystem import UNSCORED
1110+from schooltool.requirement.scoresystem import UNSCORED, ScoreValidationError
1111 from schooltool.requirement.interfaces import IDiscreteValuesScoreSystem
1112 from schooltool.requirement.interfaces import IRangedValuesScoreSystem
1113 from schooltool.common import SchoolToolMessage as _
1114@@ -85,6 +88,35 @@
1115 return queryMultiAdapter((self.context, request), name=name)
1116
1117
1118+class StudentGradebookTraverser(object):
1119+ '''Traverser that goes from a section's gradebook to a student
1120+ gradebook using the student's username as the path in the url.'''
1121+
1122+ implements(IPublishTraverse)
1123+
1124+ def __init__(self, context, request):
1125+ self.context = context
1126+ self.request = request
1127+
1128+ def publishTraverse(self, request, name):
1129+ app = ISchoolToolApplication(None)
1130+ context = removeSecurityProxy(self.context)
1131+
1132+ try:
1133+ student = app['persons'][name]
1134+ except KeyError:
1135+ return queryMultiAdapter((self.context, request), name=name)
1136+
1137+ try:
1138+ gb = getMultiAdapter((student, context), interfaces.IStudentGradebook)
1139+ except ValueError:
1140+ return queryMultiAdapter((self.context, request), name=name)
1141+
1142+ # location looks like http://host/path/to/gradebook/studentsUsername
1143+ gb = LocationProxy(gb, self.context, name)
1144+ return gb
1145+
1146+
1147 class GradebookBase(object):
1148 def __init__(self, context):
1149 self.context = context
1150@@ -365,6 +397,49 @@
1151 self.__name__ = 'mygrades'
1152
1153
1154+class StudentGradebook(object):
1155+ """Adapter of student and gradebook used for grading one student at a
1156+ time"""
1157+ implements(interfaces.IStudentGradebook)
1158+ adapts(IBasicPerson, interfaces.IGradebook)
1159+
1160+ def __init__(self, student, gradebook):
1161+ self.student = student
1162+ self.gradebook = gradebook
1163+ activities = [(unicode(hash(IKeyReference(activity))), activity)
1164+ for activity in gradebook.activities]
1165+ self.activities = dict(activities)
1166+
1167+
1168+class StudentGradebookFormAdapter(object):
1169+ """Adapter used by grade student view to interact with student
1170+ gradebook"""
1171+ implements(interfaces.IStudentGradebookForm)
1172+ adapts(interfaces.IStudentGradebook)
1173+
1174+ def __init__(self, context):
1175+ self.__dict__['context'] = context
1176+
1177+ def __setattr__(self, name, value):
1178+ activity = self.context.activities[name]
1179+ evaluator = None
1180+ try:
1181+ score = activity.scoresystem.fromUnicode(value)
1182+ self.context.gradebook.evaluate(self.context.student, activity,
1183+ score, evaluator)
1184+ except ScoreValidationError:
1185+ pass
1186+
1187+ def __getattr__(self, name):
1188+ activity = self.context.activities[name]
1189+ ev = self.context.gradebook.getEvaluation(self.context.student,
1190+ activity)
1191+ if ev is not None and ev.value is not UNSCORED:
1192+ return ev.value
1193+ else:
1194+ return ''
1195+
1196+
1197 def getWorksheetSection(worksheet):
1198 """Adapt IWorksheet to ISection."""
1199 return worksheet.__parent__.__parent__
1200
1201=== modified file 'src/schooltool/gradebook/interfaces.py'
1202--- src/schooltool/gradebook/interfaces.py 2009-06-01 21:32:36 +0000
1203+++ src/schooltool/gradebook/interfaces.py 2009-06-25 05:00:11 +0000
1204@@ -268,6 +268,20 @@
1205 """
1206
1207
1208+class IStudentGradebook(Interface):
1209+ """The gradebook for grading a student in a section."""
1210+
1211+ student = Attribute("""The student being graded""")
1212+
1213+ gradebook = Attribute("""The section gradebook""")
1214+
1215+ activities = Attribute("""A dictionary of activity hash to activity""")
1216+
1217+
1218+class IStudentGradebookForm(Interface):
1219+ """Interface for fields that are stored in student gradebook."""
1220+
1221+
1222 class IMyGrades(Interface):
1223 """The students gradebook for a section.
1224
1225
1226=== modified file 'src/schooltool/requirement/README.txt'
1227--- src/schooltool/requirement/README.txt 2009-05-14 08:34:27 +0000
1228+++ src/schooltool/requirement/README.txt 2009-06-25 05:00:11 +0000
1229@@ -229,7 +229,7 @@
1230 >>> check.fromUnicode('f')
1231 Traceback (most recent call last):
1232 ...
1233- ValidationError: 'f' is not a valid score.
1234+ ScoreValidationError
1235
1236 >>> check.fromUnicode('') is scoresystem.UNSCORED
1237 True
1238@@ -446,7 +446,7 @@
1239 >>> quizScore.fromUnicode('This causes a ValueError.')
1240 Traceback (most recent call last):
1241 ...
1242- ValueError
1243+ ScoreValidationError
1244
1245 Since we have not defined a minimum passing grade, we cannot get a meaningful
1246 answer from the passing score evaluation:
1247
1248=== modified file 'src/schooltool/requirement/browser/README.txt'
1249--- src/schooltool/requirement/browser/README.txt 2009-06-05 00:11:04 +0000
1250+++ src/schooltool/requirement/browser/README.txt 2009-07-06 02:39:56 +0000
1251@@ -95,7 +95,7 @@
1252
1253 We'll send the form values necessary to add a score system called 'Good/Bad'.
1254
1255- >>> url = save_url + '&title=Good/Bad&displayed1=G&value1=1&percent1=60'
1256+ >>> url = update_url + '&title=Good/Bad&displayed1=G&value1=1&percent1=60'
1257 >>> url = url + '&displayed2=B&value2=0&percent2=0'
1258 >>> manager.open(url)
1259
1260
1261=== modified file 'src/schooltool/requirement/browser/scoresystem.py'
1262--- src/schooltool/requirement/browser/scoresystem.py 2009-05-14 08:34:27 +0000
1263+++ src/schooltool/requirement/browser/scoresystem.py 2009-07-06 02:39:56 +0000
1264@@ -90,7 +90,7 @@
1265
1266 if not self.validateForm():
1267 return
1268- if 'SAVE' in self.request:
1269+ if 'UPDATE_SUBMIT' in self.request:
1270 if not self.validateScores():
1271 return
1272 target = scoresystem.CustomScoreSystem()
1273
1274=== modified file 'src/schooltool/requirement/browser/scoresystem_add.pt'
1275--- src/schooltool/requirement/browser/scoresystem_add.pt 2009-05-08 18:19:21 +0000
1276+++ src/schooltool/requirement/browser/scoresystem_add.pt 2009-07-06 02:59:50 +0000
1277@@ -5,34 +5,48 @@
1278 </head>
1279 <body>
1280
1281-<h1 metal:fill-slot="content-header"
1282- i18n:translate="">Add Score System</h1>
1283-
1284 <metal:block metal:fill-slot="body">
1285-
1286- <div class="message" style="color:red; padding:1em"
1287- tal:condition="view/message"
1288- tal:content="view/message">
1289- Message
1290- </div>
1291-
1292-
1293- <form method="post"
1294+ <form method="post" class="addscoresystem"
1295 tal:attributes="action string:${context/@@absolute_url}/add.html">
1296 <input type="hidden" name="form-submitted" value="" />
1297
1298+ <h3>Add Score System</h3>
1299+
1300+ <div class="message" style="color:red; padding:1em"
1301+ tal:condition="view/message"
1302+ tal:content="view/message">
1303+ Message
1304+ </div>
1305+ <p>
1306+ This form allows you to create a customized "score system" or grading scale.
1307+ </p>
1308+ <p>
1309+ Each score system needs a short title to identify it to users, for example, if your school's grades go from "A" to "E," the title could be "A-E."
1310+ </p>
1311+ <p>
1312+ A score system is made up of a sequence of possible scores with decreasing values.
1313+ </p>
1314+ <p>
1315+ Each score is associated with a numeric point value. For example, in the US, an "A" is usually worth 4 points, a "B," 3 points, etc. This value will be used in any calculations made with the scores, such as finding a grade point average.
1316+ </p>
1317+ <p>
1318+ 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.
1319+ </p>
1320+ <p>
1321+ 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.
1322+ </p>
1323+ <fieldset>
1324+ <legend><b>New Score System</b><legend>
1325 <label for="title" class="bold padded" i18n:translate="">Title</label>
1326 <input type="text" name="title" id="title"
1327 tal:attributes="value view/title_value" />
1328- <span>&nbsp&nbsp</span>
1329- <input type="submit" class="button-ok" name="SAVE" value="Submit" />
1330 <div style="height: 31px;"></div>
1331
1332 <table class="schooltool_gradebook">
1333 <tr>
1334- <th class="cell header fully_padded">Score Displayed</th>
1335- <th class="cell header fully_padded">Score Value</th>
1336- <th class="cell header fully_padded">Low Persntage</th>
1337+ <th class="cell header fully_padded">Score</th>
1338+ <th class="cell header fully_padded">Point Value</th>
1339+ <th class="cell header fully_padded">Minimum Percentage</th>
1340 </tr>
1341
1342 <tal:block repeat="score view/scores">
1343@@ -53,10 +67,13 @@
1344 </tal:block>
1345
1346 </table>
1347- <div style="height: 11px;"></div>
1348+ <div class="controls">
1349+ <input type="submit" class="button-ok" name="SAVE" value="Add a lower score" />
1350+ </div>
1351+ </fieldset>
1352
1353 <div class="controls">
1354- <input type="submit" class="button-ok" name="UPDATE_SUBMIT" value="Update" />
1355+ <input type="submit" class="button-ok" name="UPDATE_SUBMIT" value="Save" />
1356 <input type="submit" class="button-cancel" name="CANCEL" value="Cancel" />
1357 </div>
1358 </form>
1359
1360=== modified file 'src/schooltool/requirement/configure.zcml'
1361--- src/schooltool/requirement/configure.zcml 2009-06-05 00:11:04 +0000
1362+++ src/schooltool/requirement/configure.zcml 2009-06-29 07:43:38 +0000
1363@@ -47,6 +47,16 @@
1364 </class>
1365
1366 <!-- Scoresystem Content -->
1367+ <class class=".scoresystem.GlobalCommentScoreSystem">
1368+ <require
1369+ permission="zope.View"
1370+ interface=".interfaces.ICommentScoreSystem"
1371+ />
1372+ <require
1373+ permission="schooltool.edit"
1374+ set_schema=".interfaces.IScoreSystem"
1375+ />
1376+ </class>
1377 <class class=".scoresystem.DiscreteValuesScoreSystem">
1378 <require
1379 permission="zope.View"
1380@@ -119,6 +129,11 @@
1381 component=".scoresystem.HundredPointsScoreSystem"
1382 name="100 Points"
1383 />
1384+ <utility
1385+ provides="schooltool.requirement.interfaces.IScoreSystem"
1386+ component=".scoresystem.CommentScoreSystem"
1387+ name="Comment"
1388+ />
1389
1390 <!-- Evaluations Content -->
1391 <class class=".evaluation.Evaluations">
1392
1393=== modified file 'src/schooltool/requirement/interfaces.py'
1394--- src/schooltool/requirement/interfaces.py 2009-05-08 18:19:21 +0000
1395+++ src/schooltool/requirement/interfaces.py 2009-06-29 07:43:38 +0000
1396@@ -84,6 +84,10 @@
1397 """
1398
1399
1400+class ICommentScoreSystem(IScoreSystem):
1401+ """A Score System for free-form comments."""
1402+
1403+
1404 class IValuesScoreSystem(IScoreSystem):
1405 """A Score System that deal with specific score values."""
1406
1407
1408=== modified file 'src/schooltool/requirement/scoresystem.py'
1409--- src/schooltool/requirement/scoresystem.py 2009-05-14 08:34:27 +0000
1410+++ src/schooltool/requirement/scoresystem.py 2009-06-29 07:43:38 +0000
1411@@ -40,6 +40,15 @@
1412 from schooltool.requirement.interfaces import IScoreSystemsProxy
1413
1414
1415+class ScoreValidationError(zope.schema.ValidationError):
1416+ """Validation error for scores"""
1417+ def __init__(self, score):
1418+ self.score = score
1419+
1420+ def doc(self):
1421+ return "'%s' is not a valid score." % self.score
1422+
1423+
1424 def ScoreSystemsVocabulary(context):
1425 return UtilityVocabulary(context,
1426 interface=interfaces.IScoreSystem)
1427@@ -87,7 +96,12 @@
1428 raise NotImplementedError
1429
1430
1431-class CommentScoreSystem(AbstractScoreSystem):
1432+class GlobalCommentScoreSystem(AbstractScoreSystem):
1433+ zope.interface.implements(interfaces.ICommentScoreSystem)
1434+
1435+ def __init__(self, title, description=None):
1436+ super(GlobalCommentScoreSystem, self).__init__(title, description)
1437+ self.__name__ = title
1438
1439 def isValidScore(self, score):
1440 """See interfaces.IScoreSystem"""
1441@@ -106,7 +120,7 @@
1442
1443
1444 # Singelton
1445-CommentScoreSystem = CommentScoreSystem(
1446+CommentScoreSystem = GlobalCommentScoreSystem(
1447 u'Comments', u'Scores are commentary text.')
1448
1449
1450@@ -177,8 +191,7 @@
1451 return UNSCORED
1452
1453 if not self.isValidScore(rawScore):
1454- raise zope.schema.ValidationError(
1455- "'%s' is not a valid score." %rawScore)
1456+ raise ScoreValidationError(rawScore)
1457 return rawScore
1458
1459 def getNumericalValue(self, score):
1460@@ -290,11 +303,10 @@
1461 try:
1462 score = Decimal(rawScore)
1463 except:
1464- raise ValueError
1465+ raise ScoreValidationError(rawScore)
1466
1467 if not self.isValidScore(score):
1468- raise zope.schema.ValidationError(
1469- "%r is not a valid score." %score)
1470+ raise ScoreValidationError(rawScore)
1471 return score
1472
1473 def getNumericalValue(self, score):

Subscribers

People subscribed via source and target branches