Commit 8cc73e47cb1c9072c6bf46d80db08e90ab31cbd7

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

minor fixes

BUGS.md
... ... @@ -18,6 +18,7 @@
18 18  
19 19 # TODO
20 20  
  21 +- shuffle das perguntas dentro de um topico
21 22 - alterar tabelas para incluir email de recuperacao de password (e outros avisos)
22 23 - registar last_seen e remover os antigos de cada vez que houver um login.
23 24 - indicar qtos topicos faltam (>=50%) para terminar o curso.
... ...
QUESTIONS.md
1 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 6 ```yaml
6 7 - type: radio
... ... @@ -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 22 The following types of questions are supported:
21 23  
... ... @@ -34,15 +36,16 @@ type | kind of answer
34 36  
35 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 42 The general format is
40 43  
41 44 ```yaml
42 45 - type: radio
43   - ref: question_reference
  46 + ref: question_reference
44 47 title: My first question
45   - text: |
  48 + text: |
46 49 Please select one option.
47 50 options:
48 51 - this one is the correct one
... ... @@ -54,30 +57,37 @@ The general format is
54 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 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 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 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 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 86 ```yaml
77 87 - type: checkbox
78 88 ref: question_reference
79 89 title: My second question
80   - text: |
  90 + text: |
81 91 Please mark the correct options.
82 92 options:
83 93 - this one is correct
... ... @@ -89,16 +99,22 @@ The simplest format is
89 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 119 ```yaml
104 120 options:
... ... @@ -108,11 +124,12 @@ For example,
108 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 134 ```yaml
118 135 options:
... ... @@ -122,7 +139,8 @@ It also minimizes solution memorization. Example:
122 139  
123 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 145 ```yaml
128 146 - type: text
... ... @@ -144,12 +162,14 @@ Similar to text, but answers are validated by a regular expression.
144 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 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 174 ```yaml
155 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 181  
162 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 189 ```yaml
169 190 - type: textarea
... ... @@ -185,7 +206,6 @@ comments: Almost there
185 206  
186 207 It can also just print the grade as a single number.
187 208  
188   -
189 209 ### information, warning, alert and success
190 210  
191 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 221  
202 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 227 ```yaml
208 228 type: generator
... ... @@ -211,16 +231,18 @@ script: executable_program
211 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 235 In the example above, the program to be run is `executable_program`.
216 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 247 ```python
226 248 #!/usr/bin/env python3
... ... @@ -234,7 +256,8 @@ a,b = (int(n) for n in arg.split(','))
234 256 q = fr'''
235 257 type: checkbox
236 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 262 Os números foram gerados aleatoriamente no intervalo de {a} a {b}.
240 263 options:
... ... @@ -254,14 +277,14 @@ print(q)
254 277  
255 278 A generator cannot generate another generator, only real questions are acceptable.
256 279  
257   -# Writing text
  280 +## Writing text
258 281  
259 282 The text in the questions is interpreted as markdown with support for LaTeX formulas.
260 283 The best way to write text is to use indentation like this:
261 284  
262 285 ```yaml
263 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 288 and colon would be interpreted as a dictionary key.
266 289  
267 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 17 import bcrypt
18 18 import networkx as nx
19 19 import sqlalchemy as sa
  20 +import sqlalchemy.orm as orm
20 21  
21 22 # this project
22 23 from aprendizations.models import Student, Answer, Topic, StudentTopic
... ... @@ -112,7 +113,7 @@ class LearnApp():
112 113 self.courses = config['courses']
113 114 logger.info('Courses: %s', ', '.join(self.courses.keys()))
114 115 for cid, course in self.courses.items():
115   - course.setdefault('title', '') # course title undefined
  116 + course.setdefault('title', cid) # course title undefined
116 117 for goal in course['goals']:
117 118 if goal not in self.deps.nodes():
118 119 msg = f'Goal "{goal}" from course "{cid}" does not exist'
... ... @@ -249,14 +250,14 @@ class LearnApp():
249 250 return False
250 251  
251 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 258 with self._db_session() as sess:
258 259 user = sess.query(Student).get(uid)
259   - user.password = password
  260 + user.password = pw
260 261  
261 262 logger.info('User "%s" changed password', uid)
262 263 return True
... ... @@ -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 370 Fill db table 'Topic' with topics from the graph, if new
370 371 '''
... ... @@ -386,7 +387,7 @@ class LearnApp():
386 387 'Use "initdb-aprendizations" to create')
387 388  
388 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 391 try:
391 392 with self._db_session() as sess:
392 393 count_students: int = sess.query(Student).count()
... ... @@ -501,6 +502,7 @@ class LearnApp():
501 502 qref = question.get('ref', str(i)) # ref or number
502 503 if qref in localrefs:
503 504 msg = f'Duplicate ref "{qref}" in "{topic["path"]}"'
  505 + logger.error(msg)
504 506 raise LearnException(msg)
505 507 localrefs.add(qref)
506 508  
... ...
aprendizations/main.py
... ... @@ -195,9 +195,10 @@ def main():
195 195 '--------------------------------------------------------------',
196 196 sep='\n')
197 197 sys.exit(1)
198   - except LearnException:
  198 + except LearnException as exc:
199 199 logging.critical('Failed to start backend')
200   - sys.exit(1)
  200 + raise
  201 + # sys.exit(1)
201 202 except Exception:
202 203 logging.critical('Unknown error')
203 204 # sys.exit(1)
... ...