Commit dbdd58fe15e1cd88fb28cf6a13f13b5447cdca79

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

- added option 'append_wrong' to enable/disable appending new instance of questi…

…ons when answered wrong.
- added option 'max_tries'.
- added option 'shuffle' to que order of the questions in a topic.
- images are no longer in a html figure tag, and are now centered.
1 1
2 # BUGS 2 # BUGS
3 3
  4 +- nos topicos learn.yaml, qd falha acrescenta no fim. nao faz sentido.
  5 +- 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 +
  7 +- mathjax, formulas $$f(x)$$ nas opções de escolha multipla, não ficam centradas em toda a coluna mas apenas na largura do parágrafo.
4 - mostrar feedback/solucoes quando acerta, ou excede max tries. 8 - mostrar feedback/solucoes quando acerta, ou excede max tries.
5 - default prefix should be obtained from each course (yaml conf)? 9 - default prefix should be obtained from each course (yaml conf)?
6 - tabelas nas perguntas radio/checkbox não ocupam todo o espaço como em question. 10 - tabelas nas perguntas radio/checkbox não ocupam todo o espaço como em question.
@@ -71,13 +71,14 @@ class StudentKnowledge(object): @@ -71,13 +71,14 @@ class StudentKnowledge(object):
71 # questions: list of generated questions to do in the topic 71 # questions: list of generated questions to do in the topic
72 # current_question: the current question to be presented 72 # current_question: the current question to be presented
73 # ------------------------------------------------------------------------ 73 # ------------------------------------------------------------------------
74 - async def init_topic(self, topic):  
75 - logger.debug(f'StudentKnowledge.init_topic({topic})') 74 + # FIXME async mas nao tem awaits...
  75 + async def start_topic(self, topic):
  76 + logger.debug(f'StudentKnowledge.start_topic({topic})')
76 77
77 # do not allow locked topics 78 # do not allow locked topics
78 if self.is_locked(topic): 79 if self.is_locked(topic):
79 logger.debug(f'Topic {topic} is locked') 80 logger.debug(f'Topic {topic} is locked')
80 - return 81 + return False
81 82
82 # starting new topic 83 # starting new topic
83 self.current_topic = topic 84 self.current_topic = topic
@@ -85,7 +86,11 @@ class StudentKnowledge(object): @@ -85,7 +86,11 @@ class StudentKnowledge(object):
85 self.wrong_answers = 0 86 self.wrong_answers = 0
86 87
87 t = self.deps.node[topic] 88 t = self.deps.node[topic]
88 - questions = random.sample(t['questions'], k=t['choose']) 89 + k = t['choose']
  90 + if t['shuffle']:
  91 + questions = random.sample(t['questions'], k=k)
  92 + else:
  93 + questions = t['questions'][:k]
89 logger.debug(f'Questions: {", ".join(questions)}') 94 logger.debug(f'Questions: {", ".join(questions)}')
90 95
91 # generate instances of questions 96 # generate instances of questions
@@ -94,6 +99,7 @@ class StudentKnowledge(object): @@ -94,6 +99,7 @@ class StudentKnowledge(object):
94 99
95 # get first question 100 # get first question
96 self.next_question() 101 self.next_question()
  102 + return True
97 103
98 104
99 # ------------------------------------------------------------------------ 105 # ------------------------------------------------------------------------
@@ -140,8 +146,10 @@ class StudentKnowledge(object): @@ -140,8 +146,10 @@ class StudentKnowledge(object):
140 logger.debug(f'Wrong answers = {self.wrong_answers}; Tries = {self.current_question["tries"]}') 146 logger.debug(f'Wrong answers = {self.wrong_answers}; Tries = {self.current_question["tries"]}')
141 147
142 if self.current_question['tries'] <= 0: 148 if self.current_question['tries'] <= 0:
143 - logger.debug("Appending new instance of this question to the end")  
144 - self.questions.append(self.factory[q['ref']].generate()) 149 + if self.current_question['append_wrong']:
  150 + logger.debug("Appending new instance of this question to the end")
  151 + self.questions.append(self.factory[q['ref']].generate())
  152 +
145 if self.next_question() is None: 153 if self.next_question() is None:
146 action = 'finished_topic' 154 action = 'finished_topic'
147 else: 155 else:
@@ -165,7 +173,8 @@ class StudentKnowledge(object): @@ -165,7 +173,8 @@ class StudentKnowledge(object):
165 self.finish_topic() 173 self.finish_topic()
166 else: 174 else:
167 self.current_question['start_time'] = datetime.now() 175 self.current_question['start_time'] = datetime.now()
168 - self.current_question['tries'] = self.current_question.get('max_tries', 3) # FIXME hardcoded 3 176 + default_maxtries = self.deps.nodes[self.current_topic]['max_tries']
  177 + self.current_question['tries'] = self.current_question.get('max_tries', default_maxtries)
169 logger.debug(f'Next question is "{self.current_question["ref"]}"') 178 logger.debug(f'Next question is "{self.current_question["ref"]}"')
170 179
171 return self.current_question # question or None 180 return self.current_question # question or None
@@ -186,7 +186,7 @@ class LearnApp(object): @@ -186,7 +186,7 @@ class LearnApp(object):
186 async def start_topic(self, uid, topic): 186 async def start_topic(self, uid, topic):
187 student = self.online[uid]['state'] 187 student = self.online[uid]['state']
188 try: 188 try:
189 - await student.init_topic(topic) 189 + await student.start_topic(topic)
190 except KeyError as e: 190 except KeyError as e:
191 logger.warning(f'User "{uid}" tried to open nonexistent topic: "{topic}"') 191 logger.warning(f'User "{uid}" tried to open nonexistent topic: "{topic}"')
192 raise e 192 raise e
@@ -242,6 +242,8 @@ class LearnApp(object): @@ -242,6 +242,8 @@ class LearnApp(object):
242 default_shuffle = config.get('shuffle', True) 242 default_shuffle = config.get('shuffle', True)
243 default_choose = config.get('choose', 9999) 243 default_choose = config.get('choose', 9999)
244 default_forgetting_factor = config.get('forgetting_factor', 1.0) 244 default_forgetting_factor = config.get('forgetting_factor', 1.0)
  245 + default_maxtries = config.get('max_tries', 3)
  246 + default_append_wrong = config.get('append_wrong', True)
245 247
246 # iterate over topics and populate graph 248 # iterate over topics and populate graph
247 topics = config.get('topics', {}) 249 topics = config.get('topics', {})
@@ -257,8 +259,10 @@ class LearnApp(object): @@ -257,8 +259,10 @@ class LearnApp(object):
257 t['path'] = path.join(g.graph['prefix'], tref) # prefix/topic 259 t['path'] = path.join(g.graph['prefix'], tref) # prefix/topic
258 t['file'] = attr.get('file', default_file) # questions.yaml 260 t['file'] = attr.get('file', default_file) # questions.yaml
259 t['shuffle'] = attr.get('shuffle', default_shuffle) 261 t['shuffle'] = attr.get('shuffle', default_shuffle)
  262 + t['max_tries'] = attr.get('max_tries', default_maxtries)
260 t['forgetting_factor'] = attr.get('forgetting_factor', default_forgetting_factor) 263 t['forgetting_factor'] = attr.get('forgetting_factor', default_forgetting_factor)
261 t['choose'] = attr.get('choose', default_choose) 264 t['choose'] = attr.get('choose', default_choose)
  265 + t['append_wrong'] = attr.get('append_wrong', default_append_wrong)
262 t['questions'] = attr.get('questions', []) 266 t['questions'] = attr.get('questions', [])
263 267
264 logger.info(f'Loaded {g.number_of_nodes()} topics') 268 logger.info(f'Loaded {g.number_of_nodes()} topics')
@@ -286,6 +290,7 @@ class LearnApp(object): @@ -286,6 +290,7 @@ class LearnApp(object):
286 qref = q.get('ref', str(i)) # ref or number 290 qref = q.get('ref', str(i)) # ref or number
287 q['ref'] = tref + ':' + qref 291 q['ref'] = tref + ':' + qref
288 q['path'] = topicpath 292 q['path'] = topicpath
  293 + q.setdefault('append_wrong', t['append_wrong'])
289 294
290 # if questions are left undefined, include all. 295 # if questions are left undefined, include all.
291 if not t['questions']: 296 if not t['questions']:
@@ -149,6 +149,8 @@ class RootHandler(BaseHandler): @@ -149,6 +149,8 @@ class RootHandler(BaseHandler):
149 # Start a given topic: /topic/... 149 # Start a given topic: /topic/...
150 # ---------------------------------------------------------------------------- 150 # ----------------------------------------------------------------------------
151 class TopicHandler(BaseHandler): 151 class TopicHandler(BaseHandler):
  152 + SUPPORTED_METHODS = ['GET']
  153 +
152 @tornado.web.authenticated 154 @tornado.web.authenticated
153 async def get(self, topic): 155 async def get(self, topic):
154 uid = self.current_user 156 uid = self.current_user
templates/question.html
1 {% autoescape %} 1 {% autoescape %}
2 2
3 -<h4 class="page-header">{{ question['title'] }}</h4> 3 +<h2 class="page-header">{{ question['title'] }}</h4>
4 4
5 <div id="text"> 5 <div id="text">
6 {{ md(question['text']) }} 6 {{ md(question['text']) }}
@@ -102,18 +102,18 @@ class HighlightRenderer(mistune.Renderer): @@ -102,18 +102,18 @@ class HighlightRenderer(mistune.Renderer):
102 def image(self, src, title, alt): 102 def image(self, src, title, alt):
103 alt = mistune.escape(alt, quote=True) 103 alt = mistune.escape(alt, quote=True)
104 title = mistune.escape(title or '', quote=True) 104 title = mistune.escape(title or '', quote=True)
105 - if title:  
106 - caption = f'<figcaption class="figure-caption">{title}</figcaption>'  
107 - else:  
108 - caption = ''  
109 -  
110 - return f'''  
111 - <figure class="figure">  
112 - <img src="/file/{src}" class="figure-img img-fluid rounded" alt="{alt}" title="{title}">  
113 - {caption}  
114 - </figure>  
115 - '''  
116 - # return f'<img src="/file/{src}" class="img-fluid mx-auto d-block" alt="{alt}" title="{title}">' 105 + # if title:
  106 + # caption = f'<figcaption class="figure-caption">{title}</figcaption>'
  107 + # else:
  108 + # caption = ''
  109 +
  110 + # return f'''
  111 + # <figure class="figure">
  112 + # <img src="/file/{src}" class="figure-img img-fluid rounded" alt="{alt}" title="{title}">
  113 + # {caption}
  114 + # </figure>
  115 + # '''
  116 + return f'<img src="/file/{src}" class="img-fluid mx-auto d-block" alt="{alt}" title="{title}">'
117 117
118 # Pass math through unaltered - mathjax does the rendering in the browser 118 # Pass math through unaltered - mathjax does the rendering in the browser
119 def block_math(self, text): 119 def block_math(self, text):