Merge lp:~ellimistd/schooltool.quiz/SchoolToolQuizDev into lp:schooltool.quiz
- SchoolToolQuizDev
- Merge into trunk
Proposed by
David Reich
Status: | Needs review |
---|---|
Proposed branch: | lp:~ellimistd/schooltool.quiz/SchoolToolQuizDev |
Merge into: | lp:schooltool.quiz |
Diff against target: |
2678 lines (+1242/-563) (has conflicts) 17 files modified
src/schooltool/quiz/browser/deployedquiz.py (+29/-2) src/schooltool/quiz/browser/quiz.py (+845/-475) src/schooltool/quiz/browser/quizitem.py (+26/-3) src/schooltool/quiz/browser/stests/quiz_deployment.txt (+26/-7) src/schooltool/quiz/browser/stests/quiz_duplicate.txt (+15/-4) src/schooltool/quiz/browser/stests/quiz_export.txt (+5/-0) src/schooltool/quiz/browser/stests/quiz_import.txt (+7/-1) src/schooltool/quiz/browser/stests/quiz_management.txt (+114/-3) src/schooltool/quiz/browser/stests/student_answers.txt (+29/-0) src/schooltool/quiz/browser/stests/test_selenium.py (+1/-1) src/schooltool/quiz/browser/templates/answers.pt (+4/-4) src/schooltool/quiz/browser/templates/quiz_item_body.pt (+5/-3) src/schooltool/quiz/browser/templates/quiz_item_form_script.pt (+5/-1) src/schooltool/quiz/quiz.py (+5/-0) src/schooltool/quiz/quizitem.py (+1/-0) src/schooltool/quiz/rational.py (+125/-0) src/schooltool/quiz/utils.py (+0/-59) Text conflict in src/schooltool/quiz/browser/quiz.py Text conflict in src/schooltool/quiz/quiz.py |
To merge this branch: | bzr merge lp:~ellimistd/schooltool.quiz/SchoolToolQuizDev |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Douglas Cerna | Pending | ||
Review via email: mp+149160@code.launchpad.net |
Commit message
Description of the change
Changed tests (quiz_duplicate, quiz_management, quiz_deployment, quiz_import, quiz_export, and student_answers) to account for rational tests. I also fixed bugs that had been indicated by failing tests in quiz_import and quiz_duplicate, but quiz_export is still not functional.
To post a comment you must log in.
- 39. By David Reich <email address hidden>
-
changed tests to account for rational questions
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/schooltool/quiz/browser/deployedquiz.py' |
2 | --- src/schooltool/quiz/browser/deployedquiz.py 2013-01-23 23:42:02 +0000 |
3 | +++ src/schooltool/quiz/browser/deployedquiz.py 2013-03-04 22:05:25 +0000 |
4 | @@ -78,6 +78,7 @@ |
5 | from schooltool.quiz.utils import render_rst, render_pre |
6 | from schooltool.quiz.browser.quiz import SolutionDoneLinkViewlet |
7 | from schooltool.quiz.utils import render_date |
8 | +from schooltool.quiz.utils import RationalValidator |
9 | |
10 | |
11 | def getStartTime(adapter): |
12 | @@ -941,8 +942,10 @@ |
13 | return self.createOpenQuestionField(item) |
14 | elif term.token == 'multiple': |
15 | return self.createMultipleSelectionField(item) |
16 | - else: |
17 | + elif term.token == 'selection': |
18 | return self.createSelectionField(item) |
19 | + else: |
20 | + return self.createRationalQuestionField(item) |
21 | |
22 | def createOpenQuestionField(self, item): |
23 | name = item.__name__.encode('utf-8') |
24 | @@ -953,6 +956,16 @@ |
25 | datamanager.DictionaryField(self.values, schema_field) |
26 | result = field.Fields(schema_field) |
27 | return result |
28 | + |
29 | + def createRationalQuestionField(self, item): |
30 | + name = item.__name__.encode('utf-8') |
31 | + schema_field = interfaces.ReStructuredText( |
32 | + __name__=name, |
33 | + description=item.body) |
34 | + self.values[name] = None |
35 | + datamanager.DictionaryField(self.values, schema_field) |
36 | + result = field.Fields(schema_field) |
37 | + return result |
38 | |
39 | def choicesVocabulary(self, item, shuffle=False): |
40 | terms = [] |
41 | @@ -1013,8 +1026,10 @@ |
42 | grade = UNSCORED |
43 | elif term.token == 'multiple': |
44 | grade = self.calculateMultipleSelectionGrade(item, answer) |
45 | - else: |
46 | + elif term.token == "selection": |
47 | grade = self.calculateSelectionGrade(item, answer) |
48 | + else: |
49 | + grade = self.calculateRationalGrade(item, answer) |
50 | evaluation = AnsweredEvaluation(item, answer, grade) |
51 | evaluations.addEvaluation(evaluation) |
52 | self.request.response.redirect(self.nextURL()) |
53 | @@ -1041,6 +1056,11 @@ |
54 | if choice.correct and choice in given_choices: |
55 | return 100 |
56 | return 0 |
57 | + |
58 | + def calculateRationalGrade(self, item, answer): |
59 | + if RationalValidator(item.solution, answer): |
60 | + return 100 |
61 | + return 0 |
62 | |
63 | @button.buttonAndHandler(_('Cancel'), name='cancel') |
64 | def handle_cancel(self, action): |
65 | @@ -1132,6 +1152,13 @@ |
66 | term = self.types_vocabulary.getTerm(item.type_) |
67 | return term.token == 'open' |
68 | |
69 | + def is_rational(self, item): |
70 | + term = self.types_vocabulary.getTerm(item.type_) |
71 | + return term.token == 'rational' |
72 | + |
73 | + def has_open_view(self, item): |
74 | + return self.is_rational(item) or self.is_open(item) |
75 | + |
76 | def is_graded(self, evaluation): |
77 | return evaluation and evaluation.value not in (UNSCORED, None) |
78 | |
79 | |
80 | === modified file 'src/schooltool/quiz/browser/quiz.py' |
81 | --- src/schooltool/quiz/browser/quiz.py 2013-01-24 10:01:26 +0000 |
82 | +++ src/schooltool/quiz/browser/quiz.py 2013-03-04 22:05:25 +0000 |
83 | @@ -73,56 +73,72 @@ |
84 | choices_column_separator = '**|**' |
85 | |
86 | |
87 | +<<<<<<< TREE |
88 | +======= |
89 | +class PersonSections(object): |
90 | + |
91 | + person = None |
92 | + |
93 | + @Lazy |
94 | + def learner_sections(self): |
95 | + return list(ILearner(self.person).sections()) |
96 | + |
97 | + @Lazy |
98 | + def instructor_sections(self): |
99 | + return list(IInstructor(self.person).sections()) |
100 | + |
101 | + |
102 | +>>>>>>> MERGE-SOURCE |
103 | class QuizContainerAbsoluteURLAdapter(BrowserView): |
104 | - |
105 | - adapts(interfaces.IQuizContainer, IBrowserRequest) |
106 | - implements(IAbsoluteURL) |
107 | - |
108 | - def __str__(self): |
109 | - app = ISchoolToolApplication(None) |
110 | - return '%s/quizzes' % absoluteURL(app, self.request) |
111 | - |
112 | - __call__ = __str__ |
113 | + adapts(interfaces.IQuizContainer, IBrowserRequest) |
114 | + implements(IAbsoluteURL) |
115 | + |
116 | + def __str__(self): |
117 | + app = ISchoolToolApplication(None) |
118 | + return '%s/quizzes' % absoluteURL(app, self.request) |
119 | + |
120 | + __call__ = __str__ |
121 | |
122 | |
123 | class QuizzesLink(flourish.page.LinkViewlet, PersonSections): |
124 | |
125 | - startup_view_name = 'quizzes.html' |
126 | - |
127 | - @Lazy |
128 | - def person(self): |
129 | - return IPerson(self.request.principal, None) |
130 | - |
131 | - @property |
132 | - def enabled(self): |
133 | - if self.person is None: |
134 | - return False |
135 | - editable_quizzes = getRelatedObjects( |
136 | - self.person, relationships.URIQuiz, relationships.URIQuizEditors) |
137 | - return self.instructor_sections or \ |
138 | - self.learner_sections or \ |
139 | - editable_quizzes |
140 | - |
141 | - @property |
142 | - def url(self): |
143 | - if self.person is None: |
144 | - return '' |
145 | - return '%s/%s' % (absoluteURL(self.person, self.request), |
146 | - self.startup_view_name) |
147 | + startup_view_name = 'quizzes.html' |
148 | + |
149 | + @Lazy |
150 | + def person(self): |
151 | + return IPerson(self.request.principal, None) |
152 | + |
153 | + @property |
154 | + def enabled(self): |
155 | + if self.person is None: |
156 | + return False |
157 | + editable_quizzes = getRelatedObjects( |
158 | + self.person, relationships.URIQuiz, relationships.URIQuizEditors) |
159 | + return self.instructor_sections or \ |
160 | + self.learner_sections or \ |
161 | + editable_quizzes |
162 | + |
163 | + @property |
164 | + def url(self): |
165 | + if self.person is None: |
166 | + return '' |
167 | + return '%s/%s' % (absoluteURL(self.person, self.request), |
168 | + self.startup_view_name) |
169 | |
170 | |
171 | def taken(person, deployed): |
172 | - result = False |
173 | - evaluations = IEvaluations(removeSecurityProxy(person)) |
174 | - for item in deployed: |
175 | - evaluation = evaluations.get(item, None) |
176 | - if evaluation is not None: |
177 | - return True |
178 | - return result |
179 | + result = False |
180 | + evaluations = IEvaluations(removeSecurityProxy(person)) |
181 | + for item in deployed: |
182 | + evaluation = evaluations.get(item, None) |
183 | + if evaluation is not None: |
184 | + return True |
185 | + return result |
186 | |
187 | |
188 | class PersonQuizzesView(flourish.page.Page, PersonSections): |
189 | |
190 | +<<<<<<< TREE |
191 | implements(interfaces.ISchoolToolQuizView, |
192 | interfaces.IPersonQuizView) |
193 | |
194 | @@ -176,163 +192,228 @@ |
195 | |
196 | def taken(self, deployed): |
197 | return taken(self.context, deployed) |
198 | +======= |
199 | + implements(interfaces.ISchoolToolQuizView) |
200 | + |
201 | + container_class = 'container widecontainer' |
202 | + |
203 | + @Lazy |
204 | + def person(self): |
205 | + return self.context |
206 | + |
207 | + @property |
208 | + def quizContainer(self): |
209 | + app = ISchoolToolApplication(None) |
210 | + return interfaces.IQuizContainer(app) |
211 | + |
212 | + @Lazy |
213 | + def editable_quizzes(self): |
214 | + result = [] |
215 | + for quiz in self.quizContainer.values(): |
216 | + if self.person in quiz.editors: |
217 | + result.append(quiz) |
218 | + return result |
219 | + |
220 | + @Lazy |
221 | + def deployed_quizzes(self): |
222 | + result = [] |
223 | + for section in self.instructor_sections: |
224 | + container = interfaces.IDeployedQuizContainer(section) |
225 | + for deployed in container.values(): |
226 | + result.append(deployed) |
227 | + return result |
228 | + |
229 | + @Lazy |
230 | + def takeable_quizzes(self): |
231 | + result = [] |
232 | + for section in self.learner_sections: |
233 | + container = interfaces.IDeployedQuizContainer(section) |
234 | + for deployed in container.values(): |
235 | + if not self.taken(deployed): |
236 | + result.append(deployed) |
237 | + return result |
238 | + |
239 | + @Lazy |
240 | + def taken_quizzes(self): |
241 | + result = [] |
242 | + for section in self.learner_sections: |
243 | + container = interfaces.IDeployedQuizContainer(section) |
244 | + for deployed in container.values(): |
245 | + if self.taken(deployed): |
246 | + result.append(deployed) |
247 | + return result |
248 | + |
249 | + def taken(self, deployed): |
250 | + return taken(self.context, deployed) |
251 | +>>>>>>> MERGE-SOURCE |
252 | |
253 | |
254 | class QuizContainerView(flourish.page.Page): |
255 | |
256 | - def update(self): |
257 | - print list(self.context.keys()) |
258 | + def update(self): |
259 | + print list(self.context.keys()) |
260 | |
261 | |
262 | class QuizContainerAddLinks(flourish.page.RefineLinksViewlet): |
263 | |
264 | - pass |
265 | + pass |
266 | |
267 | |
268 | class QuizContainerActionsLinks(flourish.page.RefineLinksViewlet): |
269 | |
270 | - pass |
271 | + pass |
272 | |
273 | |
274 | class QuizAddLinks(flourish.page.RefineLinksViewlet): |
275 | |
276 | - pass |
277 | + pass |
278 | |
279 | |
280 | class QuizViewLinks(flourish.page.RefineLinksViewlet): |
281 | |
282 | - pass |
283 | + pass |
284 | |
285 | |
286 | class QuizActionLinks(flourish.page.RefineLinksViewlet): |
287 | |
288 | - pass |
289 | + pass |
290 | |
291 | |
292 | class QuizAddLink(flourish.page.LinkViewlet): |
293 | |
294 | - @property |
295 | - def url(self): |
296 | - app = ISchoolToolApplication(None) |
297 | - container = interfaces.IQuizContainer(app) |
298 | - return '%s/add.html' % absoluteURL(container, self.request) |
299 | + @property |
300 | + def url(self): |
301 | + app = ISchoolToolApplication(None) |
302 | + container = interfaces.IQuizContainer(app) |
303 | + return '%s/add.html' % absoluteURL(container, self.request) |
304 | |
305 | |
306 | class QuizDeployLinkViewlet(flourish.page.LinkViewlet): |
307 | |
308 | - @property |
309 | - def enabled(self): |
310 | - return len(list(self.context)) |
311 | + @property |
312 | + def enabled(self): |
313 | + return len(list(self.context)) |
314 | |
315 | |
316 | class PersonQuizAddLink(QuizAddLink, PersonSections): |
317 | |
318 | - @Lazy |
319 | - def person(self): |
320 | - return IPerson(self.request.principal, None) |
321 | - |
322 | - @property |
323 | - def enabled(self): |
324 | - if self.person is not None: |
325 | - return self.instructor_sections |
326 | - return False |
327 | - |
328 | - @property |
329 | - def url(self): |
330 | - app = ISchoolToolApplication(None) |
331 | - container = interfaces.IQuizContainer(app) |
332 | - camefrom = '%s/quizzes.html' % absoluteURL(self.context, self.request) |
333 | - return '%s/add.html?camefrom=%s' % ( |
334 | - absoluteURL(container, self.request), camefrom) |
335 | + @Lazy |
336 | + def person(self): |
337 | + return IPerson(self.request.principal, None) |
338 | + |
339 | + @property |
340 | + def enabled(self): |
341 | + if self.person is not None: |
342 | + return self.instructor_sections |
343 | + return False |
344 | + |
345 | + @property |
346 | + def url(self): |
347 | + app = ISchoolToolApplication(None) |
348 | + container = interfaces.IQuizContainer(app) |
349 | + camefrom = '%s/quizzes.html' % absoluteURL(self.context, self.request) |
350 | + return '%s/add.html?camefrom=%s' % ( |
351 | + absoluteURL(container, self.request), camefrom) |
352 | |
353 | |
354 | class PersonQuizImportLink(QuizAddLink, PersonSections): |
355 | |
356 | - @Lazy |
357 | - def person(self): |
358 | - return IPerson(self.request.principal, None) |
359 | - |
360 | - @property |
361 | - def enabled(self): |
362 | - if self.person is not None: |
363 | - return self.instructor_sections |
364 | - return False |
365 | - |
366 | - @property |
367 | - def url(self): |
368 | - app = ISchoolToolApplication(None) |
369 | - container = interfaces.IQuizContainer(app) |
370 | - camefrom = '%s/quizzes.html' % absoluteURL(self.context, self.request) |
371 | - return '%s/import.html?camefrom=%s' % ( |
372 | - absoluteURL(container, self.request), camefrom) |
373 | + @Lazy |
374 | + def person(self): |
375 | + return IPerson(self.request.principal, None) |
376 | + |
377 | + @property |
378 | + def enabled(self): |
379 | + if self.person is not None: |
380 | + return self.instructor_sections |
381 | + return False |
382 | + |
383 | + @property |
384 | + def url(self): |
385 | + app = ISchoolToolApplication(None) |
386 | + container = interfaces.IQuizContainer(app) |
387 | + camefrom = '%s/quizzes.html' % absoluteURL(self.context, self.request) |
388 | + return '%s/import.html?camefrom=%s' % ( |
389 | + absoluteURL(container, self.request), camefrom) |
390 | |
391 | |
392 | class QuizSolutionLinkViewlet(flourish.page.LinkViewlet): |
393 | |
394 | - @property |
395 | - def enabled(self): |
396 | - return len(list(self.context)) |
397 | + @property |
398 | + def enabled(self): |
399 | + return len(list(self.context)) |
400 | |
401 | |
402 | class QuizView(flourish.page.Page): |
403 | |
404 | - implements(interfaces.ISchoolToolQuizView, |
405 | - interfaces.IMathJaxView) |
406 | - |
407 | - container_class = 'container widecontainer' |
408 | - |
409 | - @property |
410 | - def title(self): |
411 | - return self.context.title |
412 | - |
413 | - @property |
414 | - def can_modify(self): |
415 | - person = IPerson(self.request.principal, None) |
416 | - return person in self.context.editors |
417 | - |
418 | - @Lazy |
419 | - def quiz_items(self): |
420 | - return list(self.context) |
421 | - |
422 | - @Lazy |
423 | - def deployed_quizzes(self): |
424 | - return list(self.context.deployed) |
425 | + implements(interfaces.ISchoolToolQuizView, |
426 | + interfaces.IMathJaxView) |
427 | + |
428 | + container_class = 'container widecontainer' |
429 | + |
430 | + @property |
431 | + def title(self): |
432 | + return self.context.title |
433 | + |
434 | + @property |
435 | + def can_modify(self): |
436 | + person = IPerson(self.request.principal, None) |
437 | + return person in self.context.editors |
438 | + |
439 | + @Lazy |
440 | + def quiz_items(self): |
441 | + return list(self.context) |
442 | + |
443 | + @Lazy |
444 | + def deployed_quizzes(self): |
445 | + return list(self.context.deployed) |
446 | |
447 | |
448 | class EditableQuizzesViewlet(flourish.viewlet.Viewlet, PersonSections): |
449 | |
450 | - template = InlineViewPageTemplate(''' |
451 | - <tal:block i18n:domain="schooltool.quiz" |
452 | - tal:define="quizzes view/view/editable_quizzes"> |
453 | - <h3 i18n:translate="">Editable Quizzes</h3> |
454 | - <div tal:condition="quizzes" |
455 | - tal:define="ajax nocall:view/view/providers/ajax" |
456 | - tal:content="structure ajax/view/context/editable_quizzes_table" |
457 | - /> |
458 | - <p i18n:translate="" tal:condition="not:quizzes"> |
459 | - There are no quizzes you can edit yet. |
460 | - </p> |
461 | - </tal:block> |
462 | - ''') |
463 | - |
464 | - @Lazy |
465 | - def person(self): |
466 | - return self.context |
467 | - |
468 | - @property |
469 | - def enabled(self): |
470 | - editable_quizzes = getRelatedObjects(self.person, |
471 | - relationships.URIQuiz, |
472 | - relationships.URIQuizEditors) |
473 | - return self.instructor_sections or editable_quizzes |
474 | - |
475 | - def render(self, *args, **kw): |
476 | - if self.enabled: |
477 | - return self.template(*args, **kw) |
478 | - |
479 | - |
480 | + template = InlineViewPageTemplate(''' |
481 | + <tal:block i18n:domain="schooltool.quiz" |
482 | + tal:define="quizzes view/view/editable_quizzes"> |
483 | + <h3 i18n:translate="">Editable Quizzes</h3> |
484 | + <div tal:condition="quizzes" |
485 | + tal:define="ajax nocall:view/view/providers/ajax" |
486 | + tal:content="structure ajax/view/context/editable_quizzes_table" |
487 | + /> |
488 | + <p i18n:translate="" tal:condition="not:quizzes"> |
489 | + There are no quizzes you can edit yet. |
490 | + </p> |
491 | + </tal:block> |
492 | + ''') |
493 | + |
494 | + @Lazy |
495 | + def person(self): |
496 | + return self.context |
497 | + |
498 | + @property |
499 | + def enabled(self): |
500 | + editable_quizzes = getRelatedObjects(self.person, |
501 | + relationships.URIQuiz, |
502 | + relationships.URIQuizEditors) |
503 | + return self.instructor_sections or editable_quizzes |
504 | + |
505 | + def render(self, *args, **kw): |
506 | + if self.enabled: |
507 | + return self.template(*args, **kw) |
508 | + |
509 | + |
510 | +<<<<<<< TREE |
511 | +======= |
512 | +def quiz_editors_formatter(editors, quiz, formatter): |
513 | + collator = ICollator(formatter.request.locale) |
514 | + result = sorted([person.title for person in editors], |
515 | + cmp=collator.cmp) |
516 | + return '<br />'.join(result) |
517 | + |
518 | + |
519 | +>>>>>>> MERGE-SOURCE |
520 | class EditableQuizzesTable(table.ajax.Table): |
521 | |
522 | +<<<<<<< TREE |
523 | def items(self): |
524 | return self.view.editable_quizzes |
525 | |
526 | @@ -371,259 +452,333 @@ |
527 | batch_size=self.batch_size, |
528 | prefix=self.__name__, |
529 | css_classes={'table': 'data editable-quizzes-table'}) |
530 | +======= |
531 | + table_formatter = AJAXCSSTableFormatter |
532 | + |
533 | + def items(self): |
534 | + return self.view.editable_quizzes |
535 | + |
536 | + def columns(self): |
537 | + default = super(EditableQuizzesTable, self).columns() |
538 | + questions = GetterColumn( |
539 | + name='questions', |
540 | + title=_('Questions'), |
541 | + getter=lambda quiz, formatter: len(list(quiz))) |
542 | + editors = GetterColumn( |
543 | + name='editors', |
544 | + title=_('Editors'), |
545 | + getter=lambda quiz, formatter: quiz.editors, |
546 | + cell_formatter=quiz_editors_formatter) |
547 | + created = GetterColumn( |
548 | + name='created', |
549 | + title=_('Created'), |
550 | + getter=lambda i, f: IZopeDublinCore(i).created, |
551 | + cell_formatter=lambda v, i, f: render_date(v), |
552 | + subsort=True) |
553 | + directlyProvides(created, ISortableColumn) |
554 | + duplicate = QuizActionColumn( |
555 | + 'duplicate', |
556 | + title=_('Duplicate this quiz'), |
557 | + action='duplicate_quiz.html', |
558 | + library='schooltool.quiz.flourish', |
559 | + image='duplicate-icon.png') |
560 | + return default + [questions, editors, created, duplicate] |
561 | + |
562 | + def sortOn(self): |
563 | + return (('created', True),) |
564 | + |
565 | + def updateFormatter(self): |
566 | + if self._table_formatter is None: |
567 | + self.setUp(table_formatter=self.table_formatter, |
568 | + batch_size=self.batch_size, |
569 | + prefix=self.__name__, |
570 | + css_classes={'table': 'data editable-quizzes-table'}) |
571 | +>>>>>>> MERGE-SOURCE |
572 | |
573 | |
574 | class QuizAddView(flourish.form.AddForm): |
575 | |
576 | - legend = _('Quiz Information') |
577 | - fields = field.Fields(interfaces.IQuiz['title']) |
578 | - content_template = ViewPageTemplateFile('templates/form.pt') |
579 | - |
580 | - def updateActions(self): |
581 | - super(QuizAddView, self).updateActions() |
582 | - self.actions['add'].addClass('button-ok') |
583 | - self.actions['cancel'].addClass('button-cancel') |
584 | - |
585 | - def create(self, data): |
586 | - quiz = Quiz() |
587 | - form.applyChanges(self, quiz, data) |
588 | - return quiz |
589 | - |
590 | - def add(self, quiz): |
591 | - chooser = INameChooser(self.context) |
592 | - name = chooser.chooseName('', quiz) |
593 | - self.context[name] = quiz |
594 | - self.setCreator(quiz) |
595 | - self._quiz = quiz |
596 | - return quiz |
597 | - |
598 | - def nextURL(self): |
599 | - if self._finishedAdd: |
600 | - url = absoluteURL(self._quiz, self.request) |
601 | - else: |
602 | - url = self.request.get('camefrom', |
603 | - absoluteURL(self.context, self.request)) |
604 | - return url |
605 | - |
606 | - def setCreator(self, quiz): |
607 | - creator = IPerson(self.request.principal) |
608 | - quiz.editors.add(removeSecurityProxy(creator)) |
609 | + legend = _('Quiz Information') |
610 | + fields = field.Fields(interfaces.IQuiz['title']) |
611 | + content_template = ViewPageTemplateFile('templates/form.pt') |
612 | + |
613 | + def updateActions(self): |
614 | + super(QuizAddView, self).updateActions() |
615 | + self.actions['add'].addClass('button-ok') |
616 | + self.actions['cancel'].addClass('button-cancel') |
617 | + |
618 | + def create(self, data): |
619 | + quiz = Quiz() |
620 | + form.applyChanges(self, quiz, data) |
621 | + return quiz |
622 | + |
623 | + def add(self, quiz): |
624 | + chooser = INameChooser(self.context) |
625 | + name = chooser.chooseName('', quiz) |
626 | + self.context[name] = quiz |
627 | + self.setCreator(quiz) |
628 | + self._quiz = quiz |
629 | + return quiz |
630 | + |
631 | + def nextURL(self): |
632 | + if self._finishedAdd: |
633 | + url = absoluteURL(self._quiz, self.request) |
634 | + else: |
635 | + url = self.request.get('camefrom', |
636 | + absoluteURL(self.context, self.request)) |
637 | + return url |
638 | + |
639 | + def setCreator(self, quiz): |
640 | + creator = IPerson(self.request.principal) |
641 | + quiz.editors.add(removeSecurityProxy(creator)) |
642 | |
643 | |
644 | class QuizEditView(flourish.form.Form, form.EditForm): |
645 | |
646 | - legend = _('Quiz Information') |
647 | - fields = field.Fields(interfaces.IQuiz['title']) |
648 | - content_template = ViewPageTemplateFile('templates/form.pt') |
649 | - |
650 | - @property |
651 | - def title(self): |
652 | - return self.context.title |
653 | - |
654 | - def update(self): |
655 | - return form.EditForm.update(self) |
656 | - |
657 | - def updateActions(self): |
658 | - super(QuizEditView, self).updateActions() |
659 | - self.actions['submit'].addClass('button-ok') |
660 | - self.actions['cancel'].addClass('button-cancel') |
661 | - |
662 | - def nextURL(self): |
663 | - return absoluteURL(self.context, self.request) |
664 | - |
665 | - @button.buttonAndHandler(_('Submit'), name='submit') |
666 | - def handle_submit(self, action): |
667 | - super(QuizEditView, self).handleApply.func(self, action) |
668 | - if (self.status == self.successMessage or |
669 | - self.status == self.noChangesMessage): |
670 | - self.request.response.redirect(self.nextURL()) |
671 | - |
672 | - @button.buttonAndHandler(_('Cancel'), name='cancel') |
673 | - def handle_cancel(self, action): |
674 | - self.request.response.redirect(self.nextURL()) |
675 | + legend = _('Quiz Information') |
676 | + fields = field.Fields(interfaces.IQuiz['title']) |
677 | + content_template = ViewPageTemplateFile('templates/form.pt') |
678 | + |
679 | + @property |
680 | + def title(self): |
681 | + return self.context.title |
682 | + |
683 | + def update(self): |
684 | + return form.EditForm.update(self) |
685 | + |
686 | + def updateActions(self): |
687 | + super(QuizEditView, self).updateActions() |
688 | + self.actions['submit'].addClass('button-ok') |
689 | + self.actions['cancel'].addClass('button-cancel') |
690 | + |
691 | + def nextURL(self): |
692 | + return absoluteURL(self.context, self.request) |
693 | + |
694 | + @button.buttonAndHandler(_('Submit'), name='submit') |
695 | + def handle_submit(self, action): |
696 | + super(QuizEditView, self).handleApply.func(self, action) |
697 | + if (self.status == self.successMessage or |
698 | + self.status == self.noChangesMessage): |
699 | + self.request.response.redirect(self.nextURL()) |
700 | + |
701 | + @button.buttonAndHandler(_('Cancel'), name='cancel') |
702 | + def handle_cancel(self, action): |
703 | + self.request.response.redirect(self.nextURL()) |
704 | |
705 | |
706 | class QuizEditorsView(EditPersonRelationships): |
707 | |
708 | - current_title = _('Current editors') |
709 | - available_title = _('Add editors') |
710 | - |
711 | - @property |
712 | - def title(self): |
713 | - return self.context.title |
714 | - |
715 | - def getCollection(self): |
716 | - return self.context.editors |
717 | + current_title = _('Current editors') |
718 | + available_title = _('Add editors') |
719 | + |
720 | + @property |
721 | + def title(self): |
722 | + return self.context.title |
723 | + |
724 | + def getCollection(self): |
725 | + return self.context.editors |
726 | |
727 | |
728 | class QuizDeleteView(flourish.form.DialogForm): |
729 | |
730 | - template = ViewPageTemplateFile('templates/confirm_delete_quiz.pt') |
731 | - |
732 | - dialog_submit_actions = ('delete',) |
733 | - dialog_close_actions = ('cancel',) |
734 | - label = None |
735 | - |
736 | - @button.buttonAndHandler(_('Delete'), name='delete') |
737 | - def handle_delete(self, action): |
738 | - url = '%s/delete.html?delete.%s&CONFIRM' % ( |
739 | - absoluteURL(self.context.__parent__, self.request), |
740 | - self.context.__name__) |
741 | - self.request.response.redirect(url) |
742 | - self.ajax_settings['dialog'] = 'close' |
743 | - |
744 | - @button.buttonAndHandler(_('Cancel'), name='cancel') |
745 | - def handle_cancel(self, action): |
746 | - pass |
747 | - |
748 | - def updateActions(self): |
749 | - super(QuizDeleteView, self).updateActions() |
750 | - self.actions['delete'].addClass('button-ok') |
751 | - self.actions['cancel'].addClass('button-cancel') |
752 | + template = ViewPageTemplateFile('templates/confirm_delete_quiz.pt') |
753 | + |
754 | + dialog_submit_actions = ('delete',) |
755 | + dialog_close_actions = ('cancel',) |
756 | + label = None |
757 | + |
758 | + @button.buttonAndHandler(_('Delete'), name='delete') |
759 | + def handle_delete(self, action): |
760 | + url = '%s/delete.html?delete.%s&CONFIRM' % ( |
761 | + absoluteURL(self.context.__parent__, self.request), |
762 | + self.context.__name__) |
763 | + self.request.response.redirect(url) |
764 | + self.ajax_settings['dialog'] = 'close' |
765 | + |
766 | + @button.buttonAndHandler(_('Cancel'), name='cancel') |
767 | + def handle_cancel(self, action): |
768 | + pass |
769 | + |
770 | + def updateActions(self): |
771 | + super(QuizDeleteView, self).updateActions() |
772 | + self.actions['delete'].addClass('button-ok') |
773 | + self.actions['cancel'].addClass('button-cancel') |
774 | |
775 | |
776 | class QuizDuplicateView(flourish.page.Page): |
777 | |
778 | - def __call__(self): |
779 | - container = self.context.__parent__ |
780 | - copy = removeSecurityProxy(self.context).copy() |
781 | - chooser = INameChooser(container) |
782 | - name = chooser.chooseName('', copy) |
783 | - container[name] = copy |
784 | - copy.title = translate( |
785 | - _('DUPLICATED: ${quiz}', mapping={'quiz': self.context.title}), |
786 | - context=self.request) |
787 | - person = IPerson(self.request.principal, None) |
788 | - if person is not None: |
789 | - copy.editors.add(removeSecurityProxy(person)) |
790 | - notify(ObjectCreatedEvent(copy)) |
791 | - camefrom = self.request.get('camefrom', |
792 | - absoluteURL(container, self.request)) |
793 | - self.request.response.redirect(camefrom) |
794 | + def __call__(self): |
795 | + container = self.context.__parent__ |
796 | + copy = removeSecurityProxy(self.context).copy() |
797 | + chooser = INameChooser(container) |
798 | + name = chooser.chooseName('', copy) |
799 | + container[name] = copy |
800 | + copy.title = translate( |
801 | + _('DUPLICATED: ${quiz}', mapping={'quiz': self.context.title}), |
802 | + context=self.request) |
803 | + person = IPerson(self.request.principal, None) |
804 | + if person is not None: |
805 | + copy.editors.add(removeSecurityProxy(person)) |
806 | + notify(ObjectCreatedEvent(copy)) |
807 | + camefrom = self.request.get('camefrom', |
808 | + absoluteURL(container, self.request)) |
809 | + self.request.response.redirect(camefrom) |
810 | |
811 | |
812 | class QuizContainerDeleteView(flourish.containers.ContainerDeleteView): |
813 | |
814 | - def nextURL(self): |
815 | - if 'CONFIRM' in self.request: |
816 | - person = IPerson(self.request.principal) |
817 | - url = '%s/quizzes.html' % absoluteURL(person, self.request) |
818 | - return url |
819 | - return super(QuizContainerDeleteView, self).nextURL() |
820 | + def nextURL(self): |
821 | + if 'CONFIRM' in self.request: |
822 | + person = IPerson(self.request.principal) |
823 | + url = '%s/quizzes.html' % absoluteURL(person, self.request) |
824 | + return url |
825 | + return super(QuizContainerDeleteView, self).nextURL() |
826 | |
827 | |
828 | class QuizSolutionView(flourish.page.Page): |
829 | |
830 | - implements(interfaces.ISchoolToolQuizView, |
831 | - interfaces.IMathJaxView) |
832 | - |
833 | - container_class = 'container extra-wide-container' |
834 | - |
835 | - @property |
836 | - def title(self): |
837 | - return self.context.title |
838 | - |
839 | - @Lazy |
840 | - def quiz_items(self): |
841 | - return self.context |
842 | + implements(interfaces.ISchoolToolQuizView, |
843 | + interfaces.IMathJaxView) |
844 | + |
845 | + container_class = 'container extra-wide-container' |
846 | + |
847 | + @property |
848 | + def title(self): |
849 | + return self.context.title |
850 | + |
851 | + @Lazy |
852 | + def quiz_items(self): |
853 | + return self.context |
854 | |
855 | |
856 | class QuizTitleViewlet(flourish.viewlet.Viewlet): |
857 | |
858 | - template = ViewPageTemplateFile('templates/quiz_title.pt') |
859 | + template = ViewPageTemplateFile('templates/quiz_title.pt') |
860 | |
861 | |
862 | class QuizItemsViewlet(flourish.viewlet.Viewlet): |
863 | |
864 | - template = InlineViewPageTemplate(''' |
865 | - <tal:block i18n:domain="schooltool.quiz" |
866 | - tal:define="items view/view/quiz_items"> |
867 | - <h3 i18n:translate="">Questions</h3> |
868 | - <div tal:condition="items" |
869 | - tal:define="ajax nocall:view/view/providers/ajax" |
870 | - tal:content="structure ajax/view/context/quiz_items_table" |
871 | - /> |
872 | - <p i18n:translate="" tal:condition="not:items"> |
873 | - This quiz has no questions yet. |
874 | - </p> |
875 | - </tal:block> |
876 | - ''') |
877 | - |
878 | - |
879 | + template = InlineViewPageTemplate(''' |
880 | + <tal:block i18n:domain="schooltool.quiz" |
881 | + tal:define="items view/view/quiz_items"> |
882 | + <h3 i18n:translate="">Questions</h3> |
883 | + <div tal:condition="items" |
884 | + tal:define="ajax nocall:view/view/providers/ajax" |
885 | + tal:content="structure ajax/view/context/quiz_items_table" |
886 | + /> |
887 | + <p i18n:translate="" tal:condition="not:items"> |
888 | + This quiz has no questions yet. |
889 | + </p> |
890 | + </tal:block> |
891 | + ''') |
892 | + |
893 | + |
894 | +<<<<<<< TREE |
895 | +======= |
896 | +class ReStructuredTextColumn(GetterColumn): |
897 | + |
898 | + def __init__(self, *args, **kw): |
899 | + self.schema_field = kw.pop('schema_field') |
900 | + super(ReStructuredTextColumn, self).__init__(*args, **kw) |
901 | + |
902 | + def renderCell(self, item, formatter): |
903 | + renderer = flourish.form.Form(item, formatter.request) |
904 | + renderer.mode = DISPLAY_MODE |
905 | + renderer.fields = field.Fields(self.schema_field) |
906 | + renderer.update() |
907 | + return renderer.widgets['body'].render() |
908 | + |
909 | + |
910 | +class TypeColumn(GetterColumn): |
911 | + |
912 | + def __init__(self, *args, **kw): |
913 | + super(TypeColumn, self).__init__(*args, **kw) |
914 | + factory = getUtility(IVocabularyFactory, |
915 | + name='schooltool.quiz.quiz_item_types') |
916 | + vocabulary = factory(None) |
917 | + self.vocabulary = vocabulary |
918 | + |
919 | + def getter(self, item, formatter): |
920 | + term = self.vocabulary.getTerm(item.type_) |
921 | + return term.title |
922 | + |
923 | + |
924 | +>>>>>>> MERGE-SOURCE |
925 | class ActionColumn(table.column.ImageInputColumn): |
926 | |
927 | - def __init__(self, name, title=None, action=None, library=None, |
928 | - image=None): |
929 | - super(ActionColumn, self).__init__( |
930 | - 'item.', '', name, library=library, image=image) |
931 | - self.alt = title |
932 | - self.link_title = title |
933 | - self.action = action |
934 | + def __init__(self, name, title=None, action=None, library=None, |
935 | + image=None): |
936 | + super(ActionColumn, self).__init__( |
937 | + 'item.', '', name, library=library, image=image) |
938 | + self.alt = title |
939 | + self.link_title = title |
940 | + self.action = action |
941 | |
942 | - def template(self): |
943 | - return '\n'.join([ |
944 | - '<a href="%(href)s" title="%(title)s">', |
945 | - '<img src="%(src)s" alt="%(alt)s" />', |
946 | - '</a>' |
947 | - ]) |
948 | + def template(self): |
949 | + return '\n'.join([ |
950 | + '<a href="%(href)s" title="%(title)s">', |
951 | + '<img src="%(src)s" alt="%(alt)s" />', |
952 | + '</a>' |
953 | + ]) |
954 | |
955 | |
956 | class QuizActionColumn(ActionColumn): |
957 | |
958 | - def params(self, item, formatter): |
959 | - result = super(QuizActionColumn, self).params(item, formatter) |
960 | - person_url = absoluteURL(formatter.context, formatter.request) |
961 | - camefrom = '%s/quizzes.html' % person_url |
962 | - quiz_url = absoluteURL(item, formatter.request) |
963 | - result['title'] = translate(self.link_title, |
964 | - context=formatter.request) or '' |
965 | - result['href'] = '%s/%s?camefrom=%s' % ( |
966 | - quiz_url, self.action, camefrom) |
967 | - return result |
968 | + def params(self, item, formatter): |
969 | + result = super(QuizActionColumn, self).params(item, formatter) |
970 | + person_url = absoluteURL(formatter.context, formatter.request) |
971 | + camefrom = '%s/quizzes.html' % person_url |
972 | + quiz_url = absoluteURL(item, formatter.request) |
973 | + result['title'] = translate(self.link_title, |
974 | + context=formatter.request) or '' |
975 | + result['href'] = '%s/%s?camefrom=%s' % ( |
976 | + quiz_url, self.action, camefrom) |
977 | + return result |
978 | |
979 | |
980 | class ItemActionColumn(ActionColumn): |
981 | |
982 | - def params(self, item, formatter): |
983 | - result = super(ItemActionColumn, self).params(item, formatter) |
984 | - quiz_url = absoluteURL(formatter.context, formatter.request) |
985 | - result['title'] = translate(self.link_title, |
986 | - context=formatter.request) or '' |
987 | - result['href'] = '%s/%s?item_id=%s&camefrom=%s' % ( |
988 | - quiz_url, self.action, item.__name__, quiz_url) |
989 | - return result |
990 | + def params(self, item, formatter): |
991 | + result = super(ItemActionColumn, self).params(item, formatter) |
992 | + quiz_url = absoluteURL(formatter.context, formatter.request) |
993 | + result['title'] = translate(self.link_title, |
994 | + context=formatter.request) or '' |
995 | + result['href'] = '%s/%s?item_id=%s&camefrom=%s' % ( |
996 | + quiz_url, self.action, item.__name__, quiz_url) |
997 | + return result |
998 | |
999 | |
1000 | class ModalItemActionColumn(ItemActionColumn): |
1001 | |
1002 | - template = ViewPageTemplateFile('templates/modal_item_column.pt') |
1003 | + template = ViewPageTemplateFile('templates/modal_item_column.pt') |
1004 | |
1005 | - def renderCell(self, item, formatter): |
1006 | - params = self.params(item, formatter) |
1007 | - params['dialog_title'] = translate(_('Remove this question?'), |
1008 | - context=formatter.request) |
1009 | - self.context = item |
1010 | - self.request = formatter.request |
1011 | - return self.template(params=params) |
1012 | + def renderCell(self, item, formatter): |
1013 | + params = self.params(item, formatter) |
1014 | + params['dialog_title'] = translate(_('Remove this question?'), |
1015 | + context=formatter.request) |
1016 | + self.context = item |
1017 | + self.request = formatter.request |
1018 | + return self.template(params=params) |
1019 | |
1020 | |
1021 | class PositionColumn(GetterColumn): |
1022 | |
1023 | - def getter(self, item, formatter): |
1024 | - for position, current in enumerate(formatter.context): |
1025 | - if sameProxiedObjects(current, item): |
1026 | - return position |
1027 | + def getter(self, item, formatter): |
1028 | + for position, current in enumerate(formatter.context): |
1029 | + if sameProxiedObjects(current, item): |
1030 | + return position |
1031 | |
1032 | - def cell_formatter(self, value, item, formatter): |
1033 | - template = '<a href="%(href)s">%(title)s</a>' |
1034 | - quiz_url = absoluteURL(formatter.context, formatter.request) |
1035 | - href = '%s/view_item.html?item_id=%s' % (quiz_url, item.__name__) |
1036 | - params = {'href': href, 'title': value + 1} |
1037 | - return template % params |
1038 | + def cell_formatter(self, value, item, formatter): |
1039 | + template = '<a href="%(href)s">%(title)s</a>' |
1040 | + quiz_url = absoluteURL(formatter.context, formatter.request) |
1041 | + href = '%s/view_item.html?item_id=%s' % (quiz_url, item.__name__) |
1042 | + params = {'href': href, 'title': value + 1} |
1043 | + return template % params |
1044 | |
1045 | |
1046 | class SortColumn(PositionColumn): |
1047 | |
1048 | +<<<<<<< TREE |
1049 | def cell_formatter(self, value, item, formatter): |
1050 | template = '<select name="%(name)s" class="item-sort">%(options)s</select>' |
1051 | options = [] |
1052 | @@ -638,10 +793,27 @@ |
1053 | 'options': ''.join(options), |
1054 | } |
1055 | return template % params |
1056 | +======= |
1057 | + def cell_formatter(self, value, item, formatter): |
1058 | + template = '<select id="%(name)s" class="item-sort">%(options)s</select>' |
1059 | + options = [] |
1060 | + for position, current in enumerate(formatter.context): |
1061 | + display_position = position + 1 |
1062 | + option = '<option value="%d">%d</option>' |
1063 | + if sameProxiedObjects(current, item): |
1064 | + option = '<option value="%d" selected="selected">%d</option>' |
1065 | + options.append(option % (position, display_position)) |
1066 | + params = { |
1067 | + 'name': item.__name__, |
1068 | + 'options': ''.join(options), |
1069 | + } |
1070 | + return template % params |
1071 | +>>>>>>> MERGE-SOURCE |
1072 | |
1073 | |
1074 | class QuizItemsTable(table.ajax.Table): |
1075 | |
1076 | +<<<<<<< TREE |
1077 | def columns(self): |
1078 | position = PositionColumn( |
1079 | name='position', |
1080 | @@ -707,67 +879,137 @@ |
1081 | new_position = int(toChange) |
1082 | self.context.updateOrder(changePosition, new_position) |
1083 | super(QuizItemsTable, self).update() |
1084 | +======= |
1085 | + table_formatter = AJAXCSSTableFormatter |
1086 | + |
1087 | + def columns(self): |
1088 | + position = PositionColumn( |
1089 | + name='position', |
1090 | + title=_('No.')) |
1091 | + body = ReStructuredTextColumn( |
1092 | + name='body', |
1093 | + title=_('Body'), |
1094 | + schema_field=interfaces.IQuizItem['body'] |
1095 | + ) |
1096 | + type_ = TypeColumn( |
1097 | + name='type', |
1098 | + title=_('Type')) |
1099 | + result = [position, body, type_] |
1100 | + result.extend(self.getActionColumns()) |
1101 | + return result |
1102 | + |
1103 | + def getActionColumns(self): |
1104 | + # XXX: set action columns only if principal is editor |
1105 | + sort = SortColumn( |
1106 | + name='sort', |
1107 | + title=u'') |
1108 | + edit = ItemActionColumn( |
1109 | + 'edit', |
1110 | + title=_('Edit this item'), |
1111 | + action='edit_item.html', |
1112 | + library='schooltool.skin.flourish', |
1113 | + image='edit-icon.png') |
1114 | + duplicate = ItemActionColumn( |
1115 | + 'duplicate', |
1116 | + title=_('Duplicate this item'), |
1117 | + action='duplicate_item.html', |
1118 | + library='schooltool.quiz.flourish', |
1119 | + image='duplicate-icon.png') |
1120 | + remove = ModalItemActionColumn( |
1121 | + 'remove', |
1122 | + title=_('Remove this item'), |
1123 | + action='remove_item.html', |
1124 | + library='schooltool.skin.flourish', |
1125 | + image='remove-icon.png') |
1126 | + return [sort, edit, duplicate, remove] |
1127 | + |
1128 | + def items(self): |
1129 | + return self.view.quiz_items |
1130 | + |
1131 | + def sortOn(self): |
1132 | + return (('position', False),) |
1133 | + |
1134 | + def updateFormatter(self): |
1135 | + # XXX: set -with-actions class only if principal is editor |
1136 | + klass = 'data quiz-items-table quiz-items-table-with-actions' |
1137 | + css_classes = {'table': klass} |
1138 | + if self._table_formatter is None: |
1139 | + self.setUp(table_formatter=self.table_formatter, |
1140 | + batch_size=self.batch_size, |
1141 | + prefix=self.__name__, |
1142 | + css_classes=css_classes) |
1143 | + |
1144 | + def update(self): |
1145 | + changePosition = self.request.get('changePosition') |
1146 | + if changePosition is not None: |
1147 | + toChange = self.request.get(changePosition) |
1148 | + if toChange is not None: |
1149 | + new_position = int(toChange) |
1150 | + self.context.updateOrder(changePosition, new_position) |
1151 | + super(QuizItemsTable, self).update() |
1152 | +>>>>>>> MERGE-SOURCE |
1153 | |
1154 | |
1155 | class QuizViewDoneLinkViewlet(flourish.viewlet.Viewlet): |
1156 | |
1157 | - template = InlineViewPageTemplate(''' |
1158 | - <h3 i18n:domain="schooltool" class="done-link"> |
1159 | - <a tal:attributes="href view/url" |
1160 | - i18n:translate=""> |
1161 | - Done |
1162 | - </a> |
1163 | - </h3> |
1164 | - ''') |
1165 | + template = InlineViewPageTemplate(''' |
1166 | + <h3 i18n:domain="schooltool" class="done-link"> |
1167 | + <a tal:attributes="href view/url" |
1168 | + i18n:translate=""> |
1169 | + Done |
1170 | + </a> |
1171 | + </h3> |
1172 | + ''') |
1173 | |
1174 | - def url(self): |
1175 | - person = IPerson(self.request.principal, None) |
1176 | - if person is not None: |
1177 | - return '%s/quizzes.html' % (absoluteURL(person, self.request)) |
1178 | + def url(self): |
1179 | + person = IPerson(self.request.principal, None) |
1180 | + if person is not None: |
1181 | + return '%s/quizzes.html' % (absoluteURL(person, self.request)) |
1182 | |
1183 | |
1184 | class QuizDeleteLinkViewlet(flourish.page.ModalFormLinkViewlet): |
1185 | |
1186 | - @property |
1187 | - def dialog_title(self): |
1188 | - title = _('Delete this quiz?') |
1189 | - return translate(title, context=self.request) |
1190 | + @property |
1191 | + def dialog_title(self): |
1192 | + title = _('Delete this quiz?') |
1193 | + return translate(title, context=self.request) |
1194 | |
1195 | |
1196 | class QuizSolutionTableViewlet(flourish.viewlet.Viewlet): |
1197 | |
1198 | - template = InlineViewPageTemplate(''' |
1199 | - <div tal:define="ajax nocall:view/view/providers/ajax" |
1200 | - tal:content="structure ajax/view/context/solution_table" /> |
1201 | - ''') |
1202 | + template = InlineViewPageTemplate(''' |
1203 | + <div tal:define="ajax nocall:view/view/providers/ajax" |
1204 | + tal:content="structure ajax/view/context/solution_table" /> |
1205 | + ''') |
1206 | |
1207 | |
1208 | class SolutionPositionColumn(PositionColumn): |
1209 | |
1210 | - def cell_formatter(self, value, item, formatter): |
1211 | - return value + 1 |
1212 | + def cell_formatter(self, value, item, formatter): |
1213 | + return value + 1 |
1214 | |
1215 | |
1216 | def solution_question_formatter(value, item, formatter): |
1217 | - return render_rst(item.body) |
1218 | + return render_rst(item.body) |
1219 | |
1220 | |
1221 | def solution_solution_formatter(value, item, formatter): |
1222 | - if item.type_ == u'Open': |
1223 | - return render_rst(item.solution) |
1224 | - choices = [] |
1225 | - for choice in item.choices: |
1226 | - body = render_rst(choice.body) |
1227 | - if choice.correct: |
1228 | - choices.append( |
1229 | - '<span class="correct">%s</span>' % body) |
1230 | - else: |
1231 | - choices.append(body) |
1232 | - return ''.join(choices) |
1233 | + if item.type_ in (u'Open',u'Rational'): |
1234 | + return render_rst(item.solution) |
1235 | + choices = [] |
1236 | + for choice in item.choices: |
1237 | + body = render_rst(choice.body) |
1238 | + if choice.correct: |
1239 | + choices.append( |
1240 | + '<span class="correct">%s</span>' % body) |
1241 | + else: |
1242 | + choices.append(body) |
1243 | + return ''.join(choices) |
1244 | |
1245 | |
1246 | class SolutionTable(table.ajax.Table): |
1247 | |
1248 | +<<<<<<< TREE |
1249 | def items(self): |
1250 | return self.view.quiz_items |
1251 | |
1252 | @@ -799,23 +1041,59 @@ |
1253 | batch_size=self.batch_size, |
1254 | prefix=self.__name__, |
1255 | css_classes={'table': 'data solution-table'}) |
1256 | +======= |
1257 | + table_formatter = AJAXCSSTableFormatter |
1258 | + |
1259 | + def items(self): |
1260 | + return self.view.quiz_items |
1261 | + |
1262 | + def columns(self): |
1263 | + position = SolutionPositionColumn( |
1264 | + name='position', |
1265 | + title=_('No.')) |
1266 | + type_ = TypeColumn( |
1267 | + name='type', |
1268 | + title=_('Type')) |
1269 | + question = GetterColumn( |
1270 | + name='question', |
1271 | + title=_('Question'), |
1272 | + getter=lambda i, f: '', |
1273 | + cell_formatter=solution_question_formatter) |
1274 | + solution = GetterColumn( |
1275 | + name='solution', |
1276 | + title=_('Solution'), |
1277 | + getter=lambda i, f: '', |
1278 | + cell_formatter=solution_solution_formatter) |
1279 | + return [position, type_, question, solution] |
1280 | + |
1281 | + def sortOn(self): |
1282 | + return (('position', False),) |
1283 | + |
1284 | + def updateFormatter(self): |
1285 | + if self._table_formatter is None: |
1286 | + self.setUp(table_formatter=self.table_formatter, |
1287 | + batch_size=self.batch_size, |
1288 | + prefix=self.__name__, |
1289 | + css_classes={'table': 'data solution-table'}) |
1290 | +>>>>>>> MERGE-SOURCE |
1291 | |
1292 | |
1293 | class SolutionDoneLinkViewlet(flourish.viewlet.Viewlet): |
1294 | |
1295 | - template = InlineViewPageTemplate(''' |
1296 | - <h3 i18n:domain="schooltool" class="done-link"> |
1297 | - <a tal:attributes="href view/url" i18n:translate="">Done</a> |
1298 | - </h3> |
1299 | - ''') |
1300 | + template = InlineViewPageTemplate(''' |
1301 | + <h3 i18n:domain="schooltool" class="done-link"> |
1302 | + <a tal:attributes="href view/url" i18n:translate="">Done</a> |
1303 | + </h3> |
1304 | + ''') |
1305 | |
1306 | - def url(self): |
1307 | - default = absoluteURL(self.context, self.request) |
1308 | - return self.request.get('camefrom', default) |
1309 | + def url(self): |
1310 | + default = absoluteURL(self.context, self.request) |
1311 | + return self.request.get('camefrom', default) |
1312 | |
1313 | |
1314 | class QuizImporter(ImporterBase): |
1315 | |
1316 | +<<<<<<< TREE |
1317 | sheet_name = 'QuizItems' |
1318 | |
1319 | @Lazy |
1320 | @@ -883,126 +1161,196 @@ |
1321 | continue |
1322 | item = self.createQuizItem(data) |
1323 | self.addItem(quiz, item, data) |
1324 | +======= |
1325 | + sheet_name = 'QuizItems' |
1326 | + |
1327 | + @Lazy |
1328 | + def types_vocabulary(self): |
1329 | + factory = getUtility(IVocabularyFactory, |
1330 | + name='schooltool.quiz.quiz_item_types') |
1331 | + return factory(None) |
1332 | + |
1333 | + def createQuiz(self, title): |
1334 | + quiz = Quiz() |
1335 | + quiz.title = translate(_('IMPORTED: ${quiz}', mapping={'quiz': title}), |
1336 | + context=self.request) |
1337 | + return quiz |
1338 | + |
1339 | + def createQuizItem(self, data): |
1340 | + item = QuizItem() |
1341 | + item.body = data['body'] |
1342 | + item.solution = data['solution'] |
1343 | + item.type_ = self.types_vocabulary.getTermByToken(data['type_']).value |
1344 | + if data['choices']: |
1345 | + choices = [] |
1346 | + for choice in data['choices'].split(choices_row_separator): |
1347 | + body, correct = choice.split(choices_column_separator) |
1348 | + item_choice = Choice() |
1349 | + item_choice.body = body |
1350 | + try: |
1351 | + item_choice.correct = bool(int(correct)) |
1352 | + except (ValueError,): |
1353 | + item_choice.correct = False |
1354 | + choices.append(item_choice) |
1355 | + item.choices = choices |
1356 | + return item |
1357 | + |
1358 | + def addItem(self, quiz, item, data): |
1359 | + app = ISchoolToolApplication(None) |
1360 | + container = interfaces.IQuizItemContainer(app) |
1361 | + chooser = INameChooser(container) |
1362 | + name = chooser.chooseName('', item) |
1363 | + container[name] = item |
1364 | + item.quizzes.add(quiz) |
1365 | + quiz.order.append(item.__name__) |
1366 | + notify(ObjectCreatedEvent(item)) |
1367 | + |
1368 | + def process(self): |
1369 | + sh = self.sheet |
1370 | + num_errors = len(self.errors) |
1371 | + title = self.getRequiredTextFromCell(sh, 0, 1) |
1372 | + if num_errors < len(self.errors): |
1373 | + return |
1374 | + quiz = self.createQuiz(title) |
1375 | + chooser = INameChooser(self.context) |
1376 | + name = chooser.chooseName('', quiz) |
1377 | + self.context[name] = quiz |
1378 | + person = IPerson(self.request.principal) |
1379 | + quiz.editors.add(removeSecurityProxy(person)) |
1380 | + notify(ObjectCreatedEvent(quiz)) |
1381 | + for row in range(3, sh.nrows): |
1382 | + num_errors = len(self.errors) |
1383 | + data = {} |
1384 | + data['body'] = self.getRequiredTextFromCell(sh, row, 0) |
1385 | + data['type_'] = self.getRequiredTextFromCell(sh, row, 1) |
1386 | + data['choices'] = self.getTextFromCell(sh, row, 2) |
1387 | + data['solution'] = self.getTextFromCell(sh, row, 3) |
1388 | + if num_errors < len(self.errors): |
1389 | + continue |
1390 | + item = self.createQuizItem(data) |
1391 | + self.addItem(quiz, item, data) |
1392 | +>>>>>>> MERGE-SOURCE |
1393 | |
1394 | |
1395 | class QuizImportView(FlourishMegaImporter): |
1396 | |
1397 | - content_template = ViewPageTemplateFile('templates/quiz_import.pt') |
1398 | - |
1399 | - @property |
1400 | - def importers(self): |
1401 | - return [ |
1402 | - QuizImporter, |
1403 | - ] |
1404 | - |
1405 | - def nextURL(self): |
1406 | - return self.request.get('camefrom', |
1407 | - absoluteURL(self.context, self.request)) |
1408 | + content_template = ViewPageTemplateFile('templates/quiz_import.pt') |
1409 | + |
1410 | + @property |
1411 | + def importers(self): |
1412 | + return [ |
1413 | + QuizImporter, |
1414 | + ] |
1415 | + |
1416 | + def nextURL(self): |
1417 | + return self.request.get('camefrom', |
1418 | + absoluteURL(self.context, self.request)) |
1419 | |
1420 | |
1421 | class QuizExportView(MegaExporter): |
1422 | |
1423 | - def __call__(self): |
1424 | - wb = xlwt.Workbook() |
1425 | - self.export_items(wb) |
1426 | - datafile = StringIO() |
1427 | - wb.save(datafile) |
1428 | - data = datafile.getvalue() |
1429 | - self.setUpHeaders(data) |
1430 | - return data |
1431 | - |
1432 | - def export_items(self, wb): |
1433 | - ws = wb.add_sheet('QuizItems') |
1434 | - self.write_header(ws, 0, 0, 'Quiz Title') |
1435 | - self.write(ws, 0, 1, self.context.title) |
1436 | - self.print_table(self.format_items(), ws, offset=2) |
1437 | - |
1438 | - def print_table(self, table, ws, offset=0): |
1439 | - for x, row in enumerate(table): |
1440 | - for y, cell in enumerate(row): |
1441 | - self.write(ws, x + offset, y, cell.data, **cell.style) |
1442 | - return len(table) |
1443 | - |
1444 | - def format_items(self): |
1445 | - factory = getUtility(IVocabularyFactory, |
1446 | - name='schooltool.quiz.quiz_item_types') |
1447 | - vocabulary = factory(None) |
1448 | - |
1449 | - def type_getter(item): |
1450 | - return vocabulary.getTerm(item.type_).token |
1451 | - |
1452 | - def choices_getter(item): |
1453 | - result = [] |
1454 | - if item.choices is not None: |
1455 | - for choice in item.choices: |
1456 | - result.append(self.format_choice(choice)) |
1457 | - return choices_row_separator.join(result) |
1458 | - |
1459 | - fields = [ |
1460 | - ('Body', Text, attrgetter('body')), |
1461 | - ('Type', Text, type_getter), |
1462 | - ('Choices', Text, choices_getter), |
1463 | - ('Solution', Text, attrgetter('solution')), |
1464 | - ] |
1465 | - return self.format_table(fields, self.context) |
1466 | - |
1467 | - def format_choice(self, choice): |
1468 | - correct = [0, 1][choice.correct] |
1469 | - return choices_column_separator.join([choice.body, str(correct)]) |
1470 | + def __call__(self): |
1471 | + wb = xlwt.Workbook() |
1472 | + self.export_items(wb) |
1473 | + datafile = StringIO() |
1474 | + wb.save(datafile) |
1475 | + data = datafile.getvalue() |
1476 | + self.setUpHeaders(data) |
1477 | + return data |
1478 | + |
1479 | + def export_items(self, wb): |
1480 | + ws = wb.add_sheet('QuizItems') |
1481 | + self.write_header(ws, 0, 0, 'Quiz Title') |
1482 | + self.write(ws, 0, 1, self.context.title) |
1483 | + self.print_table(self.format_items(), ws, offset=2) |
1484 | + |
1485 | + def print_table(self, table, ws, offset=0): |
1486 | + for x, row in enumerate(table): |
1487 | + for y, cell in enumerate(row): |
1488 | + self.write(ws, x + offset, y, cell.data, **cell.style) |
1489 | + return len(table) |
1490 | + |
1491 | + def format_items(self): |
1492 | + factory = getUtility(IVocabularyFactory, |
1493 | + name='schooltool.quiz.quiz_item_types') |
1494 | + vocabulary = factory(None) |
1495 | + |
1496 | + def type_getter(item): |
1497 | + return vocabulary.getTerm(item.type_).token |
1498 | + |
1499 | + def choices_getter(item): |
1500 | + result = [] |
1501 | + if item.choices is not None: |
1502 | + for choice in item.choices: |
1503 | + result.append(self.format_choice(choice)) |
1504 | + return choices_row_separator.join(result) |
1505 | + |
1506 | + fields = [ |
1507 | + ('Body', Text, attrgetter('body')), |
1508 | + ('Type', Text, type_getter), |
1509 | + ('Choices', Text, choices_getter), |
1510 | + ('Solution', Text, attrgetter('solution')), |
1511 | + ] |
1512 | + return self.format_table(fields, self.context) |
1513 | + |
1514 | + def format_choice(self, choice): |
1515 | + correct = [0, 1][choice.correct] |
1516 | + return choices_column_separator.join([choice.body, str(correct)]) |
1517 | |
1518 | |
1519 | class QuizHelpLinks(flourish.page.RefineLinksViewlet, PersonSections): |
1520 | |
1521 | - @Lazy |
1522 | - def person(self): |
1523 | - return IPerson(self.request.principal, None) |
1524 | - |
1525 | - @property |
1526 | - def enabled(self): |
1527 | - if self.person is not None: |
1528 | - editable_quizzes = getRelatedObjects(self.person, |
1529 | - relationships.URIQuiz, |
1530 | - relationships.URIQuizEditors) |
1531 | - return self.instructor_sections or editable_quizzes |
1532 | - |
1533 | - def render(self, *args, **kw): |
1534 | - if self.enabled: |
1535 | - return super(QuizHelpLinks, self).render(*args, **kw) |
1536 | - return '' |
1537 | + @Lazy |
1538 | + def person(self): |
1539 | + return IPerson(self.request.principal, None) |
1540 | + |
1541 | + @property |
1542 | + def enabled(self): |
1543 | + if self.person is not None: |
1544 | + editable_quizzes = getRelatedObjects(self.person, |
1545 | + relationships.URIQuiz, |
1546 | + relationships.URIQuizEditors) |
1547 | + return self.instructor_sections or editable_quizzes |
1548 | + |
1549 | + def render(self, *args, **kw): |
1550 | + if self.enabled: |
1551 | + return super(QuizHelpLinks, self).render(*args, **kw) |
1552 | + return '' |
1553 | |
1554 | |
1555 | class QuizEditingHelpViewlet(flourish.page.LinkIdViewlet): |
1556 | |
1557 | - template = InlineViewPageTemplate(''' |
1558 | - <a tal:attributes="href view/url; |
1559 | - onclick view/script" |
1560 | - tal:content="view/title" /> |
1561 | - ''') |
1562 | - |
1563 | - @property |
1564 | - def enabled(self): |
1565 | - app = ISchoolToolApplication(None) |
1566 | - preferences = interfaces.IApplicationPreferences(app) |
1567 | - return preferences.mathjax_help is not None |
1568 | - |
1569 | - @property |
1570 | - def script(self): |
1571 | - url = self.url |
1572 | - name = self.html_id |
1573 | - params = { |
1574 | - 'width': 640, |
1575 | - 'height': 480, |
1576 | - 'resizable': 'yes', |
1577 | - 'scrollbars': 'yes', |
1578 | - 'toolbar': 'no', |
1579 | - 'location': 'no', |
1580 | - } |
1581 | - return "window.open('%s', '%s', '%s'); return false" % ( |
1582 | - url, name, ','.join(['%s=%s' % (k, v) for k, v in params.items()])) |
1583 | + template = InlineViewPageTemplate(''' |
1584 | + <a tal:attributes="href view/url; |
1585 | + onclick view/script" |
1586 | + tal:content="view/title" /> |
1587 | + ''') |
1588 | + |
1589 | + @property |
1590 | + def enabled(self): |
1591 | + app = ISchoolToolApplication(None) |
1592 | + preferences = interfaces.IApplicationPreferences(app) |
1593 | + return preferences.mathjax_help is not None |
1594 | + |
1595 | + @property |
1596 | + def script(self): |
1597 | + url = self.url |
1598 | + name = self.html_id |
1599 | + params = { |
1600 | + 'width': 640, |
1601 | + 'height': 480, |
1602 | + 'resizable': 'yes', |
1603 | + 'scrollbars': 'yes', |
1604 | + 'toolbar': 'no', |
1605 | + 'location': 'no', |
1606 | + } |
1607 | + return "window.open('%s', '%s', '%s'); return false" % ( |
1608 | + url, name, ','.join(['%s=%s' % (k, v) for k, v in params.items()])) |
1609 | |
1610 | |
1611 | class QuizEditingHelpView(flourish.page.Page): |
1612 | |
1613 | +<<<<<<< TREE |
1614 | implements(interfaces.ISchoolToolQuizView, |
1615 | interfaces.IMathJaxView) |
1616 | |
1617 | @@ -1032,3 +1380,25 @@ |
1618 | MathJax.Hub.Queue(['Typeset', MathJax.Hub]); |
1619 | </script> |
1620 | ''') |
1621 | +======= |
1622 | + implements(interfaces.ISchoolToolQuizView, |
1623 | + interfaces.IMathJaxView) |
1624 | + |
1625 | + template = InlineViewPageTemplate(''' |
1626 | + <tal:block content="structure view/mathjax_help" /> |
1627 | + ''') |
1628 | + |
1629 | + def mathjax_help(self): |
1630 | + app = ISchoolToolApplication(None) |
1631 | + preferences = interfaces.IApplicationPreferences(app) |
1632 | + return self.render_rst(preferences, |
1633 | + interfaces.IApplicationPreferences, |
1634 | + 'mathjax_help') |
1635 | + |
1636 | + def render_rst(self, value, iface, field_name): |
1637 | + renderer = flourish.form.Form(value, self.request) |
1638 | + renderer.mode = DISPLAY_MODE |
1639 | + renderer.fields = field.Fields(iface[field_name]) |
1640 | + renderer.update() |
1641 | + return renderer.widgets[field_name].render() |
1642 | +>>>>>>> MERGE-SOURCE |
1643 | |
1644 | === modified file 'src/schooltool/quiz/browser/quizitem.py' |
1645 | --- src/schooltool/quiz/browser/quizitem.py 2013-01-24 06:54:46 +0000 |
1646 | +++ src/schooltool/quiz/browser/quizitem.py 2013-03-04 22:05:25 +0000 |
1647 | @@ -59,6 +59,7 @@ |
1648 | from schooltool.quiz.browser.widget import LabeledCheckBoxFieldWidget |
1649 | from schooltool.quiz.quizitem import Choice |
1650 | from schooltool.quiz.quizitem import QuizItem |
1651 | +from schooltool.quiz.utils import Validator |
1652 | |
1653 | registerFactoryAdapter(interfaces.IChoice, Choice) |
1654 | |
1655 | @@ -66,6 +67,10 @@ |
1656 | class OneCorrectChoiceRequired(ValidationError): |
1657 | |
1658 | __doc__ = _('One correct choice is required.') |
1659 | + |
1660 | +class ValidSolutionRequired(ValidationError): |
1661 | + |
1662 | + __doc__ = _('A syntactically valid solution is required.') |
1663 | |
1664 | |
1665 | class SolutionRequiredError(ValidationError): |
1666 | @@ -83,7 +88,8 @@ |
1667 | |
1668 | def validate(self, value): |
1669 | super(ChoicesValidator, self).validate(value) |
1670 | - if not self.view.is_open_question(): |
1671 | + if not (self.view.is_open_question() \ |
1672 | + or self.view.is_rational_question()): |
1673 | if not value: |
1674 | raise OneCorrectChoiceRequired(value) |
1675 | correct = [] |
1676 | @@ -98,8 +104,12 @@ |
1677 | |
1678 | def validate(self, value): |
1679 | super(SolutionValidator, self).validate(value) |
1680 | - if self.view.is_open_question() and not value: |
1681 | + if (self.view.is_open_question() \ |
1682 | + or self.view.is_rational_question()) \ |
1683 | + and not value: |
1684 | raise SolutionRequiredError(value) |
1685 | + if self.view.is_rational_question() and not Validator(value): |
1686 | + raise ValidSolutionRequired(value) |
1687 | |
1688 | |
1689 | class ChoiceSubForm(ObjectSubForm): |
1690 | @@ -118,6 +128,7 @@ |
1691 | container_class = 'container widecontainer' |
1692 | content_template = ViewPageTemplateFile('templates/form.pt') |
1693 | _open_question_token = 'open' |
1694 | + _rational_question_token = 'rational' |
1695 | |
1696 | @property |
1697 | def fields(self): |
1698 | @@ -146,7 +157,7 @@ |
1699 | result = {} |
1700 | for widget in self.widgets.values(): |
1701 | result.update(self.encodeWidget(widget)) |
1702 | - if self.is_open_question(): |
1703 | + if self.is_open_question() or self.is_rational_question(): |
1704 | solution_widget = self.widgets['solution'] |
1705 | result.update(self.encodeWidget(solution_widget)) |
1706 | else: |
1707 | @@ -162,6 +173,10 @@ |
1708 | def is_open_question(self): |
1709 | item_type = getattr(self.widgets['type_'], 'value', []) |
1710 | return self._open_question_token in item_type |
1711 | + |
1712 | + def is_rational_question(self): |
1713 | + item_type = getattr(self.widgets['type_'], 'value', []) |
1714 | + return self._rational_question_token in item_type |
1715 | |
1716 | def setQuiz(self, item): |
1717 | quiz = removeSecurityProxy(self.context) |
1718 | @@ -419,6 +434,7 @@ |
1719 | 'selection': _('Selection Question'), |
1720 | 'multiple': _('Multiple Selection Question'), |
1721 | 'open': _('Open Question'), |
1722 | + 'rational': _('Rational Question'), |
1723 | } |
1724 | return result[self.type_token] |
1725 | |
1726 | @@ -431,6 +447,7 @@ |
1727 | 'selection': _('Selection Question'), |
1728 | 'multiple': _('Multiple Selection Question'), |
1729 | 'open': _('Open Question'), |
1730 | + 'rational': _('Rational Question'), |
1731 | } |
1732 | return result[self.type_token] |
1733 | |
1734 | @@ -556,7 +573,13 @@ |
1735 | |
1736 | def is_open_question(self): |
1737 | return self.view.type_token == 'open' |
1738 | + |
1739 | + def is_rational_question(self): |
1740 | + return self.view.type_token == 'rational' |
1741 | |
1742 | + def has_open_field(self): |
1743 | + return self.is_rational_question() or self.is_open_question() |
1744 | + |
1745 | def has_skill(self): |
1746 | return None not in (self.view.item.course, self.view.item.skill) |
1747 | |
1748 | |
1749 | === modified file 'src/schooltool/quiz/browser/stests/quiz_deployment.txt' |
1750 | --- src/schooltool/quiz/browser/stests/quiz_deployment.txt 2012-10-07 06:00:01 +0000 |
1751 | +++ src/schooltool/quiz/browser/stests/quiz_deployment.txt 2013-03-04 22:05:25 +0000 |
1752 | @@ -64,6 +64,11 @@ |
1753 | ... ('Eric Raymond', False), |
1754 | ... ] |
1755 | >>> fill_question_form(teacher, 'Selection', body, choices) |
1756 | + >>> teacher.query.id('form-buttons-submitadd').click() |
1757 | + |
1758 | + >>> body = 'What is the first prime number higher than 5?' |
1759 | + >>> solution = '3 + 4' |
1760 | + >>> fill_question_form(teacher, 'Rational', body, solution=solution) |
1761 | >>> teacher.query.id('form-buttons-submit').click() |
1762 | |
1763 | >>> print_quiz_item_bodies(teacher) |
1764 | @@ -82,6 +87,11 @@ |
1765 | Who is Python's original author? |
1766 | </p> |
1767 | </div> |
1768 | + <div ...> |
1769 | + <p> |
1770 | + What is the first prime number higher than 5? |
1771 | + </p> |
1772 | + </div> |
1773 | |
1774 | Deploy the quiz to one of the instructor's sections: |
1775 | |
1776 | @@ -162,6 +172,9 @@ |
1777 | >>> sel = '#form-widgets-QuizItem-3-row .radio-widget[value="4"]' |
1778 | >>> camila.query.css(sel).click() |
1779 | |
1780 | + >>> answer_4 = '9/3 + 4' |
1781 | + >>> camila.query.id('form-widgets-QuizItem-4').ui.set_value(answer_4) |
1782 | + |
1783 | >>> camila.query.id('form-buttons-submit').click() |
1784 | |
1785 | >>> sel = '.taken-quizzes-table tbody tr' |
1786 | @@ -189,6 +202,9 @@ |
1787 | >>> sel = '#form-widgets-QuizItem-3-row .radio-widget[value="3"]' |
1788 | >>> mario.query.css(sel).click() |
1789 | |
1790 | + >>> answer_4 = '6' |
1791 | + >>> mario.query.id('form-widgets-QuizItem-4').ui.set_value(answer_4) |
1792 | + |
1793 | >>> mario.query.id('form-buttons-submit').click() |
1794 | |
1795 | >>> sel = '.taken-quizzes-table tbody tr' |
1796 | @@ -216,6 +232,9 @@ |
1797 | >>> sel = '#form-widgets-QuizItem-3-row .radio-widget[value="3"]' |
1798 | >>> liliana.query.css(sel).click() |
1799 | |
1800 | + >>> answer_4 = '7' |
1801 | + >>> liliana.query.id('form-widgets-QuizItem-4').ui.set_value(answer_4) |
1802 | + |
1803 | >>> liliana.query.id('form-buttons-submit').click() |
1804 | |
1805 | >>> sel = '.taken-quizzes-table tbody tr' |
1806 | @@ -238,7 +257,7 @@ |
1807 | The instructor updates the grades: |
1808 | |
1809 | >>> teacher.query.link('Update Grades').click() |
1810 | - |
1811 | + |
1812 | The instructor goes to see the gradebook: |
1813 | |
1814 | >>> teacher.ui.section.go('2012', '2012', 'Programming A1') |
1815 | @@ -252,9 +271,9 @@ |
1816 | | Name | FreeS | Total | Ave. | |
1817 | | | 100 | | | |
1818 | +------------------+-------+-------+-------+ |
1819 | - | Cerna, Camila | 94.4 | 94.4 | 94.4% | |
1820 | - | Tejada, Mario | 16.7 | 16.7 | 16.7% | |
1821 | - | Vividor, Liliana | 61.1 | 61.1 | 61.1% | |
1822 | + | Cerna, Camila | 95.8 | 95.8 | 95.8% | |
1823 | + | Tejada, Mario | 12.5 | 12.5 | 12.5% | |
1824 | + | Vividor, Liliana | 70.8 | 70.8 | 70.8% | |
1825 | +------------------+-------+-------+-------+ |
1826 | |
1827 | Camila checks her grade: |
1828 | @@ -264,7 +283,7 @@ |
1829 | >>> for row in camila.query_all.css(sel): |
1830 | ... columns = row.query_all.tag('td') |
1831 | ... print ' | '.join([column.text for column in columns]) |
1832 | - Free Software Exam | Programming A1 | | 94% |
1833 | + Free Software Exam | Programming A1 | | 96% |
1834 | |
1835 | Mario checks his grade: |
1836 | |
1837 | @@ -273,7 +292,7 @@ |
1838 | >>> for row in mario.query_all.css(sel): |
1839 | ... columns = row.query_all.tag('td') |
1840 | ... print ' | '.join([column.text for column in columns]) |
1841 | - Free Software Exam | Programming A1 | | 17% |
1842 | + Free Software Exam | Programming A1 | | 12% |
1843 | |
1844 | Liliana checks her grade: |
1845 | |
1846 | @@ -282,4 +301,4 @@ |
1847 | >>> for row in liliana.query_all.css(sel): |
1848 | ... columns = row.query_all.tag('td') |
1849 | ... print ' | '.join([column.text for column in columns]) |
1850 | - Free Software Exam | Programming A1 | | 61% |
1851 | + Free Software Exam | Programming A1 | | 71% |
1852 | |
1853 | === modified file 'src/schooltool/quiz/browser/stests/quiz_duplicate.txt' |
1854 | --- src/schooltool/quiz/browser/stests/quiz_duplicate.txt 2012-12-06 18:26:06 +0000 |
1855 | +++ src/schooltool/quiz/browser/stests/quiz_duplicate.txt 2013-03-04 22:05:25 +0000 |
1856 | @@ -59,6 +59,11 @@ |
1857 | ... ('Eric Raymond', False), |
1858 | ... ] |
1859 | >>> fill_question_form(teacher, 'Selection', body, choices) |
1860 | + >>> teacher.query.id('form-buttons-submitadd').click() |
1861 | + |
1862 | + >>> body = 'What is the first prime number higher than 5?' |
1863 | + >>> solution = '3 + 4' |
1864 | + >>> fill_question_form(teacher, 'Rational', body, solution=solution) |
1865 | >>> teacher.query.id('form-buttons-submit').click() |
1866 | |
1867 | Check the quiz: |
1868 | @@ -68,7 +73,7 @@ |
1869 | >>> for row in teacher.query_all.css(sel): |
1870 | ... columns = row.query_all.tag('td') |
1871 | ... print ' | '.join([column.text for column in columns]) |
1872 | - Free/Open Source Software Quiz | 3 | Elkner, Jeffrey | ... |
1873 | + Free/Open Source Software Quiz | 4 | Elkner, Jeffrey | ... |
1874 | |
1875 | Duplicate the quiz: |
1876 | |
1877 | @@ -81,8 +86,8 @@ |
1878 | >>> for row in teacher.query_all.css(sel): |
1879 | ... columns = row.query_all.tag('td') |
1880 | ... print ' | '.join([column.text for column in columns]) |
1881 | - DUPLICATED: Free/Open Source Software Quiz | 3 | Elkner, Jeffrey | ... |
1882 | - Free/Open Source Software Quiz | 3 | Elkner, Jeffrey | ... |
1883 | + DUPLICATED: Free/Open Source Software Quiz | 4 | Elkner, Jeffrey | ... |
1884 | + Free/Open Source Software Quiz | 4 | Elkner, Jeffrey | ... |
1885 | |
1886 | >>> teacher.query.link('DUPLICATED: Free/Open Source Software Quiz').click() |
1887 | >>> print_quiz_item_bodies(teacher) |
1888 | @@ -101,8 +106,14 @@ |
1889 | Who is Python's original author? |
1890 | </p> |
1891 | </div> |
1892 | + <div ...> |
1893 | + <p> |
1894 | + What is the first prime number higher than 5? |
1895 | + </p> |
1896 | + </div> |
1897 | |
1898 | >>> print_quiz_item_types(teacher) |
1899 | Open |
1900 | Multiple Selection |
1901 | - Selection |
1902 | + Selection |
1903 | + Rational |
1904 | |
1905 | === modified file 'src/schooltool/quiz/browser/stests/quiz_export.txt' |
1906 | --- src/schooltool/quiz/browser/stests/quiz_export.txt 2012-10-10 23:31:01 +0000 |
1907 | +++ src/schooltool/quiz/browser/stests/quiz_export.txt 2013-03-04 22:05:25 +0000 |
1908 | @@ -59,6 +59,11 @@ |
1909 | ... ('Eric Raymond', False), |
1910 | ... ] |
1911 | >>> fill_question_form(teacher, 'Selection', body, choices) |
1912 | + >>> teacher.query.id('form-buttons-submitadd').click() |
1913 | + |
1914 | + >>> body = 'What is the first prime number higher than 5?' |
1915 | + >>> solution = '3 + 4' |
1916 | + >>> fill_question_form(teacher, 'Rational', body, solution=solution) |
1917 | >>> teacher.query.id('form-buttons-submit').click() |
1918 | |
1919 | Export the quiz: |
1920 | |
1921 | === modified file 'src/schooltool/quiz/browser/stests/quiz_import.txt' |
1922 | --- src/schooltool/quiz/browser/stests/quiz_import.txt 2012-12-06 18:26:06 +0000 |
1923 | +++ src/schooltool/quiz/browser/stests/quiz_import.txt 2013-03-04 22:05:25 +0000 |
1924 | @@ -35,7 +35,7 @@ |
1925 | >>> for row in teacher.query_all.css(sel): |
1926 | ... columns = row.query_all.tag('td') |
1927 | ... print ' | '.join([column.text for column in columns]) |
1928 | - IMPORTED: Prime Factors 1 | 9 | Elkner, Jeffrey | ... |
1929 | + IMPORTED: Prime Factors 1 | 11 | Elkner, Jeffrey | ... |
1930 | |
1931 | >>> teacher.query.link('IMPORTED: Prime Factors 1').click() |
1932 | >>> print teacher.query.css('.page .header h1').text |
1933 | @@ -69,3 +69,9 @@ |
1934 | <div class="textarea-widget ..."> |
1935 | ... |
1936 | </div> |
1937 | + <div class="textarea-widget ..."> |
1938 | + ... |
1939 | + </div> |
1940 | + <div class="textarea-widget ..."> |
1941 | + ... |
1942 | + </div> |
1943 | |
1944 | === modified file 'src/schooltool/quiz/browser/stests/quiz_import.xls' |
1945 | Binary files src/schooltool/quiz/browser/stests/quiz_import.xls 2012-10-10 23:31:01 +0000 and src/schooltool/quiz/browser/stests/quiz_import.xls 2013-03-04 22:05:25 +0000 differ |
1946 | === modified file 'src/schooltool/quiz/browser/stests/quiz_management.txt' |
1947 | --- src/schooltool/quiz/browser/stests/quiz_management.txt 2012-09-06 17:35:42 +0000 |
1948 | +++ src/schooltool/quiz/browser/stests/quiz_management.txt 2013-03-04 22:05:25 +0000 |
1949 | @@ -138,6 +138,7 @@ |
1950 | Selection |
1951 | Multiple Selection |
1952 | Open |
1953 | + Rational |
1954 | |
1955 | With Selection being selected by default: |
1956 | |
1957 | @@ -319,8 +320,72 @@ |
1958 | Which of the following are free software? |
1959 | </p> |
1960 | </div> |
1961 | - |
1962 | -Let's add one more question: |
1963 | + |
1964 | +And this time let's try a rational question. We'll try two examples |
1965 | +of invalid solutions, to test the validator. |
1966 | + |
1967 | + >>> teacher.query.link('Question').click() |
1968 | + >>> sel = '//span[@class="label" and text()="Rational"]' |
1969 | + >>> teacher.query.xpath(sel).click() |
1970 | + |
1971 | +It has a solution, not choices |
1972 | + |
1973 | + >>> teacher.query.id('form-widgets-solution').is_displayed() |
1974 | + True |
1975 | + |
1976 | +An empty solution gives an error: |
1977 | + |
1978 | + >>> body = 'What is the first prime number higher than 5?' |
1979 | + >>> solution = '' |
1980 | + >>> fill_question_form(teacher, 'Rational', body, solution=solution) |
1981 | + >>> teacher.query.id('form-buttons-submit').click() |
1982 | + |
1983 | + >>> sel = '#form-widgets-solution-row .error div.error' |
1984 | + >>> print teacher.query.css(sel).text |
1985 | + Required input is missing. |
1986 | + |
1987 | +So does an unformatted one: |
1988 | + |
1989 | + >>> body = 'What is the first prime number higher than 5?' |
1990 | + >>> solution = '3+4' |
1991 | + >>> fill_question_form(teacher, 'Rational', body, solution=solution) |
1992 | + >>> teacher.query.id('form-buttons-submit').click() |
1993 | + |
1994 | + >>> sel = '#form-widgets-solution-row .error div.error' |
1995 | + >>> print teacher.query.css(sel).text |
1996 | + A syntactically valid solution is required. |
1997 | + |
1998 | +So let's close this, open a new one, and submit that: |
1999 | + |
2000 | + >>> teacher.query.id('form-buttons-cancel').click() |
2001 | + >>> teacher.query.link('Question').click() |
2002 | + |
2003 | + >>> body = 'What is the first prime number higher than 5?' |
2004 | + >>> solution = '3 + 4' |
2005 | + >>> fill_question_form(teacher, 'Rational', body, solution=solution) |
2006 | + >>> teacher.query.id('form-buttons-submit').click() |
2007 | + |
2008 | +Now the rational question should also be visible: |
2009 | + |
2010 | + >>> print_quiz_item_bodies(teacher) |
2011 | + <div ...> |
2012 | + <p> |
2013 | + What is the meaning of RMS? |
2014 | + </p> |
2015 | + </div> |
2016 | + <div ...> |
2017 | + <p> |
2018 | + Which of the following are free software? |
2019 | + </p> |
2020 | + </div> |
2021 | + <div ...> |
2022 | + <p> |
2023 | + What is the first prime number higher than 5? |
2024 | + </p> |
2025 | + </div> |
2026 | + |
2027 | + |
2028 | +And just one more: |
2029 | |
2030 | >>> teacher.query.link('Question').click() |
2031 | >>> body = "Who is Python's original author?" |
2032 | @@ -378,6 +443,11 @@ |
2033 | </div> |
2034 | <div ...> |
2035 | <p> |
2036 | + What is the first prime number higher than 5? |
2037 | + </p> |
2038 | + </div> |
2039 | + <div ...> |
2040 | + <p> |
2041 | Who is Python's original author? |
2042 | </p> |
2043 | </div> |
2044 | @@ -385,9 +455,10 @@ |
2045 | >>> print_quiz_item_types(teacher) |
2046 | Open |
2047 | Multiple Selection |
2048 | + Rational |
2049 | Selection |
2050 | |
2051 | -Now, let's visit eache of these questions, to see their content. To do |
2052 | +Now, let's visit each of these questions, to see their content. To do |
2053 | so, we click the link in the No. column: |
2054 | |
2055 | >>> teacher.query.link('1').click() |
2056 | @@ -488,6 +559,34 @@ |
2057 | >>> teacher.query.link('Done').click() |
2058 | |
2059 | >>> teacher.query.link('3').click() |
2060 | + |
2061 | + >>> print teacher.query.css('.page .header h1').text |
2062 | + Free/Open Source Software Quiz |
2063 | + |
2064 | + >>> print teacher.query.css('.page .header h2').text |
2065 | + Rational Question |
2066 | + |
2067 | + >>> sel = '.quiz-item-body .body .restructuredtext-widget' |
2068 | + >>> for content in teacher.query_all.css(sel): |
2069 | + ... print content |
2070 | + <div ...> |
2071 | + <p> |
2072 | + What is the first prime number higher than 5? |
2073 | + </p> |
2074 | + </div> |
2075 | + |
2076 | + >>> sel = '.quiz-item-body .solution .restructuredtext-widget' |
2077 | + >>> for content in teacher.query_all.css(sel): |
2078 | + ... print content |
2079 | + <div ...> |
2080 | + <p> |
2081 | + 3 + 4 |
2082 | + </p> |
2083 | + </div> |
2084 | + |
2085 | + >>> teacher.query.link('Done').click() |
2086 | + |
2087 | + >>> teacher.query.link('4').click() |
2088 | |
2089 | >>> print teacher.query.css('.page .header h1').text |
2090 | Free/Open Source Software Quiz |
2091 | @@ -581,6 +680,11 @@ |
2092 | </div> |
2093 | <div ...> |
2094 | <p> |
2095 | + What is the first prime number higher than 5? |
2096 | + </p> |
2097 | + </div> |
2098 | + <div ...> |
2099 | + <p> |
2100 | Who is Python's original author? |
2101 | </p> |
2102 | </div> |
2103 | @@ -593,6 +697,7 @@ |
2104 | >>> print_quiz_item_types(teacher) |
2105 | Open |
2106 | Multiple Selection |
2107 | + Rational |
2108 | Selection |
2109 | Multiple Selection |
2110 | |
2111 | @@ -618,6 +723,11 @@ |
2112 | </div> |
2113 | <div ...> |
2114 | <p> |
2115 | + What is the first prime number higher than 5? |
2116 | + </p> |
2117 | + </div> |
2118 | + <div ...> |
2119 | + <p> |
2120 | Who is Python's original author? |
2121 | </p> |
2122 | </div> |
2123 | @@ -629,6 +739,7 @@ |
2124 | |
2125 | >>> print_quiz_item_types(teacher) |
2126 | Multiple Selection |
2127 | + Rational |
2128 | Selection |
2129 | Multiple Selection |
2130 | |
2131 | |
2132 | === modified file 'src/schooltool/quiz/browser/stests/student_answers.txt' |
2133 | --- src/schooltool/quiz/browser/stests/student_answers.txt 2012-10-07 17:40:13 +0000 |
2134 | +++ src/schooltool/quiz/browser/stests/student_answers.txt 2013-03-04 22:05:25 +0000 |
2135 | @@ -64,6 +64,11 @@ |
2136 | ... ('Eric Raymond', False), |
2137 | ... ] |
2138 | >>> fill_question_form(teacher, 'Selection', body, choices) |
2139 | + >>> teacher.query.id('form-buttons-submitadd').click() |
2140 | + |
2141 | + >>> body = 'What is the first prime number higher than 5?' |
2142 | + >>> solution = '3 + 4' |
2143 | + >>> fill_question_form(teacher, 'Rational', body, solution=solution) |
2144 | >>> teacher.query.id('form-buttons-submit').click() |
2145 | |
2146 | Deploy the quiz to one of the instructor's sections: |
2147 | @@ -123,6 +128,9 @@ |
2148 | |
2149 | >>> sel = '#form-widgets-QuizItem-3-row .radio-widget[value="4"]' |
2150 | >>> camila.query.css(sel).click() |
2151 | + |
2152 | + >>> answer_4 = '9/3 + 4' |
2153 | + >>> camila.query.id('form-widgets-QuizItem-4').ui.set_value(answer_4) |
2154 | |
2155 | >>> camila.query.id('form-buttons-submit').click() |
2156 | |
2157 | @@ -151,6 +159,9 @@ |
2158 | >>> sel = '#form-widgets-QuizItem-3-row .radio-widget[value="3"]' |
2159 | >>> mario.query.css(sel).click() |
2160 | |
2161 | + >>> answer_4 = '6' |
2162 | + >>> mario.query.id('form-widgets-QuizItem-4').ui.set_value(answer_4) |
2163 | + |
2164 | >>> mario.query.id('form-buttons-submit').click() |
2165 | |
2166 | >>> sel = '.taken-quizzes-table tbody tr' |
2167 | @@ -178,6 +189,9 @@ |
2168 | >>> sel = '#form-widgets-QuizItem-3-row .radio-widget[value="3"]' |
2169 | >>> liliana.query.css(sel).click() |
2170 | |
2171 | + >>> answer_4 = '7' |
2172 | + >>> liliana.query.id('form-widgets-QuizItem-4').ui.set_value(answer_4) |
2173 | + |
2174 | >>> liliana.query.id('form-buttons-submit').click() |
2175 | |
2176 | >>> sel = '.taken-quizzes-table tbody tr' |
2177 | @@ -262,3 +276,18 @@ |
2178 | <table ...> |
2179 | </table> |
2180 | </div> |
2181 | + Question 4 |
2182 | + <div ...> |
2183 | + ... |
2184 | + <p> |
2185 | + What is the first prime number higher than 5? |
2186 | + </p> |
2187 | + ... |
2188 | + </div> |
2189 | + <div ...> |
2190 | + ... |
2191 | + <pre> |
2192 | + 9/3 + 4 |
2193 | + </pre> |
2194 | + ... |
2195 | + </div> |
2196 | |
2197 | === modified file 'src/schooltool/quiz/browser/stests/test_selenium.py' |
2198 | --- src/schooltool/quiz/browser/stests/test_selenium.py 2012-12-02 07:10:04 +0000 |
2199 | +++ src/schooltool/quiz/browser/stests/test_selenium.py 2013-03-04 22:05:25 +0000 |
2200 | @@ -56,7 +56,7 @@ |
2201 | sel = '//span[@class="label" and text()="%s"]' % type_ |
2202 | browser.query.xpath(sel).click() |
2203 | browser.query.id('form-widgets-body').ui.set_value(body) |
2204 | - if type_ == 'Open': |
2205 | + if type_ in ('Open','Rational'): |
2206 | sel = 'form-widgets-solution' |
2207 | browser.query.id(sel).ui.set_value(solution) |
2208 | else: |
2209 | |
2210 | === modified file 'src/schooltool/quiz/browser/templates/answers.pt' |
2211 | --- src/schooltool/quiz/browser/templates/answers.pt 2012-11-29 15:28:05 +0000 |
2212 | +++ src/schooltool/quiz/browser/templates/answers.pt 2013-03-04 22:05:25 +0000 |
2213 | @@ -7,7 +7,7 @@ |
2214 | <div class="evaluation" |
2215 | tal:define="item evaluation/requirement; |
2216 | answer evaluation/answer; |
2217 | - is_open_question python:view.is_open(item)"> |
2218 | + has_open_view python:view.has_open_view(item);"> |
2219 | <h3 class="number" i18n:translate="" |
2220 | style="border-top: 1px solid #000; border-bottom: 1px solid #000; padding: 4px 0;"> |
2221 | Question |
2222 | @@ -22,13 +22,13 @@ |
2223 | Your answer |
2224 | </h3> |
2225 | <div class="answer"> |
2226 | - <div tal:condition="is_open_question" |
2227 | + <div tal:condition="has_open_view" |
2228 | tal:define="score_info python:view.score_info(evaluation)" |
2229 | tal:attributes="class score_info/css"> |
2230 | <h3 class="score-type" tal:content="score_info/label" /> |
2231 | <div tal:content="structure python:view.render_pre(answer)" /> |
2232 | </div> |
2233 | - <table class="data" tal:condition="not:is_open_question"> |
2234 | + <table class="data" tal:condition="not:has_open_view"> |
2235 | <thead> |
2236 | <th i18n:translate="">Choice</th> |
2237 | <th i18n:translate="">Correct Answers</th> |
2238 | @@ -61,7 +61,7 @@ |
2239 | </table> |
2240 | </div> |
2241 | <tal:block define="graded python:view.is_graded(evaluation)" |
2242 | - condition="python:is_open_question and graded"> |
2243 | + condition="python:has_open_view and graded"> |
2244 | <h3 i18n:translate=""> |
2245 | Solution |
2246 | </h3> |
2247 | |
2248 | === modified file 'src/schooltool/quiz/browser/templates/quiz_item_body.pt' |
2249 | --- src/schooltool/quiz/browser/templates/quiz_item_body.pt 2012-10-25 20:31:25 +0000 |
2250 | +++ src/schooltool/quiz/browser/templates/quiz_item_body.pt 2013-03-04 22:05:25 +0000 |
2251 | @@ -1,5 +1,5 @@ |
2252 | <tal:block i18n:domain="schooltool.quiz" |
2253 | - define="is_open_question view/is_open_question"> |
2254 | + define="has_open_field view/has_open_field"> |
2255 | <table class="quiz-item-body"> |
2256 | <thead> |
2257 | <tr> |
2258 | @@ -12,7 +12,8 @@ |
2259 | </tr> |
2260 | </tbody> |
2261 | </table> |
2262 | - <table class="data quiz-item-body" tal:condition="is_open_question"> |
2263 | + <table class="data quiz-item-body" |
2264 | + tal:condition="has_open_field"> |
2265 | <thead> |
2266 | <tr> |
2267 | <th i18n:translate="">Solution</th> |
2268 | @@ -25,7 +26,8 @@ |
2269 | </tbody> |
2270 | </table> |
2271 | <table |
2272 | - class="data quiz-item-body" tal:condition="not:is_open_question" |
2273 | + class="data quiz-item-body" |
2274 | + tal:condition="not:has_open_field" |
2275 | tal:define="flourish nocall:context/++resource++schooltool.quiz.flourish; |
2276 | correct_icon flourish/correct-icon.png; |
2277 | error_icon flourish/error-icon.png;"> |
2278 | |
2279 | === modified file 'src/schooltool/quiz/browser/templates/quiz_item_form_script.pt' |
2280 | --- src/schooltool/quiz/browser/templates/quiz_item_form_script.pt 2012-10-25 20:31:25 +0000 |
2281 | +++ src/schooltool/quiz/browser/templates/quiz_item_form_script.pt 2013-03-04 22:05:25 +0000 |
2282 | @@ -2,6 +2,7 @@ |
2283 | <tal:script |
2284 | tal:define="widgets view/view/widgets; |
2285 | open_question_token view/view/_open_question_token; |
2286 | + rational_question_token view/view/_rational_question_token; |
2287 | course nocall:widgets/course|nothing; |
2288 | skill nocall:widgets/skill|nothing;" |
2289 | tal:replace="structure scriptlocal: |
2290 | @@ -9,6 +10,7 @@ |
2291 | choices_id widgets/choices/id; |
2292 | solution_id widgets/solution/id; |
2293 | open_question_token; |
2294 | + rational_question_token; |
2295 | course_id course/id|nothing; |
2296 | course_novalue_token course/noValueToken|nothing; |
2297 | skill_id skill/id|nothing; |
2298 | @@ -24,6 +26,7 @@ |
2299 | skill_id = ST.local.skill_id, |
2300 | skill_novalue_token = ST.local.skill_novalue_token, |
2301 | open_question_token = ST.local.open_question_token; |
2302 | + rational_question_token = ST.local.rational_question_token; |
2303 | return function(e) { |
2304 | var item_types = $('input[id^="'+type_id+'"]'), |
2305 | course, |
2306 | @@ -66,7 +69,8 @@ |
2307 | var type_value = $(this).attr('value'), |
2308 | choices_row = $('.row[id^="'+choices_id+'"]'), |
2309 | solution_row = $('.row[id^="'+solution_id+'"]'); |
2310 | - if (type_value == open_question_token) { |
2311 | + if ((type_value == open_question_token) |
2312 | + || (type_value == rational_question_token)){ |
2313 | solution_row.show(); |
2314 | choices_row.hide(); |
2315 | } else { |
2316 | |
2317 | === modified file 'src/schooltool/quiz/quiz.py' |
2318 | --- src/schooltool/quiz/quiz.py 2013-01-10 07:34:28 +0000 |
2319 | +++ src/schooltool/quiz/quiz.py 2013-03-04 22:05:25 +0000 |
2320 | @@ -78,7 +78,12 @@ |
2321 | name = chooser.chooseName('', copy) |
2322 | item_container[name] = copy |
2323 | result.items.add(copy) |
2324 | +<<<<<<< TREE |
2325 | result.order.append(name) |
2326 | +======= |
2327 | + for item_id in self.order: |
2328 | + result.order.append(item_id) #formerly copy.order... |
2329 | +>>>>>>> MERGE-SOURCE |
2330 | return result |
2331 | |
2332 | def updateOrder(self, item_id, position): |
2333 | |
2334 | === modified file 'src/schooltool/quiz/quizitem.py' |
2335 | --- src/schooltool/quiz/quizitem.py 2013-01-17 08:12:40 +0000 |
2336 | +++ src/schooltool/quiz/quizitem.py 2013-03-04 22:05:25 +0000 |
2337 | @@ -102,6 +102,7 @@ |
2338 | ('selection', _('Selection')), |
2339 | ('multiple', _('Multiple Selection')), |
2340 | ('open', _('Open')), |
2341 | + ('rational', _('Rational')), |
2342 | ] |
2343 | terms = [SimpleTerm(item, token=token, title=item) |
2344 | for token, item in items] |
2345 | |
2346 | === added file 'src/schooltool/quiz/rational.py' |
2347 | --- src/schooltool/quiz/rational.py 1970-01-01 00:00:00 +0000 |
2348 | +++ src/schooltool/quiz/rational.py 2013-03-04 22:05:25 +0000 |
2349 | @@ -0,0 +1,125 @@ |
2350 | +'''rational.py: Module to do rational arithmetic. |
2351 | + |
2352 | + For full documentation, see http://www.nmt.edu/tcc/help/lang/python/examples/rational/. |
2353 | + Exports: |
2354 | + gcd ( a, b ): |
2355 | + [ a and b are integers -> |
2356 | + return the greatest common divisor of a and b ] |
2357 | + Rational ( a, b ): |
2358 | + [ (a is a nonnegative integer) and |
2359 | + (b is a positive integer) -> |
2360 | + return a new Rational instance with |
2361 | + numerator a and denominator b ] |
2362 | + .n: [ the numerator ] |
2363 | + .d: [ the denominator ] |
2364 | + .__add__(self, other): |
2365 | + [ other is a Rational instance -> |
2366 | + return the sum of self and other as a Rational instance ] |
2367 | + .__sub__(self, other): |
2368 | + [ other is a Rational instance -> |
2369 | + return the difference of self and other as a Rational |
2370 | + instance ] |
2371 | + .__mul__(self, other): |
2372 | + [ other is a Rational instance -> |
2373 | + return the product of self and other as a Rational |
2374 | + instance ] |
2375 | + .__div__(self, other): |
2376 | + [ other is a Rational instance -> |
2377 | + return the quotient of self and other as a Rational |
2378 | + instance ] |
2379 | + .__exp__(self, integer): |
2380 | + [ integer is an Integer instance -> |
2381 | + return self exponentiated to other as a Rational |
2382 | + instance ] |
2383 | + .__str__(self): |
2384 | + [ return a string representation of self ] |
2385 | + .__float__(self): |
2386 | + [ return a float approximation of self ] |
2387 | + .mixed(self): |
2388 | + [ return a string representation of self as a mixed |
2389 | + fraction ] |
2390 | +''' |
2391 | +def gcd ( a, b ): |
2392 | + '''Greatest common divisor function; Euclid's algorithm. |
2393 | + |
2394 | + [ a and b are integers -> |
2395 | + return the greatest common divisor of a and b ] |
2396 | + ''' |
2397 | + if b == 0: |
2398 | + return a |
2399 | + else: |
2400 | + return gcd(b, a%b) |
2401 | + |
2402 | +class Rational: |
2403 | + """An instance represents a rational number. |
2404 | + """ |
2405 | + def __init__ ( self, a, b ): |
2406 | + """Constructor for Rational. |
2407 | + """ |
2408 | + if b == 0: |
2409 | + raise ZeroDivisionError, ( "Denominator of a rational " |
2410 | + "may not be zero." ) |
2411 | + else: |
2412 | + g = gcd ( a, b ) |
2413 | + self.n = a / g |
2414 | + self.d = b / g |
2415 | + def __add__ ( self, other ): |
2416 | + """Add two rational numbers. |
2417 | + """ |
2418 | + return Rational ( self.n * other.d + other.n * self.d, |
2419 | + self.d * other.d ) |
2420 | + def __sub__ ( self, other ): |
2421 | + """Return self minus other. |
2422 | + """ |
2423 | + return Rational ( self.n * other.d - other.n * self.d, |
2424 | + self.d * other.d ) |
2425 | + def __mul__ ( self, other ): |
2426 | + """Implement multiplication. |
2427 | + """ |
2428 | + return Rational ( self.n * other.n, self.d * other.d ) |
2429 | + def __div__ ( self, other ): |
2430 | + """Implement division. |
2431 | + """ |
2432 | + return Rational ( self.n * other.d, self.d * other.n ) |
2433 | + def __pow__ ( self, integer): |
2434 | + """Implement division. |
2435 | + """ |
2436 | + return Rational ( self.n ** integer, self.d ** integer ) |
2437 | + def __str__ ( self ): |
2438 | + '''Display self as a string. |
2439 | + ''' |
2440 | + return "%d/%d" % ( self.n, self.d ) |
2441 | + def __repr__ (self): |
2442 | + """ Works |
2443 | + """ |
2444 | + return str(self) |
2445 | + def __eq__ (self, other): |
2446 | + """ test for equality |
2447 | + """ |
2448 | + if type(other) != type(self): |
2449 | + return False |
2450 | + return (self.d == other.d and self.n == other.n) |
2451 | + def __float__ ( self ): |
2452 | + """Implement the float() conversion function. |
2453 | + """ |
2454 | + return float ( self.n ) / float ( self.d ) |
2455 | + def mixed ( self ): |
2456 | + """Render self as a mixed fraction in string form. |
2457 | + """ |
2458 | + #-- 1 -- |
2459 | + # [ whole := self.n / self.d, truncated |
2460 | + # n2 := self.n % self.d ] |
2461 | + whole, n2 = divmod ( self.n, self.d ) |
2462 | + #-- 2 -- |
2463 | + # [ if self.d == 1 -> |
2464 | + # return str(self.n) |
2465 | + # else if whole == zero -> |
2466 | + # return str(n2)+"/"+str(self.d) |
2467 | + # else -> |
2468 | + # return str(whole)+" and "+str(n2)+"/"+str(self.d) ] |
2469 | + if self.d == 1: |
2470 | + return str(self.n) |
2471 | + elif whole == 0: |
2472 | + return "%s/%s" % (n2, self.d) |
2473 | + else: |
2474 | + return "%s and %s/%s" % (whole, n2, self.d) |
2475 | |
2476 | === added file 'src/schooltool/quiz/utils.py' |
2477 | --- src/schooltool/quiz/utils.py 1970-01-01 00:00:00 +0000 |
2478 | +++ src/schooltool/quiz/utils.py 2013-03-04 22:05:25 +0000 |
2479 | @@ -0,0 +1,135 @@ |
2480 | +# |
2481 | +# SchoolTool - common information systems platform for school administration |
2482 | +# Copyright (c) 2012 Shuttleworth Foundation, |
2483 | +# |
2484 | +# This program is free software; you can redistribute it and/or modify |
2485 | +# it under the terms of the GNU General Public License as published by |
2486 | +# the Free Software Foundation; either version 2 of the License, or |
2487 | +# (at your option) any later version. |
2488 | +# |
2489 | +# This program is distributed in the hope that it will be useful, |
2490 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2491 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2492 | +# GNU General Public License for more details. |
2493 | +# |
2494 | +# You should have received a copy of the GNU General Public License along |
2495 | +# with this program; if not, write to the Free Software Foundation, Inc., |
2496 | +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
2497 | +# |
2498 | +""" |
2499 | +SchoolTool Quiz utility functions. |
2500 | +""" |
2501 | + |
2502 | +from cgi import escape |
2503 | +from docutils.core import publish_parts |
2504 | + |
2505 | +from schooltool.quiz import QuizMessage as _ |
2506 | +from schooltool.quiz.rst import QuizHTMLWriter |
2507 | + |
2508 | +import rational |
2509 | + |
2510 | +# XXX: customize and use IApplicationPreferences for this |
2511 | +DATETIME_FORMAT = _('YYYY-MM-DD HH:MM:SS') |
2512 | +DATETIME_INPUT_FORMAT = '%Y-%m-%d %H:%M:%S' |
2513 | +DATE_FORMAT = _('YYYY-MM-DD') |
2514 | +DATE_INPUT_FORMAT = '%Y-%m-%d' |
2515 | + |
2516 | + |
2517 | +def render_rst(rst): |
2518 | + # XXX: sanitize before rendering |
2519 | + return publish_parts(rst, writer=QuizHTMLWriter())['body'] |
2520 | + |
2521 | + |
2522 | +def render_pre(text): |
2523 | + return '<pre>%s</pre>' % escape(text) |
2524 | + |
2525 | + |
2526 | +def render_datetime(value): |
2527 | + return value.strftime(DATETIME_INPUT_FORMAT) |
2528 | + |
2529 | + |
2530 | +def render_date(value): |
2531 | + return value.strftime(DATE_INPUT_FORMAT) |
2532 | + |
2533 | + |
2534 | +def render_bool(value): |
2535 | + return [_('No'), _('Yes')][value] |
2536 | + |
2537 | + |
2538 | +def format_percentage(number): |
2539 | + return '%.0f%%' % number |
2540 | + |
2541 | + |
2542 | +def Validator(instring): |
2543 | + instring = str(instring) |
2544 | + inlist = instring.split(' ') |
2545 | + operators = ['+','-','*','/','**'] |
2546 | + validated = [] |
2547 | + while '' in inlist: |
2548 | + inlist.remove('') |
2549 | + for substring in inlist: #Convert to list |
2550 | + if substring in operators: |
2551 | + validated.append(substring) |
2552 | + elif CharacterValidation(substring): |
2553 | + #Convert to Rational |
2554 | + if "/" in substring: |
2555 | + parts = substring.split("/") |
2556 | + if len(parts) != 2: |
2557 | + return False |
2558 | + A = rational.Rational(int(parts[0]),int(parts[1])) |
2559 | + validated.append(A) |
2560 | + else: |
2561 | + A = rational.Rational(int(substring),1) |
2562 | + validated.append(A) |
2563 | + else: |
2564 | + return False |
2565 | + dup = 0 |
2566 | + for item in validated: #Check for order |
2567 | + if dup == 0: |
2568 | + if type(item).__name__ != "instance": |
2569 | + return False |
2570 | + dup = 1 |
2571 | + elif dup == 1: |
2572 | + if not item in operators: |
2573 | + return False |
2574 | + dup = 0 |
2575 | + if validated[-1] in operators: |
2576 | + return False |
2577 | + for index in range(0,len(validated)-1): |
2578 | + if validated[index] == "**": |
2579 | + if validated[index+1].d != 1: |
2580 | + return False |
2581 | + return validated |
2582 | + |
2583 | + |
2584 | +def CharacterValidation(string): |
2585 | + valid = "1234567890/" |
2586 | + for char in string: |
2587 | + if not char in valid: |
2588 | + return False |
2589 | + return True |
2590 | + |
2591 | +def Evaluator(validated): |
2592 | + if len(validated) == 1: |
2593 | + return validated[0] |
2594 | + for index in range(0,(len(validated)-1)): |
2595 | + if type(validated[index]) == type("") and validated[index] == "**": |
2596 | + result = validated[index-1] ** validated[index+1].n |
2597 | + return Evaluator(validated[:index-1]+[result]+validated[index+2:]) |
2598 | + for index in range(0,(len(validated)-1)): |
2599 | + if type(validated[index]) == type("") and validated[index] == "*": |
2600 | + result = validated[index-1] * validated[index+1] |
2601 | + return Evaluator(validated[:index-1]+[result]+validated[index+2:]) |
2602 | + elif type(validated[index]) == type("") and validated[index] == "/": |
2603 | + result = validated[index-1] / validated[index+1] |
2604 | + return Evaluator(validated[:index-1]+[result]+validated[index+2:]) |
2605 | + for index in range(0,(len(validated)-1)): |
2606 | + if type(validated[index]) == type("") and validated[index] == "+": |
2607 | + result = validated[index-1] + validated[index+1] |
2608 | + return Evaluator(validated[:index-1]+[result]+validated[index+2:]) |
2609 | + elif type(validated[index]) == type("") and validated[index] == "-": |
2610 | + result = validated[index-1] - validated[index+1] |
2611 | + return Evaluator(validated[:index-1]+[result]+validated[index+2:]) |
2612 | + |
2613 | +def RationalValidator(studentstring, teacherstring): |
2614 | + return Evaluator(Validator(studentstring)) == Evaluator(Validator(teacherstring)) |
2615 | |
2616 | === removed file 'src/schooltool/quiz/utils.py' |
2617 | --- src/schooltool/quiz/utils.py 2012-09-19 20:06:18 +0000 |
2618 | +++ src/schooltool/quiz/utils.py 1970-01-01 00:00:00 +0000 |
2619 | @@ -1,59 +0,0 @@ |
2620 | -# |
2621 | -# SchoolTool - common information systems platform for school administration |
2622 | -# Copyright (c) 2012 Shuttleworth Foundation, |
2623 | -# |
2624 | -# This program is free software; you can redistribute it and/or modify |
2625 | -# it under the terms of the GNU General Public License as published by |
2626 | -# the Free Software Foundation; either version 2 of the License, or |
2627 | -# (at your option) any later version. |
2628 | -# |
2629 | -# This program is distributed in the hope that it will be useful, |
2630 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2631 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2632 | -# GNU General Public License for more details. |
2633 | -# |
2634 | -# You should have received a copy of the GNU General Public License along |
2635 | -# with this program; if not, write to the Free Software Foundation, Inc., |
2636 | -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
2637 | -# |
2638 | -""" |
2639 | -SchoolTool Quiz utility functions. |
2640 | -""" |
2641 | - |
2642 | -from cgi import escape |
2643 | -from docutils.core import publish_parts |
2644 | - |
2645 | -from schooltool.quiz import QuizMessage as _ |
2646 | -from schooltool.quiz.rst import QuizHTMLWriter |
2647 | - |
2648 | - |
2649 | -# XXX: customize and use IApplicationPreferences for this |
2650 | -DATETIME_FORMAT = _('YYYY-MM-DD HH:MM:SS') |
2651 | -DATETIME_INPUT_FORMAT = '%Y-%m-%d %H:%M:%S' |
2652 | -DATE_FORMAT = _('YYYY-MM-DD') |
2653 | -DATE_INPUT_FORMAT = '%Y-%m-%d' |
2654 | - |
2655 | - |
2656 | -def render_rst(rst): |
2657 | - # XXX: sanitize before rendering |
2658 | - return publish_parts(rst, writer=QuizHTMLWriter())['body'] |
2659 | - |
2660 | - |
2661 | -def render_pre(text): |
2662 | - return '<pre>%s</pre>' % escape(text) |
2663 | - |
2664 | - |
2665 | -def render_datetime(value): |
2666 | - return value.strftime(DATETIME_INPUT_FORMAT) |
2667 | - |
2668 | - |
2669 | -def render_date(value): |
2670 | - return value.strftime(DATE_INPUT_FORMAT) |
2671 | - |
2672 | - |
2673 | -def render_bool(value): |
2674 | - return [_('No'), _('Yes')][value] |
2675 | - |
2676 | - |
2677 | -def format_percentage(number): |
2678 | - return '%.0f%%' % number |