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
1 | BUGS: | 1 | BUGS: |
2 | 2 | ||
3 | +- de vez em quando o browser é redireccionado para /question em vez de fazer um post?? não percebo... | ||
3 | - load/save the knowledge state of the student | 4 | - load/save the knowledge state of the student |
4 | - se students.db não existe, rebenta. | 5 | - se students.db não existe, rebenta. |
5 | - database hardcoded in LearnApp. | 6 | - database hardcoded in LearnApp. |
app.py
@@ -66,8 +66,31 @@ class LearnApp(object): | @@ -66,8 +66,31 @@ class LearnApp(object): | ||
66 | # ------------------------------------------------------------------------ | 66 | # ------------------------------------------------------------------------ |
67 | # logout | 67 | # logout |
68 | def logout(self, uid): | 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 | logger.info(f'User "{uid}" logged out') | 93 | logger.info(f'User "{uid}" logged out') |
70 | - del self.online[uid] # FIXME save current state | ||
71 | 94 | ||
72 | # ------------------------------------------------------------------------ | 95 | # ------------------------------------------------------------------------ |
73 | def get_student_name(self, uid): | 96 | def get_student_name(self, uid): |
@@ -105,8 +128,8 @@ class LearnApp(object): | @@ -105,8 +128,8 @@ class LearnApp(object): | ||
105 | # helper to manage db sessions using the `with` statement, for example | 128 | # helper to manage db sessions using the `with` statement, for example |
106 | # with self.db_session() as s: s.query(...) | 129 | # with self.db_session() as s: s.query(...) |
107 | @contextmanager | 130 | @contextmanager |
108 | - def db_session(self): | ||
109 | - session = self.Session() | 131 | + def db_session(self, **kw): |
132 | + session = self.Session(**kw) | ||
110 | try: | 133 | try: |
111 | yield session | 134 | yield session |
112 | session.commit() | 135 | session.commit() |
knowledge.py
@@ -24,6 +24,8 @@ class Knowledge(object): | @@ -24,6 +24,8 @@ class Knowledge(object): | ||
24 | self.seq = nx.topological_sort(self.depgraph) | 24 | self.seq = nx.topological_sort(self.depgraph) |
25 | self.topic = None | 25 | self.topic = None |
26 | 26 | ||
27 | + self.state = {'a': 3, 'b': 4} | ||
28 | + | ||
27 | def get_current_question(self): | 29 | def get_current_question(self): |
28 | return self.current_question | 30 | return self.current_question |
29 | 31 |
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 | # the argument is the reference of the question we wish to produce. | 5 | # the argument is the reference of the question we wish to produce. |
6 | # | 6 | # |
7 | # Example: | 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 | # # experiment answering one question and correct it | 14 | # # experiment answering one question and correct it |
16 | -# question['answer'] = 42 # insert answer | 15 | +# question.updateAnswer('42') # insert answer |
17 | # grade = question.correct() # correct answer | 16 | # grade = question.correct() # correct answer |
18 | 17 | ||
19 | # An instance of an actual question is an object that inherits from Question() | 18 | # An instance of an actual question is an object that inherits from Question() |
@@ -28,7 +27,6 @@ | @@ -28,7 +27,6 @@ | ||
28 | 27 | ||
29 | import random | 28 | import random |
30 | import re | 29 | import re |
31 | -# import subprocess | ||
32 | from os import path | 30 | from os import path |
33 | import logging | 31 | import logging |
34 | import sys | 32 | import sys |
@@ -419,8 +417,8 @@ class QFactory(object): | @@ -419,8 +417,8 @@ class QFactory(object): | ||
419 | if q['type'] == 'generator': | 417 | if q['type'] == 'generator': |
420 | logger.debug('Running script to generate question "{0}".'.format(q['ref'])) | 418 | logger.debug('Running script to generate question "{0}".'.format(q['ref'])) |
421 | q.setdefault('arg', '') # optional arguments will be sent to stdin | 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 | script = path.join(q['path'], q['script']) | 422 | script = path.join(q['path'], q['script']) |
425 | out = run_script(script=script, stdin=q['arg']) | 423 | out = run_script(script=script, stdin=q['arg']) |
426 | q.update(out) | 424 | q.update(out) |
@@ -450,122 +448,3 @@ class QFactory(object): | @@ -450,122 +448,3 @@ class QFactory(object): | ||
450 | logger.debug('returning') | 448 | logger.debug('returning') |
451 | return qinstance | 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,13 +111,13 @@ function updateQuestion(response){ | ||
111 | getQuestion(); | 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 | $('#question_div').animateCSS('pulse'); | 116 | $('#question_div').animateCSS('pulse'); |
117 | break; | 117 | break; |
118 | case "shake": | 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 | $('#question_div').animateCSS('shake'); | 121 | $('#question_div').animateCSS('shake'); |
122 | break; | 122 | break; |
123 | } | 123 | } |
@@ -138,8 +138,8 @@ function getQuestion() { | @@ -138,8 +138,8 @@ function getQuestion() { | ||
138 | } | 138 | } |
139 | 139 | ||
140 | $(document).ready(function() { | 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 | $("#submit").click(getQuestion); | 143 | $("#submit").click(getQuestion); |
144 | }); | 144 | }); |
145 | </script> | 145 | </script> |