Commit 0a96984cde48d2c23d83b0cb8648e3b5391d7123
1 parent
e447d0c2
Exists in
master
and in
1 other branch
database file is now selected as a command line option, defaults to students.db
Showing
5 changed files
with
81 additions
and
47 deletions
Show diff stats
knowledge.py
| @@ -129,8 +129,10 @@ class StudentKnowledge(object): | @@ -129,8 +129,10 @@ class StudentKnowledge(object): | ||
| 129 | 129 | ||
| 130 | if grade > 0.999: | 130 | if grade > 0.999: |
| 131 | self.correct_answers += 1 | 131 | self.correct_answers += 1 |
| 132 | - self.next_question() | ||
| 133 | - action = 'new_question' | 132 | + if self.next_question() is None: |
| 133 | + action = 'finished_topic' | ||
| 134 | + else: | ||
| 135 | + action = 'new_question' | ||
| 134 | 136 | ||
| 135 | else: | 137 | else: |
| 136 | self.wrong_answers += 1 | 138 | self.wrong_answers += 1 |
| @@ -140,13 +142,15 @@ class StudentKnowledge(object): | @@ -140,13 +142,15 @@ class StudentKnowledge(object): | ||
| 140 | if self.current_question['tries'] <= 0: | 142 | if self.current_question['tries'] <= 0: |
| 141 | logger.debug("Appending new instance of this question to the end") | 143 | logger.debug("Appending new instance of this question to the end") |
| 142 | self.questions.append(self.factory[q['ref']].generate()) | 144 | self.questions.append(self.factory[q['ref']].generate()) |
| 143 | - self.next_question() | ||
| 144 | - action = 'new_question' | 145 | + if self.next_question() is None: |
| 146 | + action = 'finished_topic' | ||
| 147 | + else: | ||
| 148 | + action = 'new_question' # FIXME show comments | ||
| 145 | 149 | ||
| 146 | else: | 150 | else: |
| 147 | - action = 'wrong' | 151 | + action = 'try_again' |
| 148 | 152 | ||
| 149 | - # returns answered and corrected question (not new one) | 153 | + # returns corrected question (not new one) which might include comments |
| 150 | return q, action | 154 | return q, action |
| 151 | 155 | ||
| 152 | 156 |
learnapp.py
| @@ -58,8 +58,8 @@ class LearnApp(object): | @@ -58,8 +58,8 @@ class LearnApp(object): | ||
| 58 | session.close() | 58 | session.close() |
| 59 | 59 | ||
| 60 | # ------------------------------------------------------------------------ | 60 | # ------------------------------------------------------------------------ |
| 61 | - def __init__(self, config_files, prefix): | ||
| 62 | - self.db_setup() # setup database and check students | 61 | + def __init__(self, config_files, prefix, db): |
| 62 | + self.db_setup(db) # setup database and check students | ||
| 63 | self.online = dict() # online students | 63 | self.online = dict() # online students |
| 64 | 64 | ||
| 65 | self.deps = nx.DiGraph(prefix=prefix) | 65 | self.deps = nx.DiGraph(prefix=prefix) |
| @@ -152,7 +152,7 @@ class LearnApp(object): | @@ -152,7 +152,7 @@ class LearnApp(object): | ||
| 152 | topic_id=topic)) | 152 | topic_id=topic)) |
| 153 | logger.debug(f'Saved "{q["ref"]}" into database') | 153 | logger.debug(f'Saved "{q["ref"]}" into database') |
| 154 | 154 | ||
| 155 | - if knowledge.get_current_question() is None: | 155 | + if action == 'finished_topic': |
| 156 | # finished topic, save into database | 156 | # finished topic, save into database |
| 157 | logger.info(f'User "{uid}" finished "{topic}"') | 157 | logger.info(f'User "{uid}" finished "{topic}"') |
| 158 | level = knowledge.get_topic_level(topic) | 158 | level = knowledge.get_topic_level(topic) |
| @@ -176,9 +176,8 @@ class LearnApp(object): | @@ -176,9 +176,8 @@ class LearnApp(object): | ||
| 176 | s.add(a) | 176 | s.add(a) |
| 177 | 177 | ||
| 178 | logger.debug(f'Saved topic "{topic}" into database') | 178 | logger.debug(f'Saved topic "{topic}" into database') |
| 179 | - action = 'finished_topic' # FIXME | ||
| 180 | 179 | ||
| 181 | - return action | 180 | + return q, action |
| 182 | 181 | ||
| 183 | 182 | ||
| 184 | # ------------------------------------------------------------------------ | 183 | # ------------------------------------------------------------------------ |
| @@ -208,7 +207,7 @@ class LearnApp(object): | @@ -208,7 +207,7 @@ class LearnApp(object): | ||
| 208 | # ------------------------------------------------------------------------ | 207 | # ------------------------------------------------------------------------ |
| 209 | # setup and check database | 208 | # setup and check database |
| 210 | # ------------------------------------------------------------------------ | 209 | # ------------------------------------------------------------------------ |
| 211 | - def db_setup(self, db='students.db'): | 210 | + def db_setup(self, db): |
| 212 | logger.info(f'Checking database "{db}":') | 211 | logger.info(f'Checking database "{db}":') |
| 213 | engine = create_engine(f'sqlite:///{db}', echo=False) | 212 | engine = create_engine(f'sqlite:///{db}', echo=False) |
| 214 | self.Session = sessionmaker(bind=engine) | 213 | self.Session = sessionmaker(bind=engine) |
questions.py
| @@ -42,10 +42,6 @@ class Question(dict): | @@ -42,10 +42,6 @@ class Question(dict): | ||
| 42 | 'files': {}, | 42 | 'files': {}, |
| 43 | }) | 43 | }) |
| 44 | 44 | ||
| 45 | - # FIXME unused. do childs need do override this? | ||
| 46 | - # def updateAnswer(answer=None): | ||
| 47 | - # self['answer'] = answer | ||
| 48 | - | ||
| 49 | def correct(self): | 45 | def correct(self): |
| 50 | self['comments'] = '' | 46 | self['comments'] = '' |
| 51 | self['grade'] = 0.0 | 47 | self['grade'] = 0.0 |
serve.py
| @@ -248,16 +248,17 @@ class QuestionHandler(BaseHandler): | @@ -248,16 +248,17 @@ class QuestionHandler(BaseHandler): | ||
| 248 | answer = answer[0] | 248 | answer = answer[0] |
| 249 | 249 | ||
| 250 | # check answer in another thread (nonblocking) | 250 | # check answer in another thread (nonblocking) |
| 251 | - action = await self.learn.check_answer(user, answer) | 251 | + # and get corrected question |
| 252 | + q, action = await self.learn.check_answer(user, answer) | ||
| 252 | 253 | ||
| 253 | # get next question (same, new or None) | 254 | # get next question (same, new or None) |
| 254 | question = self.learn.get_current_question(user) | 255 | question = self.learn.get_current_question(user) |
| 255 | 256 | ||
| 256 | - if action == 'wrong': | 257 | + if action == 'try_again': |
| 257 | comments_html = self.render_string('comments.html', | 258 | comments_html = self.render_string('comments.html', |
| 258 | comments=question['comments'], md=md_to_html) | 259 | comments=question['comments'], md=md_to_html) |
| 259 | self.write({ | 260 | self.write({ |
| 260 | - 'method': action, | 261 | + 'method': 'try_again', # FIXME js |
| 261 | 'params': { | 262 | 'params': { |
| 262 | 'progress': self.learn.get_student_progress(user), | 263 | 'progress': self.learn.get_student_progress(user), |
| 263 | 'comments': tornado.escape.to_unicode(comments_html), # FIXME | 264 | 'comments': tornado.escape.to_unicode(comments_html), # FIXME |
| @@ -265,14 +266,20 @@ class QuestionHandler(BaseHandler): | @@ -265,14 +266,20 @@ class QuestionHandler(BaseHandler): | ||
| 265 | } | 266 | } |
| 266 | }) | 267 | }) |
| 267 | 268 | ||
| 268 | - elif action == 'finished_topic': # right answer, finished topic | ||
| 269 | - finished_topic_html = self.render_string('finished_topic.html') | ||
| 270 | - self.write({ | ||
| 271 | - 'method': 'finished_topic', | ||
| 272 | - 'params': { | ||
| 273 | - 'question': tornado.escape.to_unicode(finished_topic_html) | ||
| 274 | - } | ||
| 275 | - }) | 269 | + # if action == 'wrong': |
| 270 | + # comments_html = self.render_string('comments.html', | ||
| 271 | + # comments=question['comments'], md=md_to_html) | ||
| 272 | + # template = self.templates[question['type']] | ||
| 273 | + # question_html = self.render_string(template, question=question, md=md_to_html) | ||
| 274 | + # self.write({ | ||
| 275 | + # 'method': 'wrong', # FIXME js | ||
| 276 | + # 'params': { | ||
| 277 | + # 'question': tornado.escape.to_unicode(question_html), | ||
| 278 | + # 'progress': self.learn.get_student_progress(user), | ||
| 279 | + # 'comments': tornado.escape.to_unicode(comments_html), # FIXME | ||
| 280 | + # 'tries': question['tries'], | ||
| 281 | + # } | ||
| 282 | + # }) | ||
| 276 | 283 | ||
| 277 | elif action == 'new_question': # get next question in the topic | 284 | elif action == 'new_question': # get next question in the topic |
| 278 | template = self.templates[question['type']] | 285 | template = self.templates[question['type']] |
| @@ -287,6 +294,15 @@ class QuestionHandler(BaseHandler): | @@ -287,6 +294,15 @@ class QuestionHandler(BaseHandler): | ||
| 287 | } | 294 | } |
| 288 | }) | 295 | }) |
| 289 | 296 | ||
| 297 | + elif action == 'finished_topic': # right answer, finished topic | ||
| 298 | + finished_topic_html = self.render_string('finished_topic.html') | ||
| 299 | + self.write({ | ||
| 300 | + 'method': 'finished_topic', | ||
| 301 | + 'params': { | ||
| 302 | + 'question': tornado.escape.to_unicode(finished_topic_html) | ||
| 303 | + } | ||
| 304 | + }) | ||
| 305 | + | ||
| 290 | else: | 306 | else: |
| 291 | logger.error(f'Unknown action {action}') | 307 | logger.error(f'Unknown action {action}') |
| 292 | 308 | ||
| @@ -316,6 +332,8 @@ def main(): | @@ -316,6 +332,8 @@ def main(): | ||
| 316 | help='Path prefix under which the topic directories can be found, e.g. ~/topics') | 332 | help='Path prefix under which the topic directories can be found, e.g. ~/topics') |
| 317 | argparser.add_argument('--port', type=int, default=8443, | 333 | argparser.add_argument('--port', type=int, default=8443, |
| 318 | help='Port to be used by the HTTPS server, e.g. 8443') | 334 | help='Port to be used by the HTTPS server, e.g. 8443') |
| 335 | + argparser.add_argument('--db', type=str, default='students.db', | ||
| 336 | + help='SQLite3 database file, e.g. students.db') | ||
| 319 | argparser.add_argument('--debug', action='store_true', | 337 | argparser.add_argument('--debug', action='store_true', |
| 320 | help='Enable debug messages') | 338 | help='Enable debug messages') |
| 321 | arg = argparser.parse_args() | 339 | arg = argparser.parse_args() |
| @@ -336,7 +354,7 @@ def main(): | @@ -336,7 +354,7 @@ def main(): | ||
| 336 | # --- start application | 354 | # --- start application |
| 337 | logging.info('Starting App') | 355 | logging.info('Starting App') |
| 338 | try: | 356 | try: |
| 339 | - learnapp = LearnApp(arg.conffile, prefix=arg.prefix) | 357 | + learnapp = LearnApp(arg.conffile, prefix=arg.prefix, db=arg.db) |
| 340 | except Exception as e: | 358 | except Exception as e: |
| 341 | logging.critical('Failed to start backend application') | 359 | logging.critical('Failed to start backend application') |
| 342 | raise e | 360 | raise e |
static/js/topic.js
| @@ -7,31 +7,34 @@ $.fn.extend({ | @@ -7,31 +7,34 @@ $.fn.extend({ | ||
| 7 | } | 7 | } |
| 8 | }); | 8 | }); |
| 9 | 9 | ||
| 10 | + | ||
| 11 | +function new_question(question, tries, progress) { | ||
| 12 | + $("#question_div").html(question); | ||
| 13 | + $("#comments").html(""); | ||
| 14 | + $("#tries").html(tries); | ||
| 15 | + $('#topic_progress').css('width', (100*progress)+'%').attr('aria-valuenow', 100*progress); | ||
| 16 | + MathJax.Hub.Queue(["Typeset",MathJax.Hub,"question_div"]); | ||
| 17 | + $('#question_div').animateCSS('bounceInDown'); | ||
| 18 | + | ||
| 19 | + // enable shift+enter to submit and tab to spaces conversion | ||
| 20 | + $("input:text, input:radio, input:checkbox").keydown(function (e) { | ||
| 21 | + if (e.keyCode == 13) { | ||
| 22 | + e.preventDefault(); | ||
| 23 | + if (e.shiftKey) postQuestion(); | ||
| 24 | + return false; | ||
| 25 | + }}); | ||
| 26 | +} | ||
| 27 | + | ||
| 10 | // updates question according to the response given by the server | 28 | // updates question according to the response given by the server |
| 11 | function updateQuestion(response){ | 29 | function updateQuestion(response){ |
| 12 | 30 | ||
| 13 | switch (response["method"]) { | 31 | switch (response["method"]) { |
| 14 | case "new_question": | 32 | case "new_question": |
| 15 | - $("#question_div").html(response["params"]["question"]); | ||
| 16 | - $("#comments").html(""); | ||
| 17 | - $("#tries").html(response["params"]["tries"]); | ||
| 18 | - | ||
| 19 | - $('#topic_progress').css('width', (100*response["params"]["progress"])+'%').attr('aria-valuenow', 100*response["params"]["progress"]); | ||
| 20 | - | ||
| 21 | - MathJax.Hub.Queue(["Typeset",MathJax.Hub,"question_div"]); | ||
| 22 | - | ||
| 23 | - // enable shift+enter to submit and tab to spaces conversion | ||
| 24 | - $("input:text, input:radio, input:checkbox").keydown(function (e) { | ||
| 25 | - if (e.keyCode == 13) { | ||
| 26 | - e.preventDefault(); | ||
| 27 | - if (e.shiftKey) postQuestion(); | ||
| 28 | - return false; | ||
| 29 | - }}); | ||
| 30 | - | ||
| 31 | - $('#question_div').animateCSS('bounceInDown'); | 33 | + params = response["params"]; |
| 34 | + new_question(params["question"], params["tries"], params["progress"]); | ||
| 32 | break; | 35 | break; |
| 33 | 36 | ||
| 34 | - case "wrong": | 37 | + case "try_again": |
| 35 | $('#topic_progress').css('width', (100*response["params"]["progress"])+'%').attr('aria-valuenow', 100*response["params"]["progress"]); | 38 | $('#topic_progress').css('width', (100*response["params"]["progress"])+'%').attr('aria-valuenow', 100*response["params"]["progress"]); |
| 36 | $('#question_div').animateCSS('shake'); | 39 | $('#question_div').animateCSS('shake'); |
| 37 | $('#comments').html(response['params']['comments']); | 40 | $('#comments').html(response['params']['comments']); |
| @@ -39,6 +42,20 @@ function updateQuestion(response){ | @@ -39,6 +42,20 @@ function updateQuestion(response){ | ||
| 39 | MathJax.Hub.Queue(["Typeset",MathJax.Hub,"#comments"]); | 42 | MathJax.Hub.Queue(["Typeset",MathJax.Hub,"#comments"]); |
| 40 | break; | 43 | break; |
| 41 | 44 | ||
| 45 | + // case "wrong": | ||
| 46 | + // $('#topic_progress').css('width', (100*response["params"]["progress"])+'%').attr('aria-valuenow', 100*response["params"]["progress"]); | ||
| 47 | + // $('#question_div').animateCSS('shake'); | ||
| 48 | + // $('#comments').html(response['params']['comments']); | ||
| 49 | + // $("#tries").html(response["params"]["tries"]); | ||
| 50 | + // MathJax.Hub.Queue(["Typeset",MathJax.Hub,"#comments"]); | ||
| 51 | + | ||
| 52 | + // // setTimeout(function(){ | ||
| 53 | + // new_question(response["params"]["question"], response["params"]["tries"], response["params"]["progress"]); | ||
| 54 | + // // }, 5000); | ||
| 55 | + // break; | ||
| 56 | + | ||
| 57 | + | ||
| 58 | + | ||
| 42 | case "finished_topic": | 59 | case "finished_topic": |
| 43 | $('#submit').css("visibility", "hidden"); | 60 | $('#submit').css("visibility", "hidden"); |
| 44 | $("#content").html(response["params"]["question"]); | 61 | $("#content").html(response["params"]["question"]); |
| @@ -62,7 +79,7 @@ function getQuestion() { | @@ -62,7 +79,7 @@ function getQuestion() { | ||
| 62 | 79 | ||
| 63 | // Send answer and receive a response. | 80 | // Send answer and receive a response. |
| 64 | // The response can be a new_question or a shake if the answer is wrong, which | 81 | // The response can be a new_question or a shake if the answer is wrong, which |
| 65 | -// is then passed to updateQuestion() | 82 | +// is then passed to updateQuestion() |
| 66 | function postQuestion() { | 83 | function postQuestion() { |
| 67 | if (typeof editor === 'object') | 84 | if (typeof editor === 'object') |
| 68 | editor.save(); | 85 | editor.save(); |