From ea950c895f1217578cdb676043eb893a7baa7f56 Mon Sep 17 00:00:00 2001 From: Miguel Barão Date: Sat, 11 Mar 2017 18:01:45 +0000 Subject: [PATCH] - support for configuration file with more information for each node, including: - name (user friendly name for the web) - deps (list of dependencies) --- BUGS.md | 10 +++++++--- app.py | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------- models.py | 1 - serve.py | 10 +++++++--- templates/learn.html | 30 ++++++++---------------------- templates/question.html | 7 ------- templates/topics.html | 2 +- 7 files changed, 137 insertions(+), 63 deletions(-) diff --git a/BUGS.md b/BUGS.md index 73df427..24a8735 100644 --- a/BUGS.md +++ b/BUGS.md @@ -1,16 +1,20 @@ BUGS: +- guardar state cada vez que topico termina +- alterar password. +- logs mostram que está a gerar cada pergunta 2 vezes...?? - reload da página rebenta o estado. - indicar o topico actual no sidebar - session management. close after inactive time. -- guardar state cada vez que topico termina -- logs mostram que está a gerar cada pergunta 2 vezes...?? - implementar xsrf. Ver [http://www.tornadoweb.org/en/stable/guide/security.html#cross-site-request-forgery-protection]() TODO: +- letsencrypt.org +- logs de debug devem indicar o user. +- implementar http com redirect para https. - topicos no sidebar devem ser links para iniciar um topico acessivel. os inacessiveis devem estar inactivos. -- usar codemirror +- usar codemirror no textarea - mostrar comments quando falha a resposta - generators not working: bcrypt (ver blog) diff --git a/app.py b/app.py index 9c90a09..3cce938 100644 --- a/app.py +++ b/app.py @@ -81,7 +81,6 @@ class LearnApp(object): if a.topic_id in state: a.level = state.pop(a.topic_id) # update s.add(a) - # s.add_all(aa) # insert the remaining ones u = s.query(Student).get(uid) @@ -114,6 +113,10 @@ class LearnApp(object): return self.depgraph.graph['title'] # ------------------------------------------------------------------------ + def get_topic_name(self, ref): + return self.depgraph.node[ref]['name'] + + # ------------------------------------------------------------------------ def get_current_public_dir(self, uid): topic = self.online[uid]['state'].get_current_topic() p = self.depgraph.graph['path'] @@ -140,19 +143,49 @@ class LearnApp(object): return knowledge.new_question() # ------------------------------------------------------------------------ - # helper to manage db sessions using the `with` statement, for example - # with self.db_session() as s: s.query(...) - @contextmanager - def db_session(self, **kw): - session = self.Session(**kw) - try: - yield session - session.commit() - except Exception as e: - session.rollback() - raise e - finally: - session.close() + # Receives a set of topics (strings like "math/algebra"), + # and recursively adds dependencies to the dependency graph + # def build_dependency_graph_old(self, config_file): + # logger.debug(f'LearnApp.build_dependency_graph("{config_file}")') + + # # Load configuration file + # try: + # with open(config_file, 'r') as f: + # logger.info(f'Loading configuration file "{config_file}"') + # config = yaml.load(f) + # except FileNotFoundError as e: + # logger.error(f'File not found: "{config_file}"') + # sys.exit(1) + # # config file parsed + + # prefix = config.get('path', '.') + # title = config.get('title', '') + # database = config.get('database', 'students.db') + # g = nx.DiGraph(path=prefix, title=title, database=database) + + # # Build dependency graph + # deps = config.get('dependencies', {}) + # for n,dd in deps.items(): + # g.add_edges_from((d,n) for d in dd) + + # # Builds factories for each node + # for n in g.nodes_iter(): + # fullpath = path.expanduser(path.join(prefix, n)) + # if path.isdir(fullpath): + # # if directory defaults to "prefix/questions.yaml" + # filename = path.join(fullpath, "questions.yaml") + # else: + # logger.error(f'build_dependency_graph: "{fullpath}" is not a directory') + + # if path.isfile(filename): + # logger.info(f'Loading questions from "{filename}"') + # questions = load_yaml(filename, default=[]) + # for q in questions: + # q['path'] = fullpath + + # g.node[n]['factory'] = [QFactory(q) for q in questions] + + # self.depgraph = g # ------------------------------------------------------------------------ # Receives a set of topics (strings like "math/algebra"), @@ -168,22 +201,33 @@ class LearnApp(object): except FileNotFoundError as e: logger.error(f'File not found: "{config_file}"') sys.exit(1) + # config file parsed - prefix = config['path'] # FIXME default if does not exist? + prefix = config.get('path', '.') title = config.get('title', '') database = config.get('database', 'students.db') g = nx.DiGraph(path=prefix, title=title, database=database) - # Build dependency graph - deps = config.get('dependencies', {}) - for n,dd in deps.items(): - g.add_edges_from((d,n) for d in dd) - - # Builds factories for each node - for n in g.nodes_iter(): - fullpath = path.expanduser(path.join(prefix, n)) + # iterate over topics and build graph + topics = config.get('topics', {}) + for ref,attr in topics.items(): + g.add_node(ref) + if isinstance(attr, list): + # if prop is a list, we assume it's just a list of dependencies + g.add_edges_from((d,ref) for d in attr) + + elif isinstance(attr, dict): + g.node[ref]['name'] = attr.get('name', ref) + g.add_edges_from((d,ref) for d in attr.get('deps', [])) + + elif isinstance(attr, str): + g.node[ref]['name'] = attr + + # iterate over topics and create question factories + for ref in g.nodes_iter(): + g.node[ref].setdefault('name', ref) + fullpath = path.expanduser(path.join(prefix, ref)) if path.isdir(fullpath): - # if directory defaults to "prefix/questions.yaml" filename = path.join(fullpath, "questions.yaml") else: logger.error(f'build_dependency_graph: "{fullpath}" is not a directory') @@ -193,10 +237,39 @@ class LearnApp(object): questions = load_yaml(filename, default=[]) for q in questions: q['path'] = fullpath - - g.node[n]['factory'] = [QFactory(q) for q in questions] + g.node[ref]['factory'] = [QFactory(q) for q in questions] + else: + g.node[ref]['factory'] = [] + logger.error(f'build_dependency_graph: "{filename}" does not exist') self.depgraph = g + return g + + + # # Build dependency graph + # deps = config.get('dependencies', {}) + # for n,dd in deps.items(): + # g.add_edges_from((d,n) for d in dd) + + # # Builds factories for each node + # for n in g.nodes_iter(): + # fullpath = path.expanduser(path.join(prefix, n)) + # if path.isdir(fullpath): + # # if directory defaults to "prefix/questions.yaml" + # filename = path.join(fullpath, "questions.yaml") + # else: + # logger.error(f'build_dependency_graph: "{fullpath}" is not a directory') + + # if path.isfile(filename): + # logger.info(f'Loading questions from "{filename}"') + # questions = load_yaml(filename, default=[]) + # for q in questions: + # q['path'] = fullpath + + # g.node[n]['factory'] = [QFactory(q) for q in questions] + + # self.depgraph = g + # ------------------------------------------------------------------------ def db_add_topics(self): @@ -220,3 +293,18 @@ class LearnApp(object): else: logger.info(f'Database has {n} students registered.') + # ------------------------------------------------------------------------ + # helper to manage db sessions using the `with` statement, for example + # with self.db_session() as s: s.query(...) + @contextmanager + def db_session(self, **kw): + session = self.Session(**kw) + try: + yield session + session.commit() + except Exception as e: + session.rollback() + raise e + finally: + session.close() + diff --git a/models.py b/models.py index 910041b..9af7e97 100644 --- a/models.py +++ b/models.py @@ -40,7 +40,6 @@ class Student(Base): name: "{self.name}" password: "{self.password}"''' - # --------------------------------------------------------------------------- # Table with every answer given # --------------------------------------------------------------------------- diff --git a/serve.py b/serve.py index 5ca9584..b71b735 100755 --- a/serve.py +++ b/serve.py @@ -153,11 +153,16 @@ class QuestionHandler(BaseHandler): progress = self.learn.get_student_progress(user) # in the current topic if next_question is not None: - question_html = self.render_string(self.templates[next_question['type']], + question_html = self.render_string( + self.templates[next_question['type']], question=next_question, # dictionary with the question md=md, # function that renders markdown to html ) - topics_html = self.render_string('topics.html', state=state) + topics_html = self.render_string( + 'topics.html', + state=state, + topicname=self.learn.get_topic_name, # function that translates topic references to names + ) self.write({ 'method': 'new_question', @@ -175,7 +180,6 @@ class QuestionHandler(BaseHandler): }, }) - # ---------------------------------------------------------------------------- def main(): SERVER_PATH = os.path.dirname(os.path.realpath(__file__)) diff --git a/templates/learn.html b/templates/learn.html index 1629d43..0b4e15f 100644 --- a/templates/learn.html +++ b/templates/learn.html @@ -34,11 +34,10 @@ -
+ - +
- - + -->
@@ -99,29 +96,18 @@ {% module xsrf_form_html() %}
- Pronto? + FIXME
-
- - - - -
-
- - - - diff --git a/templates/question.html b/templates/question.html index 676066a..11273a2 100644 --- a/templates/question.html +++ b/templates/question.html @@ -1,15 +1,8 @@ {% autoescape %} -

{{ question['title'] }}

{{ md(question['text']) }}
{% block answer %}{% end %} - - \ No newline at end of file diff --git a/templates/topics.html b/templates/topics.html index 17e12ab..c021335 100644 --- a/templates/topics.html +++ b/templates/topics.html @@ -5,7 +5,7 @@