Commit ed34db4c3f94e62db9d0a52efa31a21acee63133

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

- save topic state on logout

BUGS.md
1 1 BUGS:
2 2  
  3 +- de vez em quando o browser é redireccionado para /question em vez de fazer um post?? não percebo...
3 4 - load/save the knowledge state of the student
4 5 - se students.db não existe, rebenta.
5 6 - database hardcoded in LearnApp.
... ...
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
... ... @@ -24,6 +24,8 @@ class Knowledge(object):
24 24 self.seq = nx.topological_sort(self.depgraph)
25 25 self.topic = None
26 26  
  27 + self.state = {'a': 3, 'b': 4}
  28 +
27 29 def get_current_question(self):
28 30 return self.current_question
29 31  
... ...
models.py
... ... @@ -74,5 +74,5 @@ class Topic(Base):
74 74 # ---
75 75 students = relationship('StudentTopic', back_populates='topic')
76 76  
77   -
78   -
  77 + def __init__(self, id):
  78 + self.id = id
... ...
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>
... ...