Commit bbfe1f31bd7dcc3390e7e388fd522f8632d0ea1e

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

- the topic state now has level and date.

1 1
2 BUGS: 2 BUGS:
3 3
4 -- guardar state cada vez que topico termina 4 +- error if demo.yaml has no topics
  5 +- pymips a funcionar
5 - reload da página rebenta o estado. 6 - reload da página rebenta o estado.
  7 +- guardar state cada vez que topico termina
6 - indicar o topico actual no sidebar 8 - indicar o topico actual no sidebar
7 - session management. close after inactive time. 9 - session management. close after inactive time.
8 - implementar xsrf. Ver [http://www.tornadoweb.org/en/stable/guide/security.html#cross-site-request-forgery-protection]() 10 - implementar xsrf. Ver [http://www.tornadoweb.org/en/stable/guide/security.html#cross-site-request-forgery-protection]()
9 11
10 TODO: 12 TODO:
11 13
12 -- logs de debug devem indicar o user.  
13 - implementar http com redirect para https. 14 - implementar http com redirect para https.
14 - topicos no sidebar devem ser links para iniciar um topico acessivel. os inacessiveis devem estar inactivos. 15 - topicos no sidebar devem ser links para iniciar um topico acessivel. os inacessiveis devem estar inactivos.
15 - usar codemirror no textarea 16 - usar codemirror no textarea
@@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
3 from contextlib import contextmanager # `with` statement in db sessions 3 from contextlib import contextmanager # `with` statement in db sessions
4 import logging 4 import logging
5 from os import path, sys 5 from os import path, sys
  6 +from datetime import datetime
6 7
7 # user installed libraries 8 # user installed libraries
8 try: 9 try:
@@ -58,7 +59,12 @@ class LearnApp(object): @@ -58,7 +59,12 @@ class LearnApp(object):
58 # success 59 # success
59 60
60 tt = s.query(StudentTopic).filter(StudentTopic.student_id == uid) 61 tt = s.query(StudentTopic).filter(StudentTopic.student_id == uid)
61 - state = {t.topic_id: t.level for t in tt} 62 + state = {}
  63 + for t in tt:
  64 + state[t.topic_id] = {
  65 + 'level': t.level,
  66 + 'date': datetime.strptime(t.date, "%Y-%m-%d %H:%M:%S.%f"),
  67 + }
62 68
63 self.online[uid] = { 69 self.online[uid] = {
64 'name': student.name, 70 'name': student.name,
@@ -79,13 +85,15 @@ class LearnApp(object): @@ -79,13 +85,15 @@ class LearnApp(object):
79 # update existing associations and remove from state dict 85 # update existing associations and remove from state dict
80 for a in s.query(StudentTopic).filter_by(student_id=uid): 86 for a in s.query(StudentTopic).filter_by(student_id=uid):
81 if a.topic_id in state: 87 if a.topic_id in state:
82 - a.level = state.pop(a.topic_id) # update 88 + d = state.pop(a.topic_id)
  89 + a.level = d['level'] #state.pop(a.topic_id) # update
  90 + a.date = str(d['date'])
83 s.add(a) 91 s.add(a)
84 92
85 # insert the remaining ones 93 # insert the remaining ones
86 u = s.query(Student).get(uid) 94 u = s.query(Student).get(uid)
87 - for n,l in state.items():  
88 - a = StudentTopic(level=l) 95 + for n,d in state.items():
  96 + a = StudentTopic(level=d['level'], date=str(d['date']))
89 t = s.query(Topic).get(n) 97 t = s.query(Topic).get(n)
90 if t is None: # create if topic doesn't exist yet 98 if t is None: # create if topic doesn't exist yet
91 t = Topic(id=n) 99 t = Topic(id=n)
@@ -134,6 +142,10 @@ class LearnApp(object): @@ -134,6 +142,10 @@ class LearnApp(object):
134 return path.join(p, topic, 'public') 142 return path.join(p, topic, 'public')
135 143
136 # ------------------------------------------------------------------------ 144 # ------------------------------------------------------------------------
  145 + # def get_current_question(self, uid):
  146 + # return self.online[uid]['state'].current_question
  147 +
  148 + # ------------------------------------------------------------------------
137 # check answer and if correct returns new question, otherise returns None 149 # check answer and if correct returns new question, otherise returns None
138 def check_answer(self, uid, answer): 150 def check_answer(self, uid, answer):
139 knowledge = self.online[uid]['state'] 151 knowledge = self.online[uid]['state']
@@ -10,7 +10,7 @@ import bcrypt @@ -10,7 +10,7 @@ import bcrypt
10 from sqlalchemy import create_engine 10 from sqlalchemy import create_engine
11 from sqlalchemy.orm import sessionmaker 11 from sqlalchemy.orm import sessionmaker
12 12
13 -from models import Base, Student, Answer 13 +from models import Base, Student #, Answer
14 14
15 # SIIUE names have alien strings like "(TE)" and are sometimes capitalized 15 # SIIUE names have alien strings like "(TE)" and are sometimes capitalized
16 # We remove them so that students dont keep asking what it means 16 # We remove them so that students dont keep asking what it means
@@ -18,19 +18,21 @@ logger = logging.getLogger(__name__) @@ -18,19 +18,21 @@ logger = logging.getLogger(__name__)
18 class Knowledge(object): 18 class Knowledge(object):
19 def __init__(self, depgraph, state={}): 19 def __init__(self, depgraph, state={}):
20 self.depgraph = depgraph 20 self.depgraph = depgraph
21 - self.state = state # {node: level, node: level, ...}  
22 -  
23 self.topic_sequence = nx.topological_sort(self.depgraph) # FIXME 21 self.topic_sequence = nx.topological_sort(self.depgraph) # FIXME
24 22
  23 + # state = {'topic_id': {'level':0.5, 'date': datetime}, ...}
  24 + self.state = state
  25 +
25 # select a topic to do 26 # select a topic to do
26 self.new_topic() 27 self.new_topic()
27 28
28 # ------------------------------------------------------------------------ 29 # ------------------------------------------------------------------------
29 def new_topic(self, topic=None): 30 def new_topic(self, topic=None):
  31 + logger.debug(f'new_topic {topic}')
30 if topic is None: 32 if topic is None:
31 # select the first topic that has level < 0.9 33 # select the first topic that has level < 0.9
32 for topic in self.topic_sequence: 34 for topic in self.topic_sequence:
33 - if self.state.get(topic, 0.0) < 0.9: 35 + if topic not in self.state or self.state[topic]['level'] < 0.9:
34 break 36 break
35 37
36 # FIXME if all are > 0.9, will stay in the last one forever... 38 # FIXME if all are > 0.9, will stay in the last one forever...
@@ -55,17 +57,25 @@ class Knowledge(object): @@ -55,17 +57,25 @@ class Knowledge(object):
55 57
56 # ------------------------------------------------------------------------ 58 # ------------------------------------------------------------------------
57 def get_knowledge_state(self): 59 def get_knowledge_state(self):
58 - return [(t, self.state.get(t, 0.0)) for t in self.topic_sequence] 60 + ts = []
  61 + for t in self.topic_sequence:
  62 + if t in self.state:
  63 + ts.append((t, self.state[t]['level']))
  64 + else:
  65 + ts.append((t, 0.0))
  66 + return ts
  67 + # return [(t, self.state.get(t, 0.0)) for t in self.topic_sequence]
59 68
60 # ------------------------------------------------------------------------ 69 # ------------------------------------------------------------------------
61 def get_topic_progress(self): 70 def get_topic_progress(self):
  71 + logger.debug('-> Knowledge.get_topic_progress()')
62 return len(self.finished_questions) / (len(self.finished_questions) + len(self.questions)) 72 return len(self.finished_questions) / (len(self.finished_questions) + len(self.questions))
63 73
64 # ------------------------------------------------------------------------ 74 # ------------------------------------------------------------------------
65 # if answer to current question is correct generates a new question 75 # if answer to current question is correct generates a new question
66 # otherwise returns none 76 # otherwise returns none
67 def new_question(self): 77 def new_question(self):
68 - logger.debug('Knowledge.new_question()') 78 + logger.debug('-> Knowledge.new_question()')
69 79
70 if self.current_question is None or \ 80 if self.current_question is None or \
71 self.current_question.get('grade', 0.0) > 0.9: 81 self.current_question.get('grade', 0.0) > 0.9:
@@ -73,7 +83,10 @@ class Knowledge(object): @@ -73,7 +83,10 @@ class Knowledge(object):
73 # if no more questions in this topic, go to the next one 83 # if no more questions in this topic, go to the next one
74 # keep going if there are no questions in the next topics 84 # keep going if there are no questions in the next topics
75 while not self.questions: 85 while not self.questions:
76 - self.state[self.current_topic] = 1.0 86 + self.state[self.current_topic] = {
  87 + 'level': 1.0,
  88 + 'date': datetime.now()
  89 + }
77 self.new_topic() 90 self.new_topic()
78 91
79 self.current_question = self.questions.pop(0) 92 self.current_question = self.questions.pop(0)
@@ -82,10 +95,11 @@ class Knowledge(object): @@ -82,10 +95,11 @@ class Knowledge(object):
82 95
83 return self.current_question 96 return self.current_question
84 97
  98 +
85 # --- checks answer ------------------------------------------------------ 99 # --- checks answer ------------------------------------------------------
86 # returns current question with correction, time and comments updated 100 # returns current question with correction, time and comments updated
87 def check_answer(self, answer): 101 def check_answer(self, answer):
88 - logger.debug(f'Knowledge.check_answer({answer})') 102 + logger.debug(f'-> Knowledge.check_answer({answer})')
89 question = self.current_question 103 question = self.current_question
90 if question is not None: 104 if question is not None:
91 question['finish_time'] = datetime.now() 105 question['finish_time'] = datetime.now()
@@ -15,7 +15,7 @@ class StudentTopic(Base): @@ -15,7 +15,7 @@ class StudentTopic(Base):
15 student_id = Column(String, ForeignKey('students.id'), primary_key=True) 15 student_id = Column(String, ForeignKey('students.id'), primary_key=True)
16 topic_id = Column(String, ForeignKey('topics.id'), primary_key=True) 16 topic_id = Column(String, ForeignKey('topics.id'), primary_key=True)
17 level = Column(Float) 17 level = Column(Float)
18 - # date = Column(String) 18 + date = Column(String)
19 19
20 # --- 20 # ---
21 student = relationship('Student', back_populates='topics') 21 student = relationship('Student', back_populates='topics')
@@ -434,7 +434,7 @@ class QFactory(object): @@ -434,7 +434,7 @@ class QFactory(object):
434 try: 434 try:
435 qinstance = self._types[q['type']](q) # instance with correct class 435 qinstance = self._types[q['type']](q) # instance with correct class
436 except KeyError as e: 436 except KeyError as e:
437 - logger.error(f'Unknown question type "{q["type"]}"') 437 + logger.error(f'Failed to generate question "{q["ref"]}"')
438 raise e 438 raise e
439 else: 439 else:
440 return qinstance 440 return qinstance
@@ -157,9 +157,9 @@ class QuestionHandler(BaseHandler): @@ -157,9 +157,9 @@ class QuestionHandler(BaseHandler):
157 'textarea': 'question-textarea.html', 157 'textarea': 'question-textarea.html',
158 } 158 }
159 159
160 - @tornado.web.authenticated  
161 - def get(self):  
162 - self.redirect('/') 160 + # @tornado.web.authenticated
  161 + # def get(self): # FIXME unused
  162 + # self.redirect('/')
163 163
164 @tornado.web.authenticated 164 @tornado.web.authenticated
165 def post(self): 165 def post(self):
@@ -222,6 +222,8 @@ def main(): @@ -222,6 +222,8 @@ def main():
222 print('Common causes:\n - inexistent directory "logs"?\n - write permission to "logs" directory?') 222 print('Common causes:\n - inexistent directory "logs"?\n - write permission to "logs" directory?')
223 sys.exit(1) 223 sys.exit(1)
224 224
  225 + logging.info('===========================================================')
  226 +
225 # --- start application 227 # --- start application
226 learnapp = LearnApp(arg.conffile[0]) 228 learnapp = LearnApp(arg.conffile[0])
227 try: 229 try:
templates/learn.html
@@ -153,7 +153,7 @@ $.fn.extend({ @@ -153,7 +153,7 @@ $.fn.extend({
153 } 153 }
154 }); 154 });
155 155
156 -// Processes the response given by the served after an answer is submitted. 156 +// Processes the response given by the server after an answer is submitted.
157 function updateQuestion(response){ 157 function updateQuestion(response){
158 switch (response["method"]) { 158 switch (response["method"]) {
159 case "new_question": 159 case "new_question":