Commit ed34db4c3f94e62db9d0a52efa31a21acee63133

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

- save topic state on logout

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.
@@ -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()
@@ -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
@@ -74,5 +74,5 @@ class Topic(Base): @@ -74,5 +74,5 @@ class Topic(Base):
74 # --- 74 # ---
75 students = relationship('StudentTopic', back_populates='topic') 75 students = relationship('StudentTopic', back_populates='topic')
76 76
77 -  
78 - 77 + def __init__(self, id):
  78 + self.id = id
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>