Merge lp:~aelkner/schooltool/schooltool.gradebook_migrate_sla into lp:~schooltool-owners/schooltool/schooltool.gradebook
- schooltool.gradebook_migrate_sla
- Merge into schooltool.gradebook
Proposed by
Alan Elkner
Status: | Merged |
---|---|
Merge reported by: | Justas Sadzevičius |
Merged at revision: | not available |
Proposed branch: | lp:~aelkner/schooltool/schooltool.gradebook_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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Justas Sadzevičius (community) | Approve | ||
Review via email: mp+8248@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Justas Sadzevičius (justas.sadzevicius) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/schooltool/gradebook/browser/README.txt' |
2 | --- src/schooltool/gradebook/browser/README.txt 2009-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>  </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): |