Commit 775dd8eb5a7e4ef72e414a250667a8a28c0f877a

Authored by Miguel Barão
1 parent 9aeb5485
Exists in master and in 1 other branch dev

- reorganized how the state machine of question answering to be in two steps (post and get).

1 1
2 # BUGS 2 # BUGS
3 3
4 -- errar no ultimo topico nao mostra solucao? 4 +- falha no file handler de vez em quando, nao sei porquê...
5 - nos topicos learn.yaml, qd falha acrescenta no fim. nao faz sentido. 5 - nos topicos learn.yaml, qd falha acrescenta no fim. nao faz sentido.
6 - ocorreu uma vez o sqlalchemy dar mesg erro a indicar que as threads sao diferents quando se faz o get da primeira pergunta do topico. Muitas vezes nao mostar erro, mas a pagina da erro ou fica em branco... 6 - ocorreu uma vez o sqlalchemy dar mesg erro a indicar que as threads sao diferents quando se faz o get da primeira pergunta do topico. Muitas vezes nao mostar erro, mas a pagina da erro ou fica em branco...
7 7
@@ -32,6 +32,7 @@ @@ -32,6 +32,7 @@
32 32
33 # FIXED 33 # FIXED
34 34
  35 +- errar no ultimo topico nao mostra solucao?
35 - quando a pergunta devolve comments, este é apresentado, mas fica persistente nas tentativas seguintes. devia ser limpo apos a segunda submissao. 36 - quando a pergunta devolve comments, este é apresentado, mas fica persistente nas tentativas seguintes. devia ser limpo apos a segunda submissao.
36 - na definicao dos topicos, indicar: 37 - na definicao dos topicos, indicar:
37 "file: questions.yaml" (default questions.yaml) 38 "file: questions.yaml" (default questions.yaml)
demo/math/questions.yaml
@@ -15,48 +15,48 @@ @@ -15,48 +15,48 @@
15 correct: [1,1,0,0,0] 15 correct: [1,1,0,0,0]
16 choose: 3 16 choose: 3
17 17
18 -# - ref: numbers  
19 -# type: checkbox  
20 -# title: Números pares e primos  
21 -# text: Indique as afirmações verdadeiras.  
22 -# options:  
23 -# - ['3 é primo', '4 é primo']  
24 -# - ['2 é par', '3 é par']  
25 -# - ['1 é ímpar', '2 é ímpar']  
26 -# correct: [1,1,1] 18 +- ref: numbers
  19 + type: checkbox
  20 + title: Números pares e primos
  21 + text: Indique as afirmações verdadeiras.
  22 + options:
  23 + - ['3 é primo', '4 é primo']
  24 + - ['2 é par', '3 é par']
  25 + - ['1 é ímpar', '2 é ímpar']
  26 + correct: [1,1,1]
27 27
28 -# -  
29 -# ref: prime_numbers  
30 -# type: radio  
31 -# title: Números primos  
32 -# text: Qual dos seguintes números é primo?  
33 -# options:  
34 -# - 13  
35 -# - 12  
36 -# - 14  
37 -# - 1, a **unidade** 28 +-
  29 + ref: prime_numbers
  30 + type: radio
  31 + title: Números primos
  32 + text: Qual dos seguintes números é primo?
  33 + options:
  34 + - 13
  35 + - 12
  36 + - 14
  37 + - 1, a **unidade**
38 38
39 39
40 -# # ---------------------------------------------------------------------------  
41 -# -  
42 -# ref: math-expressions  
43 -# type: checkbox  
44 -# title: Expressões matemáticas  
45 -# text: Quais das seguintes expressões são verdadeiras?  
46 -# options:  
47 -# - $1 > 0$  
48 -# - $\sqrt{3} > \sqrt{2}$  
49 -# - $e^{i\pi} + 1 = 0$  
50 -# - $\frac{\partial f(x,y)}{\partial z} = 1$  
51 -# - $-1 > 1$  
52 -# # how many points for each checkmark (normalized afterwards):  
53 -# correct: [1, 1, 1, -1, -1] 40 +# ---------------------------------------------------------------------------
  41 +-
  42 + ref: math-expressions
  43 + type: checkbox
  44 + title: Expressões matemáticas
  45 + text: Quais das seguintes expressões são verdadeiras?
  46 + options:
  47 + - $1 > 0$
  48 + - $\sqrt{3} > \sqrt{2}$
  49 + - $e^{i\pi} + 1 = 0$
  50 + - $\frac{\partial f(x,y)}{\partial z} = 1$
  51 + - $-1 > 1$
  52 + # how many points for each checkmark (normalized afterwards):
  53 + correct: [1, 1, 1, -1, -1]
54 54
55 -# # ---------------------------------------------------------------------------  
56 -# -  
57 -# ref: overflow  
58 -# type: generator  
59 -# script: generate-overflow.py  
60 -# # opcional  
61 -# arg: "11,120"  
62 -# # the script should print a question dict in yaml format. 55 +# ---------------------------------------------------------------------------
  56 +-
  57 + ref: overflow
  58 + type: generator
  59 + script: generate-overflow.py
  60 + # opcional
  61 + arg: "11,120"
  62 + # the script should print a question dict in yaml format.
@@ -135,28 +135,22 @@ class StudentKnowledge(object): @@ -135,28 +135,22 @@ class StudentKnowledge(object):
135 135
136 if grade > 0.999: 136 if grade > 0.999:
137 self.correct_answers += 1 137 self.correct_answers += 1
138 - if self.next_question() is None:  
139 - action = 'finished_topic'  
140 - else:  
141 - action = 'new_question' 138 + self.next_question()
  139 + action = 'right'
142 140
143 else: 141 else:
144 self.wrong_answers += 1 142 self.wrong_answers += 1
145 self.current_question['tries'] -= 1 143 self.current_question['tries'] -= 1
146 logger.debug(f'Wrong answers = {self.wrong_answers}; Tries = {self.current_question["tries"]}') 144 logger.debug(f'Wrong answers = {self.wrong_answers}; Tries = {self.current_question["tries"]}')
147 145
148 - if self.current_question['tries'] <= 0: 146 + if self.current_question['tries'] > 0:
  147 + action = 'try_again'
  148 + else:
  149 + action = 'wrong'
149 if self.current_question['append_wrong']: 150 if self.current_question['append_wrong']:
150 logger.debug("Appending new instance of this question to the end") 151 logger.debug("Appending new instance of this question to the end")
151 self.questions.append(self.factory[q['ref']].generate()) 152 self.questions.append(self.factory[q['ref']].generate())
152 -  
153 - if self.next_question() is None:  
154 - action = 'finished_topic'  
155 - else:  
156 - action = 'wrong' # FIXME show comments  
157 -  
158 - else:  
159 - action = 'try_again' 153 + self.next_question()
160 154
161 # returns corrected question (not new one) which might include comments 155 # returns corrected question (not new one) which might include comments
162 return q, action 156 return q, action
@@ -39,6 +39,7 @@ class Question(dict): @@ -39,6 +39,7 @@ class Question(dict):
39 'title': '', 39 'title': '',
40 'answer': None, 40 'answer': None,
41 'comments': '', 41 'comments': '',
  42 + 'solution': '',
42 'files': {}, 43 'files': {},
43 }) 44 })
44 45
@@ -170,6 +170,9 @@ class TopicHandler(BaseHandler): @@ -170,6 +170,9 @@ class TopicHandler(BaseHandler):
170 # Serves files from the /public subdir of the topics. 170 # Serves files from the /public subdir of the topics.
171 # Based on https://bhch.github.io/posts/2017/12/serving-large-files-with-tornado-safely-without-blocking/ 171 # Based on https://bhch.github.io/posts/2017/12/serving-large-files-with-tornado-safely-without-blocking/
172 # ---------------------------------------------------------------------------- 172 # ----------------------------------------------------------------------------
  173 +
  174 +# FIXME error in many situations... images are not shown...
  175 +
173 class FileHandler(BaseHandler): 176 class FileHandler(BaseHandler):
174 SUPPORTED_METHODS = ['GET'] 177 SUPPORTED_METHODS = ['GET']
175 178
@@ -221,18 +224,29 @@ class QuestionHandler(BaseHandler): @@ -221,18 +224,29 @@ class QuestionHandler(BaseHandler):
221 logging.debug('QuestionHandler.get()') 224 logging.debug('QuestionHandler.get()')
222 user = self.current_user 225 user = self.current_user
223 q = self.learn.get_current_question(user) 226 q = self.learn.get_current_question(user)
224 - question_html = self.render_string(self.templates[q['type']],  
225 - question=q, md=md_to_html)  
226 -  
227 - response = {  
228 - 'method': 'new_question',  
229 - 'params': {  
230 - 'type': q['type'],  
231 - 'question': tornado.escape.to_unicode(question_html),  
232 - 'progress': self.learn.get_student_progress(user),  
233 - 'tries': q['tries'],  
234 - },  
235 - } 227 +
  228 + if q is not None:
  229 + question_html = self.render_string(self.templates[q['type']],
  230 + question=q, md=md_to_html)
  231 + response = {
  232 + 'method': 'new_question',
  233 + 'params': {
  234 + 'type': q['type'],
  235 + 'question': tornado.escape.to_unicode(question_html),
  236 + 'progress': self.learn.get_student_progress(user),
  237 + 'tries': q['tries'],
  238 + },
  239 + }
  240 +
  241 + else:
  242 + finished_topic_html = self.render_string('finished_topic.html')
  243 + response = {
  244 + 'method': 'finished_topic',
  245 + 'params': {
  246 + 'question': tornado.escape.to_unicode(finished_topic_html)
  247 + }
  248 + }
  249 +
236 self.write(response) 250 self.write(response)
237 251
238 # --- post answer, returns what to do next: shake, new_question, finished 252 # --- post answer, returns what to do next: shake, new_question, finished
@@ -251,26 +265,30 @@ class QuestionHandler(BaseHandler): @@ -251,26 +265,30 @@ class QuestionHandler(BaseHandler):
251 elif qtype != 'checkbox': # radio, text, textarea, ... 265 elif qtype != 'checkbox': # radio, text, textarea, ...
252 answer = answer[0] 266 answer = answer[0]
253 267
254 - # check answer in another thread (nonblocking)  
255 - # and get corrected question 268 + # check answer (nonblocking) and get corrected question
256 q, action = await self.learn.check_answer(user, answer) 269 q, action = await self.learn.check_answer(user, answer)
257 270
258 - print(action) 271 + response = {'method': action, 'params': {}}
  272 +
  273 + if action == 'right': # get next question in the topic
  274 + comments_html = self.render_string('comments-right.html',
  275 + comments=q['comments'], md=md_to_html)
  276 +
  277 + response['params'] = {
  278 + 'progress': self.learn.get_student_progress(user),
  279 + 'comments': tornado.escape.to_unicode(comments_html),
  280 + 'tries': q['tries'],
  281 + }
259 282
260 - if action == 'try_again': 283 + elif action == 'try_again':
261 comments_html = self.render_string('comments.html', 284 comments_html = self.render_string('comments.html',
262 comments=q['comments'], md=md_to_html) 285 comments=q['comments'], md=md_to_html)
263 286
264 - response = {  
265 - 'method': 'try_again',  
266 - 'params': { 287 + response['params'] = {
267 'progress': self.learn.get_student_progress(user), 288 'progress': self.learn.get_student_progress(user),
268 'comments': tornado.escape.to_unicode(comments_html), # FIXME 289 'comments': tornado.escape.to_unicode(comments_html), # FIXME
269 'tries': q['tries'], 290 'tries': q['tries'],
270 } 291 }
271 - }  
272 - print(response)  
273 - self.write(response)  
274 292
275 elif action == 'wrong': # no more tries 293 elif action == 'wrong': # no more tries
276 comments_html = self.render_string('comments.html', 294 comments_html = self.render_string('comments.html',
@@ -278,52 +296,17 @@ class QuestionHandler(BaseHandler): @@ -278,52 +296,17 @@ class QuestionHandler(BaseHandler):
278 solution_html = self.render_string('solution.html', 296 solution_html = self.render_string('solution.html',
279 solution=q['solution'], md=md_to_html) 297 solution=q['solution'], md=md_to_html)
280 298
281 - # template = self.templates[question['type']]  
282 - # question_html = self.render_string(template, question=question, md=md_to_html)  
283 - response = {  
284 - 'method': 'wrong', # FIXME js  
285 - 'params': {  
286 - # 'question': tornado.escape.to_unicode(question_html),  
287 - 'progress': self.learn.get_student_progress(user),  
288 - 'comments': tornado.escape.to_unicode(comments_html), # FIXME  
289 - 'solution': tornado.escape.to_unicode(solution_html), # FIXME  
290 - 'tries': q['tries'],  
291 - }  
292 - }  
293 - print(response)  
294 - self.write(response)  
295 -  
296 - elif action == 'new_question': # get next question in the topic  
297 - self.get()  
298 - # question = self.learn.get_current_question(user)  
299 -  
300 - # template = self.templates[question['type']]  
301 - # question_html = self.render_string(template,  
302 - # question=question, md=md_to_html)  
303 - # response = {  
304 - # 'method': 'new_question',  
305 - # 'params': {  
306 - # 'type': question['type'],  
307 - # 'question': tornado.escape.to_unicode(question_html),  
308 - # 'progress': self.learn.get_student_progress(user),  
309 - # 'tries': question['tries'],  
310 - # }  
311 - # }  
312 - # print(response)  
313 - # self.write(response)  
314 -  
315 - elif action == 'finished_topic': # right answer, finished topic  
316 - finished_topic_html = self.render_string('finished_topic.html')  
317 - response = {  
318 - 'method': 'finished_topic',  
319 - 'params': {  
320 - 'question': tornado.escape.to_unicode(finished_topic_html)  
321 - } 299 + response['params'] = {
  300 + 'progress': self.learn.get_student_progress(user),
  301 + 'comments': tornado.escape.to_unicode(comments_html),
  302 + 'solution': tornado.escape.to_unicode(solution_html),
  303 + 'tries': q['tries'],
322 } 304 }
323 - self.write(response)  
324 305
325 else: 306 else:
326 - logger.error(f'Unknown action {action}') 307 + logger.error(f'Unknown action: {action}')
  308 +
  309 + self.write(response)
327 310
328 311
329 # ---------------------------------------------------------------------------- 312 # ----------------------------------------------------------------------------
static/js/topic.js
@@ -7,6 +7,38 @@ $.fn.extend({ @@ -7,6 +7,38 @@ $.fn.extend({
7 } 7 }
8 }); 8 });
9 9
  10 +// Get current question
  11 +function getQuestion() {
  12 + $.ajax({
  13 + type: "GET",
  14 + url: "/question",
  15 + dataType: "json", // expected from server
  16 + success: updateQuestion,
  17 + error: function() {alert("O servidor não responde.");}
  18 + });
  19 +}
  20 +
  21 +
  22 +// updates question according to the response given by the server
  23 +function updateQuestion(response) {
  24 + var method = response["method"];
  25 + var params = response["params"];
  26 +
  27 + switch (method) {
  28 + case "new_question":
  29 + console.log(params["type"]);
  30 + new_question(params["type"], params["question"], params["tries"], params["progress"]);
  31 + break;
  32 + case "finished_topic":
  33 + $('#submit').hide();
  34 + $("#content").html(response["params"]["question"]);
  35 + $('#topic_progress').css('width', '100%').attr('aria-valuenow', 100);
  36 + $("#content").animateCSS('tada');
  37 + setTimeout(function(){ window.location.replace('/'); }, 2000);
  38 + break;
  39 + }
  40 +}
  41 +
10 42
11 function new_question(type, question, tries, progress) { 43 function new_question(type, question, tries, progress) {
12 console.log("new_question " + type); 44 console.log("new_question " + type);
@@ -39,19 +71,21 @@ function new_question(type, question, tries, progress) { @@ -39,19 +71,21 @@ function new_question(type, question, tries, progress) {
39 } 71 }
40 72
41 73
42 -  
43 -// updates question according to the response given by the server  
44 -function updateQuestion(response){ 74 +function getFeedback(response) {
45 console.log('updateQuestion '+response["method"]); 75 console.log('updateQuestion '+response["method"]);
46 76
47 var method = response["method"]; 77 var method = response["method"];
48 var params = response["params"]; 78 var params = response["params"];
49 79
50 -  
51 switch (method) { 80 switch (method) {
52 - case "new_question":  
53 - console.log(params["type"]);  
54 - new_question(params["type"], params["question"], params["tries"], params["progress"]); 81 + case "right":
  82 + console.log(params['comments']);
  83 + $('#comments').html(params['comments']);
  84 +
  85 + // MathJax.Hub.Queue(["Typeset", MathJax.Hub, "#comments"]);
  86 + $("#submit").html("Continuar");
  87 + $("#submit").off();
  88 + $("#submit").click(getQuestion);
55 break; 89 break;
56 90
57 case "try_again": 91 case "try_again":
@@ -75,30 +109,9 @@ function updateQuestion(response){ @@ -75,30 +109,9 @@ function updateQuestion(response){
75 $("#submit").off(); 109 $("#submit").off();
76 $("#submit").click(getQuestion); 110 $("#submit").click(getQuestion);
77 break; 111 break;
78 -  
79 - case "finished_topic":  
80 - $('#submit').hide(); //css("visibility", "hidden");  
81 - $("#content").html(response["params"]["question"]);  
82 - $('#topic_progress').css('width', '100%').attr('aria-valuenow', 100);  
83 - $("#content").animateCSS('tada');  
84 - setTimeout(function(){ window.location.replace('/'); }, 2000);  
85 - break;  
86 } 112 }
87 } 113 }
88 114
89 -// Get current question  
90 -function getQuestion() {  
91 - console.log("getQuestion");  
92 -  
93 - $.ajax({  
94 - type: "GET",  
95 - url: "/question",  
96 - dataType: "json", // expected from server  
97 - success: updateQuestion,  
98 - error: function() {alert("O servidor não responde.");}  
99 - });  
100 -}  
101 -  
102 // Send answer and receive a response. 115 // Send answer and receive a response.
103 // The response can be a new_question or a shake if the answer is wrong, which 116 // The response can be a new_question or a shake if the answer is wrong, which
104 // is then passed to updateQuestion() 117 // is then passed to updateQuestion()
@@ -113,7 +126,7 @@ function postAnswer() { @@ -113,7 +126,7 @@ function postAnswer() {
113 url: "/question", 126 url: "/question",
114 data: $("#question_form").serialize(), // {'a':10,'b':20}, 127 data: $("#question_form").serialize(), // {'a':10,'b':20},
115 dataType: "json", // expected from server 128 dataType: "json", // expected from server
116 - success: updateQuestion, 129 + success: getFeedback,
117 error: function() {alert("O servidor não responde.");} 130 error: function() {alert("O servidor não responde.");}
118 }); 131 });
119 } 132 }
templates/comments-right.html 0 → 100644
@@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
  1 +{% autoescape %}
  2 +
  3 +<div class="alert alert-success">
  4 + <i class="fas fa-thumbs-up fa-3x"></i> Certo!
  5 + {% if comments %}
  6 + <hr>
  7 + {{ md(comments) }}
  8 + {% end %}
  9 +</div>