diff --git a/BUGS.md b/BUGS.md index 2ae79a1..76c7b66 100644 --- a/BUGS.md +++ b/BUGS.md @@ -1,15 +1,16 @@ BUGS: -- guardar state cada vez que topico termina +- error if demo.yaml has no topics +- pymips a funcionar - reload da página rebenta o estado. +- guardar state cada vez que topico termina - indicar o topico actual no sidebar - session management. close after inactive time. - implementar xsrf. Ver [http://www.tornadoweb.org/en/stable/guide/security.html#cross-site-request-forgery-protection]() TODO: -- 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 no textarea diff --git a/app.py b/app.py index e95db1a..4b5cbd5 100644 --- a/app.py +++ b/app.py @@ -3,6 +3,7 @@ from contextlib import contextmanager # `with` statement in db sessions import logging from os import path, sys +from datetime import datetime # user installed libraries try: @@ -58,7 +59,12 @@ class LearnApp(object): # success tt = s.query(StudentTopic).filter(StudentTopic.student_id == uid) - state = {t.topic_id: t.level for t in tt} + state = {} + for t in tt: + state[t.topic_id] = { + 'level': t.level, + 'date': datetime.strptime(t.date, "%Y-%m-%d %H:%M:%S.%f"), + } self.online[uid] = { 'name': student.name, @@ -79,13 +85,15 @@ class LearnApp(object): # update existing associations and remove from state dict for a in s.query(StudentTopic).filter_by(student_id=uid): if a.topic_id in state: - a.level = state.pop(a.topic_id) # update + d = state.pop(a.topic_id) + a.level = d['level'] #state.pop(a.topic_id) # update + a.date = str(d['date']) s.add(a) # insert the remaining ones u = s.query(Student).get(uid) - for n,l in state.items(): - a = StudentTopic(level=l) + for n,d in state.items(): + a = StudentTopic(level=d['level'], date=str(d['date'])) t = s.query(Topic).get(n) if t is None: # create if topic doesn't exist yet t = Topic(id=n) @@ -134,6 +142,10 @@ class LearnApp(object): return path.join(p, topic, 'public') # ------------------------------------------------------------------------ + # def get_current_question(self, uid): + # return self.online[uid]['state'].current_question + + # ------------------------------------------------------------------------ # check answer and if correct returns new question, otherise returns None def check_answer(self, uid, answer): knowledge = self.online[uid]['state'] diff --git a/initdb.py b/initdb.py index 6afb130..e916cc2 100755 --- a/initdb.py +++ b/initdb.py @@ -10,7 +10,7 @@ import bcrypt from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker -from models import Base, Student, Answer +from models import Base, Student #, Answer # SIIUE names have alien strings like "(TE)" and are sometimes capitalized # We remove them so that students dont keep asking what it means diff --git a/knowledge.py b/knowledge.py index da0d844..16d03be 100644 --- a/knowledge.py +++ b/knowledge.py @@ -18,19 +18,21 @@ logger = logging.getLogger(__name__) class Knowledge(object): def __init__(self, depgraph, state={}): self.depgraph = depgraph - self.state = state # {node: level, node: level, ...} - self.topic_sequence = nx.topological_sort(self.depgraph) # FIXME + # state = {'topic_id': {'level':0.5, 'date': datetime}, ...} + self.state = state + # select a topic to do self.new_topic() # ------------------------------------------------------------------------ def new_topic(self, topic=None): + logger.debug(f'new_topic {topic}') if topic is None: # select the first topic that has level < 0.9 for topic in self.topic_sequence: - if self.state.get(topic, 0.0) < 0.9: + if topic not in self.state or self.state[topic]['level'] < 0.9: break # FIXME if all are > 0.9, will stay in the last one forever... @@ -55,17 +57,25 @@ class Knowledge(object): # ------------------------------------------------------------------------ def get_knowledge_state(self): - return [(t, self.state.get(t, 0.0)) for t in self.topic_sequence] + ts = [] + for t in self.topic_sequence: + if t in self.state: + ts.append((t, self.state[t]['level'])) + else: + ts.append((t, 0.0)) + return ts + # return [(t, self.state.get(t, 0.0)) for t in self.topic_sequence] # ------------------------------------------------------------------------ def get_topic_progress(self): + logger.debug('-> Knowledge.get_topic_progress()') return len(self.finished_questions) / (len(self.finished_questions) + len(self.questions)) # ------------------------------------------------------------------------ # if answer to current question is correct generates a new question # otherwise returns none def new_question(self): - logger.debug('Knowledge.new_question()') + logger.debug('-> Knowledge.new_question()') if self.current_question is None or \ self.current_question.get('grade', 0.0) > 0.9: @@ -73,7 +83,10 @@ class Knowledge(object): # if no more questions in this topic, go to the next one # keep going if there are no questions in the next topics while not self.questions: - self.state[self.current_topic] = 1.0 + self.state[self.current_topic] = { + 'level': 1.0, + 'date': datetime.now() + } self.new_topic() self.current_question = self.questions.pop(0) @@ -82,10 +95,11 @@ class Knowledge(object): return self.current_question + # --- checks answer ------------------------------------------------------ # returns current question with correction, time and comments updated def check_answer(self, answer): - logger.debug(f'Knowledge.check_answer({answer})') + logger.debug(f'-> Knowledge.check_answer({answer})') question = self.current_question if question is not None: question['finish_time'] = datetime.now() diff --git a/models.py b/models.py index 9af7e97..99fe742 100644 --- a/models.py +++ b/models.py @@ -15,7 +15,7 @@ class StudentTopic(Base): student_id = Column(String, ForeignKey('students.id'), primary_key=True) topic_id = Column(String, ForeignKey('topics.id'), primary_key=True) level = Column(Float) - # date = Column(String) + date = Column(String) # --- student = relationship('Student', back_populates='topics') diff --git a/questions.py b/questions.py index 0b7559f..4987756 100644 --- a/questions.py +++ b/questions.py @@ -434,7 +434,7 @@ class QFactory(object): try: qinstance = self._types[q['type']](q) # instance with correct class except KeyError as e: - logger.error(f'Unknown question type "{q["type"]}"') + logger.error(f'Failed to generate question "{q["ref"]}"') raise e else: return qinstance diff --git a/serve.py b/serve.py index 8f4e9e7..28c294a 100755 --- a/serve.py +++ b/serve.py @@ -157,9 +157,9 @@ class QuestionHandler(BaseHandler): 'textarea': 'question-textarea.html', } - @tornado.web.authenticated - def get(self): - self.redirect('/') + # @tornado.web.authenticated + # def get(self): # FIXME unused + # self.redirect('/') @tornado.web.authenticated def post(self): @@ -222,6 +222,8 @@ def main(): print('Common causes:\n - inexistent directory "logs"?\n - write permission to "logs" directory?') sys.exit(1) + logging.info('===========================================================') + # --- start application learnapp = LearnApp(arg.conffile[0]) try: diff --git a/templates/learn.html b/templates/learn.html index 5067f54..79993fc 100644 --- a/templates/learn.html +++ b/templates/learn.html @@ -153,7 +153,7 @@ $.fn.extend({ } }); -// Processes the response given by the served after an answer is submitted. +// Processes the response given by the server after an answer is submitted. function updateQuestion(response){ switch (response["method"]) { case "new_question": -- libgit2 0.21.2