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> | ... | ... |