Merge lp:~ellimistd/schooltool.quiz/SchoolToolQuizDev into lp:schooltool.quiz

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
Reviewer Review Type Date Requested Status
Douglas Cerna Pending
Review via email: mp+149160@code.launchpad.net

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

Unmerged revisions

39. By David Reich <email address hidden>

changed tests to account for rational questions

38. By David Reich <email address hidden>

Added rational questions, with automatic validation (No tests yet)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/schooltool/quiz/browser/deployedquiz.py'
--- src/schooltool/quiz/browser/deployedquiz.py 2013-01-23 23:42:02 +0000
+++ src/schooltool/quiz/browser/deployedquiz.py 2013-03-04 22:05:25 +0000
@@ -78,6 +78,7 @@
78from schooltool.quiz.utils import render_rst, render_pre78from schooltool.quiz.utils import render_rst, render_pre
79from schooltool.quiz.browser.quiz import SolutionDoneLinkViewlet79from schooltool.quiz.browser.quiz import SolutionDoneLinkViewlet
80from schooltool.quiz.utils import render_date80from schooltool.quiz.utils import render_date
81from schooltool.quiz.utils import RationalValidator
8182
8283
83def getStartTime(adapter):84def getStartTime(adapter):
@@ -941,8 +942,10 @@
941 return self.createOpenQuestionField(item)942 return self.createOpenQuestionField(item)
942 elif term.token == 'multiple':943 elif term.token == 'multiple':
943 return self.createMultipleSelectionField(item)944 return self.createMultipleSelectionField(item)
944 else:945 elif term.token == 'selection':
945 return self.createSelectionField(item)946 return self.createSelectionField(item)
947 else:
948 return self.createRationalQuestionField(item)
946949
947 def createOpenQuestionField(self, item):950 def createOpenQuestionField(self, item):
948 name = item.__name__.encode('utf-8')951 name = item.__name__.encode('utf-8')
@@ -953,6 +956,16 @@
953 datamanager.DictionaryField(self.values, schema_field)956 datamanager.DictionaryField(self.values, schema_field)
954 result = field.Fields(schema_field)957 result = field.Fields(schema_field)
955 return result958 return result
959
960 def createRationalQuestionField(self, item):
961 name = item.__name__.encode('utf-8')
962 schema_field = interfaces.ReStructuredText(
963 __name__=name,
964 description=item.body)
965 self.values[name] = None
966 datamanager.DictionaryField(self.values, schema_field)
967 result = field.Fields(schema_field)
968 return result
956969
957 def choicesVocabulary(self, item, shuffle=False):970 def choicesVocabulary(self, item, shuffle=False):
958 terms = []971 terms = []
@@ -1013,8 +1026,10 @@
1013 grade = UNSCORED1026 grade = UNSCORED
1014 elif term.token == 'multiple':1027 elif term.token == 'multiple':
1015 grade = self.calculateMultipleSelectionGrade(item, answer)1028 grade = self.calculateMultipleSelectionGrade(item, answer)
1016 else:1029 elif term.token == "selection":
1017 grade = self.calculateSelectionGrade(item, answer)1030 grade = self.calculateSelectionGrade(item, answer)
1031 else:
1032 grade = self.calculateRationalGrade(item, answer)
1018 evaluation = AnsweredEvaluation(item, answer, grade)1033 evaluation = AnsweredEvaluation(item, answer, grade)
1019 evaluations.addEvaluation(evaluation)1034 evaluations.addEvaluation(evaluation)
1020 self.request.response.redirect(self.nextURL())1035 self.request.response.redirect(self.nextURL())
@@ -1041,6 +1056,11 @@
1041 if choice.correct and choice in given_choices:1056 if choice.correct and choice in given_choices:
1042 return 1001057 return 100
1043 return 01058 return 0
1059
1060 def calculateRationalGrade(self, item, answer):
1061 if RationalValidator(item.solution, answer):
1062 return 100
1063 return 0
10441064
1045 @button.buttonAndHandler(_('Cancel'), name='cancel')1065 @button.buttonAndHandler(_('Cancel'), name='cancel')
1046 def handle_cancel(self, action):1066 def handle_cancel(self, action):
@@ -1132,6 +1152,13 @@
1132 term = self.types_vocabulary.getTerm(item.type_)1152 term = self.types_vocabulary.getTerm(item.type_)
1133 return term.token == 'open'1153 return term.token == 'open'
11341154
1155 def is_rational(self, item):
1156 term = self.types_vocabulary.getTerm(item.type_)
1157 return term.token == 'rational'
1158
1159 def has_open_view(self, item):
1160 return self.is_rational(item) or self.is_open(item)
1161
1135 def is_graded(self, evaluation):1162 def is_graded(self, evaluation):
1136 return evaluation and evaluation.value not in (UNSCORED, None)1163 return evaluation and evaluation.value not in (UNSCORED, None)
11371164
11381165
=== modified file 'src/schooltool/quiz/browser/quiz.py'
--- src/schooltool/quiz/browser/quiz.py 2013-01-24 10:01:26 +0000
+++ src/schooltool/quiz/browser/quiz.py 2013-03-04 22:05:25 +0000
@@ -73,56 +73,72 @@
73choices_column_separator = '**|**'73choices_column_separator = '**|**'
7474
7575
76<<<<<<< TREE
77=======
78class PersonSections(object):
79
80 person = None
81
82 @Lazy
83 def learner_sections(self):
84 return list(ILearner(self.person).sections())
85
86 @Lazy
87 def instructor_sections(self):
88 return list(IInstructor(self.person).sections())
89
90
91>>>>>>> MERGE-SOURCE
76class QuizContainerAbsoluteURLAdapter(BrowserView):92class QuizContainerAbsoluteURLAdapter(BrowserView):
7793 adapts(interfaces.IQuizContainer, IBrowserRequest)
78 adapts(interfaces.IQuizContainer, IBrowserRequest)94 implements(IAbsoluteURL)
79 implements(IAbsoluteURL)95
8096 def __str__(self):
81 def __str__(self):97 app = ISchoolToolApplication(None)
82 app = ISchoolToolApplication(None)98 return '%s/quizzes' % absoluteURL(app, self.request)
83 return '%s/quizzes' % absoluteURL(app, self.request)99
84100 __call__ = __str__
85 __call__ = __str__
86101
87102
88class QuizzesLink(flourish.page.LinkViewlet, PersonSections):103class QuizzesLink(flourish.page.LinkViewlet, PersonSections):
89104
90 startup_view_name = 'quizzes.html'105 startup_view_name = 'quizzes.html'
91106
92 @Lazy107 @Lazy
93 def person(self):108 def person(self):
94 return IPerson(self.request.principal, None)109 return IPerson(self.request.principal, None)
95110
96 @property111 @property
97 def enabled(self):112 def enabled(self):
98 if self.person is None:113 if self.person is None:
99 return False114 return False
100 editable_quizzes = getRelatedObjects(115 editable_quizzes = getRelatedObjects(
101 self.person, relationships.URIQuiz, relationships.URIQuizEditors)116 self.person, relationships.URIQuiz, relationships.URIQuizEditors)
102 return self.instructor_sections or \117 return self.instructor_sections or \
103 self.learner_sections or \118 self.learner_sections or \
104 editable_quizzes119 editable_quizzes
105120
106 @property121 @property
107 def url(self):122 def url(self):
108 if self.person is None:123 if self.person is None:
109 return ''124 return ''
110 return '%s/%s' % (absoluteURL(self.person, self.request),125 return '%s/%s' % (absoluteURL(self.person, self.request),
111 self.startup_view_name)126 self.startup_view_name)
112127
113128
114def taken(person, deployed):129def taken(person, deployed):
115 result = False130 result = False
116 evaluations = IEvaluations(removeSecurityProxy(person))131 evaluations = IEvaluations(removeSecurityProxy(person))
117 for item in deployed:132 for item in deployed:
118 evaluation = evaluations.get(item, None)133 evaluation = evaluations.get(item, None)
119 if evaluation is not None:134 if evaluation is not None:
120 return True135 return True
121 return result136 return result
122137
123138
124class PersonQuizzesView(flourish.page.Page, PersonSections):139class PersonQuizzesView(flourish.page.Page, PersonSections):
125140
141<<<<<<< TREE
126 implements(interfaces.ISchoolToolQuizView,142 implements(interfaces.ISchoolToolQuizView,
127 interfaces.IPersonQuizView)143 interfaces.IPersonQuizView)
128144
@@ -176,163 +192,228 @@
176192
177 def taken(self, deployed):193 def taken(self, deployed):
178 return taken(self.context, deployed)194 return taken(self.context, deployed)
195=======
196 implements(interfaces.ISchoolToolQuizView)
197
198 container_class = 'container widecontainer'
199
200 @Lazy
201 def person(self):
202 return self.context
203
204 @property
205 def quizContainer(self):
206 app = ISchoolToolApplication(None)
207 return interfaces.IQuizContainer(app)
208
209 @Lazy
210 def editable_quizzes(self):
211 result = []
212 for quiz in self.quizContainer.values():
213 if self.person in quiz.editors:
214 result.append(quiz)
215 return result
216
217 @Lazy
218 def deployed_quizzes(self):
219 result = []
220 for section in self.instructor_sections:
221 container = interfaces.IDeployedQuizContainer(section)
222 for deployed in container.values():
223 result.append(deployed)
224 return result
225
226 @Lazy
227 def takeable_quizzes(self):
228 result = []
229 for section in self.learner_sections:
230 container = interfaces.IDeployedQuizContainer(section)
231 for deployed in container.values():
232 if not self.taken(deployed):
233 result.append(deployed)
234 return result
235
236 @Lazy
237 def taken_quizzes(self):
238 result = []
239 for section in self.learner_sections:
240 container = interfaces.IDeployedQuizContainer(section)
241 for deployed in container.values():
242 if self.taken(deployed):
243 result.append(deployed)
244 return result
245
246 def taken(self, deployed):
247 return taken(self.context, deployed)
248>>>>>>> MERGE-SOURCE
179249
180250
181class QuizContainerView(flourish.page.Page):251class QuizContainerView(flourish.page.Page):
182252
183 def update(self):253 def update(self):
184 print list(self.context.keys())254 print list(self.context.keys())
185255
186256
187class QuizContainerAddLinks(flourish.page.RefineLinksViewlet):257class QuizContainerAddLinks(flourish.page.RefineLinksViewlet):
188258
189 pass259 pass
190260
191261
192class QuizContainerActionsLinks(flourish.page.RefineLinksViewlet):262class QuizContainerActionsLinks(flourish.page.RefineLinksViewlet):
193263
194 pass264 pass
195265
196266
197class QuizAddLinks(flourish.page.RefineLinksViewlet):267class QuizAddLinks(flourish.page.RefineLinksViewlet):
198268
199 pass269 pass
200270
201271
202class QuizViewLinks(flourish.page.RefineLinksViewlet):272class QuizViewLinks(flourish.page.RefineLinksViewlet):
203273
204 pass274 pass
205275
206276
207class QuizActionLinks(flourish.page.RefineLinksViewlet):277class QuizActionLinks(flourish.page.RefineLinksViewlet):
208278
209 pass279 pass
210280
211281
212class QuizAddLink(flourish.page.LinkViewlet):282class QuizAddLink(flourish.page.LinkViewlet):
213283
214 @property284 @property
215 def url(self):285 def url(self):
216 app = ISchoolToolApplication(None)286 app = ISchoolToolApplication(None)
217 container = interfaces.IQuizContainer(app)287 container = interfaces.IQuizContainer(app)
218 return '%s/add.html' % absoluteURL(container, self.request)288 return '%s/add.html' % absoluteURL(container, self.request)
219289
220290
221class QuizDeployLinkViewlet(flourish.page.LinkViewlet):291class QuizDeployLinkViewlet(flourish.page.LinkViewlet):
222292
223 @property293 @property
224 def enabled(self):294 def enabled(self):
225 return len(list(self.context))295 return len(list(self.context))
226296
227297
228class PersonQuizAddLink(QuizAddLink, PersonSections):298class PersonQuizAddLink(QuizAddLink, PersonSections):
229299
230 @Lazy300 @Lazy
231 def person(self):301 def person(self):
232 return IPerson(self.request.principal, None)302 return IPerson(self.request.principal, None)
233303
234 @property304 @property
235 def enabled(self):305 def enabled(self):
236 if self.person is not None:306 if self.person is not None:
237 return self.instructor_sections307 return self.instructor_sections
238 return False308 return False
239309
240 @property310 @property
241 def url(self):311 def url(self):
242 app = ISchoolToolApplication(None)312 app = ISchoolToolApplication(None)
243 container = interfaces.IQuizContainer(app)313 container = interfaces.IQuizContainer(app)
244 camefrom = '%s/quizzes.html' % absoluteURL(self.context, self.request)314 camefrom = '%s/quizzes.html' % absoluteURL(self.context, self.request)
245 return '%s/add.html?camefrom=%s' % (315 return '%s/add.html?camefrom=%s' % (
246 absoluteURL(container, self.request), camefrom)316 absoluteURL(container, self.request), camefrom)
247317
248318
249class PersonQuizImportLink(QuizAddLink, PersonSections):319class PersonQuizImportLink(QuizAddLink, PersonSections):
250320
251 @Lazy321 @Lazy
252 def person(self):322 def person(self):
253 return IPerson(self.request.principal, None)323 return IPerson(self.request.principal, None)
254324
255 @property325 @property
256 def enabled(self):326 def enabled(self):
257 if self.person is not None:327 if self.person is not None:
258 return self.instructor_sections328 return self.instructor_sections
259 return False329 return False
260330
261 @property331 @property
262 def url(self):332 def url(self):
263 app = ISchoolToolApplication(None)333 app = ISchoolToolApplication(None)
264 container = interfaces.IQuizContainer(app)334 container = interfaces.IQuizContainer(app)
265 camefrom = '%s/quizzes.html' % absoluteURL(self.context, self.request)335 camefrom = '%s/quizzes.html' % absoluteURL(self.context, self.request)
266 return '%s/import.html?camefrom=%s' % (336 return '%s/import.html?camefrom=%s' % (
267 absoluteURL(container, self.request), camefrom)337 absoluteURL(container, self.request), camefrom)
268338
269339
270class QuizSolutionLinkViewlet(flourish.page.LinkViewlet):340class QuizSolutionLinkViewlet(flourish.page.LinkViewlet):
271341
272 @property342 @property
273 def enabled(self):343 def enabled(self):
274 return len(list(self.context))344 return len(list(self.context))
275345
276346
277class QuizView(flourish.page.Page):347class QuizView(flourish.page.Page):
278348
279 implements(interfaces.ISchoolToolQuizView,349 implements(interfaces.ISchoolToolQuizView,
280 interfaces.IMathJaxView)350 interfaces.IMathJaxView)
281351
282 container_class = 'container widecontainer'352 container_class = 'container widecontainer'
283353
284 @property354 @property
285 def title(self):355 def title(self):
286 return self.context.title356 return self.context.title
287357
288 @property358 @property
289 def can_modify(self):359 def can_modify(self):
290 person = IPerson(self.request.principal, None)360 person = IPerson(self.request.principal, None)
291 return person in self.context.editors361 return person in self.context.editors
292362
293 @Lazy363 @Lazy
294 def quiz_items(self):364 def quiz_items(self):
295 return list(self.context)365 return list(self.context)
296366
297 @Lazy367 @Lazy
298 def deployed_quizzes(self):368 def deployed_quizzes(self):
299 return list(self.context.deployed)369 return list(self.context.deployed)
300370
301371
302class EditableQuizzesViewlet(flourish.viewlet.Viewlet, PersonSections):372class EditableQuizzesViewlet(flourish.viewlet.Viewlet, PersonSections):
303373
304 template = InlineViewPageTemplate('''374 template = InlineViewPageTemplate('''
305 <tal:block i18n:domain="schooltool.quiz"375 <tal:block i18n:domain="schooltool.quiz"
306 tal:define="quizzes view/view/editable_quizzes">376 tal:define="quizzes view/view/editable_quizzes">
307 <h3 i18n:translate="">Editable Quizzes</h3>377 <h3 i18n:translate="">Editable Quizzes</h3>
308 <div tal:condition="quizzes"378 <div tal:condition="quizzes"
309 tal:define="ajax nocall:view/view/providers/ajax"379 tal:define="ajax nocall:view/view/providers/ajax"
310 tal:content="structure ajax/view/context/editable_quizzes_table"380 tal:content="structure ajax/view/context/editable_quizzes_table"
311 />381 />
312 <p i18n:translate="" tal:condition="not:quizzes">382 <p i18n:translate="" tal:condition="not:quizzes">
313 There are no quizzes you can edit yet.383 There are no quizzes you can edit yet.
314 </p>384 </p>
315 </tal:block>385 </tal:block>
316 ''')386 ''')
317387
318 @Lazy388 @Lazy
319 def person(self):389 def person(self):
320 return self.context390 return self.context
321391
322 @property392 @property
323 def enabled(self):393 def enabled(self):
324 editable_quizzes = getRelatedObjects(self.person,394 editable_quizzes = getRelatedObjects(self.person,
325 relationships.URIQuiz,395 relationships.URIQuiz,
326 relationships.URIQuizEditors)396 relationships.URIQuizEditors)
327 return self.instructor_sections or editable_quizzes397 return self.instructor_sections or editable_quizzes
328398
329 def render(self, *args, **kw):399 def render(self, *args, **kw):
330 if self.enabled:400 if self.enabled:
331 return self.template(*args, **kw)401 return self.template(*args, **kw)
332402
333403
404<<<<<<< TREE
405=======
406def quiz_editors_formatter(editors, quiz, formatter):
407 collator = ICollator(formatter.request.locale)
408 result = sorted([person.title for person in editors],
409 cmp=collator.cmp)
410 return '<br />'.join(result)
411
412
413>>>>>>> MERGE-SOURCE
334class EditableQuizzesTable(table.ajax.Table):414class EditableQuizzesTable(table.ajax.Table):
335415
416<<<<<<< TREE
336 def items(self):417 def items(self):
337 return self.view.editable_quizzes418 return self.view.editable_quizzes
338419
@@ -371,259 +452,333 @@
371 batch_size=self.batch_size,452 batch_size=self.batch_size,
372 prefix=self.__name__,453 prefix=self.__name__,
373 css_classes={'table': 'data editable-quizzes-table'})454 css_classes={'table': 'data editable-quizzes-table'})
455=======
456 table_formatter = AJAXCSSTableFormatter
457
458 def items(self):
459 return self.view.editable_quizzes
460
461 def columns(self):
462 default = super(EditableQuizzesTable, self).columns()
463 questions = GetterColumn(
464 name='questions',
465 title=_('Questions'),
466 getter=lambda quiz, formatter: len(list(quiz)))
467 editors = GetterColumn(
468 name='editors',
469 title=_('Editors'),
470 getter=lambda quiz, formatter: quiz.editors,
471 cell_formatter=quiz_editors_formatter)
472 created = GetterColumn(
473 name='created',
474 title=_('Created'),
475 getter=lambda i, f: IZopeDublinCore(i).created,
476 cell_formatter=lambda v, i, f: render_date(v),
477 subsort=True)
478 directlyProvides(created, ISortableColumn)
479 duplicate = QuizActionColumn(
480 'duplicate',
481 title=_('Duplicate this quiz'),
482 action='duplicate_quiz.html',
483 library='schooltool.quiz.flourish',
484 image='duplicate-icon.png')
485 return default + [questions, editors, created, duplicate]
486
487 def sortOn(self):
488 return (('created', True),)
489
490 def updateFormatter(self):
491 if self._table_formatter is None:
492 self.setUp(table_formatter=self.table_formatter,
493 batch_size=self.batch_size,
494 prefix=self.__name__,
495 css_classes={'table': 'data editable-quizzes-table'})
496>>>>>>> MERGE-SOURCE
374497
375498
376class QuizAddView(flourish.form.AddForm):499class QuizAddView(flourish.form.AddForm):
377500
378 legend = _('Quiz Information')501 legend = _('Quiz Information')
379 fields = field.Fields(interfaces.IQuiz['title'])502 fields = field.Fields(interfaces.IQuiz['title'])
380 content_template = ViewPageTemplateFile('templates/form.pt')503 content_template = ViewPageTemplateFile('templates/form.pt')
381504
382 def updateActions(self):505 def updateActions(self):
383 super(QuizAddView, self).updateActions()506 super(QuizAddView, self).updateActions()
384 self.actions['add'].addClass('button-ok')507 self.actions['add'].addClass('button-ok')
385 self.actions['cancel'].addClass('button-cancel')508 self.actions['cancel'].addClass('button-cancel')
386509
387 def create(self, data):510 def create(self, data):
388 quiz = Quiz()511 quiz = Quiz()
389 form.applyChanges(self, quiz, data)512 form.applyChanges(self, quiz, data)
390 return quiz513 return quiz
391514
392 def add(self, quiz):515 def add(self, quiz):
393 chooser = INameChooser(self.context)516 chooser = INameChooser(self.context)
394 name = chooser.chooseName('', quiz)517 name = chooser.chooseName('', quiz)
395 self.context[name] = quiz518 self.context[name] = quiz
396 self.setCreator(quiz)519 self.setCreator(quiz)
397 self._quiz = quiz520 self._quiz = quiz
398 return quiz521 return quiz
399522
400 def nextURL(self):523 def nextURL(self):
401 if self._finishedAdd:524 if self._finishedAdd:
402 url = absoluteURL(self._quiz, self.request)525 url = absoluteURL(self._quiz, self.request)
403 else:526 else:
404 url = self.request.get('camefrom',527 url = self.request.get('camefrom',
405 absoluteURL(self.context, self.request))528 absoluteURL(self.context, self.request))
406 return url529 return url
407530
408 def setCreator(self, quiz):531 def setCreator(self, quiz):
409 creator = IPerson(self.request.principal)532 creator = IPerson(self.request.principal)
410 quiz.editors.add(removeSecurityProxy(creator))533 quiz.editors.add(removeSecurityProxy(creator))
411534
412535
413class QuizEditView(flourish.form.Form, form.EditForm):536class QuizEditView(flourish.form.Form, form.EditForm):
414537
415 legend = _('Quiz Information')538 legend = _('Quiz Information')
416 fields = field.Fields(interfaces.IQuiz['title'])539 fields = field.Fields(interfaces.IQuiz['title'])
417 content_template = ViewPageTemplateFile('templates/form.pt')540 content_template = ViewPageTemplateFile('templates/form.pt')
418541
419 @property542 @property
420 def title(self):543 def title(self):
421 return self.context.title544 return self.context.title
422545
423 def update(self):546 def update(self):
424 return form.EditForm.update(self)547 return form.EditForm.update(self)
425548
426 def updateActions(self):549 def updateActions(self):
427 super(QuizEditView, self).updateActions()550 super(QuizEditView, self).updateActions()
428 self.actions['submit'].addClass('button-ok')551 self.actions['submit'].addClass('button-ok')
429 self.actions['cancel'].addClass('button-cancel')552 self.actions['cancel'].addClass('button-cancel')
430553
431 def nextURL(self):554 def nextURL(self):
432 return absoluteURL(self.context, self.request)555 return absoluteURL(self.context, self.request)
433556
434 @button.buttonAndHandler(_('Submit'), name='submit')557 @button.buttonAndHandler(_('Submit'), name='submit')
435 def handle_submit(self, action):558 def handle_submit(self, action):
436 super(QuizEditView, self).handleApply.func(self, action)559 super(QuizEditView, self).handleApply.func(self, action)
437 if (self.status == self.successMessage or560 if (self.status == self.successMessage or
438 self.status == self.noChangesMessage):561 self.status == self.noChangesMessage):
439 self.request.response.redirect(self.nextURL())562 self.request.response.redirect(self.nextURL())
440563
441 @button.buttonAndHandler(_('Cancel'), name='cancel')564 @button.buttonAndHandler(_('Cancel'), name='cancel')
442 def handle_cancel(self, action):565 def handle_cancel(self, action):
443 self.request.response.redirect(self.nextURL())566 self.request.response.redirect(self.nextURL())
444567
445568
446class QuizEditorsView(EditPersonRelationships):569class QuizEditorsView(EditPersonRelationships):
447570
448 current_title = _('Current editors')571 current_title = _('Current editors')
449 available_title = _('Add editors')572 available_title = _('Add editors')
450573
451 @property574 @property
452 def title(self):575 def title(self):
453 return self.context.title576 return self.context.title
454577
455 def getCollection(self):578 def getCollection(self):
456 return self.context.editors579 return self.context.editors
457580
458581
459class QuizDeleteView(flourish.form.DialogForm):582class QuizDeleteView(flourish.form.DialogForm):
460583
461 template = ViewPageTemplateFile('templates/confirm_delete_quiz.pt')584 template = ViewPageTemplateFile('templates/confirm_delete_quiz.pt')
462585
463 dialog_submit_actions = ('delete',)586 dialog_submit_actions = ('delete',)
464 dialog_close_actions = ('cancel',)587 dialog_close_actions = ('cancel',)
465 label = None588 label = None
466589
467 @button.buttonAndHandler(_('Delete'), name='delete')590 @button.buttonAndHandler(_('Delete'), name='delete')
468 def handle_delete(self, action):591 def handle_delete(self, action):
469 url = '%s/delete.html?delete.%s&CONFIRM' % (592 url = '%s/delete.html?delete.%s&CONFIRM' % (
470 absoluteURL(self.context.__parent__, self.request),593 absoluteURL(self.context.__parent__, self.request),
471 self.context.__name__)594 self.context.__name__)
472 self.request.response.redirect(url)595 self.request.response.redirect(url)
473 self.ajax_settings['dialog'] = 'close'596 self.ajax_settings['dialog'] = 'close'
474597
475 @button.buttonAndHandler(_('Cancel'), name='cancel')598 @button.buttonAndHandler(_('Cancel'), name='cancel')
476 def handle_cancel(self, action):599 def handle_cancel(self, action):
477 pass600 pass
478601
479 def updateActions(self):602 def updateActions(self):
480 super(QuizDeleteView, self).updateActions()603 super(QuizDeleteView, self).updateActions()
481 self.actions['delete'].addClass('button-ok')604 self.actions['delete'].addClass('button-ok')
482 self.actions['cancel'].addClass('button-cancel')605 self.actions['cancel'].addClass('button-cancel')
483606
484607
485class QuizDuplicateView(flourish.page.Page):608class QuizDuplicateView(flourish.page.Page):
486609
487 def __call__(self):610 def __call__(self):
488 container = self.context.__parent__611 container = self.context.__parent__
489 copy = removeSecurityProxy(self.context).copy()612 copy = removeSecurityProxy(self.context).copy()
490 chooser = INameChooser(container)613 chooser = INameChooser(container)
491 name = chooser.chooseName('', copy)614 name = chooser.chooseName('', copy)
492 container[name] = copy615 container[name] = copy
493 copy.title = translate(616 copy.title = translate(
494 _('DUPLICATED: ${quiz}', mapping={'quiz': self.context.title}),617 _('DUPLICATED: ${quiz}', mapping={'quiz': self.context.title}),
495 context=self.request)618 context=self.request)
496 person = IPerson(self.request.principal, None)619 person = IPerson(self.request.principal, None)
497 if person is not None:620 if person is not None:
498 copy.editors.add(removeSecurityProxy(person))621 copy.editors.add(removeSecurityProxy(person))
499 notify(ObjectCreatedEvent(copy))622 notify(ObjectCreatedEvent(copy))
500 camefrom = self.request.get('camefrom',623 camefrom = self.request.get('camefrom',
501 absoluteURL(container, self.request))624 absoluteURL(container, self.request))
502 self.request.response.redirect(camefrom)625 self.request.response.redirect(camefrom)
503626
504627
505class QuizContainerDeleteView(flourish.containers.ContainerDeleteView):628class QuizContainerDeleteView(flourish.containers.ContainerDeleteView):
506629
507 def nextURL(self):630 def nextURL(self):
508 if 'CONFIRM' in self.request:631 if 'CONFIRM' in self.request:
509 person = IPerson(self.request.principal)632 person = IPerson(self.request.principal)
510 url = '%s/quizzes.html' % absoluteURL(person, self.request)633 url = '%s/quizzes.html' % absoluteURL(person, self.request)
511 return url634 return url
512 return super(QuizContainerDeleteView, self).nextURL()635 return super(QuizContainerDeleteView, self).nextURL()
513636
514637
515class QuizSolutionView(flourish.page.Page):638class QuizSolutionView(flourish.page.Page):
516639
517 implements(interfaces.ISchoolToolQuizView,640 implements(interfaces.ISchoolToolQuizView,
518 interfaces.IMathJaxView)641 interfaces.IMathJaxView)
519642
520 container_class = 'container extra-wide-container'643 container_class = 'container extra-wide-container'
521644
522 @property645 @property
523 def title(self):646 def title(self):
524 return self.context.title647 return self.context.title
525648
526 @Lazy649 @Lazy
527 def quiz_items(self):650 def quiz_items(self):
528 return self.context651 return self.context
529652
530653
531class QuizTitleViewlet(flourish.viewlet.Viewlet):654class QuizTitleViewlet(flourish.viewlet.Viewlet):
532655
533 template = ViewPageTemplateFile('templates/quiz_title.pt')656 template = ViewPageTemplateFile('templates/quiz_title.pt')
534657
535658
536class QuizItemsViewlet(flourish.viewlet.Viewlet):659class QuizItemsViewlet(flourish.viewlet.Viewlet):
537660
538 template = InlineViewPageTemplate('''661 template = InlineViewPageTemplate('''
539 <tal:block i18n:domain="schooltool.quiz"662 <tal:block i18n:domain="schooltool.quiz"
540 tal:define="items view/view/quiz_items">663 tal:define="items view/view/quiz_items">
541 <h3 i18n:translate="">Questions</h3>664 <h3 i18n:translate="">Questions</h3>
542 <div tal:condition="items"665 <div tal:condition="items"
543 tal:define="ajax nocall:view/view/providers/ajax"666 tal:define="ajax nocall:view/view/providers/ajax"
544 tal:content="structure ajax/view/context/quiz_items_table"667 tal:content="structure ajax/view/context/quiz_items_table"
545 />668 />
546 <p i18n:translate="" tal:condition="not:items">669 <p i18n:translate="" tal:condition="not:items">
547 This quiz has no questions yet.670 This quiz has no questions yet.
548 </p>671 </p>
549 </tal:block>672 </tal:block>
550 ''')673 ''')
551674
552675
676<<<<<<< TREE
677=======
678class ReStructuredTextColumn(GetterColumn):
679
680 def __init__(self, *args, **kw):
681 self.schema_field = kw.pop('schema_field')
682 super(ReStructuredTextColumn, self).__init__(*args, **kw)
683
684 def renderCell(self, item, formatter):
685 renderer = flourish.form.Form(item, formatter.request)
686 renderer.mode = DISPLAY_MODE
687 renderer.fields = field.Fields(self.schema_field)
688 renderer.update()
689 return renderer.widgets['body'].render()
690
691
692class TypeColumn(GetterColumn):
693
694 def __init__(self, *args, **kw):
695 super(TypeColumn, self).__init__(*args, **kw)
696 factory = getUtility(IVocabularyFactory,
697 name='schooltool.quiz.quiz_item_types')
698 vocabulary = factory(None)
699 self.vocabulary = vocabulary
700
701 def getter(self, item, formatter):
702 term = self.vocabulary.getTerm(item.type_)
703 return term.title
704
705
706>>>>>>> MERGE-SOURCE
553class ActionColumn(table.column.ImageInputColumn):707class ActionColumn(table.column.ImageInputColumn):
554708
555 def __init__(self, name, title=None, action=None, library=None,709 def __init__(self, name, title=None, action=None, library=None,
556 image=None):710 image=None):
557 super(ActionColumn, self).__init__(711 super(ActionColumn, self).__init__(
558 'item.', '', name, library=library, image=image)712 'item.', '', name, library=library, image=image)
559 self.alt = title713 self.alt = title
560 self.link_title = title714 self.link_title = title
561 self.action = action715 self.action = action
562716
563 def template(self):717 def template(self):
564 return '\n'.join([718 return '\n'.join([
565 '<a href="%(href)s" title="%(title)s">',719 '<a href="%(href)s" title="%(title)s">',
566 '<img src="%(src)s" alt="%(alt)s" />',720 '<img src="%(src)s" alt="%(alt)s" />',
567 '</a>'721 '</a>'
568 ])722 ])
569723
570724
571class QuizActionColumn(ActionColumn):725class QuizActionColumn(ActionColumn):
572726
573 def params(self, item, formatter):727 def params(self, item, formatter):
574 result = super(QuizActionColumn, self).params(item, formatter)728 result = super(QuizActionColumn, self).params(item, formatter)
575 person_url = absoluteURL(formatter.context, formatter.request)729 person_url = absoluteURL(formatter.context, formatter.request)
576 camefrom = '%s/quizzes.html' % person_url730 camefrom = '%s/quizzes.html' % person_url
577 quiz_url = absoluteURL(item, formatter.request)731 quiz_url = absoluteURL(item, formatter.request)
578 result['title'] = translate(self.link_title,732 result['title'] = translate(self.link_title,
579 context=formatter.request) or ''733 context=formatter.request) or ''
580 result['href'] = '%s/%s?camefrom=%s' % (734 result['href'] = '%s/%s?camefrom=%s' % (
581 quiz_url, self.action, camefrom)735 quiz_url, self.action, camefrom)
582 return result736 return result
583737
584738
585class ItemActionColumn(ActionColumn):739class ItemActionColumn(ActionColumn):
586740
587 def params(self, item, formatter):741 def params(self, item, formatter):
588 result = super(ItemActionColumn, self).params(item, formatter)742 result = super(ItemActionColumn, self).params(item, formatter)
589 quiz_url = absoluteURL(formatter.context, formatter.request)743 quiz_url = absoluteURL(formatter.context, formatter.request)
590 result['title'] = translate(self.link_title,744 result['title'] = translate(self.link_title,
591 context=formatter.request) or ''745 context=formatter.request) or ''
592 result['href'] = '%s/%s?item_id=%s&camefrom=%s' % (746 result['href'] = '%s/%s?item_id=%s&camefrom=%s' % (
593 quiz_url, self.action, item.__name__, quiz_url)747 quiz_url, self.action, item.__name__, quiz_url)
594 return result748 return result
595749
596750
597class ModalItemActionColumn(ItemActionColumn):751class ModalItemActionColumn(ItemActionColumn):
598752
599 template = ViewPageTemplateFile('templates/modal_item_column.pt')753 template = ViewPageTemplateFile('templates/modal_item_column.pt')
600754
601 def renderCell(self, item, formatter):755 def renderCell(self, item, formatter):
602 params = self.params(item, formatter)756 params = self.params(item, formatter)
603 params['dialog_title'] = translate(_('Remove this question?'),757 params['dialog_title'] = translate(_('Remove this question?'),
604 context=formatter.request)758 context=formatter.request)
605 self.context = item759 self.context = item
606 self.request = formatter.request760 self.request = formatter.request
607 return self.template(params=params)761 return self.template(params=params)
608762
609763
610class PositionColumn(GetterColumn):764class PositionColumn(GetterColumn):
611765
612 def getter(self, item, formatter):766 def getter(self, item, formatter):
613 for position, current in enumerate(formatter.context):767 for position, current in enumerate(formatter.context):
614 if sameProxiedObjects(current, item):768 if sameProxiedObjects(current, item):
615 return position769 return position
616770
617 def cell_formatter(self, value, item, formatter):771 def cell_formatter(self, value, item, formatter):
618 template = '<a href="%(href)s">%(title)s</a>'772 template = '<a href="%(href)s">%(title)s</a>'
619 quiz_url = absoluteURL(formatter.context, formatter.request)773 quiz_url = absoluteURL(formatter.context, formatter.request)
620 href = '%s/view_item.html?item_id=%s' % (quiz_url, item.__name__)774 href = '%s/view_item.html?item_id=%s' % (quiz_url, item.__name__)
621 params = {'href': href, 'title': value + 1}775 params = {'href': href, 'title': value + 1}
622 return template % params776 return template % params
623777
624778
625class SortColumn(PositionColumn):779class SortColumn(PositionColumn):
626780
781<<<<<<< TREE
627 def cell_formatter(self, value, item, formatter):782 def cell_formatter(self, value, item, formatter):
628 template = '<select name="%(name)s" class="item-sort">%(options)s</select>'783 template = '<select name="%(name)s" class="item-sort">%(options)s</select>'
629 options = []784 options = []
@@ -638,10 +793,27 @@
638 'options': ''.join(options),793 'options': ''.join(options),
639 }794 }
640 return template % params795 return template % params
796=======
797 def cell_formatter(self, value, item, formatter):
798 template = '<select id="%(name)s" class="item-sort">%(options)s</select>'
799 options = []
800 for position, current in enumerate(formatter.context):
801 display_position = position + 1
802 option = '<option value="%d">%d</option>'
803 if sameProxiedObjects(current, item):
804 option = '<option value="%d" selected="selected">%d</option>'
805 options.append(option % (position, display_position))
806 params = {
807 'name': item.__name__,
808 'options': ''.join(options),
809 }
810 return template % params
811>>>>>>> MERGE-SOURCE
641812
642813
643class QuizItemsTable(table.ajax.Table):814class QuizItemsTable(table.ajax.Table):
644815
816<<<<<<< TREE
645 def columns(self):817 def columns(self):
646 position = PositionColumn(818 position = PositionColumn(
647 name='position',819 name='position',
@@ -707,67 +879,137 @@
707 new_position = int(toChange)879 new_position = int(toChange)
708 self.context.updateOrder(changePosition, new_position)880 self.context.updateOrder(changePosition, new_position)
709 super(QuizItemsTable, self).update()881 super(QuizItemsTable, self).update()
882=======
883 table_formatter = AJAXCSSTableFormatter
884
885 def columns(self):
886 position = PositionColumn(
887 name='position',
888 title=_('No.'))
889 body = ReStructuredTextColumn(
890 name='body',
891 title=_('Body'),
892 schema_field=interfaces.IQuizItem['body']
893 )
894 type_ = TypeColumn(
895 name='type',
896 title=_('Type'))
897 result = [position, body, type_]
898 result.extend(self.getActionColumns())
899 return result
900
901 def getActionColumns(self):
902 # XXX: set action columns only if principal is editor
903 sort = SortColumn(
904 name='sort',
905 title=u'')
906 edit = ItemActionColumn(
907 'edit',
908 title=_('Edit this item'),
909 action='edit_item.html',
910 library='schooltool.skin.flourish',
911 image='edit-icon.png')
912 duplicate = ItemActionColumn(
913 'duplicate',
914 title=_('Duplicate this item'),
915 action='duplicate_item.html',
916 library='schooltool.quiz.flourish',
917 image='duplicate-icon.png')
918 remove = ModalItemActionColumn(
919 'remove',
920 title=_('Remove this item'),
921 action='remove_item.html',
922 library='schooltool.skin.flourish',
923 image='remove-icon.png')
924 return [sort, edit, duplicate, remove]
925
926 def items(self):
927 return self.view.quiz_items
928
929 def sortOn(self):
930 return (('position', False),)
931
932 def updateFormatter(self):
933 # XXX: set -with-actions class only if principal is editor
934 klass = 'data quiz-items-table quiz-items-table-with-actions'
935 css_classes = {'table': klass}
936 if self._table_formatter is None:
937 self.setUp(table_formatter=self.table_formatter,
938 batch_size=self.batch_size,
939 prefix=self.__name__,
940 css_classes=css_classes)
941
942 def update(self):
943 changePosition = self.request.get('changePosition')
944 if changePosition is not None:
945 toChange = self.request.get(changePosition)
946 if toChange is not None:
947 new_position = int(toChange)
948 self.context.updateOrder(changePosition, new_position)
949 super(QuizItemsTable, self).update()
950>>>>>>> MERGE-SOURCE
710951
711952
712class QuizViewDoneLinkViewlet(flourish.viewlet.Viewlet):953class QuizViewDoneLinkViewlet(flourish.viewlet.Viewlet):
713954
714 template = InlineViewPageTemplate('''955 template = InlineViewPageTemplate('''
715 <h3 i18n:domain="schooltool" class="done-link">956 <h3 i18n:domain="schooltool" class="done-link">
716 <a tal:attributes="href view/url"957 <a tal:attributes="href view/url"
717 i18n:translate="">958 i18n:translate="">
718 Done959 Done
719 </a>960 </a>
720 </h3>961 </h3>
721 ''')962 ''')
722963
723 def url(self):964 def url(self):
724 person = IPerson(self.request.principal, None)965 person = IPerson(self.request.principal, None)
725 if person is not None:966 if person is not None:
726 return '%s/quizzes.html' % (absoluteURL(person, self.request))967 return '%s/quizzes.html' % (absoluteURL(person, self.request))
727968
728969
729class QuizDeleteLinkViewlet(flourish.page.ModalFormLinkViewlet):970class QuizDeleteLinkViewlet(flourish.page.ModalFormLinkViewlet):
730971
731 @property972 @property
732 def dialog_title(self):973 def dialog_title(self):
733 title = _('Delete this quiz?')974 title = _('Delete this quiz?')
734 return translate(title, context=self.request)975 return translate(title, context=self.request)
735976
736977
737class QuizSolutionTableViewlet(flourish.viewlet.Viewlet):978class QuizSolutionTableViewlet(flourish.viewlet.Viewlet):
738979
739 template = InlineViewPageTemplate('''980 template = InlineViewPageTemplate('''
740 <div tal:define="ajax nocall:view/view/providers/ajax"981 <div tal:define="ajax nocall:view/view/providers/ajax"
741 tal:content="structure ajax/view/context/solution_table" />982 tal:content="structure ajax/view/context/solution_table" />
742 ''')983 ''')
743984
744985
745class SolutionPositionColumn(PositionColumn):986class SolutionPositionColumn(PositionColumn):
746987
747 def cell_formatter(self, value, item, formatter):988 def cell_formatter(self, value, item, formatter):
748 return value + 1989 return value + 1
749990
750991
751def solution_question_formatter(value, item, formatter):992def solution_question_formatter(value, item, formatter):
752 return render_rst(item.body)993 return render_rst(item.body)
753994
754995
755def solution_solution_formatter(value, item, formatter):996def solution_solution_formatter(value, item, formatter):
756 if item.type_ == u'Open':997 if item.type_ in (u'Open',u'Rational'):
757 return render_rst(item.solution)998 return render_rst(item.solution)
758 choices = []999 choices = []
759 for choice in item.choices:1000 for choice in item.choices:
760 body = render_rst(choice.body)1001 body = render_rst(choice.body)
761 if choice.correct:1002 if choice.correct:
762 choices.append(1003 choices.append(
763 '<span class="correct">%s</span>' % body)1004 '<span class="correct">%s</span>' % body)
764 else:1005 else:
765 choices.append(body)1006 choices.append(body)
766 return ''.join(choices)1007 return ''.join(choices)
7671008
7681009
769class SolutionTable(table.ajax.Table):1010class SolutionTable(table.ajax.Table):
7701011
1012<<<<<<< TREE
771 def items(self):1013 def items(self):
772 return self.view.quiz_items1014 return self.view.quiz_items
7731015
@@ -799,23 +1041,59 @@
799 batch_size=self.batch_size,1041 batch_size=self.batch_size,
800 prefix=self.__name__,1042 prefix=self.__name__,
801 css_classes={'table': 'data solution-table'})1043 css_classes={'table': 'data solution-table'})
1044=======
1045 table_formatter = AJAXCSSTableFormatter
1046
1047 def items(self):
1048 return self.view.quiz_items
1049
1050 def columns(self):
1051 position = SolutionPositionColumn(
1052 name='position',
1053 title=_('No.'))
1054 type_ = TypeColumn(
1055 name='type',
1056 title=_('Type'))
1057 question = GetterColumn(
1058 name='question',
1059 title=_('Question'),
1060 getter=lambda i, f: '',
1061 cell_formatter=solution_question_formatter)
1062 solution = GetterColumn(
1063 name='solution',
1064 title=_('Solution'),
1065 getter=lambda i, f: '',
1066 cell_formatter=solution_solution_formatter)
1067 return [position, type_, question, solution]
1068
1069 def sortOn(self):
1070 return (('position', False),)
1071
1072 def updateFormatter(self):
1073 if self._table_formatter is None:
1074 self.setUp(table_formatter=self.table_formatter,
1075 batch_size=self.batch_size,
1076 prefix=self.__name__,
1077 css_classes={'table': 'data solution-table'})
1078>>>>>>> MERGE-SOURCE
8021079
8031080
804class SolutionDoneLinkViewlet(flourish.viewlet.Viewlet):1081class SolutionDoneLinkViewlet(flourish.viewlet.Viewlet):
8051082
806 template = InlineViewPageTemplate('''1083 template = InlineViewPageTemplate('''
807 <h3 i18n:domain="schooltool" class="done-link">1084 <h3 i18n:domain="schooltool" class="done-link">
808 <a tal:attributes="href view/url" i18n:translate="">Done</a>1085 <a tal:attributes="href view/url" i18n:translate="">Done</a>
809 </h3>1086 </h3>
810 ''')1087 ''')
8111088
812 def url(self):1089 def url(self):
813 default = absoluteURL(self.context, self.request)1090 default = absoluteURL(self.context, self.request)
814 return self.request.get('camefrom', default)1091 return self.request.get('camefrom', default)
8151092
8161093
817class QuizImporter(ImporterBase):1094class QuizImporter(ImporterBase):
8181095
1096<<<<<<< TREE
819 sheet_name = 'QuizItems'1097 sheet_name = 'QuizItems'
8201098
821 @Lazy1099 @Lazy
@@ -883,126 +1161,196 @@
883 continue1161 continue
884 item = self.createQuizItem(data)1162 item = self.createQuizItem(data)
885 self.addItem(quiz, item, data)1163 self.addItem(quiz, item, data)
1164=======
1165 sheet_name = 'QuizItems'
1166
1167 @Lazy
1168 def types_vocabulary(self):
1169 factory = getUtility(IVocabularyFactory,
1170 name='schooltool.quiz.quiz_item_types')
1171 return factory(None)
1172
1173 def createQuiz(self, title):
1174 quiz = Quiz()
1175 quiz.title = translate(_('IMPORTED: ${quiz}', mapping={'quiz': title}),
1176 context=self.request)
1177 return quiz
1178
1179 def createQuizItem(self, data):
1180 item = QuizItem()
1181 item.body = data['body']
1182 item.solution = data['solution']
1183 item.type_ = self.types_vocabulary.getTermByToken(data['type_']).value
1184 if data['choices']:
1185 choices = []
1186 for choice in data['choices'].split(choices_row_separator):
1187 body, correct = choice.split(choices_column_separator)
1188 item_choice = Choice()
1189 item_choice.body = body
1190 try:
1191 item_choice.correct = bool(int(correct))
1192 except (ValueError,):
1193 item_choice.correct = False
1194 choices.append(item_choice)
1195 item.choices = choices
1196 return item
1197
1198 def addItem(self, quiz, item, data):
1199 app = ISchoolToolApplication(None)
1200 container = interfaces.IQuizItemContainer(app)
1201 chooser = INameChooser(container)
1202 name = chooser.chooseName('', item)
1203 container[name] = item
1204 item.quizzes.add(quiz)
1205 quiz.order.append(item.__name__)
1206 notify(ObjectCreatedEvent(item))
1207
1208 def process(self):
1209 sh = self.sheet
1210 num_errors = len(self.errors)
1211 title = self.getRequiredTextFromCell(sh, 0, 1)
1212 if num_errors < len(self.errors):
1213 return
1214 quiz = self.createQuiz(title)
1215 chooser = INameChooser(self.context)
1216 name = chooser.chooseName('', quiz)
1217 self.context[name] = quiz
1218 person = IPerson(self.request.principal)
1219 quiz.editors.add(removeSecurityProxy(person))
1220 notify(ObjectCreatedEvent(quiz))
1221 for row in range(3, sh.nrows):
1222 num_errors = len(self.errors)
1223 data = {}
1224 data['body'] = self.getRequiredTextFromCell(sh, row, 0)
1225 data['type_'] = self.getRequiredTextFromCell(sh, row, 1)
1226 data['choices'] = self.getTextFromCell(sh, row, 2)
1227 data['solution'] = self.getTextFromCell(sh, row, 3)
1228 if num_errors < len(self.errors):
1229 continue
1230 item = self.createQuizItem(data)
1231 self.addItem(quiz, item, data)
1232>>>>>>> MERGE-SOURCE
8861233
8871234
888class QuizImportView(FlourishMegaImporter):1235class QuizImportView(FlourishMegaImporter):
8891236
890 content_template = ViewPageTemplateFile('templates/quiz_import.pt')1237 content_template = ViewPageTemplateFile('templates/quiz_import.pt')
8911238
892 @property1239 @property
893 def importers(self):1240 def importers(self):
894 return [1241 return [
895 QuizImporter,1242 QuizImporter,
896 ]1243 ]
8971244
898 def nextURL(self):1245 def nextURL(self):
899 return self.request.get('camefrom',1246 return self.request.get('camefrom',
900 absoluteURL(self.context, self.request))1247 absoluteURL(self.context, self.request))
9011248
9021249
903class QuizExportView(MegaExporter):1250class QuizExportView(MegaExporter):
9041251
905 def __call__(self):1252 def __call__(self):
906 wb = xlwt.Workbook()1253 wb = xlwt.Workbook()
907 self.export_items(wb)1254 self.export_items(wb)
908 datafile = StringIO()1255 datafile = StringIO()
909 wb.save(datafile)1256 wb.save(datafile)
910 data = datafile.getvalue()1257 data = datafile.getvalue()
911 self.setUpHeaders(data)1258 self.setUpHeaders(data)
912 return data1259 return data
9131260
914 def export_items(self, wb):1261 def export_items(self, wb):
915 ws = wb.add_sheet('QuizItems')1262 ws = wb.add_sheet('QuizItems')
916 self.write_header(ws, 0, 0, 'Quiz Title')1263 self.write_header(ws, 0, 0, 'Quiz Title')
917 self.write(ws, 0, 1, self.context.title)1264 self.write(ws, 0, 1, self.context.title)
918 self.print_table(self.format_items(), ws, offset=2)1265 self.print_table(self.format_items(), ws, offset=2)
9191266
920 def print_table(self, table, ws, offset=0):1267 def print_table(self, table, ws, offset=0):
921 for x, row in enumerate(table):1268 for x, row in enumerate(table):
922 for y, cell in enumerate(row):1269 for y, cell in enumerate(row):
923 self.write(ws, x + offset, y, cell.data, **cell.style)1270 self.write(ws, x + offset, y, cell.data, **cell.style)
924 return len(table)1271 return len(table)
9251272
926 def format_items(self):1273 def format_items(self):
927 factory = getUtility(IVocabularyFactory,1274 factory = getUtility(IVocabularyFactory,
928 name='schooltool.quiz.quiz_item_types')1275 name='schooltool.quiz.quiz_item_types')
929 vocabulary = factory(None)1276 vocabulary = factory(None)
9301277
931 def type_getter(item):1278 def type_getter(item):
932 return vocabulary.getTerm(item.type_).token1279 return vocabulary.getTerm(item.type_).token
9331280
934 def choices_getter(item):1281 def choices_getter(item):
935 result = []1282 result = []
936 if item.choices is not None:1283 if item.choices is not None:
937 for choice in item.choices:1284 for choice in item.choices:
938 result.append(self.format_choice(choice))1285 result.append(self.format_choice(choice))
939 return choices_row_separator.join(result)1286 return choices_row_separator.join(result)
9401287
941 fields = [1288 fields = [
942 ('Body', Text, attrgetter('body')),1289 ('Body', Text, attrgetter('body')),
943 ('Type', Text, type_getter),1290 ('Type', Text, type_getter),
944 ('Choices', Text, choices_getter),1291 ('Choices', Text, choices_getter),
945 ('Solution', Text, attrgetter('solution')),1292 ('Solution', Text, attrgetter('solution')),
946 ]1293 ]
947 return self.format_table(fields, self.context)1294 return self.format_table(fields, self.context)
9481295
949 def format_choice(self, choice):1296 def format_choice(self, choice):
950 correct = [0, 1][choice.correct]1297 correct = [0, 1][choice.correct]
951 return choices_column_separator.join([choice.body, str(correct)])1298 return choices_column_separator.join([choice.body, str(correct)])
9521299
9531300
954class QuizHelpLinks(flourish.page.RefineLinksViewlet, PersonSections):1301class QuizHelpLinks(flourish.page.RefineLinksViewlet, PersonSections):
9551302
956 @Lazy1303 @Lazy
957 def person(self):1304 def person(self):
958 return IPerson(self.request.principal, None)1305 return IPerson(self.request.principal, None)
9591306
960 @property1307 @property
961 def enabled(self):1308 def enabled(self):
962 if self.person is not None:1309 if self.person is not None:
963 editable_quizzes = getRelatedObjects(self.person,1310 editable_quizzes = getRelatedObjects(self.person,
964 relationships.URIQuiz,1311 relationships.URIQuiz,
965 relationships.URIQuizEditors)1312 relationships.URIQuizEditors)
966 return self.instructor_sections or editable_quizzes1313 return self.instructor_sections or editable_quizzes
9671314
968 def render(self, *args, **kw):1315 def render(self, *args, **kw):
969 if self.enabled:1316 if self.enabled:
970 return super(QuizHelpLinks, self).render(*args, **kw)1317 return super(QuizHelpLinks, self).render(*args, **kw)
971 return ''1318 return ''
9721319
9731320
974class QuizEditingHelpViewlet(flourish.page.LinkIdViewlet):1321class QuizEditingHelpViewlet(flourish.page.LinkIdViewlet):
9751322
976 template = InlineViewPageTemplate('''1323 template = InlineViewPageTemplate('''
977 <a tal:attributes="href view/url;1324 <a tal:attributes="href view/url;
978 onclick view/script"1325 onclick view/script"
979 tal:content="view/title" />1326 tal:content="view/title" />
980 ''')1327 ''')
9811328
982 @property1329 @property
983 def enabled(self):1330 def enabled(self):
984 app = ISchoolToolApplication(None)1331 app = ISchoolToolApplication(None)
985 preferences = interfaces.IApplicationPreferences(app)1332 preferences = interfaces.IApplicationPreferences(app)
986 return preferences.mathjax_help is not None1333 return preferences.mathjax_help is not None
9871334
988 @property1335 @property
989 def script(self):1336 def script(self):
990 url = self.url1337 url = self.url
991 name = self.html_id1338 name = self.html_id
992 params = {1339 params = {
993 'width': 640,1340 'width': 640,
994 'height': 480,1341 'height': 480,
995 'resizable': 'yes',1342 'resizable': 'yes',
996 'scrollbars': 'yes',1343 'scrollbars': 'yes',
997 'toolbar': 'no',1344 'toolbar': 'no',
998 'location': 'no',1345 'location': 'no',
999 }1346 }
1000 return "window.open('%s', '%s', '%s'); return false" % (1347 return "window.open('%s', '%s', '%s'); return false" % (
1001 url, name, ','.join(['%s=%s' % (k, v) for k, v in params.items()]))1348 url, name, ','.join(['%s=%s' % (k, v) for k, v in params.items()]))
10021349
10031350
1004class QuizEditingHelpView(flourish.page.Page):1351class QuizEditingHelpView(flourish.page.Page):
10051352
1353<<<<<<< TREE
1006 implements(interfaces.ISchoolToolQuizView,1354 implements(interfaces.ISchoolToolQuizView,
1007 interfaces.IMathJaxView)1355 interfaces.IMathJaxView)
10081356
@@ -1032,3 +1380,25 @@
1032 MathJax.Hub.Queue(['Typeset', MathJax.Hub]);1380 MathJax.Hub.Queue(['Typeset', MathJax.Hub]);
1033 </script>1381 </script>
1034 ''')1382 ''')
1383=======
1384 implements(interfaces.ISchoolToolQuizView,
1385 interfaces.IMathJaxView)
1386
1387 template = InlineViewPageTemplate('''
1388 <tal:block content="structure view/mathjax_help" />
1389 ''')
1390
1391 def mathjax_help(self):
1392 app = ISchoolToolApplication(None)
1393 preferences = interfaces.IApplicationPreferences(app)
1394 return self.render_rst(preferences,
1395 interfaces.IApplicationPreferences,
1396 'mathjax_help')
1397
1398 def render_rst(self, value, iface, field_name):
1399 renderer = flourish.form.Form(value, self.request)
1400 renderer.mode = DISPLAY_MODE
1401 renderer.fields = field.Fields(iface[field_name])
1402 renderer.update()
1403 return renderer.widgets[field_name].render()
1404>>>>>>> MERGE-SOURCE
10351405
=== modified file 'src/schooltool/quiz/browser/quizitem.py'
--- src/schooltool/quiz/browser/quizitem.py 2013-01-24 06:54:46 +0000
+++ src/schooltool/quiz/browser/quizitem.py 2013-03-04 22:05:25 +0000
@@ -59,6 +59,7 @@
59from schooltool.quiz.browser.widget import LabeledCheckBoxFieldWidget59from schooltool.quiz.browser.widget import LabeledCheckBoxFieldWidget
60from schooltool.quiz.quizitem import Choice60from schooltool.quiz.quizitem import Choice
61from schooltool.quiz.quizitem import QuizItem61from schooltool.quiz.quizitem import QuizItem
62from schooltool.quiz.utils import Validator
6263
63registerFactoryAdapter(interfaces.IChoice, Choice)64registerFactoryAdapter(interfaces.IChoice, Choice)
6465
@@ -66,6 +67,10 @@
66class OneCorrectChoiceRequired(ValidationError):67class OneCorrectChoiceRequired(ValidationError):
6768
68 __doc__ = _('One correct choice is required.')69 __doc__ = _('One correct choice is required.')
70
71class ValidSolutionRequired(ValidationError):
72
73 __doc__ = _('A syntactically valid solution is required.')
6974
7075
71class SolutionRequiredError(ValidationError):76class SolutionRequiredError(ValidationError):
@@ -83,7 +88,8 @@
8388
84 def validate(self, value):89 def validate(self, value):
85 super(ChoicesValidator, self).validate(value)90 super(ChoicesValidator, self).validate(value)
86 if not self.view.is_open_question():91 if not (self.view.is_open_question() \
92 or self.view.is_rational_question()):
87 if not value:93 if not value:
88 raise OneCorrectChoiceRequired(value)94 raise OneCorrectChoiceRequired(value)
89 correct = []95 correct = []
@@ -98,8 +104,12 @@
98104
99 def validate(self, value):105 def validate(self, value):
100 super(SolutionValidator, self).validate(value)106 super(SolutionValidator, self).validate(value)
101 if self.view.is_open_question() and not value:107 if (self.view.is_open_question() \
108 or self.view.is_rational_question()) \
109 and not value:
102 raise SolutionRequiredError(value)110 raise SolutionRequiredError(value)
111 if self.view.is_rational_question() and not Validator(value):
112 raise ValidSolutionRequired(value)
103113
104114
105class ChoiceSubForm(ObjectSubForm):115class ChoiceSubForm(ObjectSubForm):
@@ -118,6 +128,7 @@
118 container_class = 'container widecontainer'128 container_class = 'container widecontainer'
119 content_template = ViewPageTemplateFile('templates/form.pt')129 content_template = ViewPageTemplateFile('templates/form.pt')
120 _open_question_token = 'open'130 _open_question_token = 'open'
131 _rational_question_token = 'rational'
121132
122 @property133 @property
123 def fields(self):134 def fields(self):
@@ -146,7 +157,7 @@
146 result = {}157 result = {}
147 for widget in self.widgets.values():158 for widget in self.widgets.values():
148 result.update(self.encodeWidget(widget))159 result.update(self.encodeWidget(widget))
149 if self.is_open_question():160 if self.is_open_question() or self.is_rational_question():
150 solution_widget = self.widgets['solution']161 solution_widget = self.widgets['solution']
151 result.update(self.encodeWidget(solution_widget))162 result.update(self.encodeWidget(solution_widget))
152 else:163 else:
@@ -162,6 +173,10 @@
162 def is_open_question(self):173 def is_open_question(self):
163 item_type = getattr(self.widgets['type_'], 'value', [])174 item_type = getattr(self.widgets['type_'], 'value', [])
164 return self._open_question_token in item_type175 return self._open_question_token in item_type
176
177 def is_rational_question(self):
178 item_type = getattr(self.widgets['type_'], 'value', [])
179 return self._rational_question_token in item_type
165180
166 def setQuiz(self, item):181 def setQuiz(self, item):
167 quiz = removeSecurityProxy(self.context)182 quiz = removeSecurityProxy(self.context)
@@ -419,6 +434,7 @@
419 'selection': _('Selection Question'),434 'selection': _('Selection Question'),
420 'multiple': _('Multiple Selection Question'),435 'multiple': _('Multiple Selection Question'),
421 'open': _('Open Question'),436 'open': _('Open Question'),
437 'rational': _('Rational Question'),
422 }438 }
423 return result[self.type_token]439 return result[self.type_token]
424440
@@ -431,6 +447,7 @@
431 'selection': _('Selection Question'),447 'selection': _('Selection Question'),
432 'multiple': _('Multiple Selection Question'),448 'multiple': _('Multiple Selection Question'),
433 'open': _('Open Question'),449 'open': _('Open Question'),
450 'rational': _('Rational Question'),
434 }451 }
435 return result[self.type_token]452 return result[self.type_token]
436453
@@ -556,7 +573,13 @@
556573
557 def is_open_question(self):574 def is_open_question(self):
558 return self.view.type_token == 'open'575 return self.view.type_token == 'open'
576
577 def is_rational_question(self):
578 return self.view.type_token == 'rational'
559579
580 def has_open_field(self):
581 return self.is_rational_question() or self.is_open_question()
582
560 def has_skill(self):583 def has_skill(self):
561 return None not in (self.view.item.course, self.view.item.skill)584 return None not in (self.view.item.course, self.view.item.skill)
562585
563586
=== modified file 'src/schooltool/quiz/browser/stests/quiz_deployment.txt'
--- src/schooltool/quiz/browser/stests/quiz_deployment.txt 2012-10-07 06:00:01 +0000
+++ src/schooltool/quiz/browser/stests/quiz_deployment.txt 2013-03-04 22:05:25 +0000
@@ -64,6 +64,11 @@
64 ... ('Eric Raymond', False),64 ... ('Eric Raymond', False),
65 ... ] 65 ... ]
66 >>> fill_question_form(teacher, 'Selection', body, choices)66 >>> fill_question_form(teacher, 'Selection', body, choices)
67 >>> teacher.query.id('form-buttons-submitadd').click()
68
69 >>> body = 'What is the first prime number higher than 5?'
70 >>> solution = '3 + 4'
71 >>> fill_question_form(teacher, 'Rational', body, solution=solution)
67 >>> teacher.query.id('form-buttons-submit').click()72 >>> teacher.query.id('form-buttons-submit').click()
6873
69 >>> print_quiz_item_bodies(teacher)74 >>> print_quiz_item_bodies(teacher)
@@ -82,6 +87,11 @@
82 Who is Python's original author?87 Who is Python's original author?
83 </p>88 </p>
84 </div>89 </div>
90 <div ...>
91 <p>
92 What is the first prime number higher than 5?
93 </p>
94 </div>
8595
86Deploy the quiz to one of the instructor's sections:96Deploy the quiz to one of the instructor's sections:
8797
@@ -162,6 +172,9 @@
162 >>> sel = '#form-widgets-QuizItem-3-row .radio-widget[value="4"]'172 >>> sel = '#form-widgets-QuizItem-3-row .radio-widget[value="4"]'
163 >>> camila.query.css(sel).click()173 >>> camila.query.css(sel).click()
164174
175 >>> answer_4 = '9/3 + 4'
176 >>> camila.query.id('form-widgets-QuizItem-4').ui.set_value(answer_4)
177
165 >>> camila.query.id('form-buttons-submit').click()178 >>> camila.query.id('form-buttons-submit').click()
166179
167 >>> sel = '.taken-quizzes-table tbody tr'180 >>> sel = '.taken-quizzes-table tbody tr'
@@ -189,6 +202,9 @@
189 >>> sel = '#form-widgets-QuizItem-3-row .radio-widget[value="3"]'202 >>> sel = '#form-widgets-QuizItem-3-row .radio-widget[value="3"]'
190 >>> mario.query.css(sel).click()203 >>> mario.query.css(sel).click()
191204
205 >>> answer_4 = '6'
206 >>> mario.query.id('form-widgets-QuizItem-4').ui.set_value(answer_4)
207
192 >>> mario.query.id('form-buttons-submit').click()208 >>> mario.query.id('form-buttons-submit').click()
193209
194 >>> sel = '.taken-quizzes-table tbody tr'210 >>> sel = '.taken-quizzes-table tbody tr'
@@ -216,6 +232,9 @@
216 >>> sel = '#form-widgets-QuizItem-3-row .radio-widget[value="3"]'232 >>> sel = '#form-widgets-QuizItem-3-row .radio-widget[value="3"]'
217 >>> liliana.query.css(sel).click()233 >>> liliana.query.css(sel).click()
218234
235 >>> answer_4 = '7'
236 >>> liliana.query.id('form-widgets-QuizItem-4').ui.set_value(answer_4)
237
219 >>> liliana.query.id('form-buttons-submit').click()238 >>> liliana.query.id('form-buttons-submit').click()
220239
221 >>> sel = '.taken-quizzes-table tbody tr'240 >>> sel = '.taken-quizzes-table tbody tr'
@@ -238,7 +257,7 @@
238The instructor updates the grades:257The instructor updates the grades:
239258
240 >>> teacher.query.link('Update Grades').click()259 >>> teacher.query.link('Update Grades').click()
241260
242The instructor goes to see the gradebook:261The instructor goes to see the gradebook:
243262
244 >>> teacher.ui.section.go('2012', '2012', 'Programming A1')263 >>> teacher.ui.section.go('2012', '2012', 'Programming A1')
@@ -252,9 +271,9 @@
252 | Name | FreeS | Total | Ave. |271 | Name | FreeS | Total | Ave. |
253 | | 100 | | |272 | | 100 | | |
254 +------------------+-------+-------+-------+273 +------------------+-------+-------+-------+
255 | Cerna, Camila | 94.4 | 94.4 | 94.4% |274 | Cerna, Camila | 95.8 | 95.8 | 95.8% |
256 | Tejada, Mario | 16.7 | 16.7 | 16.7% |275 | Tejada, Mario | 12.5 | 12.5 | 12.5% |
257 | Vividor, Liliana | 61.1 | 61.1 | 61.1% |276 | Vividor, Liliana | 70.8 | 70.8 | 70.8% |
258 +------------------+-------+-------+-------+277 +------------------+-------+-------+-------+
259278
260Camila checks her grade:279Camila checks her grade:
@@ -264,7 +283,7 @@
264 >>> for row in camila.query_all.css(sel):283 >>> for row in camila.query_all.css(sel):
265 ... columns = row.query_all.tag('td')284 ... columns = row.query_all.tag('td')
266 ... print ' | '.join([column.text for column in columns])285 ... print ' | '.join([column.text for column in columns])
267 Free Software Exam | Programming A1 | | 94%286 Free Software Exam | Programming A1 | | 96%
268287
269Mario checks his grade:288Mario checks his grade:
270289
@@ -273,7 +292,7 @@
273 >>> for row in mario.query_all.css(sel):292 >>> for row in mario.query_all.css(sel):
274 ... columns = row.query_all.tag('td')293 ... columns = row.query_all.tag('td')
275 ... print ' | '.join([column.text for column in columns])294 ... print ' | '.join([column.text for column in columns])
276 Free Software Exam | Programming A1 | | 17%295 Free Software Exam | Programming A1 | | 12%
277296
278Liliana checks her grade:297Liliana checks her grade:
279298
@@ -282,4 +301,4 @@
282 >>> for row in liliana.query_all.css(sel):301 >>> for row in liliana.query_all.css(sel):
283 ... columns = row.query_all.tag('td')302 ... columns = row.query_all.tag('td')
284 ... print ' | '.join([column.text for column in columns])303 ... print ' | '.join([column.text for column in columns])
285 Free Software Exam | Programming A1 | | 61%304 Free Software Exam | Programming A1 | | 71%
286305
=== modified file 'src/schooltool/quiz/browser/stests/quiz_duplicate.txt'
--- src/schooltool/quiz/browser/stests/quiz_duplicate.txt 2012-12-06 18:26:06 +0000
+++ src/schooltool/quiz/browser/stests/quiz_duplicate.txt 2013-03-04 22:05:25 +0000
@@ -59,6 +59,11 @@
59 ... ('Eric Raymond', False),59 ... ('Eric Raymond', False),
60 ... ] 60 ... ]
61 >>> fill_question_form(teacher, 'Selection', body, choices)61 >>> fill_question_form(teacher, 'Selection', body, choices)
62 >>> teacher.query.id('form-buttons-submitadd').click()
63
64 >>> body = 'What is the first prime number higher than 5?'
65 >>> solution = '3 + 4'
66 >>> fill_question_form(teacher, 'Rational', body, solution=solution)
62 >>> teacher.query.id('form-buttons-submit').click()67 >>> teacher.query.id('form-buttons-submit').click()
6368
64Check the quiz:69Check the quiz:
@@ -68,7 +73,7 @@
68 >>> for row in teacher.query_all.css(sel):73 >>> for row in teacher.query_all.css(sel):
69 ... columns = row.query_all.tag('td')74 ... columns = row.query_all.tag('td')
70 ... print ' | '.join([column.text for column in columns])75 ... print ' | '.join([column.text for column in columns])
71 Free/Open Source Software Quiz | 3 | Elkner, Jeffrey | ...76 Free/Open Source Software Quiz | 4 | Elkner, Jeffrey | ...
7277
73Duplicate the quiz:78Duplicate the quiz:
7479
@@ -81,8 +86,8 @@
81 >>> for row in teacher.query_all.css(sel):86 >>> for row in teacher.query_all.css(sel):
82 ... columns = row.query_all.tag('td')87 ... columns = row.query_all.tag('td')
83 ... print ' | '.join([column.text for column in columns])88 ... print ' | '.join([column.text for column in columns])
84 DUPLICATED: Free/Open Source Software Quiz | 3 | Elkner, Jeffrey | ...89 DUPLICATED: Free/Open Source Software Quiz | 4 | Elkner, Jeffrey | ...
85 Free/Open Source Software Quiz | 3 | Elkner, Jeffrey | ...90 Free/Open Source Software Quiz | 4 | Elkner, Jeffrey | ...
8691
87 >>> teacher.query.link('DUPLICATED: Free/Open Source Software Quiz').click()92 >>> teacher.query.link('DUPLICATED: Free/Open Source Software Quiz').click()
88 >>> print_quiz_item_bodies(teacher)93 >>> print_quiz_item_bodies(teacher)
@@ -101,8 +106,14 @@
101 Who is Python's original author?106 Who is Python's original author?
102 </p>107 </p>
103 </div>108 </div>
109 <div ...>
110 <p>
111 What is the first prime number higher than 5?
112 </p>
113 </div>
104114
105 >>> print_quiz_item_types(teacher)115 >>> print_quiz_item_types(teacher)
106 Open116 Open
107 Multiple Selection117 Multiple Selection
108 Selection 118 Selection
119 Rational
109120
=== modified file 'src/schooltool/quiz/browser/stests/quiz_export.txt'
--- src/schooltool/quiz/browser/stests/quiz_export.txt 2012-10-10 23:31:01 +0000
+++ src/schooltool/quiz/browser/stests/quiz_export.txt 2013-03-04 22:05:25 +0000
@@ -59,6 +59,11 @@
59 ... ('Eric Raymond', False),59 ... ('Eric Raymond', False),
60 ... ] 60 ... ]
61 >>> fill_question_form(teacher, 'Selection', body, choices)61 >>> fill_question_form(teacher, 'Selection', body, choices)
62 >>> teacher.query.id('form-buttons-submitadd').click()
63
64 >>> body = 'What is the first prime number higher than 5?'
65 >>> solution = '3 + 4'
66 >>> fill_question_form(teacher, 'Rational', body, solution=solution)
62 >>> teacher.query.id('form-buttons-submit').click()67 >>> teacher.query.id('form-buttons-submit').click()
6368
64Export the quiz:69Export the quiz:
6570
=== modified file 'src/schooltool/quiz/browser/stests/quiz_import.txt'
--- src/schooltool/quiz/browser/stests/quiz_import.txt 2012-12-06 18:26:06 +0000
+++ src/schooltool/quiz/browser/stests/quiz_import.txt 2013-03-04 22:05:25 +0000
@@ -35,7 +35,7 @@
35 >>> for row in teacher.query_all.css(sel):35 >>> for row in teacher.query_all.css(sel):
36 ... columns = row.query_all.tag('td')36 ... columns = row.query_all.tag('td')
37 ... print ' | '.join([column.text for column in columns])37 ... print ' | '.join([column.text for column in columns])
38 IMPORTED: Prime Factors 1 | 9 | Elkner, Jeffrey | ...38 IMPORTED: Prime Factors 1 | 11 | Elkner, Jeffrey | ...
3939
40 >>> teacher.query.link('IMPORTED: Prime Factors 1').click()40 >>> teacher.query.link('IMPORTED: Prime Factors 1').click()
41 >>> print teacher.query.css('.page .header h1').text41 >>> print teacher.query.css('.page .header h1').text
@@ -69,3 +69,9 @@
69 <div class="textarea-widget ...">69 <div class="textarea-widget ...">
70 ...70 ...
71 </div>71 </div>
72 <div class="textarea-widget ...">
73 ...
74 </div>
75 <div class="textarea-widget ...">
76 ...
77 </div>
7278
=== modified file 'src/schooltool/quiz/browser/stests/quiz_import.xls'
73Binary 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 differ79Binary 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
=== modified file 'src/schooltool/quiz/browser/stests/quiz_management.txt'
--- src/schooltool/quiz/browser/stests/quiz_management.txt 2012-09-06 17:35:42 +0000
+++ src/schooltool/quiz/browser/stests/quiz_management.txt 2013-03-04 22:05:25 +0000
@@ -138,6 +138,7 @@
138 Selection138 Selection
139 Multiple Selection139 Multiple Selection
140 Open140 Open
141 Rational
141142
142With Selection being selected by default:143With Selection being selected by default:
143144
@@ -319,8 +320,72 @@
319 Which of the following are free software?320 Which of the following are free software?
320 </p>321 </p>
321 </div>322 </div>
322323
323Let's add one more question:324And this time let's try a rational question. We'll try two examples
325of invalid solutions, to test the validator.
326
327 >>> teacher.query.link('Question').click()
328 >>> sel = '//span[@class="label" and text()="Rational"]'
329 >>> teacher.query.xpath(sel).click()
330
331It has a solution, not choices
332
333 >>> teacher.query.id('form-widgets-solution').is_displayed()
334 True
335
336An empty solution gives an error:
337
338 >>> body = 'What is the first prime number higher than 5?'
339 >>> solution = ''
340 >>> fill_question_form(teacher, 'Rational', body, solution=solution)
341 >>> teacher.query.id('form-buttons-submit').click()
342
343 >>> sel = '#form-widgets-solution-row .error div.error'
344 >>> print teacher.query.css(sel).text
345 Required input is missing.
346
347So does an unformatted one:
348
349 >>> body = 'What is the first prime number higher than 5?'
350 >>> solution = '3+4'
351 >>> fill_question_form(teacher, 'Rational', body, solution=solution)
352 >>> teacher.query.id('form-buttons-submit').click()
353
354 >>> sel = '#form-widgets-solution-row .error div.error'
355 >>> print teacher.query.css(sel).text
356 A syntactically valid solution is required.
357
358So let's close this, open a new one, and submit that:
359
360 >>> teacher.query.id('form-buttons-cancel').click()
361 >>> teacher.query.link('Question').click()
362
363 >>> body = 'What is the first prime number higher than 5?'
364 >>> solution = '3 + 4'
365 >>> fill_question_form(teacher, 'Rational', body, solution=solution)
366 >>> teacher.query.id('form-buttons-submit').click()
367
368Now the rational question should also be visible:
369
370 >>> print_quiz_item_bodies(teacher)
371 <div ...>
372 <p>
373 What is the meaning of RMS?
374 </p>
375 </div>
376 <div ...>
377 <p>
378 Which of the following are free software?
379 </p>
380 </div>
381 <div ...>
382 <p>
383 What is the first prime number higher than 5?
384 </p>
385 </div>
386
387
388And just one more:
324389
325 >>> teacher.query.link('Question').click()390 >>> teacher.query.link('Question').click()
326 >>> body = "Who is Python's original author?"391 >>> body = "Who is Python's original author?"
@@ -378,6 +443,11 @@
378 </div>443 </div>
379 <div ...>444 <div ...>
380 <p>445 <p>
446 What is the first prime number higher than 5?
447 </p>
448 </div>
449 <div ...>
450 <p>
381 Who is Python's original author?451 Who is Python's original author?
382 </p>452 </p>
383 </div>453 </div>
@@ -385,9 +455,10 @@
385 >>> print_quiz_item_types(teacher)455 >>> print_quiz_item_types(teacher)
386 Open456 Open
387 Multiple Selection457 Multiple Selection
458 Rational
388 Selection 459 Selection
389460
390Now, let's visit eache of these questions, to see their content. To do461Now, let's visit each of these questions, to see their content. To do
391so, we click the link in the No. column:462so, we click the link in the No. column:
392463
393 >>> teacher.query.link('1').click()464 >>> teacher.query.link('1').click()
@@ -488,6 +559,34 @@
488 >>> teacher.query.link('Done').click()559 >>> teacher.query.link('Done').click()
489560
490 >>> teacher.query.link('3').click()561 >>> teacher.query.link('3').click()
562
563 >>> print teacher.query.css('.page .header h1').text
564 Free/Open Source Software Quiz
565
566 >>> print teacher.query.css('.page .header h2').text
567 Rational Question
568
569 >>> sel = '.quiz-item-body .body .restructuredtext-widget'
570 >>> for content in teacher.query_all.css(sel):
571 ... print content
572 <div ...>
573 <p>
574 What is the first prime number higher than 5?
575 </p>
576 </div>
577
578 >>> sel = '.quiz-item-body .solution .restructuredtext-widget'
579 >>> for content in teacher.query_all.css(sel):
580 ... print content
581 <div ...>
582 <p>
583 3 + 4
584 </p>
585 </div>
586
587 >>> teacher.query.link('Done').click()
588
589 >>> teacher.query.link('4').click()
491590
492 >>> print teacher.query.css('.page .header h1').text591 >>> print teacher.query.css('.page .header h1').text
493 Free/Open Source Software Quiz592 Free/Open Source Software Quiz
@@ -581,6 +680,11 @@
581 </div>680 </div>
582 <div ...>681 <div ...>
583 <p>682 <p>
683 What is the first prime number higher than 5?
684 </p>
685 </div>
686 <div ...>
687 <p>
584 Who is Python's original author?688 Who is Python's original author?
585 </p>689 </p>
586 </div>690 </div>
@@ -593,6 +697,7 @@
593 >>> print_quiz_item_types(teacher)697 >>> print_quiz_item_types(teacher)
594 Open698 Open
595 Multiple Selection699 Multiple Selection
700 Rational
596 Selection 701 Selection
597 Multiple Selection702 Multiple Selection
598703
@@ -618,6 +723,11 @@
618 </div>723 </div>
619 <div ...>724 <div ...>
620 <p>725 <p>
726 What is the first prime number higher than 5?
727 </p>
728 </div>
729 <div ...>
730 <p>
621 Who is Python's original author?731 Who is Python's original author?
622 </p>732 </p>
623 </div>733 </div>
@@ -629,6 +739,7 @@
629739
630 >>> print_quiz_item_types(teacher)740 >>> print_quiz_item_types(teacher)
631 Multiple Selection741 Multiple Selection
742 Rational
632 Selection 743 Selection
633 Multiple Selection744 Multiple Selection
634745
635746
=== modified file 'src/schooltool/quiz/browser/stests/student_answers.txt'
--- src/schooltool/quiz/browser/stests/student_answers.txt 2012-10-07 17:40:13 +0000
+++ src/schooltool/quiz/browser/stests/student_answers.txt 2013-03-04 22:05:25 +0000
@@ -64,6 +64,11 @@
64 ... ('Eric Raymond', False),64 ... ('Eric Raymond', False),
65 ... ] 65 ... ]
66 >>> fill_question_form(teacher, 'Selection', body, choices)66 >>> fill_question_form(teacher, 'Selection', body, choices)
67 >>> teacher.query.id('form-buttons-submitadd').click()
68
69 >>> body = 'What is the first prime number higher than 5?'
70 >>> solution = '3 + 4'
71 >>> fill_question_form(teacher, 'Rational', body, solution=solution)
67 >>> teacher.query.id('form-buttons-submit').click()72 >>> teacher.query.id('form-buttons-submit').click()
6873
69Deploy the quiz to one of the instructor's sections:74Deploy the quiz to one of the instructor's sections:
@@ -123,6 +128,9 @@
123128
124 >>> sel = '#form-widgets-QuizItem-3-row .radio-widget[value="4"]'129 >>> sel = '#form-widgets-QuizItem-3-row .radio-widget[value="4"]'
125 >>> camila.query.css(sel).click()130 >>> camila.query.css(sel).click()
131
132 >>> answer_4 = '9/3 + 4'
133 >>> camila.query.id('form-widgets-QuizItem-4').ui.set_value(answer_4)
126134
127 >>> camila.query.id('form-buttons-submit').click()135 >>> camila.query.id('form-buttons-submit').click()
128136
@@ -151,6 +159,9 @@
151 >>> sel = '#form-widgets-QuizItem-3-row .radio-widget[value="3"]'159 >>> sel = '#form-widgets-QuizItem-3-row .radio-widget[value="3"]'
152 >>> mario.query.css(sel).click()160 >>> mario.query.css(sel).click()
153161
162 >>> answer_4 = '6'
163 >>> mario.query.id('form-widgets-QuizItem-4').ui.set_value(answer_4)
164
154 >>> mario.query.id('form-buttons-submit').click()165 >>> mario.query.id('form-buttons-submit').click()
155166
156 >>> sel = '.taken-quizzes-table tbody tr'167 >>> sel = '.taken-quizzes-table tbody tr'
@@ -178,6 +189,9 @@
178 >>> sel = '#form-widgets-QuizItem-3-row .radio-widget[value="3"]'189 >>> sel = '#form-widgets-QuizItem-3-row .radio-widget[value="3"]'
179 >>> liliana.query.css(sel).click()190 >>> liliana.query.css(sel).click()
180191
192 >>> answer_4 = '7'
193 >>> liliana.query.id('form-widgets-QuizItem-4').ui.set_value(answer_4)
194
181 >>> liliana.query.id('form-buttons-submit').click()195 >>> liliana.query.id('form-buttons-submit').click()
182196
183 >>> sel = '.taken-quizzes-table tbody tr'197 >>> sel = '.taken-quizzes-table tbody tr'
@@ -262,3 +276,18 @@
262 <table ...>276 <table ...>
263 </table>277 </table>
264 </div>278 </div>
279 Question 4
280 <div ...>
281 ...
282 <p>
283 What is the first prime number higher than 5?
284 </p>
285 ...
286 </div>
287 <div ...>
288 ...
289 <pre>
290 9/3 + 4
291 </pre>
292 ...
293 </div>
265294
=== modified file 'src/schooltool/quiz/browser/stests/test_selenium.py'
--- src/schooltool/quiz/browser/stests/test_selenium.py 2012-12-02 07:10:04 +0000
+++ src/schooltool/quiz/browser/stests/test_selenium.py 2013-03-04 22:05:25 +0000
@@ -56,7 +56,7 @@
56 sel = '//span[@class="label" and text()="%s"]' % type_56 sel = '//span[@class="label" and text()="%s"]' % type_
57 browser.query.xpath(sel).click()57 browser.query.xpath(sel).click()
58 browser.query.id('form-widgets-body').ui.set_value(body)58 browser.query.id('form-widgets-body').ui.set_value(body)
59 if type_ == 'Open':59 if type_ in ('Open','Rational'):
60 sel = 'form-widgets-solution'60 sel = 'form-widgets-solution'
61 browser.query.id(sel).ui.set_value(solution)61 browser.query.id(sel).ui.set_value(solution)
62 else:62 else:
6363
=== modified file 'src/schooltool/quiz/browser/templates/answers.pt'
--- src/schooltool/quiz/browser/templates/answers.pt 2012-11-29 15:28:05 +0000
+++ src/schooltool/quiz/browser/templates/answers.pt 2013-03-04 22:05:25 +0000
@@ -7,7 +7,7 @@
7 <div class="evaluation"7 <div class="evaluation"
8 tal:define="item evaluation/requirement;8 tal:define="item evaluation/requirement;
9 answer evaluation/answer;9 answer evaluation/answer;
10 is_open_question python:view.is_open(item)">10 has_open_view python:view.has_open_view(item);">
11 <h3 class="number" i18n:translate=""11 <h3 class="number" i18n:translate=""
12 style="border-top: 1px solid #000; border-bottom: 1px solid #000; padding: 4px 0;">12 style="border-top: 1px solid #000; border-bottom: 1px solid #000; padding: 4px 0;">
13 Question13 Question
@@ -22,13 +22,13 @@
22 Your answer22 Your answer
23 </h3>23 </h3>
24 <div class="answer">24 <div class="answer">
25 <div tal:condition="is_open_question"25 <div tal:condition="has_open_view"
26 tal:define="score_info python:view.score_info(evaluation)"26 tal:define="score_info python:view.score_info(evaluation)"
27 tal:attributes="class score_info/css">27 tal:attributes="class score_info/css">
28 <h3 class="score-type" tal:content="score_info/label" />28 <h3 class="score-type" tal:content="score_info/label" />
29 <div tal:content="structure python:view.render_pre(answer)" />29 <div tal:content="structure python:view.render_pre(answer)" />
30 </div>30 </div>
31 <table class="data" tal:condition="not:is_open_question">31 <table class="data" tal:condition="not:has_open_view">
32 <thead>32 <thead>
33 <th i18n:translate="">Choice</th>33 <th i18n:translate="">Choice</th>
34 <th i18n:translate="">Correct Answers</th>34 <th i18n:translate="">Correct Answers</th>
@@ -61,7 +61,7 @@
61 </table>61 </table>
62 </div>62 </div>
63 <tal:block define="graded python:view.is_graded(evaluation)"63 <tal:block define="graded python:view.is_graded(evaluation)"
64 condition="python:is_open_question and graded">64 condition="python:has_open_view and graded">
65 <h3 i18n:translate="">65 <h3 i18n:translate="">
66 Solution66 Solution
67 </h3>67 </h3>
6868
=== modified file 'src/schooltool/quiz/browser/templates/quiz_item_body.pt'
--- src/schooltool/quiz/browser/templates/quiz_item_body.pt 2012-10-25 20:31:25 +0000
+++ src/schooltool/quiz/browser/templates/quiz_item_body.pt 2013-03-04 22:05:25 +0000
@@ -1,5 +1,5 @@
1<tal:block i18n:domain="schooltool.quiz"1<tal:block i18n:domain="schooltool.quiz"
2 define="is_open_question view/is_open_question">2 define="has_open_field view/has_open_field">
3 <table class="quiz-item-body">3 <table class="quiz-item-body">
4 <thead>4 <thead>
5 <tr>5 <tr>
@@ -12,7 +12,8 @@
12 </tr>12 </tr>
13 </tbody>13 </tbody>
14 </table>14 </table>
15 <table class="data quiz-item-body" tal:condition="is_open_question">15 <table class="data quiz-item-body"
16 tal:condition="has_open_field">
16 <thead>17 <thead>
17 <tr>18 <tr>
18 <th i18n:translate="">Solution</th>19 <th i18n:translate="">Solution</th>
@@ -25,7 +26,8 @@
25 </tbody>26 </tbody>
26 </table>27 </table>
27 <table28 <table
28 class="data quiz-item-body" tal:condition="not:is_open_question"29 class="data quiz-item-body"
30 tal:condition="not:has_open_field"
29 tal:define="flourish nocall:context/++resource++schooltool.quiz.flourish;31 tal:define="flourish nocall:context/++resource++schooltool.quiz.flourish;
30 correct_icon flourish/correct-icon.png;32 correct_icon flourish/correct-icon.png;
31 error_icon flourish/error-icon.png;">33 error_icon flourish/error-icon.png;">
3234
=== modified file 'src/schooltool/quiz/browser/templates/quiz_item_form_script.pt'
--- src/schooltool/quiz/browser/templates/quiz_item_form_script.pt 2012-10-25 20:31:25 +0000
+++ src/schooltool/quiz/browser/templates/quiz_item_form_script.pt 2013-03-04 22:05:25 +0000
@@ -2,6 +2,7 @@
2<tal:script2<tal:script
3 tal:define="widgets view/view/widgets;3 tal:define="widgets view/view/widgets;
4 open_question_token view/view/_open_question_token;4 open_question_token view/view/_open_question_token;
5 rational_question_token view/view/_rational_question_token;
5 course nocall:widgets/course|nothing;6 course nocall:widgets/course|nothing;
6 skill nocall:widgets/skill|nothing;"7 skill nocall:widgets/skill|nothing;"
7 tal:replace="structure scriptlocal:8 tal:replace="structure scriptlocal:
@@ -9,6 +10,7 @@
9 choices_id widgets/choices/id;10 choices_id widgets/choices/id;
10 solution_id widgets/solution/id;11 solution_id widgets/solution/id;
11 open_question_token;12 open_question_token;
13 rational_question_token;
12 course_id course/id|nothing;14 course_id course/id|nothing;
13 course_novalue_token course/noValueToken|nothing;15 course_novalue_token course/noValueToken|nothing;
14 skill_id skill/id|nothing;16 skill_id skill/id|nothing;
@@ -24,6 +26,7 @@
24 skill_id = ST.local.skill_id,26 skill_id = ST.local.skill_id,
25 skill_novalue_token = ST.local.skill_novalue_token,27 skill_novalue_token = ST.local.skill_novalue_token,
26 open_question_token = ST.local.open_question_token;28 open_question_token = ST.local.open_question_token;
29 rational_question_token = ST.local.rational_question_token;
27 return function(e) {30 return function(e) {
28 var item_types = $('input[id^="'+type_id+'"]'),31 var item_types = $('input[id^="'+type_id+'"]'),
29 course,32 course,
@@ -66,7 +69,8 @@
66 var type_value = $(this).attr('value'),69 var type_value = $(this).attr('value'),
67 choices_row = $('.row[id^="'+choices_id+'"]'),70 choices_row = $('.row[id^="'+choices_id+'"]'),
68 solution_row = $('.row[id^="'+solution_id+'"]');71 solution_row = $('.row[id^="'+solution_id+'"]');
69 if (type_value == open_question_token) {72 if ((type_value == open_question_token)
73 || (type_value == rational_question_token)){
70 solution_row.show();74 solution_row.show();
71 choices_row.hide();75 choices_row.hide();
72 } else {76 } else {
7377
=== modified file 'src/schooltool/quiz/quiz.py'
--- src/schooltool/quiz/quiz.py 2013-01-10 07:34:28 +0000
+++ src/schooltool/quiz/quiz.py 2013-03-04 22:05:25 +0000
@@ -78,7 +78,12 @@
78 name = chooser.chooseName('', copy)78 name = chooser.chooseName('', copy)
79 item_container[name] = copy79 item_container[name] = copy
80 result.items.add(copy)80 result.items.add(copy)
81<<<<<<< TREE
81 result.order.append(name)82 result.order.append(name)
83=======
84 for item_id in self.order:
85 result.order.append(item_id) #formerly copy.order...
86>>>>>>> MERGE-SOURCE
82 return result87 return result
8388
84 def updateOrder(self, item_id, position):89 def updateOrder(self, item_id, position):
8590
=== modified file 'src/schooltool/quiz/quizitem.py'
--- src/schooltool/quiz/quizitem.py 2013-01-17 08:12:40 +0000
+++ src/schooltool/quiz/quizitem.py 2013-03-04 22:05:25 +0000
@@ -102,6 +102,7 @@
102 ('selection', _('Selection')),102 ('selection', _('Selection')),
103 ('multiple', _('Multiple Selection')),103 ('multiple', _('Multiple Selection')),
104 ('open', _('Open')),104 ('open', _('Open')),
105 ('rational', _('Rational')),
105 ]106 ]
106 terms = [SimpleTerm(item, token=token, title=item)107 terms = [SimpleTerm(item, token=token, title=item)
107 for token, item in items]108 for token, item in items]
108109
=== added file 'src/schooltool/quiz/rational.py'
--- src/schooltool/quiz/rational.py 1970-01-01 00:00:00 +0000
+++ src/schooltool/quiz/rational.py 2013-03-04 22:05:25 +0000
@@ -0,0 +1,125 @@
1'''rational.py: Module to do rational arithmetic.
2
3 For full documentation, see http://www.nmt.edu/tcc/help/lang/python/examples/rational/.
4 Exports:
5 gcd ( a, b ):
6 [ a and b are integers ->
7 return the greatest common divisor of a and b ]
8 Rational ( a, b ):
9 [ (a is a nonnegative integer) and
10 (b is a positive integer) ->
11 return a new Rational instance with
12 numerator a and denominator b ]
13 .n: [ the numerator ]
14 .d: [ the denominator ]
15 .__add__(self, other):
16 [ other is a Rational instance ->
17 return the sum of self and other as a Rational instance ]
18 .__sub__(self, other):
19 [ other is a Rational instance ->
20 return the difference of self and other as a Rational
21 instance ]
22 .__mul__(self, other):
23 [ other is a Rational instance ->
24 return the product of self and other as a Rational
25 instance ]
26 .__div__(self, other):
27 [ other is a Rational instance ->
28 return the quotient of self and other as a Rational
29 instance ]
30 .__exp__(self, integer):
31 [ integer is an Integer instance ->
32 return self exponentiated to other as a Rational
33 instance ]
34 .__str__(self):
35 [ return a string representation of self ]
36 .__float__(self):
37 [ return a float approximation of self ]
38 .mixed(self):
39 [ return a string representation of self as a mixed
40 fraction ]
41'''
42def gcd ( a, b ):
43 '''Greatest common divisor function; Euclid's algorithm.
44
45 [ a and b are integers ->
46 return the greatest common divisor of a and b ]
47 '''
48 if b == 0:
49 return a
50 else:
51 return gcd(b, a%b)
52
53class Rational:
54 """An instance represents a rational number.
55 """
56 def __init__ ( self, a, b ):
57 """Constructor for Rational.
58 """
59 if b == 0:
60 raise ZeroDivisionError, ( "Denominator of a rational "
61 "may not be zero." )
62 else:
63 g = gcd ( a, b )
64 self.n = a / g
65 self.d = b / g
66 def __add__ ( self, other ):
67 """Add two rational numbers.
68 """
69 return Rational ( self.n * other.d + other.n * self.d,
70 self.d * other.d )
71 def __sub__ ( self, other ):
72 """Return self minus other.
73 """
74 return Rational ( self.n * other.d - other.n * self.d,
75 self.d * other.d )
76 def __mul__ ( self, other ):
77 """Implement multiplication.
78 """
79 return Rational ( self.n * other.n, self.d * other.d )
80 def __div__ ( self, other ):
81 """Implement division.
82 """
83 return Rational ( self.n * other.d, self.d * other.n )
84 def __pow__ ( self, integer):
85 """Implement division.
86 """
87 return Rational ( self.n ** integer, self.d ** integer )
88 def __str__ ( self ):
89 '''Display self as a string.
90 '''
91 return "%d/%d" % ( self.n, self.d )
92 def __repr__ (self):
93 """ Works
94 """
95 return str(self)
96 def __eq__ (self, other):
97 """ test for equality
98 """
99 if type(other) != type(self):
100 return False
101 return (self.d == other.d and self.n == other.n)
102 def __float__ ( self ):
103 """Implement the float() conversion function.
104 """
105 return float ( self.n ) / float ( self.d )
106 def mixed ( self ):
107 """Render self as a mixed fraction in string form.
108 """
109 #-- 1 --
110 # [ whole := self.n / self.d, truncated
111 # n2 := self.n % self.d ]
112 whole, n2 = divmod ( self.n, self.d )
113 #-- 2 --
114 # [ if self.d == 1 ->
115 # return str(self.n)
116 # else if whole == zero ->
117 # return str(n2)+"/"+str(self.d)
118 # else ->
119 # return str(whole)+" and "+str(n2)+"/"+str(self.d) ]
120 if self.d == 1:
121 return str(self.n)
122 elif whole == 0:
123 return "%s/%s" % (n2, self.d)
124 else:
125 return "%s and %s/%s" % (whole, n2, self.d)
0126
=== added file 'src/schooltool/quiz/utils.py'
--- src/schooltool/quiz/utils.py 1970-01-01 00:00:00 +0000
+++ src/schooltool/quiz/utils.py 2013-03-04 22:05:25 +0000
@@ -0,0 +1,135 @@
1#
2# SchoolTool - common information systems platform for school administration
3# Copyright (c) 2012 Shuttleworth Foundation,
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License along
16# with this program; if not, write to the Free Software Foundation, Inc.,
17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18#
19"""
20SchoolTool Quiz utility functions.
21"""
22
23from cgi import escape
24from docutils.core import publish_parts
25
26from schooltool.quiz import QuizMessage as _
27from schooltool.quiz.rst import QuizHTMLWriter
28
29import rational
30
31# XXX: customize and use IApplicationPreferences for this
32DATETIME_FORMAT = _('YYYY-MM-DD HH:MM:SS')
33DATETIME_INPUT_FORMAT = '%Y-%m-%d %H:%M:%S'
34DATE_FORMAT = _('YYYY-MM-DD')
35DATE_INPUT_FORMAT = '%Y-%m-%d'
36
37
38def render_rst(rst):
39 # XXX: sanitize before rendering
40 return publish_parts(rst, writer=QuizHTMLWriter())['body']
41
42
43def render_pre(text):
44 return '<pre>%s</pre>' % escape(text)
45
46
47def render_datetime(value):
48 return value.strftime(DATETIME_INPUT_FORMAT)
49
50
51def render_date(value):
52 return value.strftime(DATE_INPUT_FORMAT)
53
54
55def render_bool(value):
56 return [_('No'), _('Yes')][value]
57
58
59def format_percentage(number):
60 return '%.0f%%' % number
61
62
63def Validator(instring):
64 instring = str(instring)
65 inlist = instring.split(' ')
66 operators = ['+','-','*','/','**']
67 validated = []
68 while '' in inlist:
69 inlist.remove('')
70 for substring in inlist: #Convert to list
71 if substring in operators:
72 validated.append(substring)
73 elif CharacterValidation(substring):
74 #Convert to Rational
75 if "/" in substring:
76 parts = substring.split("/")
77 if len(parts) != 2:
78 return False
79 A = rational.Rational(int(parts[0]),int(parts[1]))
80 validated.append(A)
81 else:
82 A = rational.Rational(int(substring),1)
83 validated.append(A)
84 else:
85 return False
86 dup = 0
87 for item in validated: #Check for order
88 if dup == 0:
89 if type(item).__name__ != "instance":
90 return False
91 dup = 1
92 elif dup == 1:
93 if not item in operators:
94 return False
95 dup = 0
96 if validated[-1] in operators:
97 return False
98 for index in range(0,len(validated)-1):
99 if validated[index] == "**":
100 if validated[index+1].d != 1:
101 return False
102 return validated
103
104
105def CharacterValidation(string):
106 valid = "1234567890/"
107 for char in string:
108 if not char in valid:
109 return False
110 return True
111
112def Evaluator(validated):
113 if len(validated) == 1:
114 return validated[0]
115 for index in range(0,(len(validated)-1)):
116 if type(validated[index]) == type("") and validated[index] == "**":
117 result = validated[index-1] ** validated[index+1].n
118 return Evaluator(validated[:index-1]+[result]+validated[index+2:])
119 for index in range(0,(len(validated)-1)):
120 if type(validated[index]) == type("") and validated[index] == "*":
121 result = validated[index-1] * validated[index+1]
122 return Evaluator(validated[:index-1]+[result]+validated[index+2:])
123 elif type(validated[index]) == type("") and validated[index] == "/":
124 result = validated[index-1] / validated[index+1]
125 return Evaluator(validated[:index-1]+[result]+validated[index+2:])
126 for index in range(0,(len(validated)-1)):
127 if type(validated[index]) == type("") and validated[index] == "+":
128 result = validated[index-1] + validated[index+1]
129 return Evaluator(validated[:index-1]+[result]+validated[index+2:])
130 elif type(validated[index]) == type("") and validated[index] == "-":
131 result = validated[index-1] - validated[index+1]
132 return Evaluator(validated[:index-1]+[result]+validated[index+2:])
133
134def RationalValidator(studentstring, teacherstring):
135 return Evaluator(Validator(studentstring)) == Evaluator(Validator(teacherstring))
0136
=== removed file 'src/schooltool/quiz/utils.py'
--- src/schooltool/quiz/utils.py 2012-09-19 20:06:18 +0000
+++ src/schooltool/quiz/utils.py 1970-01-01 00:00:00 +0000
@@ -1,59 +0,0 @@
1#
2# SchoolTool - common information systems platform for school administration
3# Copyright (c) 2012 Shuttleworth Foundation,
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License along
16# with this program; if not, write to the Free Software Foundation, Inc.,
17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18#
19"""
20SchoolTool Quiz utility functions.
21"""
22
23from cgi import escape
24from docutils.core import publish_parts
25
26from schooltool.quiz import QuizMessage as _
27from schooltool.quiz.rst import QuizHTMLWriter
28
29
30# XXX: customize and use IApplicationPreferences for this
31DATETIME_FORMAT = _('YYYY-MM-DD HH:MM:SS')
32DATETIME_INPUT_FORMAT = '%Y-%m-%d %H:%M:%S'
33DATE_FORMAT = _('YYYY-MM-DD')
34DATE_INPUT_FORMAT = '%Y-%m-%d'
35
36
37def render_rst(rst):
38 # XXX: sanitize before rendering
39 return publish_parts(rst, writer=QuizHTMLWriter())['body']
40
41
42def render_pre(text):
43 return '<pre>%s</pre>' % escape(text)
44
45
46def render_datetime(value):
47 return value.strftime(DATETIME_INPUT_FORMAT)
48
49
50def render_date(value):
51 return value.strftime(DATE_INPUT_FORMAT)
52
53
54def render_bool(value):
55 return [_('No'), _('Yes')][value]
56
57
58def format_percentage(number):
59 return '%.0f%%' % number

Subscribers

People subscribed via source and target branches

to all changes: