Commit 0b1675b0ba146a3513d7df2633f189ccd7282e4e

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

- fix browser redirection to /question when enter key is pressed.

- fix direction of graph edges.
- use config file given in a commandline option.
- improved debug messages
- removed panel from question html
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...
4   -- load/save the knowledge state of the student
  3 +- não entra à primeira
  4 +- logs mostram que está a gerar cada pergunta 2 vezes...??
  5 +- mostra tópicos do lado esquerdo, indicando quais estão feitos e quantas perguntas contêm.
5 6 - se students.db não existe, rebenta.
6 7 - database hardcoded in LearnApp.
7 8 - implementar xsrf. Ver [http://www.tornadoweb.org/en/stable/guide/security.html#cross-site-request-forgery-protection]()
... ... @@ -15,6 +16,8 @@ TODO:
15 16  
16 17 SOLVED:
17 18  
  19 +- o browser é redireccionado para /question em vez de fazer um post?? quando se pressiona enter numa caixa text edit.
  20 +- load/save the knowledge state of the student
18 21 - servir ficheiros de public temporariamente
19 22 - path dos generators scripts mal construido
20 23 - questions hardcoded in LearnApp.
... ...
app.py
... ... @@ -28,7 +28,7 @@ logger = logging.getLogger(__name__)
28 28 # LearnApp - application logic
29 29 # ============================================================================
30 30 class LearnApp(object):
31   - def __init__(self):
  31 + def __init__(self, conffile='demo/demo.yaml'):
32 32 # online students
33 33 self.online = {}
34 34  
... ... @@ -36,7 +36,7 @@ class LearnApp(object):
36 36 self.db_setup('students.db') # FIXME
37 37  
38 38 # build dependency graph
39   - self.build_dependency_graph('demo/config.yaml') # FIXME
  39 + self.build_dependency_graph(conffile) # FIXME
40 40  
41 41 # add topics from depgraph to the database
42 42 self.db_add_topics()
... ... @@ -103,7 +103,7 @@ class LearnApp(object):
103 103  
104 104 # ------------------------------------------------------------------------
105 105 def get_current_public_dir(self, uid):
106   - topic = self.online[uid]['state'].topic
  106 + topic = self.online[uid]['state'].get_current_topic()
107 107 p = self.depgraph.graph['path']
108 108 return path.join(p, topic, 'public')
109 109  
... ... @@ -164,7 +164,7 @@ class LearnApp(object):
164 164 # Build dependency graph
165 165 deps = config.get('dependencies', {})
166 166 for n,dd in deps.items():
167   - g.add_edges_from((n,d) for d in dd)
  167 + g.add_edges_from((d,n) for d in dd)
168 168  
169 169 # Builds factories for each node
170 170 for n in g.nodes_iter():
... ...
knowledge.py
... ... @@ -30,7 +30,10 @@ class Knowledge(object):
30 30 def topic_generator(self):
31 31 topics = nx.topological_sort(self.depgraph) # FIXME for now...
32 32 for t in topics:
  33 + if self.state.get(t, 0.0) > 0.999:
  34 + continue
33 35 self.questions = self.generate_questions_for_topic(t)
  36 + logger.info(f'Generated {len(self.questions)} questions for topic "{t}"')
34 37 yield t
35 38  
36 39 # ------------------------------------------------------------------------
... ... @@ -43,6 +46,10 @@ class Knowledge(object):
43 46 return self.current_question
44 47  
45 48 # ------------------------------------------------------------------------
  49 + def get_current_topic(self):
  50 + return self.current_topic
  51 +
  52 + # ------------------------------------------------------------------------
46 53 def get_knowledge_state(self):
47 54 return self.state
48 55  
... ... @@ -61,8 +68,6 @@ class Knowledge(object):
61 68 self.current_topic = next(self.topic)
62 69 self.questions = self.generate_questions_for_topic(self.current_topic)
63 70  
64   - print(self.current_topic)
65   - print(self.current_question)
66 71 self.current_question = self.questions.pop(0)
67 72 self.current_question['start_time'] = datetime.now()
68 73  
... ...
questions.py
... ... @@ -406,7 +406,7 @@ class QFactory(object):
406 406 # i.e. a question object (radio, checkbox, ...).
407 407 # -----------------------------------------------------------------------
408 408 def generate(self):
409   - logger.debug('generate()')
  409 + logger.debug(f'generate "{self.question["ref"]}"')
410 410 # Shallow copy so that script generated questions will not replace
411 411 # the original generators
412 412 q = self.question.copy()
... ... @@ -415,10 +415,8 @@ class QFactory(object):
415 415 # which will print a valid question in yaml format to stdout. This
416 416 # output is then converted to a dictionary and `q` becomes that dict.
417 417 if q['type'] == 'generator':
418   - logger.debug('Running script to generate question "{0}".'.format(q['ref']))
  418 + logger.debug(f'Running script "{q["script"]}"...')
419 419 q.setdefault('arg', '') # optional arguments will be sent to stdin
420   - # print(q['path'])
421   - # print(q['script'])
422 420 script = path.join(q['path'], q['script'])
423 421 out = run_script(script=script, stdin=q['arg'])
424 422 q.update(out)
... ... @@ -433,18 +431,11 @@ class QFactory(object):
433 431 # })
434 432  
435 433 # Finally we create an instance of Question()
436   - logger.debug('create instance...')
437   - # try:
438   - # qinstance = self._types[q['type']](q) # instance with correct class
439   - # except KeyError as e:
440   - # logger.error('Unknown question type "{0}" in "{1}:{2}".'.format(q['type'], q['filename'], q['ref']))
441   - # raise e
442   - # except:
443   - # logger.error('Failed to create question "{0}" from file "{1}".'.format(q['ref'], q['filename']))
444   - # else:
445   - # logger.debug('Generated question "{}".'.format(ref))
446   - # return qinstance
447   - qinstance = self._types[q['type']](q) # instance with correct class
448   - logger.debug('returning')
449   - return qinstance
  434 + try:
  435 + qinstance = self._types[q['type']](q) # instance with correct class
  436 + except KeyError as e:
  437 + logger.error(f'Unknown question type "{q["type"]}"')
  438 + raise e
  439 + else:
  440 + return qinstance
450 441  
... ...
serve.py
... ... @@ -8,6 +8,7 @@ import base64
8 8 import uuid
9 9 import concurrent.futures
10 10 import logging.config
  11 +import argparse
11 12  
12 13 # user installed libraries
13 14 try:
... ... @@ -32,7 +33,7 @@ from tools import load_yaml, md
32 33 # WebApplication - Tornado Web Server
33 34 # ============================================================================
34 35 class WebApplication(tornado.web.Application):
35   - def __init__(self):
  36 + def __init__(self, learnapp):
36 37 handlers = [
37 38 (r'/login', LoginHandler),
38 39 (r'/logout', LogoutHandler),
... ... @@ -50,7 +51,7 @@ class WebApplication(tornado.web.Application):
50 51 'debug': True,
51 52 }
52 53 super().__init__(handlers, **settings)
53   - self.learn = LearnApp()
  54 + self.learn = learnapp
54 55  
55 56 # ============================================================================
56 57 # Handlers
... ... @@ -169,6 +170,16 @@ def main():
169 170 SERVER_PATH = os.path.dirname(os.path.realpath(__file__))
170 171 LOGGER_CONF = os.path.join(SERVER_PATH, 'config/logger.yaml')
171 172  
  173 + # --- Commandline argument parsing
  174 + argparser = argparse.ArgumentParser(description='Server for online learning. Enrolled students and topics have to be previously configured. Please read the documentation included with this software before running the server.')
  175 + # FIXME:
  176 + # serverconf_file = path.normpath(path.join(SERVER_PATH, 'config', 'server.conf'))
  177 + # argparser.add_argument('--conf', default=serverconf_file, type=str, help='server configuration file')
  178 + # argparser.add_argument('--debug', action='store_true', help='Enable debug logging.')
  179 + # argparser.add_argument('--allow-all', action='store_true',
  180 + # help='Students are initially allowed to login (can be denied later)')
  181 + argparser.add_argument('conffile', type=str, nargs='+', help='Topics configuration file in YAML format.') # FIXME only one supported at the moment
  182 + arg = argparser.parse_args()
172 183  
173 184 # --- Setup logging
174 185 try:
... ... @@ -179,8 +190,9 @@ def main():
179 190 sys.exit(1)
180 191  
181 192 # --- start application
  193 + learnapp = LearnApp(arg.conffile[0])
182 194 try:
183   - webapp = WebApplication()
  195 + webapp = WebApplication(learnapp)
184 196 except Exception as e:
185 197 logging.critical('Can\'t start application.')
186 198 # sys.exit(1)
... ...
templates/learn.html
... ... @@ -66,12 +66,12 @@
66 66 <!-- ===================================================================== -->
67 67 <!-- Container -->
68 68 <div class="container">
69   -<audio>
  69 +<!-- <audio>
70 70 <source id="snd-intro" src="/static/sounds/intro.mp3" type="audio/mpeg">
71 71 <source id="snd-correct" src="/static/sounds/correct.mp3" type="audio/mpeg">
72 72 <source id="snd-wrong" src="/static/sounds/wrong.mp3" type="audio/mpeg">
73 73 </audio>
74   -
  74 + -->
75 75 <form action="/question" method="post" id="question_form" autocomplete="off">
76 76 {% module xsrf_form_html() %}
77 77  
... ... @@ -106,9 +106,11 @@ function updateQuestion(response){
106 106 MathJax.Hub.Queue(["Typeset",MathJax.Hub,"question_div"]);
107 107  
108 108 $("textarea, input:text, input:radio, input:checkbox").keydown(function (e) {
109   - if (e.keyCode == 13 && e.shiftKey) {
  109 + if (e.keyCode == 13) {
110 110 e.preventDefault();
111   - getQuestion();
  111 + if (e.shiftKey) {
  112 + getQuestion();
  113 + }
112 114 }
113 115 });
114 116 // var audio = new Audio('/static/sounds/correct.mp3');
... ...
templates/question.html
1 1 {% autoescape %}
2 2  
3   -<div class="panel panel-default">
4   - <div class="panel-body">
  3 +<!-- <div class="panel panel-default">
  4 + <div class="panel-body">
  5 + -->
  6 + <h3>{{ question['title'] }}</h3>
5 7  
6   -<h3>{{ question['title'] }}</h3>
  8 + <div id="text">
  9 + {{ md(question['text']) }}
  10 + </div>
7 11  
8   -<div id="text">
9   -{{ md(question['text']) }}
10   -</div>
11   -
12   -{% block answer %}{% end %}
  12 + {% block answer %}{% end %}
13 13  
14   -</div></div>
15 14 \ No newline at end of file
  15 +<!-- </div>
  16 +</div>
  17 + -->
16 18 \ No newline at end of file
... ...