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