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.
BUGS.md
1 1  
2 2 # BUGS
3 3  
  4 +- image brand da universidade está esbatida.
4 5 - generators e correct scripts que durem muito tempo podem bloquear o loop do tornado?
5 6 - detect questions in questions.yaml without ref -> error ou generate default.
6 7 - topicos virtuais nao deveriam aparecer. na construção da árvore os sucessores seriam ligados directamente aos predecessores.
7 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 9 - session management. close after inactive time.
10 10 - generators not working: bcrypt (ver blog)
11 11 - tabelas nas perguntas radio/checkbox não ocupam todo o espaço como em question.
... ... @@ -16,7 +16,6 @@
16 16 - each topic only loads a sample of K questions (max) in random order.
17 17 - servir imagens/ficheiros.
18 18 - pertuntas tipo tristate: (sim, não, não sei
19   -- forçar reload das perguntas sem ter de deitar abaixo o servidor.
20 19 - reload das perguntas enquanto online.
21 20 - tabela de progresso de todos os alunos por topico.
22 21 - tabela com perguntas / quantidade de respostas certas/erradas.
... ... @@ -31,6 +30,7 @@
31 30  
32 31 # FIXED
33 32  
  33 +- error if demo.yaml has no topics
34 34 - update de fontawesome para versão 5.0.6.
35 35 - remover learn.css uma vez que nao é usado em lado nenhum?
36 36 - check if user already logged in
... ...
knowledge.py
... ... @@ -32,7 +32,7 @@ class StudentKnowledge(object):
32 32 now = datetime.now()
33 33 for s in state.values():
34 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 37 # compute recommended sequence of topics ['a', 'b', ...]
38 38 self.topic_sequence = list(nx.topological_sort(self.deps))
... ... @@ -45,7 +45,7 @@ class StudentKnowledge(object):
45 45 def unlock_topics(self):
46 46 # minimum level that the dependencies of a topic must have
47 47 # for the topic to be unlocked.
48   - min_level = 0.01
  48 + min_level = 0.2
49 49  
50 50 for topic in self.topic_sequence:
51 51 if topic not in self.state: # if locked
... ... @@ -75,7 +75,6 @@ class StudentKnowledge(object):
75 75 return False
76 76  
77 77 self.current_topic = topic
78   - # logger.info(f'Topic set to "{topic}"')
79 78  
80 79 # generate question instances for current topic
81 80 factory = self.deps.node[topic]['factory']
... ... @@ -84,9 +83,14 @@ class StudentKnowledge(object):
84 83 self.questions = [factory[qref].generate() for qref in questionlist]
85 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 96 # The topic has finished and there are no more questions.
... ...
learnapp.py
... ... @@ -305,6 +305,6 @@ def build_dependency_graph(config={}):
305 305 q['path'] = fullpath # fullpath added to each question
306 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 310 return g
... ...
serve.py
... ... @@ -142,6 +142,7 @@ class TopicHandler(BaseHandler):
142 142 self.redirect('/')
143 143  
144 144 # ----------------------------------------------------------------------------
  145 +# FIXME
145 146 class FileHandler(BaseHandler):
146 147 @tornado.web.authenticated
147 148 def get(self, filename):
... ... @@ -176,36 +177,6 @@ class QuestionHandler(BaseHandler):
176 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 180 @tornado.web.authenticated
210 181 def get(self):
211 182 logging.debug('QuestionHandler.get()')
... ... @@ -236,7 +207,7 @@ class QuestionHandler(BaseHandler):
236 207 else:
237 208 # answers returned in a list. fix depending on question type
238 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 211 answer = None
241 212 elif qtype != 'checkbox': # radio, text, textarea, ...
242 213 answer = answer[0]
... ... @@ -244,13 +215,35 @@ class QuestionHandler(BaseHandler):
244 215 grade = self.learn.check_answer(user, answer)
245 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 249 # Tornado web server
... ...
static/css/topic.css
... ... @@ -7,16 +7,16 @@
7 7 body {
8 8 margin: 0;
9 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 13 .footer {
14 14 position: absolute;
15 15 bottom: 0;
16 16 width: 100%;
17   - height: 40px;
  17 + height: 70px;
18 18 line-height: 60px;
19   - background-color: #f5f5f5;
  19 + /*background-color: #f5f5f5;*/
20 20 }
21 21  
22 22 html {
... ...
static/js/maintopics.js
... ... @@ -9,8 +9,6 @@ function getCookie(name) {
9 9 }
10 10  
11 11 function change_password() {
12   - // alert('hello');
13   - // notify('hello!');
14 12 var token = getCookie('_xsrf');
15 13 $.ajax({
16 14 type: "POST",
... ...
static/js/topic.js
... ... @@ -12,6 +12,7 @@ function updateQuestion(response){
12 12 switch (response["method"]) {
13 13 case "new_question":
14 14 $("#question_div").html(response["params"]["question"]);
  15 + $("#comments").html("");
15 16 $('#topic_progress').css('width', (100*response["params"]["progress"])+'%').attr('aria-valuenow', 100*response["params"]["progress"]);
16 17  
17 18 MathJax.Hub.Queue(["Typeset",MathJax.Hub,"question_div"]);
... ... @@ -46,6 +47,8 @@ function updateQuestion(response){
46 47 case "shake":
47 48 $('#topic_progress').css('width', (100*response["params"]["progress"])+'%').attr('aria-valuenow', 100*response["params"]["progress"]);
48 49 $('#question_div').animateCSS('shake');
  50 + $('#comments').html(response['params']['comments']);
  51 + MathJax.Hub.Queue(["Typeset",MathJax.Hub,"#comments"]);
49 52 break;
50 53  
51 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 2 <i class="fas fa-key" aria-hidden="true"></i>
3 3 {{ msg }}
4 4 </div>
... ...
templates/topic.html
... ... @@ -83,14 +83,17 @@
83 83 <div id="question_div"></div>
84 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 88 </div>
88 89 </div>
89 90  
90 91 <footer class="footer">
91 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 95 <span class="text-muted"></span>
93   - </div>
  96 + --> </div>
94 97 </footer>
95 98  
96 99 </body>
... ...