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 |