Commit 7a51d4543fbf92340dbb0d5af1c9916fb054bf22

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

- fix check for nonexisting goals

- fix missing database
1 1
2 # BUGS 2 # BUGS
3 3
4 -  
5 -Traceback (most recent call last):  
6 - File "/home/mjsb/.local/lib/python3.7/site-packages/tornado/web.py", line 1697, in _execute  
7 - result = method(*self.path_args, **self.path_kwargs)  
8 - File "/home/mjsb/.local/lib/python3.7/site-packages/tornado/web.py", line 3174, in wrapper  
9 - return method(self, *args, **kwargs)  
10 - File "/usr/home/mjsb/Work/Projects/aprendizations/aprendizations/serve.py", line 213, in get  
11 - self.learn.start_course(uid, course)  
12 - File "/usr/home/mjsb/Work/Projects/aprendizations/aprendizations/learnapp.py", line 275, in start_course  
13 - student.start_course(course)  
14 - File "/usr/home/mjsb/Work/Projects/aprendizations/aprendizations/student.py", line 57, in start_course  
15 - self.topic_sequence = self.recommend_topic_sequence(topics)  
16 - File "/usr/home/mjsb/Work/Projects/aprendizations/aprendizations/student.py", line 216, in recommend_topic_sequence  
17 - ts.update(nx.ancestors(G, t))  
18 - File "/home/mjsb/.local/lib/python3.7/site-packages/networkx/algorithms/dag.py", line 92, in ancestors  
19 - raise nx.NetworkXError("The node %s is not in the graph." % source)  
20 -networkx.exception.NetworkXError: The node programming/languages/pseudo-tcg/functions-produtorio is not in the graph.  
21 -  
22 -- detectar se em courses.yaml falta declarar ficheiro. Por exemplo se houver goals que não estao em lado nenhum. 4 +- nao esta a seguir o max_tries definido no ficheiro de dependencias.
23 - registar last_seen e remover os antigos de cada vez que houver um login. 5 - registar last_seen e remover os antigos de cada vez que houver um login.
24 - initdb da integrity error se no mesmo comando existirem alunos repetidos (p.ex em ficheiros csv diferentes ou entre csv e opcao -a) 6 - initdb da integrity error se no mesmo comando existirem alunos repetidos (p.ex em ficheiros csv diferentes ou entre csv e opcao -a)
25 -- permite definir goal, mas nao verifica se esta no grafo. rebenta no start_topic.  
26 - double click submits twice. 7 - double click submits twice.
27 -- nao esta a seguir o max_tries definido no ficheiro de dependencias.  
28 -- impedir que quando students.db não é encontrado, crie um ficheiro vazio.  
29 - classificacoes so devia mostrar os que ja fizeram alguma coisa 8 - classificacoes so devia mostrar os que ja fizeram alguma coisa
30 - QFactory.generate() devia fazer run da gen_async, ou remover. 9 - QFactory.generate() devia fazer run da gen_async, ou remover.
31 - marking all options right in a radio question breaks! 10 - marking all options right in a radio question breaks!
@@ -70,6 +49,8 @@ sqlite3.ProgrammingError: SQLite objects created in a thread can only be used in @@ -70,6 +49,8 @@ sqlite3.ProgrammingError: SQLite objects created in a thread can only be used in
70 49
71 # FIXED 50 # FIXED
72 51
  52 +- impedir que quando students.db não é encontrado, crie um ficheiro vazio.
  53 +- permite definir goal, mas nao verifica se esta no grafo. rebenta no start_topic.
73 - se num topico, a ultima pergunta tem imagens, o servidor nao fornece as imagengs porque o current_topic passa a None antes de carregar no botao continuar. O caminho é prefix+None e dá erro. 54 - se num topico, a ultima pergunta tem imagens, o servidor nao fornece as imagengs porque o current_topic passa a None antes de carregar no botao continuar. O caminho é prefix+None e dá erro.
74 - caixas com os cursos não se ajustam bem com ecran estreito. 55 - caixas com os cursos não se ajustam bem com ecran estreito.
75 - obter rankings por curso GET course=course_id 56 - obter rankings por curso GET course=course_id
aprendizations/learnapp.py
@@ -69,28 +69,37 @@ class LearnApp(object): @@ -69,28 +69,37 @@ class LearnApp(object):
69 69
70 config: Dict[str, Any] = load_yaml(courses) 70 config: Dict[str, Any] = load_yaml(courses)
71 71
72 - # --- courses dict  
73 - self.courses = config['courses']  
74 - logger.info(f'Courses: {", ".join(self.courses.keys())}')  
75 -  
76 # --- topic dependencies are shared between all courses 72 # --- topic dependencies are shared between all courses
77 self.deps = nx.DiGraph(prefix=prefix) 73 self.deps = nx.DiGraph(prefix=prefix)
78 - logger.info('Populating graph:') 74 + logger.info('Populating topic graph:')
79 75
80 t = config.get('topics', {}) # topics defined directly in courses file 76 t = config.get('topics', {}) # topics defined directly in courses file
81 self.populate_graph(t) 77 self.populate_graph(t)
82 logger.info(f'{len(t):>6} topics in {courses}') 78 logger.info(f'{len(t):>6} topics in {courses}')
83 for f in config.get('topics_from', []): 79 for f in config.get('topics_from', []):
84 c = load_yaml(f) # course configuration 80 c = load_yaml(f) # course configuration
  81 +
  82 + # FIXME set defaults??
85 logger.info(f'{len(c["topics"]):>6} topics imported from {f}') 83 logger.info(f'{len(c["topics"]):>6} topics imported from {f}')
86 self.populate_graph(c) 84 self.populate_graph(c)
87 logger.info(f'Graph has {len(self.deps)} topics') 85 logger.info(f'Graph has {len(self.deps)} topics')
88 86
  87 + # --- courses dict
  88 + self.courses = config['courses']
  89 + logger.info(f'Courses: {", ".join(self.courses.keys())}')
  90 + for c, d in self.courses.items():
  91 + for goal in d['goals']:
  92 + if goal not in self.deps.nodes():
  93 + # logger.error(f'Goal "{goal}" of "{c}"" not in the graph')
  94 + raise LearnException(f'Goal "{goal}" from course "{c}" '
  95 + ' does not exist')
  96 +
89 # --- factory is a dict with question generators for all topics 97 # --- factory is a dict with question generators for all topics
90 self.factory: Dict[str, QFactory] = self.make_factory() 98 self.factory: Dict[str, QFactory] = self.make_factory()
91 99
92 # if graph has topics that are not in the database, add them 100 # if graph has topics that are not in the database, add them
93 - self.db_add_missing_topics(self.deps.nodes()) 101 + self.add_missing_topics(self.deps.nodes())
  102 +
94 103
95 if check: 104 if check:
96 self.sanity_check_questions() 105 self.sanity_check_questions()
@@ -301,7 +310,7 @@ class LearnApp(object): @@ -301,7 +310,7 @@ class LearnApp(object):
301 # ------------------------------------------------------------------------ 310 # ------------------------------------------------------------------------
302 # Fill db table 'Topic' with topics from the graph if not already there. 311 # Fill db table 'Topic' with topics from the graph if not already there.
303 # ------------------------------------------------------------------------ 312 # ------------------------------------------------------------------------
304 - def db_add_missing_topics(self, topics: List[str]) -> None: 313 + def add_missing_topics(self, topics: List[str]) -> None:
305 with self.db_session() as s: 314 with self.db_session() as s:
306 new_topics = [Topic(id=t) for t in topics 315 new_topics = [Topic(id=t) for t in topics
307 if (t,) not in s.query(Topic.id)] 316 if (t,) not in s.query(Topic.id)]
@@ -317,6 +326,10 @@ class LearnApp(object): @@ -317,6 +326,10 @@ class LearnApp(object):
317 def db_setup(self, db: str) -> None: 326 def db_setup(self, db: str) -> None:
318 327
319 logger.info(f'Checking database "{db}":') 328 logger.info(f'Checking database "{db}":')
  329 + if not path.exists(db):
  330 + raise LearnException('Database does not exist. '
  331 + 'Use "initdb-aprendizations" to create')
  332 +
320 engine = sa.create_engine(f'sqlite:///{db}', echo=False) 333 engine = sa.create_engine(f'sqlite:///{db}', echo=False)
321 self.Session = sa.orm.sessionmaker(bind=engine) 334 self.Session = sa.orm.sessionmaker(bind=engine)
322 try: 335 try:
aprendizations/main.py
@@ -9,7 +9,7 @@ import sys @@ -9,7 +9,7 @@ import sys
9 from typing import Any, Dict 9 from typing import Any, Dict
10 10
11 # this project 11 # this project
12 -from .learnapp import LearnApp, DatabaseUnusableError 12 +from .learnapp import LearnApp, DatabaseUnusableError, LearnException
13 from .serve import run_webserver 13 from .serve import run_webserver
14 from .tools import load_yaml 14 from .tools import load_yaml
15 from . import APP_NAME, APP_VERSION 15 from . import APP_NAME, APP_VERSION
@@ -160,7 +160,7 @@ def main(): @@ -160,7 +160,7 @@ def main():
160 else: 160 else:
161 logging.info('SSL certificates loaded') 161 logging.info('SSL certificates loaded')
162 162
163 - # --- start application 163 + # --- start application --------------------------------------------------
164 try: 164 try:
165 learnapp = LearnApp(courses=arg.courses, 165 learnapp = LearnApp(courses=arg.courses,
166 prefix=arg.prefix, 166 prefix=arg.prefix,
@@ -178,13 +178,16 @@ def main(): @@ -178,13 +178,16 @@ def main():
178 '--------------------------------------------------------------', 178 '--------------------------------------------------------------',
179 sep='\n') 179 sep='\n')
180 sys.exit(1) 180 sys.exit(1)
  181 + except LearnException as e:
  182 + logging.critical(e)
  183 + sys.exit(1)
181 except Exception: 184 except Exception:
182 logging.critical('Failed to start backend.') 185 logging.critical('Failed to start backend.')
183 sys.exit(1) 186 sys.exit(1)
184 else: 187 else:
185 logging.info('LearnApp started') 188 logging.info('LearnApp started')
186 189
187 - # --- run webserver forever 190 + # --- run webserver forever ----------------------------------------------
188 run_webserver(app=learnapp, ssl=ssl_ctx, port=arg.port, debug=arg.debug) 191 run_webserver(app=learnapp, ssl=ssl_ctx, port=arg.port, debug=arg.debug)
189 192
190 193
demo/astronomy.yaml
@@ -3,11 +3,11 @@ @@ -3,11 +3,11 @@
3 # optional values applied to each topic, if undefined there 3 # optional values applied to each topic, if undefined there
4 # ---------------------------------------------------------------------------- 4 # ----------------------------------------------------------------------------
5 5
6 -# defaults: 6 +# defaults: FIXME not working
7 # file: questions.yaml 7 # file: questions.yaml
8 # shuffle_questions: true 8 # shuffle_questions: true
9 # choose: 6 9 # choose: 6
10 -# max_tries: 2 10 +# max_tries: 200
11 # forgetting_factor: 0.97 11 # forgetting_factor: 0.97
12 # min_level: 0.01 12 # min_level: 0.01
13 # append_wrong: true 13 # append_wrong: true