Commit ed34db4c3f94e62db9d0a52efa31a21acee63133
1 parent
df6c6afa
Exists in
master
and in
1 other branch
- save topic state on logout
Showing
6 changed files
with
48 additions
and
143 deletions
Show diff stats
BUGS.md
app.py
| ... | ... | @@ -66,8 +66,31 @@ class LearnApp(object): |
| 66 | 66 | # ------------------------------------------------------------------------ |
| 67 | 67 | # logout |
| 68 | 68 | def logout(self, uid): |
| 69 | + state = self.online[uid]['state'].state # dict {node:level,...} | |
| 70 | + print(state) | |
| 71 | + | |
| 72 | + with self.db_session(autoflush=False) as s: | |
| 73 | + | |
| 74 | + # update existing associations and remove from state dict | |
| 75 | + aa = s.query(StudentTopic).filter_by(student_id=uid).all() | |
| 76 | + for a in aa: | |
| 77 | + print('update ', a) | |
| 78 | + a.level = state.pop(a.topic_id) # update | |
| 79 | + s.add_all(aa) | |
| 80 | + | |
| 81 | + # insert the remaining ones | |
| 82 | + u = s.query(Student).get(uid) | |
| 83 | + for n,l in state.items(): | |
| 84 | + a = StudentTopic(level=l) | |
| 85 | + t = s.query(Topic).get(n) | |
| 86 | + if t is None: # create if topic doesn't exist yet | |
| 87 | + t = Topic(n) | |
| 88 | + a.topic = t | |
| 89 | + u.topics.append(a) | |
| 90 | + s.add(a) | |
| 91 | + | |
| 92 | + del self.online[uid] | |
| 69 | 93 | logger.info(f'User "{uid}" logged out') |
| 70 | - del self.online[uid] # FIXME save current state | |
| 71 | 94 | |
| 72 | 95 | # ------------------------------------------------------------------------ |
| 73 | 96 | def get_student_name(self, uid): |
| ... | ... | @@ -105,8 +128,8 @@ class LearnApp(object): |
| 105 | 128 | # helper to manage db sessions using the `with` statement, for example |
| 106 | 129 | # with self.db_session() as s: s.query(...) |
| 107 | 130 | @contextmanager |
| 108 | - def db_session(self): | |
| 109 | - session = self.Session() | |
| 131 | + def db_session(self, **kw): | |
| 132 | + session = self.Session(**kw) | |
| 110 | 133 | try: |
| 111 | 134 | yield session |
| 112 | 135 | session.commit() | ... | ... |
knowledge.py
models.py
questions.py
| 1 | - | |
| 2 | -# We start with an empty QuestionFactory() that will be populated with | |
| 3 | -# question generators that we can load from YAML files. | |
| 4 | -# To generate an instance of a question we use the method generate(ref) where | |
| 1 | +# QFactory is a class that can generate question instances, e.g. by shuffling | |
| 2 | +# options, running a script to generate the question, etc. | |
| 3 | +# | |
| 4 | +# To generate an instance of a question we use the method generate() where | |
| 5 | 5 | # the argument is the reference of the question we wish to produce. |
| 6 | 6 | # |
| 7 | 7 | # Example: |
| 8 | 8 | # |
| 9 | -# # read everything from question files | |
| 10 | -# factory = QuestionFactory() | |
| 11 | -# factory.load_files(['file1.yaml', 'file1.yaml'], '/path/to') | |
| 12 | -# | |
| 13 | -# question = factory.generate('some_ref') | |
| 9 | +# # read question from file | |
| 10 | +# qdict = tools.load_yaml(filename) | |
| 11 | +# qfactory = QFactory(question) | |
| 12 | +# question = qfactory.generate() | |
| 14 | 13 | # |
| 15 | 14 | # # experiment answering one question and correct it |
| 16 | -# question['answer'] = 42 # insert answer | |
| 15 | +# question.updateAnswer('42') # insert answer | |
| 17 | 16 | # grade = question.correct() # correct answer |
| 18 | 17 | |
| 19 | 18 | # An instance of an actual question is an object that inherits from Question() |
| ... | ... | @@ -28,7 +27,6 @@ |
| 28 | 27 | |
| 29 | 28 | import random |
| 30 | 29 | import re |
| 31 | -# import subprocess | |
| 32 | 30 | from os import path |
| 33 | 31 | import logging |
| 34 | 32 | import sys |
| ... | ... | @@ -419,8 +417,8 @@ class QFactory(object): |
| 419 | 417 | if q['type'] == 'generator': |
| 420 | 418 | logger.debug('Running script to generate question "{0}".'.format(q['ref'])) |
| 421 | 419 | q.setdefault('arg', '') # optional arguments will be sent to stdin |
| 422 | - print(q['path']) | |
| 423 | - print(q['script']) | |
| 420 | + # print(q['path']) | |
| 421 | + # print(q['script']) | |
| 424 | 422 | script = path.join(q['path'], q['script']) |
| 425 | 423 | out = run_script(script=script, stdin=q['arg']) |
| 426 | 424 | q.update(out) |
| ... | ... | @@ -450,122 +448,3 @@ class QFactory(object): |
| 450 | 448 | logger.debug('returning') |
| 451 | 449 | return qinstance |
| 452 | 450 | |
| 453 | - | |
| 454 | -# =========================================================================== | |
| 455 | -# This class contains a pool of questions generators from which particular | |
| 456 | -# Question() instances are generated using QuestionsFactory.generate(ref). | |
| 457 | -# =========================================================================== | |
| 458 | -# class QuestionFactory(dict): | |
| 459 | -# # Depending on the type of question, a different question class will be | |
| 460 | -# # instantiated. All these classes derive from the base class `Question`. | |
| 461 | -# _types = { | |
| 462 | -# 'radio' : QuestionRadio, | |
| 463 | -# 'checkbox' : QuestionCheckbox, | |
| 464 | -# 'text' : QuestionText, | |
| 465 | -# 'text_regex': QuestionTextRegex, | |
| 466 | -# 'text_numeric': QuestionTextNumeric, | |
| 467 | -# 'textarea' : QuestionTextArea, | |
| 468 | -# # informative panels | |
| 469 | -# 'information': QuestionInformation, | |
| 470 | -# 'warning' : QuestionInformation, | |
| 471 | -# 'alert' : QuestionInformation, | |
| 472 | -# } | |
| 473 | - | |
| 474 | -# # ----------------------------------------------------------------------- | |
| 475 | -# def __init__(self, questions=None): | |
| 476 | -# super().__init__() | |
| 477 | -# if isinstance(questions, dict): | |
| 478 | -# self.add(questions) | |
| 479 | -# elif isinstance(questions, str): | |
| 480 | -# self.load_file(questions) | |
| 481 | - | |
| 482 | -# # ----------------------------------------------------------------------- | |
| 483 | -# # Add single question provided in a dictionary. | |
| 484 | -# # After this, each question will have at least 'ref' and 'type' keys. | |
| 485 | -# # ----------------------------------------------------------------------- | |
| 486 | -# def add(self, question): | |
| 487 | -# # if ref missing try ref='/path/file.yaml:3' | |
| 488 | -# try: | |
| 489 | -# question.setdefault('ref', question['filename'] + ':' + str(question['index'])) | |
| 490 | -# except KeyError: | |
| 491 | -# logger.error('Missing "ref". Cannot add question to the pool.') | |
| 492 | -# return | |
| 493 | - | |
| 494 | -# # check duplicate references | |
| 495 | -# if question['ref'] in self: | |
| 496 | -# logger.error('Duplicate reference "{0}". Replacing the original one!'.format(question['ref'])) | |
| 497 | - | |
| 498 | -# question.setdefault('type', 'information') | |
| 499 | - | |
| 500 | -# self[question['ref']] = question | |
| 501 | -# logger.debug('Added question "{0}" to the pool.'.format(question['ref'])) | |
| 502 | - | |
| 503 | -# # ----------------------------------------------------------------------- | |
| 504 | -# # load single YAML questions file | |
| 505 | -# # ----------------------------------------------------------------------- | |
| 506 | -# def load_file(self, filename, questions_dir=''): | |
| 507 | -# f = path.normpath(path.join(questions_dir, filename)) | |
| 508 | -# questions = load_yaml(f, default=[]) | |
| 509 | - | |
| 510 | -# n = 0 | |
| 511 | -# for i, q in enumerate(questions): | |
| 512 | -# if isinstance(q, dict): | |
| 513 | -# q.update({ | |
| 514 | -# 'filename': filename, | |
| 515 | -# 'path': questions_dir, | |
| 516 | -# 'index': i # position in the file, 0 based | |
| 517 | -# }) | |
| 518 | -# self.add(q) # add question | |
| 519 | -# n += 1 # counter | |
| 520 | -# else: | |
| 521 | -# logger.error('Question index {0} from file {1} is not a dictionary. Skipped!'.format(i, filename)) | |
| 522 | - | |
| 523 | -# logger.info('Loaded {0} questions from "{1}".'.format(n, filename)) | |
| 524 | - | |
| 525 | -# # ----------------------------------------------------------------------- | |
| 526 | -# # load multiple YAML question files | |
| 527 | -# # ----------------------------------------------------------------------- | |
| 528 | -# def load_files(self, files, questions_dir=''): | |
| 529 | -# for filename in files: | |
| 530 | -# self.load_file(filename, questions_dir) | |
| 531 | - | |
| 532 | -# # ----------------------------------------------------------------------- | |
| 533 | -# # Given a ref returns an instance of a descendent of Question(), | |
| 534 | -# # i.e. a question object (radio, checkbox, ...). | |
| 535 | -# # ----------------------------------------------------------------------- | |
| 536 | -# def generate(self, ref): | |
| 537 | - | |
| 538 | -# # Shallow copy so that script generated questions will not replace | |
| 539 | -# # the original generators | |
| 540 | -# q = self[ref].copy() | |
| 541 | - | |
| 542 | -# # If question is of generator type, an external program will be run | |
| 543 | -# # which will print a valid question in yaml format to stdout. This | |
| 544 | -# # output is then converted to a dictionary and `q` becomes that dict. | |
| 545 | -# if q['type'] == 'generator': | |
| 546 | -# logger.debug('Running script to generate question "{0}".'.format(q['ref'])) | |
| 547 | -# q.setdefault('arg', '') # optional arguments will be sent to stdin | |
| 548 | -# script = path.normpath(path.join(q['path'], q['script'])) | |
| 549 | -# out = run_script(script=script, stdin=q['arg']) | |
| 550 | -# try: | |
| 551 | -# q.update(out) | |
| 552 | -# except: | |
| 553 | -# q.update({ | |
| 554 | -# 'type': 'alert', | |
| 555 | -# 'title': 'Erro interno', | |
| 556 | -# 'text': 'Ocorreu um erro a gerar esta pergunta.' | |
| 557 | -# }) | |
| 558 | -# # The generator was replaced by a question but not yet instantiated | |
| 559 | - | |
| 560 | -# # Finally we create an instance of Question() | |
| 561 | -# try: | |
| 562 | -# qinstance = self._types[q['type']](q) # instance with correct class | |
| 563 | -# except KeyError as e: | |
| 564 | -# logger.error('Unknown question type "{0}" in "{1}:{2}".'.format(q['type'], q['filename'], q['ref'])) | |
| 565 | -# raise e | |
| 566 | -# except: | |
| 567 | -# logger.error('Failed to create question "{0}" from file "{1}".'.format(q['ref'], q['filename'])) | |
| 568 | -# else: | |
| 569 | -# logger.debug('Generated question "{}".'.format(ref)) | |
| 570 | -# return qinstance | |
| 571 | - | ... | ... |
templates/learn.html
| ... | ... | @@ -111,13 +111,13 @@ function updateQuestion(response){ |
| 111 | 111 | getQuestion(); |
| 112 | 112 | } |
| 113 | 113 | }); |
| 114 | - var audio = new Audio('/static/sounds/correct.mp3'); | |
| 115 | - audio.play(); | |
| 114 | + // var audio = new Audio('/static/sounds/correct.mp3'); | |
| 115 | + // audio.play(); | |
| 116 | 116 | $('#question_div').animateCSS('pulse'); |
| 117 | 117 | break; |
| 118 | 118 | case "shake": |
| 119 | - var audio = new Audio('/static/sounds/wrong.mp3'); | |
| 120 | - audio.play(); | |
| 119 | + // var audio = new Audio('/static/sounds/wrong.mp3'); | |
| 120 | + // audio.play(); | |
| 121 | 121 | $('#question_div').animateCSS('shake'); |
| 122 | 122 | break; |
| 123 | 123 | } |
| ... | ... | @@ -138,8 +138,8 @@ function getQuestion() { |
| 138 | 138 | } |
| 139 | 139 | |
| 140 | 140 | $(document).ready(function() { |
| 141 | - var audio = new Audio('/static/sounds/intro.mp3'); | |
| 142 | - audio.play(); | |
| 141 | + // var audio = new Audio('/static/sounds/intro.mp3'); | |
| 142 | + // audio.play(); | |
| 143 | 143 | $("#submit").click(getQuestion); |
| 144 | 144 | }); |
| 145 | 145 | </script> | ... | ... |