Commit bbc1c50606a4da44b9782157d5ddea36e0afcf11
1 parent
f75a344f
Exists in
master
and in
1 other branch
- documentation update: setting up letsencrypt certificates.
- fixed duplicate generation of questions. - fixed crash when all topics are solved.
Showing
3 changed files
with
50 additions
and
32 deletions
Show diff stats
BUGS.md
| 1 | + | ||
| 1 | BUGS: | 2 | BUGS: |
| 2 | 3 | ||
| 3 | - guardar state cada vez que topico termina | 4 | - guardar state cada vez que topico termina |
| 4 | -- logs mostram que está a gerar cada pergunta 2 vezes...?? | ||
| 5 | - reload da página rebenta o estado. | 5 | - reload da página rebenta o estado. |
| 6 | - indicar o topico actual no sidebar | 6 | - indicar o topico actual no sidebar |
| 7 | - session management. close after inactive time. | 7 | - session management. close after inactive time. |
| @@ -9,7 +9,6 @@ BUGS: | @@ -9,7 +9,6 @@ BUGS: | ||
| 9 | 9 | ||
| 10 | TODO: | 10 | TODO: |
| 11 | 11 | ||
| 12 | -- letsencrypt.org | ||
| 13 | - logs de debug devem indicar o user. | 12 | - logs de debug devem indicar o user. |
| 14 | - implementar http com redirect para https. | 13 | - implementar http com redirect para https. |
| 15 | - topicos no sidebar devem ser links para iniciar um topico acessivel. os inacessiveis devem estar inactivos. | 14 | - topicos no sidebar devem ser links para iniciar um topico acessivel. os inacessiveis devem estar inactivos. |
| @@ -17,8 +16,10 @@ TODO: | @@ -17,8 +16,10 @@ TODO: | ||
| 17 | - mostrar comments quando falha a resposta | 16 | - mostrar comments quando falha a resposta |
| 18 | - generators not working: bcrypt (ver blog) | 17 | - generators not working: bcrypt (ver blog) |
| 19 | 18 | ||
| 20 | -SOLVED: | 19 | +FIXED: |
| 21 | 20 | ||
| 21 | +- logs mostram que está a gerar cada pergunta 2 vezes...?? | ||
| 22 | +- letsencrypt.org | ||
| 22 | - alterar password. | 23 | - alterar password. |
| 23 | - barra de progresso a funcionar | 24 | - barra de progresso a funcionar |
| 24 | - mostra tópicos do lado esquerdo, indicando quais estão feitos | 25 | - mostra tópicos do lado esquerdo, indicando quais estão feitos |
README.md
| @@ -71,12 +71,28 @@ First we need to create a database: | @@ -71,12 +71,28 @@ First we need to create a database: | ||
| 71 | 71 | ||
| 72 | ### SSL Certificates | 72 | ### SSL Certificates |
| 73 | 73 | ||
| 74 | +#### Selfsigned | ||
| 74 | We also need certificates for https. Generate selfsigned certificates using openssl: | 75 | We also need certificates for https. Generate selfsigned certificates using openssl: |
| 75 | 76 | ||
| 76 | openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes | 77 | openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes |
| 77 | 78 | ||
| 78 | and place them in `aprendizations/certs`. | 79 | and place them in `aprendizations/certs`. |
| 79 | 80 | ||
| 81 | +#### LetsEcrypt | ||
| 82 | + | ||
| 83 | + sudo pkg install py27-certbot # FreeBSD | ||
| 84 | + | ||
| 85 | +Shutdown any server running and the firewall, and then run the script to generate the certificate: | ||
| 86 | + | ||
| 87 | + sudo pfctl -d # disable pf firewall | ||
| 88 | + sudo certbot certonly --standalone -d bit.xdi.uevora.pt | ||
| 89 | + sudo pfctl -e; sudo pfctl -f /etc/pf.conf # enable pf firewall | ||
| 90 | + | ||
| 91 | +Certificates are saved under `/usr/local/etc/letsencrypt/live/bit.xdi.uevora.pt/` which is readable only by root. | ||
| 92 | + | ||
| 93 | +Copy them to `aprendizations/certs` with names `cert.pem` and `key.pem`. And change permissions to be readble (FIXME how to do it with security?) | ||
| 94 | + | ||
| 95 | + | ||
| 80 | ### Testing | 96 | ### Testing |
| 81 | 97 | ||
| 82 | Run a demonstration: | 98 | Run a demonstration: |
| @@ -126,16 +142,16 @@ To correct in FreeBSD, edit `~/.login_conf` to use UTF-8, for example: | @@ -126,16 +142,16 @@ To correct in FreeBSD, edit `~/.login_conf` to use UTF-8, for example: | ||
| 126 | 142 | ||
| 127 | ## Useful sqlite3 queries | 143 | ## Useful sqlite3 queries |
| 128 | 144 | ||
| 129 | -- Which students have already done at least one topic? | 145 | +Which students have already done at least one topic? |
| 130 | 146 | ||
| 131 | sqlite3 students.db "select distinct student_id from studenttopic" | 147 | sqlite3 students.db "select distinct student_id from studenttopic" |
| 132 | 148 | ||
| 133 | 149 | ||
| 134 | -- How many topics have done each student? | 150 | +How many topics have done each student? |
| 135 | 151 | ||
| 136 | sqlite3 students.db "select student_id, count(topic_id) from studenttopic group by student_id" | 152 | sqlite3 students.db "select student_id, count(topic_id) from studenttopic group by student_id" |
| 137 | 153 | ||
| 138 | 154 | ||
| 139 | -- What questions have more wrong answers? | 155 | +What questions have more wrong answers? |
| 140 | 156 | ||
| 141 | sqlite3 students.db "select count(ref), ref from answers where grade<1.0 group by ref order by count(ref) desc" | 157 | sqlite3 students.db "select count(ref), ref from answers where grade<1.0 group by ref order by count(ref) desc" |
knowledge.py
| @@ -20,27 +20,30 @@ class Knowledge(object): | @@ -20,27 +20,30 @@ class Knowledge(object): | ||
| 20 | self.depgraph = depgraph | 20 | self.depgraph = depgraph |
| 21 | self.state = state # {node: level, node: level, ...} | 21 | self.state = state # {node: level, node: level, ...} |
| 22 | 22 | ||
| 23 | - self.topic = self.topic_generator() | ||
| 24 | - self.current_topic = next(self.topic) | 23 | + self.topic_sequence = nx.topological_sort(self.depgraph) # FIXME |
| 25 | 24 | ||
| 26 | - self.questions = self.generate_questions_for_topic(self.current_topic) | ||
| 27 | - self.current_question = None | ||
| 28 | - self.finished_questions = [] | 25 | + # select a topic to do |
| 26 | + self.new_topic() | ||
| 29 | 27 | ||
| 30 | # ------------------------------------------------------------------------ | 28 | # ------------------------------------------------------------------------ |
| 31 | - def topic_generator(self): | ||
| 32 | - self.topic_sequence = nx.topological_sort(self.depgraph) # FIXME for now... | ||
| 33 | - for t in self.topic_sequence: | ||
| 34 | - if self.state.get(t, 0.0) > 0.999: | ||
| 35 | - continue | ||
| 36 | - self.questions = self.generate_questions_for_topic(t) | ||
| 37 | - logger.info(f'Generated {len(self.questions)} questions for topic "{t}"') | ||
| 38 | - yield t | 29 | + def new_topic(self, topic=None): |
| 30 | + if topic is None: | ||
| 31 | + # select the first topic that has level < 0.9 | ||
| 32 | + for topic in self.topic_sequence: | ||
| 33 | + if self.state.get(topic, 0.0) < 0.9: | ||
| 34 | + break | ||
| 35 | + | ||
| 36 | + # FIXME if all are > 0.9, will stay in the last one forever... | ||
| 37 | + self.current_topic = topic | ||
| 38 | + self.current_topic_idx = self.topic_sequence.index(topic) | ||
| 39 | + self.questions = self.generate_questions_for_topic(topic) | ||
| 40 | + self.current_question = None | ||
| 41 | + self.finished_questions = [] | ||
| 39 | 42 | ||
| 40 | # ------------------------------------------------------------------------ | 43 | # ------------------------------------------------------------------------ |
| 41 | def generate_questions_for_topic(self, topic): | 44 | def generate_questions_for_topic(self, topic): |
| 42 | - factory = self.depgraph.node[topic]['factory'] | ||
| 43 | - return [q.generate() for q in factory] | 45 | + factory_list = self.depgraph.node[topic]['factory'] |
| 46 | + return [q.generate() for q in factory_list] | ||
| 44 | 47 | ||
| 45 | # ------------------------------------------------------------------------ | 48 | # ------------------------------------------------------------------------ |
| 46 | def get_current_question(self): | 49 | def get_current_question(self): |
| @@ -58,26 +61,24 @@ class Knowledge(object): | @@ -58,26 +61,24 @@ class Knowledge(object): | ||
| 58 | def get_topic_progress(self): | 61 | def get_topic_progress(self): |
| 59 | return len(self.finished_questions) / (len(self.finished_questions) + len(self.questions)) | 62 | return len(self.finished_questions) / (len(self.finished_questions) + len(self.questions)) |
| 60 | 63 | ||
| 61 | - # --- generates a new question given the current state ------------------- | 64 | + # ------------------------------------------------------------------------ |
| 65 | + # if answer to current question is correct generates a new question | ||
| 66 | + # otherwise returns none | ||
| 62 | def new_question(self): | 67 | def new_question(self): |
| 63 | logger.debug('Knowledge.new_question()') | 68 | logger.debug('Knowledge.new_question()') |
| 64 | 69 | ||
| 65 | - if self.current_question is None or self.current_question.get('grade', 0.0) > 0.9999: | 70 | + if self.current_question is None or \ |
| 71 | + self.current_question.get('grade', 0.0) > 0.9: | ||
| 66 | 72 | ||
| 73 | + # if no more questions in this topic, go to the next one | ||
| 74 | + # keep going if there are no questions in the next topics | ||
| 67 | while not self.questions: | 75 | while not self.questions: |
| 68 | - # finished topic! | ||
| 69 | self.state[self.current_topic] = 1.0 | 76 | self.state[self.current_topic] = 1.0 |
| 70 | - self.finished_questions = [] | ||
| 71 | - try: | ||
| 72 | - self.current_topic = next(self.topic) | ||
| 73 | - except StopIteration: | ||
| 74 | - self.topic = self.topic_generator() | ||
| 75 | - self.current_topic = next(self.topic) | ||
| 76 | - self.questions = self.generate_questions_for_topic(self.current_topic) | 77 | + self.new_topic() |
| 77 | 78 | ||
| 78 | self.current_question = self.questions.pop(0) | 79 | self.current_question = self.questions.pop(0) |
| 79 | self.current_question['start_time'] = datetime.now() | 80 | self.current_question['start_time'] = datetime.now() |
| 80 | - self.finished_questions.append(self.current_question) # FIXME not yet finished... | 81 | + self.finished_questions.append(self.current_question) |
| 81 | 82 | ||
| 82 | return self.current_question | 83 | return self.current_question |
| 83 | 84 |