Commit bbe90cadaea87632f8df2251a493f1cd720c82ae

Authored by Miguel Barão
1 parent 62e18fdc
Exists in master and in 1 other branch dev

- fix crash when topic has no questions (but not yet ok).

- shows comments on wrong answer.
1 1
2 # BUGS 2 # BUGS
3 3
  4 +- image brand da universidade está esbatida.
4 - generators e correct scripts que durem muito tempo podem bloquear o loop do tornado? 5 - generators e correct scripts que durem muito tempo podem bloquear o loop do tornado?
5 - detect questions in questions.yaml without ref -> error ou generate default. 6 - detect questions in questions.yaml without ref -> error ou generate default.
6 - topicos virtuais nao deveriam aparecer. na construção da árvore os sucessores seriam ligados directamente aos predecessores. 7 - topicos virtuais nao deveriam aparecer. na construção da árvore os sucessores seriam ligados directamente aos predecessores.
7 - Criar outra estrutura organizada em capítulos (conjuntos de tópicos). Permitir capítulos de capítulos, etc. talvez usar grafos de grafos... 8 - Criar outra estrutura organizada em capítulos (conjuntos de tópicos). Permitir capítulos de capítulos, etc. talvez usar grafos de grafos...
8 -- error if demo.yaml has no topics  
9 - session management. close after inactive time. 9 - session management. close after inactive time.
10 - generators not working: bcrypt (ver blog) 10 - generators not working: bcrypt (ver blog)
11 - tabelas nas perguntas radio/checkbox não ocupam todo o espaço como em question. 11 - tabelas nas perguntas radio/checkbox não ocupam todo o espaço como em question.
@@ -16,7 +16,6 @@ @@ -16,7 +16,6 @@
16 - each topic only loads a sample of K questions (max) in random order. 16 - each topic only loads a sample of K questions (max) in random order.
17 - servir imagens/ficheiros. 17 - servir imagens/ficheiros.
18 - pertuntas tipo tristate: (sim, não, não sei 18 - pertuntas tipo tristate: (sim, não, não sei
19 -- forçar reload das perguntas sem ter de deitar abaixo o servidor.  
20 - reload das perguntas enquanto online. 19 - reload das perguntas enquanto online.
21 - tabela de progresso de todos os alunos por topico. 20 - tabela de progresso de todos os alunos por topico.
22 - tabela com perguntas / quantidade de respostas certas/erradas. 21 - tabela com perguntas / quantidade de respostas certas/erradas.
@@ -31,6 +30,7 @@ @@ -31,6 +30,7 @@
31 30
32 # FIXED 31 # FIXED
33 32
  33 +- error if demo.yaml has no topics
34 - update de fontawesome para versão 5.0.6. 34 - update de fontawesome para versão 5.0.6.
35 - remover learn.css uma vez que nao é usado em lado nenhum? 35 - remover learn.css uma vez que nao é usado em lado nenhum?
36 - check if user already logged in 36 - check if user already logged in
@@ -32,7 +32,7 @@ class StudentKnowledge(object): @@ -32,7 +32,7 @@ class StudentKnowledge(object):
32 now = datetime.now() 32 now = datetime.now()
33 for s in state.values(): 33 for s in state.values():
34 dt = now - s['date'] 34 dt = now - s['date']
35 - s['level'] *= 0.975 ** dt.days # forgetting factor 35 + s['level'] *= 0.8 ** dt.days # forgetting factor 0.95
36 36
37 # compute recommended sequence of topics ['a', 'b', ...] 37 # compute recommended sequence of topics ['a', 'b', ...]
38 self.topic_sequence = list(nx.topological_sort(self.deps)) 38 self.topic_sequence = list(nx.topological_sort(self.deps))
@@ -45,7 +45,7 @@ class StudentKnowledge(object): @@ -45,7 +45,7 @@ class StudentKnowledge(object):
45 def unlock_topics(self): 45 def unlock_topics(self):
46 # minimum level that the dependencies of a topic must have 46 # minimum level that the dependencies of a topic must have
47 # for the topic to be unlocked. 47 # for the topic to be unlocked.
48 - min_level = 0.01 48 + min_level = 0.2
49 49
50 for topic in self.topic_sequence: 50 for topic in self.topic_sequence:
51 if topic not in self.state: # if locked 51 if topic not in self.state: # if locked
@@ -75,7 +75,6 @@ class StudentKnowledge(object): @@ -75,7 +75,6 @@ class StudentKnowledge(object):
75 return False 75 return False
76 76
77 self.current_topic = topic 77 self.current_topic = topic
78 - # logger.info(f'Topic set to "{topic}"')  
79 78
80 # generate question instances for current topic 79 # generate question instances for current topic
81 factory = self.deps.node[topic]['factory'] 80 factory = self.deps.node[topic]['factory']
@@ -84,9 +83,14 @@ class StudentKnowledge(object): @@ -84,9 +83,14 @@ class StudentKnowledge(object):
84 self.questions = [factory[qref].generate() for qref in questionlist] 83 self.questions = [factory[qref].generate() for qref in questionlist]
85 self.finished_questions = [] 84 self.finished_questions = []
86 85
87 - self.current_question = self.questions.pop(0) # FIXME crash if empty  
88 - self.current_question['start_time'] = datetime.now()  
89 - return True 86 + try:
  87 + self.current_question = self.questions.pop(0) # FIXME crash if empty
  88 + except IndexError:
  89 + self.finish_topic()
  90 + return False
  91 + else:
  92 + self.current_question['start_time'] = datetime.now()
  93 + return True
90 94
91 # ------------------------------------------------------------------------ 95 # ------------------------------------------------------------------------
92 # The topic has finished and there are no more questions. 96 # The topic has finished and there are no more questions.
@@ -305,6 +305,6 @@ def build_dependency_graph(config={}): @@ -305,6 +305,6 @@ def build_dependency_graph(config={}):
305 q['path'] = fullpath # fullpath added to each question 305 q['path'] = fullpath # fullpath added to each question
306 tnode['factory'][q['ref']] = QFactory(q) 306 tnode['factory'][q['ref']] = QFactory(q)
307 307
308 - logger.info(f'{len(tnode["questions"]):6} from {ref}') 308 + logger.info(f'{len(tnode["questions"]):6} {ref}')
309 309
310 return g 310 return g
@@ -142,6 +142,7 @@ class TopicHandler(BaseHandler): @@ -142,6 +142,7 @@ class TopicHandler(BaseHandler):
142 self.redirect('/') 142 self.redirect('/')
143 143
144 # ---------------------------------------------------------------------------- 144 # ----------------------------------------------------------------------------
  145 +# FIXME
145 class FileHandler(BaseHandler): 146 class FileHandler(BaseHandler):
146 @tornado.web.authenticated 147 @tornado.web.authenticated
147 def get(self, filename): 148 def get(self, filename):
@@ -176,36 +177,6 @@ class QuestionHandler(BaseHandler): @@ -176,36 +177,6 @@ class QuestionHandler(BaseHandler):
176 # 'alert': '', FIXME 177 # 'alert': '', FIXME
177 } 178 }
178 179
179 - def new_question(self, user):  
180 - question = self.learn.get_student_question(user) # Question  
181 - template = self.templates[question['type']]  
182 - question_html = self.render_string(template, question=question, md=md_to_html)  
183 -  
184 - return {  
185 - 'method': 'new_question',  
186 - 'params': {  
187 - 'question': tornado.escape.to_unicode(question_html),  
188 - 'progress': self.learn.get_student_progress(user),  
189 - }  
190 - }  
191 -  
192 - def wrong_answer(self, user):  
193 - progress = self.learn.get_student_progress(user) # in the current topic  
194 - return {  
195 - 'method': 'shake',  
196 - 'params': {  
197 - 'progress': progress,  
198 - }  
199 - }  
200 -  
201 - def finished_topic(self, user): # FIXME user unused  
202 - return {  
203 - 'method': 'finished_topic',  
204 - 'params': { # FIXME no html here please!  
205 - 'question': f'<img src="/static/trophy.svg" alt="trophy" class="img-fluid mx-auto d-block" width="35%">'  
206 - }  
207 - }  
208 -  
209 @tornado.web.authenticated 180 @tornado.web.authenticated
210 def get(self): 181 def get(self):
211 logging.debug('QuestionHandler.get()') 182 logging.debug('QuestionHandler.get()')
@@ -236,7 +207,7 @@ class QuestionHandler(BaseHandler): @@ -236,7 +207,7 @@ class QuestionHandler(BaseHandler):
236 else: 207 else:
237 # answers returned in a list. fix depending on question type 208 # answers returned in a list. fix depending on question type
238 qtype = self.learn.get_student_question_type(user) 209 qtype = self.learn.get_student_question_type(user)
239 - if qtype in ('success', 'information', 'info'): # FIXME danger... 210 + if qtype in ('success', 'information', 'info'): # FIXME unused?
240 answer = None 211 answer = None
241 elif qtype != 'checkbox': # radio, text, textarea, ... 212 elif qtype != 'checkbox': # radio, text, textarea, ...
242 answer = answer[0] 213 answer = answer[0]
@@ -244,13 +215,35 @@ class QuestionHandler(BaseHandler): @@ -244,13 +215,35 @@ class QuestionHandler(BaseHandler):
244 grade = self.learn.check_answer(user, answer) 215 grade = self.learn.check_answer(user, answer)
245 question = self.learn.get_student_question(user) 216 question = self.learn.get_student_question(user)
246 217
247 - if question is None:  
248 - self.write(self.finished_topic(user))  
249 - elif grade > 0.999:  
250 - self.write(self.new_question(user))  
251 - else:  
252 - self.write(self.wrong_answer(user))  
253 - 218 + if grade <= 0.999: # wrong answer
  219 + comments_html = self.render_string('comments.html', comments=question['comments'], md=md_to_html)
  220 + self.write({
  221 + 'method': 'shake',
  222 + 'params': {
  223 + 'progress': self.learn.get_student_progress(user),
  224 + 'comments': tornado.escape.to_unicode(comments_html), # FIXME
  225 + }
  226 + })
  227 + else: # answer is correct
  228 + if question is None: # finished topic
  229 + finished_topic_html = self.render_string('finished_topic.html')
  230 + self.write({
  231 + 'method': 'finished_topic',
  232 + 'params': {
  233 + 'question': tornado.escape.to_unicode(finished_topic_html)
  234 + }
  235 + })
  236 +
  237 + else: # continue with a new question
  238 + template = self.templates[question['type']]
  239 + question_html = self.render_string(template, question=question, md=md_to_html)
  240 + self.write({
  241 + 'method': 'new_question',
  242 + 'params': {
  243 + 'question': tornado.escape.to_unicode(question_html),
  244 + 'progress': self.learn.get_student_progress(user),
  245 + }
  246 + })
254 247
255 # ------------------------------------------------------------------------- 248 # -------------------------------------------------------------------------
256 # Tornado web server 249 # Tornado web server
static/css/topic.css
@@ -7,16 +7,16 @@ @@ -7,16 +7,16 @@
7 body { 7 body {
8 margin: 0; 8 margin: 0;
9 padding-top: 0px; 9 padding-top: 0px;
10 - margin-bottom: 60px; /* Margin bottom by footer height */ 10 + margin-bottom: 90px; /* Margin bottom by footer height */
11 } 11 }
12 12
13 .footer { 13 .footer {
14 position: absolute; 14 position: absolute;
15 bottom: 0; 15 bottom: 0;
16 width: 100%; 16 width: 100%;
17 - height: 40px; 17 + height: 70px;
18 line-height: 60px; 18 line-height: 60px;
19 - background-color: #f5f5f5; 19 + /*background-color: #f5f5f5;*/
20 } 20 }
21 21
22 html { 22 html {
static/js/maintopics.js
@@ -9,8 +9,6 @@ function getCookie(name) { @@ -9,8 +9,6 @@ function getCookie(name) {
9 } 9 }
10 10
11 function change_password() { 11 function change_password() {
12 - // alert('hello');  
13 - // notify('hello!');  
14 var token = getCookie('_xsrf'); 12 var token = getCookie('_xsrf');
15 $.ajax({ 13 $.ajax({
16 type: "POST", 14 type: "POST",
static/js/topic.js
@@ -12,6 +12,7 @@ function updateQuestion(response){ @@ -12,6 +12,7 @@ function updateQuestion(response){
12 switch (response["method"]) { 12 switch (response["method"]) {
13 case "new_question": 13 case "new_question":
14 $("#question_div").html(response["params"]["question"]); 14 $("#question_div").html(response["params"]["question"]);
  15 + $("#comments").html("");
15 $('#topic_progress').css('width', (100*response["params"]["progress"])+'%').attr('aria-valuenow', 100*response["params"]["progress"]); 16 $('#topic_progress').css('width', (100*response["params"]["progress"])+'%').attr('aria-valuenow', 100*response["params"]["progress"]);
16 17
17 MathJax.Hub.Queue(["Typeset",MathJax.Hub,"question_div"]); 18 MathJax.Hub.Queue(["Typeset",MathJax.Hub,"question_div"]);
@@ -46,6 +47,8 @@ function updateQuestion(response){ @@ -46,6 +47,8 @@ function updateQuestion(response){
46 case "shake": 47 case "shake":
47 $('#topic_progress').css('width', (100*response["params"]["progress"])+'%').attr('aria-valuenow', 100*response["params"]["progress"]); 48 $('#topic_progress').css('width', (100*response["params"]["progress"])+'%').attr('aria-valuenow', 100*response["params"]["progress"]);
48 $('#question_div').animateCSS('shake'); 49 $('#question_div').animateCSS('shake');
  50 + $('#comments').html(response['params']['comments']);
  51 + MathJax.Hub.Queue(["Typeset",MathJax.Hub,"#comments"]);
49 break; 52 break;
50 53
51 case "finished_topic": 54 case "finished_topic":
templates/notification.html
1 -<div class="alert alert-{{ type }} alert-dismissible" role="alert" id="notification"> 1 +<div class="alert alert-{{ type }}" role="alert" id="notification">
2 <i class="fas fa-key" aria-hidden="true"></i> 2 <i class="fas fa-key" aria-hidden="true"></i>
3 {{ msg }} 3 {{ msg }}
4 </div> 4 </div>
templates/topic.html
@@ -83,14 +83,17 @@ @@ -83,14 +83,17 @@
83 <div id="question_div"></div> 83 <div id="question_div"></div>
84 </form> 84 </form>
85 85
86 - <button class="btn btn-primary btn-lg btn-block my-3" id="submit" data-toggle="tooltip" data-placement="right" title="Shift-Enter">Continuar</button> 86 + <div id="comments"></div>
  87 +
87 </div> 88 </div>
88 </div> 89 </div>
89 90
90 <footer class="footer"> 91 <footer class="footer">
91 <div class="container"> 92 <div class="container">
  93 + <button class="btn btn-primary btn-lg btn-block my-3" id="submit" data-toggle="tooltip" data-placement="right" title="Shift-Enter">Continuar</button>
  94 +<!--
92 <span class="text-muted"></span> 95 <span class="text-muted"></span>
93 - </div> 96 + --> </div>
94 </footer> 97 </footer>
95 98
96 </body> 99 </body>