Commit 7aac15935f30d1a27b841e5684c00aa67a1d7da3
Exists in
master
and in
1 other branch
Merge branch 'dev' of https://git.xdi.uevora.pt/mjsb/aprendizations into dev
Showing
5 changed files
with
85 additions
and
58 deletions
Show diff stats
BUGS.md
@@ -2,9 +2,12 @@ | @@ -2,9 +2,12 @@ | ||
2 | # BUGS | 2 | # BUGS |
3 | 3 | ||
4 | - se na especificacao de um curso, a referencia do topico nao existir como directorio, rebenta. | 4 | - se na especificacao de um curso, a referencia do topico nao existir como directorio, rebenta. |
5 | +- internal server error ao fazer logout no macos python3.8 | ||
6 | +- GET can get filtered by browser cache | ||
5 | - topicos chapter devem ser automaticamente completos assim que as dependencias são satisfeitas. Nao devia ser necessario (ou possivel?) clicar neles. | 7 | - topicos chapter devem ser automaticamente completos assim que as dependencias são satisfeitas. Nao devia ser necessario (ou possivel?) clicar neles. |
6 | - topicos do tipo learn deviam por defeito nao ser randomizados e assumir ficheiros `learn.yaml`. | 8 | - topicos do tipo learn deviam por defeito nao ser randomizados e assumir ficheiros `learn.yaml`. |
7 | - internal server error 500... experimentar cenario: aluno tem login efectuado, prof muda pw e faz login/logout. aluno obtem erro 500. | 9 | - internal server error 500... experimentar cenario: aluno tem login efectuado, prof muda pw e faz login/logout. aluno obtem erro 500. |
10 | +- radio sem options rebenta com aprendizations --check | ||
8 | - chapters deviam ser mostrados unlocked, antes de mostrar a medalha. alunos pensam que já terminaram e não conseguem progredir por causa das dependencias. | 11 | - chapters deviam ser mostrados unlocked, antes de mostrar a medalha. alunos pensam que já terminaram e não conseguem progredir por causa das dependencias. |
9 | - if topic deps on invalid ref terminates server with "Unknown error". | 12 | - if topic deps on invalid ref terminates server with "Unknown error". |
10 | - warning nos topics que não são usados em nenhum curso | 13 | - warning nos topics que não são usados em nenhum curso |
@@ -16,6 +19,7 @@ | @@ -16,6 +19,7 @@ | ||
16 | 19 | ||
17 | # TODO | 20 | # TODO |
18 | 21 | ||
22 | +- shuffle das perguntas dentro de um topico | ||
19 | - alterar tabelas para incluir email de recuperacao de password (e outros avisos) | 23 | - alterar tabelas para incluir email de recuperacao de password (e outros avisos) |
20 | - registar last_seen e remover os antigos de cada vez que houver um login. | 24 | - registar last_seen e remover os antigos de cada vez que houver um login. |
21 | - indicar qtos topicos faltam (>=50%) para terminar o curso. | 25 | - indicar qtos topicos faltam (>=50%) para terminar o curso. |
@@ -37,7 +41,6 @@ | @@ -37,7 +41,6 @@ | ||
37 | 41 | ||
38 | # FIXED | 42 | # FIXED |
39 | 43 | ||
40 | -- GET can get filtered by browser cache. the problem was with animate.css | ||
41 | - templates question-*.html tem input hidden question_ref que não é usado. remover? | 44 | - templates question-*.html tem input hidden question_ref que não é usado. remover? |
42 | - goals se forem do tipo chapter deve importar todas as dependencias do chapter. | 45 | - goals se forem do tipo chapter deve importar todas as dependencias do chapter. |
43 | - initdb da integrity error se no mesmo comando existirem alunos repetidos (p.ex em ficheiros csv diferentes ou entre csv e opcao -a) | 46 | - initdb da integrity error se no mesmo comando existirem alunos repetidos (p.ex em ficheiros csv diferentes ou entre csv e opcao -a) |
QUESTIONS.md
1 | # Questions | 1 | # Questions |
2 | 2 | ||
3 | -Questions are saved in files in the [YAML](http://www.yaml.org/start.html) format. Each file contains a list of questions like | 3 | +Questions are saved in files in the [YAML](http://www.yaml.org/start.html) |
4 | +format. Each file contains a list of questions like | ||
4 | 5 | ||
5 | ```yaml | 6 | ```yaml |
6 | - type: radio | 7 | - type: radio |
@@ -12,10 +13,11 @@ Questions are saved in files in the [YAML](http://www.yaml.org/start.html) forma | @@ -12,10 +13,11 @@ Questions are saved in files in the [YAML](http://www.yaml.org/start.html) forma | ||
12 | ... | 13 | ... |
13 | ``` | 14 | ``` |
14 | 15 | ||
15 | -where each question is specified in a dictionary. | ||
16 | -The `type` key is mandatory and specifies the type of question (multiple choice, text, etc). | ||
17 | -The other keys available will depend on the type of question. | ||
18 | -The field `ref` is not strictly required but still recommended, if not defined it will default to a string with the filename and the question index, e.g., `questions.yaml:12`. | 16 | +where each question is specified in a dictionary. The `type` key is mandatory |
17 | +and specifies the type of question (multiple choice, text, etc). The other | ||
18 | +keys available will depend on the type of question. The field `ref` is not | ||
19 | +strictly required but still recommended, if not defined it will default to a | ||
20 | +string with the filename and the question index, e.g., `questions.yaml:12`. | ||
19 | 21 | ||
20 | The following types of questions are supported: | 22 | The following types of questions are supported: |
21 | 23 | ||
@@ -34,15 +36,16 @@ type | kind of answer | @@ -34,15 +36,16 @@ type | kind of answer | ||
34 | 36 | ||
35 | ### radio | 37 | ### radio |
36 | 38 | ||
37 | -Only one option can be selected as the answer. If no option is selected, the question is considered unanswered. | 39 | +Only one option can be selected as the answer. If no option is selected, the |
40 | +question is considered unanswered. | ||
38 | 41 | ||
39 | The general format is | 42 | The general format is |
40 | 43 | ||
41 | ```yaml | 44 | ```yaml |
42 | - type: radio | 45 | - type: radio |
43 | - ref: question_reference | 46 | + ref: question_reference |
44 | title: My first question | 47 | title: My first question |
45 | - text: | | 48 | + text: | |
46 | Please select one option. | 49 | Please select one option. |
47 | options: | 50 | options: |
48 | - this one is the correct one | 51 | - this one is the correct one |
@@ -54,30 +57,37 @@ The general format is | @@ -54,30 +57,37 @@ The general format is | ||
54 | discount: yes # default: yes | 57 | discount: yes # default: yes |
55 | ``` | 58 | ``` |
56 | 59 | ||
57 | -All fields are optional except `type` and `options`. `title` and `text` default to empty strings, `shuffle` and `discount` to `true`. | 60 | +All fields are optional except `type` and `options`. `title` and `text` default |
61 | +to empty strings, `shuffle` and `discount` to `true`. | ||
58 | 62 | ||
59 | -The `correct` field can be used in multiple ways and in combination with `shuffle`, `discount` and `choose` fields: | 63 | +The `correct` field can be used in multiple ways and in combination with |
64 | +`shuffle`, `discount` and `choose` fields: | ||
60 | 65 | ||
61 | -- if not present, the first option is considered correct (options are shuffled by default when presented to the student). | ||
62 | -- it can be the index (0-based) of the correct option, e.g., `correct: 0` for the first option. | ||
63 | -- it can be a list of numbers between 0 and 1, e.g., `correct: [1, 0, 0]`. In this case, the first option is 100% correct while the others are 0%. If `discount: true` (the default), then the wrong ones will be penalized by $-1/(n-1)=-\tfrac{1}{2}$, where $n$ is the number of options. | 66 | +- if not present, the first option is considered correct (options are shuffled |
67 | + by default when presented to the student). | ||
68 | +- it can be the index (0-based) of the correct option, e.g., `correct: 0` for | ||
69 | + the first option. | ||
70 | +- it can be a list of numbers between 0 and 1, e.g., `correct: [1, 0, 0]`. In | ||
71 | + this case, the first option is 100% correct while the others are 0%. If `discount: true` (the default), then the wrong ones will be penalized by $-1/(n-1)=-\tfrac{1}{2}$, where $n$ is the number of options. | ||
64 | - there can be more than one correct option in the list, which is then marked in the correct field, e.g. `correct: [1, 1, 0]`. In this case, one of the correct options will be randomly selected, and the remaining wrong ones appended. | 72 | - there can be more than one correct option in the list, which is then marked in the correct field, e.g. `correct: [1, 1, 0]`. In this case, one of the correct options will be randomly selected, and the remaining wrong ones appended. |
65 | - there can also be a long list of right and wrong options from which to build the question options. E.g. if `correct: [1,1,1,0,0,0,0]` and `choose: 3` is defined, then 1 correct option and 2 wrong ones are randomly selected from the list. | 73 | - there can also be a long list of right and wrong options from which to build the question options. E.g. if `correct: [1,1,1,0,0,0,0]` and `choose: 3` is defined, then 1 correct option and 2 wrong ones are randomly selected from the list. |
66 | - finally it's also possible to have a question that is *"not-completely-right"* or *"not-completely-wrong"*. This can be done using numbers between 0 and 1, e.g., `correct: [1, 0.3, 0]`. This practice is discouraged. | 74 | - finally it's also possible to have a question that is *"not-completely-right"* or *"not-completely-wrong"*. This can be done using numbers between 0 and 1, e.g., `correct: [1, 0.3, 0]`. This practice is discouraged. |
67 | 75 | ||
68 | -In some situations one may not want the options to be shuffled. In that case use `shuffle: false`. | 76 | +In some situations one may not want the options to be shuffled. In that case |
77 | +use `shuffle: false`. | ||
69 | 78 | ||
70 | ### checkbox | 79 | ### checkbox |
71 | 80 | ||
72 | -Zero, one or multiple options can be selected. The question is always considered as answered, even if no options are selected. | 81 | +Zero, one or multiple options can be selected. The question is always |
82 | +considered as answered, even if no options are selected. | ||
73 | 83 | ||
74 | -The simplest format is | 84 | +The simplest format is |
75 | 85 | ||
76 | ```yaml | 86 | ```yaml |
77 | - type: checkbox | 87 | - type: checkbox |
78 | ref: question_reference | 88 | ref: question_reference |
79 | title: My second question | 89 | title: My second question |
80 | - text: | | 90 | + text: | |
81 | Please mark the correct options. | 91 | Please mark the correct options. |
82 | options: | 92 | options: |
83 | - this one is correct | 93 | - this one is correct |
@@ -89,16 +99,22 @@ The simplest format is | @@ -89,16 +99,22 @@ The simplest format is | ||
89 | discount: yes # default: yes | 99 | discount: yes # default: yes |
90 | ``` | 100 | ``` |
91 | 101 | ||
92 | -All fields are optional except `type` and `options`. `title` and `text` default to empty strings, `shuffle` and `discount` to `true` and `choose` to the total number of options. | 102 | +All fields are optional except `type` and `options`. `title` and `text` default |
103 | +to empty strings, `shuffle` and `discount` to `true` and `choose` to the total | ||
104 | +number of options. | ||
93 | 105 | ||
94 | -When correcting an answer, each correctly marked/unmarked option gets the corresponding value from the list `correct: [1, -1, 1]` and each wrong gets its symmetrical. So in the previous example, to have a completely right answer the checboxes should be: marked, unmarked, marked. | 106 | +When correcting an answer, each correctly marked/unmarked option gets the |
107 | +corresponding value from the list `correct: [1, -1, 1]` and each wrong gets its | ||
108 | +symmetrical. So in the previous example, to have a completely right answer the | ||
109 | +checboxes should be: marked, unmarked, marked. | ||
95 | 110 | ||
96 | -If `discount: no` then wrong options are given a value of 0. | ||
97 | -Options are shuffled by default. A smaller number of options may be randomly selected by setting the option `choose`. | 111 | +If `discount: no` then wrong options are given a value of 0. Options are |
112 | +shuffled by default. A smaller number of options may be randomly selected by | ||
113 | +setting the option `choose`. | ||
98 | 114 | ||
99 | -A more advanced format is to have two versions for each option, one right and one wrong. | ||
100 | -One of the versions is randomly selected when the question is generated. | ||
101 | -For example, | 115 | +A more advanced format is to have two versions for each option, one right and |
116 | +one wrong. One of the versions is randomly selected when the question is | ||
117 | +generated. For example, | ||
102 | 118 | ||
103 | ```yaml | 119 | ```yaml |
104 | options: | 120 | options: |
@@ -108,11 +124,12 @@ For example, | @@ -108,11 +124,12 @@ For example, | ||
108 | correct: [1, -1, -1] | 124 | correct: [1, -1, -1] |
109 | ``` | 125 | ``` |
110 | 126 | ||
111 | -If the first version is selected then the corresponding `correct` value is used, otherwise if | ||
112 | -the second version is selected, then the symmetrical value is used instead. | 127 | +If the first version is selected then the corresponding `correct` value is |
128 | +used, otherwise if the second version is selected, then the symmetrical value | ||
129 | +is used instead. | ||
113 | 130 | ||
114 | -This format is useful to write questions that are presented in different ways to different students. | ||
115 | -It also minimizes solution memorization. Example: | 131 | +This format is useful to write questions that are presented in different ways |
132 | +to different students. It also minimizes solution memorization. Example: | ||
116 | 133 | ||
117 | ```yaml | 134 | ```yaml |
118 | options: | 135 | options: |
@@ -122,7 +139,8 @@ It also minimizes solution memorization. Example: | @@ -122,7 +139,8 @@ It also minimizes solution memorization. Example: | ||
122 | 139 | ||
123 | ### text | 140 | ### text |
124 | 141 | ||
125 | -The answer is a single line of text. Just compare the answered text with the strings provided in a list of answers considered to be right. | 142 | +The answer is a single line of text. Just compare the answered text with the |
143 | +strings provided in a list of answers considered to be right. | ||
126 | 144 | ||
127 | ```yaml | 145 | ```yaml |
128 | - type: text | 146 | - type: text |
@@ -144,12 +162,14 @@ Similar to text, but answers are validated by a regular expression. | @@ -144,12 +162,14 @@ Similar to text, but answers are validated by a regular expression. | ||
144 | correct: !regex '[wW]eek' # default: '$.^' always wrong | 162 | correct: !regex '[wW]eek' # default: '$.^' always wrong |
145 | ``` | 163 | ``` |
146 | 164 | ||
147 | -The regular expression is in a string and must be prefixed by the keyword `!regex`. | 165 | +The regular expression is in a string and must be prefixed by the keyword |
166 | +`!regex`. | ||
148 | 167 | ||
149 | ### numeric-interval | 168 | ### numeric-interval |
150 | 169 | ||
151 | -Similar to text, but expects an integer or floating point number. | ||
152 | -The answer is converted to a float and is considered correct if the number is in the given closed interval. | 170 | +Similar to text, but expects an integer or floating point number. The answer |
171 | +is converted to a float and is considered correct if the number is in the given | ||
172 | +closed interval. | ||
153 | 173 | ||
154 | ```yaml | 174 | ```yaml |
155 | - type: numeric-interval | 175 | - type: numeric-interval |
@@ -161,9 +181,10 @@ The answer is converted to a float and is considered correct if the number is in | @@ -161,9 +181,10 @@ The answer is converted to a float and is considered correct if the number is in | ||
161 | 181 | ||
162 | ### textarea | 182 | ### textarea |
163 | 183 | ||
164 | -Provides a multiline textarea for the answer. | ||
165 | -The answered text is sent to the standard input of an external program for grading. | ||
166 | -The printed output to standard output of the program is parsed as YAML to get the grade and optional comments. | 184 | +Provides a multiline textarea for the answer. The answered text is sent to the |
185 | +standard input of an external program for grading. The printed output to | ||
186 | +standard output of the program is parsed as YAML to get the grade and optional | ||
187 | +comments. | ||
167 | 188 | ||
168 | ```yaml | 189 | ```yaml |
169 | - type: textarea | 190 | - type: textarea |
@@ -185,7 +206,6 @@ comments: Almost there | @@ -185,7 +206,6 @@ comments: Almost there | ||
185 | 206 | ||
186 | It can also just print the grade as a single number. | 207 | It can also just print the grade as a single number. |
187 | 208 | ||
188 | - | ||
189 | ### information, warning, alert and success | 209 | ### information, warning, alert and success |
190 | 210 | ||
191 | These are not really questions, but just provides information for the student. | 211 | These are not really questions, but just provides information for the student. |
@@ -201,8 +221,8 @@ Grading these type of "questions" yields always correct. | @@ -201,8 +221,8 @@ Grading these type of "questions" yields always correct. | ||
201 | 221 | ||
202 | ### generator | 222 | ### generator |
203 | 223 | ||
204 | -Questions can be generated by external programs instead of being defined directly. | ||
205 | -This allows great flexibility, and allows each instance of a question to be always different. | 224 | +This allows great flexibility, and allows each instance of a question to be |
225 | +always different. | ||
206 | 226 | ||
207 | ```yaml | 227 | ```yaml |
208 | type: generator | 228 | type: generator |
@@ -211,16 +231,18 @@ script: executable_program | @@ -211,16 +231,18 @@ script: executable_program | ||
211 | arg: 10,20 | 231 | arg: 10,20 |
212 | ``` | 232 | ``` |
213 | 233 | ||
214 | -A generator is an external program that generates a question dynamically. | 234 | +A generator is an external program that generates a question dynamically. |
215 | In the example above, the program to be run is `executable_program`. | 235 | In the example above, the program to be run is `executable_program`. |
216 | The `arg` is sent to the standard input of the `executable_program`. | 236 | The `arg` is sent to the standard input of the `executable_program`. |
217 | 237 | ||
218 | -Questions should be printed to the stdout in YAML format, similarly to how they are defined above (but without the list dash). | ||
219 | -The printed question is then parsed to a dictionary which is then used to update the question. | ||
220 | -The `type` is redefined from generator to something else and the other fields are also updated. | 238 | +Questions should be printed to the stdout in YAML format, similarly to how they |
239 | +are defined above (but without the list dash). The printed question is then | ||
240 | +parsed to a dictionary which is then used to update the question. The `type` | ||
241 | +is redefined from generator to something else and the other fields are also | ||
242 | +updated. | ||
221 | 243 | ||
222 | -A generator can be any executable program (written in any language) that prints to the standard output. | ||
223 | -Example of a generator written in python: | 244 | +A generator can be any executable program (written in any language) that prints |
245 | +to the standard output. Example of a generator written in python: | ||
224 | 246 | ||
225 | ```python | 247 | ```python |
226 | #!/usr/bin/env python3 | 248 | #!/usr/bin/env python3 |
@@ -234,7 +256,8 @@ a,b = (int(n) for n in arg.split(',')) | @@ -234,7 +256,8 @@ a,b = (int(n) for n in arg.split(',')) | ||
234 | q = fr''' | 256 | q = fr''' |
235 | type: checkbox | 257 | type: checkbox |
236 | text: | | 258 | text: | |
237 | - Indique quais das seguintes adições resultam em overflow quando se considera a adição de números com sinal (complemento para 2) em registos de 8 bits. | 259 | + Indique quais das seguintes adições resultam em overflow quando se considera |
260 | + a adição de números com sinal (complemento para 2) em registos de 8 bits. | ||
238 | 261 | ||
239 | Os números foram gerados aleatoriamente no intervalo de {a} a {b}. | 262 | Os números foram gerados aleatoriamente no intervalo de {a} a {b}. |
240 | options: | 263 | options: |
@@ -254,14 +277,14 @@ print(q) | @@ -254,14 +277,14 @@ print(q) | ||
254 | 277 | ||
255 | A generator cannot generate another generator, only real questions are acceptable. | 278 | A generator cannot generate another generator, only real questions are acceptable. |
256 | 279 | ||
257 | -# Writing text | 280 | +## Writing text |
258 | 281 | ||
259 | The text in the questions is interpreted as markdown with support for LaTeX formulas. | 282 | The text in the questions is interpreted as markdown with support for LaTeX formulas. |
260 | The best way to write text is to use indentation like this: | 283 | The best way to write text is to use indentation like this: |
261 | 284 | ||
262 | ```yaml | 285 | ```yaml |
263 | text: | | 286 | text: | |
264 | - Yes. this is ok: If not indented, "Yes" would be a boolean | 287 | + Yes. this is ok: If not indented, "Yes" would be a boolean |
265 | and colon would be interpreted as a dictionary key. | 288 | and colon would be interpreted as a dictionary key. |
266 | 289 | ||
267 | Images placed in the `public` subdirectory are accessible by | 290 | Images placed in the `public` subdirectory are accessible by |
aprendizations/learnapp.py
@@ -17,6 +17,7 @@ from typing import Any, Dict, Iterable, List, Optional, Tuple, Set, DefaultDict | @@ -17,6 +17,7 @@ from typing import Any, Dict, Iterable, List, Optional, Tuple, Set, DefaultDict | ||
17 | import bcrypt | 17 | import bcrypt |
18 | import networkx as nx | 18 | import networkx as nx |
19 | import sqlalchemy as sa | 19 | import sqlalchemy as sa |
20 | +import sqlalchemy.orm as orm | ||
20 | 21 | ||
21 | # this project | 22 | # this project |
22 | from aprendizations.models import Student, Answer, Topic, StudentTopic | 23 | from aprendizations.models import Student, Answer, Topic, StudentTopic |
@@ -112,7 +113,7 @@ class LearnApp(): | @@ -112,7 +113,7 @@ class LearnApp(): | ||
112 | self.courses = config['courses'] | 113 | self.courses = config['courses'] |
113 | logger.info('Courses: %s', ', '.join(self.courses.keys())) | 114 | logger.info('Courses: %s', ', '.join(self.courses.keys())) |
114 | for cid, course in self.courses.items(): | 115 | for cid, course in self.courses.items(): |
115 | - course.setdefault('title', '') # course title undefined | 116 | + course.setdefault('title', cid) # course title undefined |
116 | for goal in course['goals']: | 117 | for goal in course['goals']: |
117 | if goal not in self.deps.nodes(): | 118 | if goal not in self.deps.nodes(): |
118 | msg = f'Goal "{goal}" from course "{cid}" does not exist' | 119 | msg = f'Goal "{goal}" from course "{cid}" does not exist' |
@@ -249,14 +250,14 @@ class LearnApp(): | @@ -249,14 +250,14 @@ class LearnApp(): | ||
249 | return False | 250 | return False |
250 | 251 | ||
251 | loop = asyncio.get_running_loop() | 252 | loop = asyncio.get_running_loop() |
252 | - password = await loop.run_in_executor(None, | ||
253 | - bcrypt.hashpw, | ||
254 | - password.encode('utf-8'), | ||
255 | - bcrypt.gensalt()) | 253 | + pw = await loop.run_in_executor(None, |
254 | + bcrypt.hashpw, | ||
255 | + password.encode('utf-8'), | ||
256 | + bcrypt.gensalt()) | ||
256 | 257 | ||
257 | with self._db_session() as sess: | 258 | with self._db_session() as sess: |
258 | user = sess.query(Student).get(uid) | 259 | user = sess.query(Student).get(uid) |
259 | - user.password = password | 260 | + user.password = pw |
260 | 261 | ||
261 | logger.info('User "%s" changed password', uid) | 262 | logger.info('User "%s" changed password', uid) |
262 | return True | 263 | return True |
@@ -364,7 +365,7 @@ class LearnApp(): | @@ -364,7 +365,7 @@ class LearnApp(): | ||
364 | # ------------------------------------------------------------------------ | 365 | # ------------------------------------------------------------------------ |
365 | # | 366 | # |
366 | # ------------------------------------------------------------------------ | 367 | # ------------------------------------------------------------------------ |
367 | - def _add_missing_topics(self, topics: List[str]) -> None: | 368 | + def _add_missing_topics(self, topics: Iterable[str]) -> None: |
368 | ''' | 369 | ''' |
369 | Fill db table 'Topic' with topics from the graph, if new | 370 | Fill db table 'Topic' with topics from the graph, if new |
370 | ''' | 371 | ''' |
@@ -386,7 +387,7 @@ class LearnApp(): | @@ -386,7 +387,7 @@ class LearnApp(): | ||
386 | 'Use "initdb-aprendizations" to create') | 387 | 'Use "initdb-aprendizations" to create') |
387 | 388 | ||
388 | engine = sa.create_engine(f'sqlite:///{database}', echo=False) | 389 | engine = sa.create_engine(f'sqlite:///{database}', echo=False) |
389 | - self.Session = sa.orm.sessionmaker(bind=engine) | 390 | + self.Session = orm.sessionmaker(bind=engine) |
390 | try: | 391 | try: |
391 | with self._db_session() as sess: | 392 | with self._db_session() as sess: |
392 | count_students: int = sess.query(Student).count() | 393 | count_students: int = sess.query(Student).count() |
@@ -501,6 +502,7 @@ class LearnApp(): | @@ -501,6 +502,7 @@ class LearnApp(): | ||
501 | qref = question.get('ref', str(i)) # ref or number | 502 | qref = question.get('ref', str(i)) # ref or number |
502 | if qref in localrefs: | 503 | if qref in localrefs: |
503 | msg = f'Duplicate ref "{qref}" in "{topic["path"]}"' | 504 | msg = f'Duplicate ref "{qref}" in "{topic["path"]}"' |
505 | + logger.error(msg) | ||
504 | raise LearnException(msg) | 506 | raise LearnException(msg) |
505 | localrefs.add(qref) | 507 | localrefs.add(qref) |
506 | 508 |
aprendizations/main.py
@@ -195,7 +195,7 @@ def main(): | @@ -195,7 +195,7 @@ def main(): | ||
195 | '--------------------------------------------------------------', | 195 | '--------------------------------------------------------------', |
196 | sep='\n') | 196 | sep='\n') |
197 | sys.exit(1) | 197 | sys.exit(1) |
198 | - except LearnException: | 198 | + except LearnException as exc: |
199 | logging.critical('Failed to start backend') | 199 | logging.critical('Failed to start backend') |
200 | # sys.exit(1) | 200 | # sys.exit(1) |
201 | raise | 201 | raise |
aprendizations/templates/rankings.html
@@ -83,7 +83,6 @@ | @@ -83,7 +83,6 @@ | ||
83 | {{ ' '.join(r[1].split()[n] for n in (0,-1)) }} | 83 | {{ ' '.join(r[1].split()[n] for n in (0,-1)) }} |
84 | | 84 | |
85 | {{ '<i class="far fa-thumbs-up text-success" title="Mais de 75% de respostas correctas"></i>' if r[3] > 0.75 else '' }} | 85 | {{ '<i class="far fa-thumbs-up text-success" title="Mais de 75% de respostas correctas"></i>' if r[3] > 0.75 else '' }} |
86 | - {{ '<i class="fas fa-bug" title="Menos de 50% de respostas correctas" ></i>' if 0.0 < r[3] < 0.5 else '' }} | ||
87 | </td> | 86 | </td> |
88 | <td> <!-- progress --> | 87 | <td> <!-- progress --> |
89 | <div class="progress"> | 88 | <div class="progress"> |