Commit bbe90cadaea87632f8df2251a493f1cd720c82ae
1 parent
62e18fdc
Exists in
master
and in
1 other branch
- fix crash when topic has no questions (but not yet ok).
- shows comments on wrong answer.
Showing
9 changed files
with
56 additions
and
55 deletions
Show diff stats
BUGS.md
| 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 |
knowledge.py
| @@ -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. |
learnapp.py
| @@ -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 |
serve.py
| @@ -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
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> |