Commit 34b58efe7ddde7d3dd1aec1b98410e6ec5c100dc

Authored by Miguel Barao
1 parent 9bace4f6
Exists in master and in 1 other branch dev

- replaced key 'stdin' by 'arg' in questions of type generator

- updated manual and demo
BUGS.md
... ... @@ -7,7 +7,6 @@
7 7 - parece que é preciso criar à mão a pasta para as respostas (ans/...) depois apercebo-me que os caminhos no teste dizem respeito à directoria donde o teste é corrido... as respostas deveriam guardadas no directório dado.
8 8 - se database for mal configurada, é criada uma base de dados vazia e rebenta na autenticacao.
9 9 - testar regex na definicao das perguntas. como se faz rawstring em yaml? singlequote? problemas de backslash??? sim... necessário fazer \\ em varios casos, mas não é claro! e.g. \n é convertido em espaço mas \w é convertido em \\ e w.
10   -- testar envio de parametros para stdin para perguntas tipo generator.
11 10  
12 11 # TODO
13 12  
... ... @@ -26,6 +25,7 @@
26 25  
27 26 # FIXED
28 27  
  28 +- testar envio de parametros para stdin para perguntas tipo generator.
29 29 - mathjax e jquery no login
30 30 - mostrar erro quando nao consegue importar questions files
31 31 - pacotes exactos usados para instalar.
... ...
MANUAL.md
... ... @@ -2,29 +2,29 @@
2 2  
3 3 ### Requirements and instalation
4 4  
5   -Install:
6   -
7   -- python3.4
8   -- cherrypy3
9   -- mako
10   -- yaml
11   -- markdown
  5 +Install python 3.4 and the following packages from pip:
12 6  
  7 +- CherryPy (3.7.0)
  8 +- Mako (1.0.1)
  9 +- Markdown (2.6.2)
  10 +- PyYAML (3.11)
13 11  
14 12 Before using the program you need to
15 13  
16 14 1. Create the students database
17 15 1. Create questions
18 16 1. Create a test
19   -1. Configure the server (the default may be enough)
  17 +1. Configure the server (the default should be enough)
20 18  
21 19 ### Create students database
22 20  
23 21 We need a sqlite3 database to store students, passwords, test results, and questions results, etc.
24 22  
25   -The database can be initialized from a list of students in CSV format using the script
  23 +The database can be initialized from a list of students in CSV format by running in the terminal
26 24  
27   - $ ./initdb_from_csv.py list_of_students.csv
  25 +```sh
  26 +./initdb_from_csv.py list_of_students.csv
  27 +```
28 28  
29 29 This script will create a new sqlite3 database with the correct tables and insert the students with empty passwords.
30 30 It also adds a special user number 0. This is the administrator user (Professor).
... ... @@ -36,24 +36,26 @@ The passwords will be defined on the first login.
36 36 Questions are defined in `yaml` files and can reside anywhere in the filesystem.
37 37 Each file contains a list of questions, where each question is a dictionary. Example
38 38  
39   - -
40   - ref: question-1
41   - type: radio
42   - text: Select the correct option
43   - options:
44   - - correct
45   - - wrong
46   -
47   - -
48   - ref: question-2
49   - type: checkbox
50   - text: Which ones are correct?
51   - options:
52   - - correct
53   - - correct
54   - - wrong
55   - correct: [1, 1, -1]
56   - hint: There are two correct answers!
  39 +```yaml
  40 +-
  41 + ref: question-1
  42 + type: radio
  43 + text: Select the correct option
  44 + options:
  45 + - correct
  46 + - wrong
  47 +
  48 +-
  49 + ref: question-2
  50 + type: checkbox
  51 + text: Which ones are correct?
  52 + options:
  53 + - correct
  54 + - correct
  55 + - wrong
  56 + correct: [1, 1, -1]
  57 + hint: There are two correct answers!
  58 +```
57 59  
58 60 There are several kinds of questions:
59 61  
... ... @@ -72,84 +74,90 @@ Detailed information on each question type is described later on.
72 74  
73 75 A test is a file in `yaml` format that can reside anywhere on the filesystem. It has the following structure:
74 76  
75   - ref: this-is-a-key
76   - title: Titulo do teste
77   - database: db/mystudents.db
78   -
79   - # Will save the entire test of each student in JSON format.
80   - # If tests are to be saved, we must specify the directory.
81   - # The directory is created if it doesn't exist already.
82   - # The name of the JSON files will include the student number, test
83   - # reference key, date and time.
84   - save_answers: True
85   - answers_dir: ans/asc1_test4
86   -
87   - # Some questions can contain hints, embedded videos, etc
88   - show_hints: True
89   -
90   - # Each question has some number of points. Show them normalized to 0-20.
91   - show_points: True
92   -
93   - # In train mode, the correction of the test is shown and the test can
94   - # be repeated
95   - practice_mode: True
96   -
97   - # Show the data structures obtained from the test and the questions
98   - debug: False
99   -
100   - # Show the file and ref field of each question
101   - show_ref: True
102   -
103   - # -------------------------------------------------------------------------
104   - # This are the questions database to be imported.
105   - files:
106   - - questions/file1.yaml
107   - - questions/file2.yaml
108   - - questions/file3.yaml
109   - # -------------------------------------------------------------------------
110   - # This is the actual test configuration. Selection of questions and points
111   - # It'a defined as a list of questions. Each question can be a single
112   - # question key or a list of keys from which one is chosen at random.
113   - # Each question has a default value of 1.0 point, but it can be overridden.
114   - # The points defined here do not need to be normalized (it's automatic).
115   - questions:
116   - - ref:
117   - - first-question-1 # randomly choose one from these 3 questions
118   - - first-question-2
119   - - first-question-3
120   - points: 0.5
121   -
122   - - ref: second-question # one question, 1.0 point (unnormalized)
123   -
124   - - third-question # "ref:" not needed in simple cases
  77 +```yaml
  78 +ref: this-is-a-key
  79 +title: Titulo do teste
  80 +database: db/mystudents.db
  81 +
  82 +# Will save the entire test of each student in JSON format.
  83 +# If tests are to be saved, we must specify the directory.
  84 +# The directory is created if it doesn't exist already.
  85 +# The name of the JSON files will include the student number, test
  86 +# reference key, date and time.
  87 +save_answers: True
  88 +answers_dir: ans/asc1_test4
  89 +
  90 +# Some questions can contain hints, embedded videos, etc
  91 +show_hints: True
  92 +
  93 +# Each question has some number of points. Show them normalized to 0-20.
  94 +show_points: True
  95 +
  96 +# In train mode, the correction of the test is shown and the test can
  97 +# be repeated
  98 +practice_mode: True
  99 +
  100 +# Show the data structures obtained from the test and the questions
  101 +debug: False
  102 +
  103 +# Show the file and ref field of each question
  104 +show_ref: True
  105 +
  106 +# -------------------------------------------------------------------------
  107 +# This are the questions database to be imported.
  108 +files:
  109 + - questions/file1.yaml
  110 + - questions/file2.yaml
  111 + - questions/file3.yaml
  112 +# -------------------------------------------------------------------------
  113 +# This is the actual test configuration. Selection of questions and points
  114 +# It'a defined as a list of questions. Each question can be a single
  115 +# question key or a list of keys from which one is chosen at random.
  116 +# Each question has a default value of 1.0 point, but it can be overridden.
  117 +# The points defined here do not need to be normalized (it's automatic).
  118 +questions:
  119 + - ref:
  120 + - first-question-1 # randomly choose one from these 3 questions
  121 + - first-question-2
  122 + - first-question-3
  123 + points: 0.5
  124 +
  125 + - ref: second-question # one question, 1.0 point (unnormalized)
  126 +
  127 + - third-question # "ref:" not needed in simple cases
125 128  
126 129 This following one is wrong:
127 130  
128   - - wrong-question # missing "ref:" key
129   - points: 2
  131 + - wrong-question # missing "ref:" key
  132 + points: 2
  133 +```
130 134  
131 135 Some of the options have default values if they are omitted. The defaults are the following:
132 136  
133   - ref: filename.yaml
134   - title: ''
135   - save_answers: False
136   - show_hints: False
137   - show_points: False
138   - practice_mode: False
139   - show_ref: False
140   - debug: False
141   - points: 1.0
142   -
  137 +```yaml
  138 +ref: filename.yaml
  139 +title: ''
  140 +save_answers: False
  141 +show_hints: False
  142 +show_points: False
  143 +practice_mode: False
  144 +show_ref: False
  145 +debug: False
  146 +points: 1.0
  147 +```
143 148 ### Running an existing test
144 149  
145 150 A test is a file in `yaml` format. Just run `serve.py` with the test to run as argument:
146 151  
147   - $ ./serve.py tests_dir/mytest.yaml
  152 +```sh
  153 +$ ./serve.py tests_dir/mytest.yaml
  154 +```
148 155  
149 156 Some defaults can be overriden with command line options. Example
150 157  
151   - $ ./serve.py mytest.yaml --debug --show_points --show_hints --practice_mode --save_answers
152   -
  158 +```sh
  159 +$ ./serve.py mytest.yaml --debug --show_points --show_hints --practice_mode --save_answers
  160 +```
153 161 To terminate the test just do `^C` on the keyboard.
154 162  
155 163 ## Questions
... ... @@ -160,29 +168,31 @@ Every question should have a `ref` and a `type`. The other keys depend on the ty
160 168  
161 169 Not a real question. Just text to be shown without expecting an answer.
162 170  
163   - -
164   - ref: some-key
165   - type: information
166   - text: Tomorrow will rain.
167   -
  171 +```yaml
  172 +-
  173 + ref: some-key
  174 + type: information
  175 + text: Tomorrow will rain.
  176 +```
168 177 Correcting an information will always be considered correct, but the grade will be zero because it has 0.0 points by default.
169 178  
170 179 ### Radio
171 180  
172 181 Only one option is correct.
173 182  
174   - -
175   - ref: some-key
176   - type: radio
177   - text: The horse is white. # optional (default: '')
178   - options:
179   - - The horse is white
180   - - The horse is not black
181   - - The horse is black
182   - correct: 0 # optional (default: 0). Index is 0-based.
183   - shuffle: True # optional (default: True)
184   - discount: True # optional (default: True)
185   -
  183 +```yaml
  184 +-
  185 + ref: some-key
  186 + type: radio
  187 + text: The horse is white. # optional (default: '')
  188 + options:
  189 + - The horse is white
  190 + - The horse is not black
  191 + - The horse is black
  192 + correct: 0 # optional (default: 0). Index is 0-based.
  193 + shuffle: True # optional (default: True)
  194 + discount: True # optional (default: True)
  195 +```
186 196 The `correct` value can also be defined as a list of degrees of correctness between 0 (wrong) and 1 (correct), e.g. if answering "the horse is not black" should be considered half-right, then we should use `correct: [1, 0.5, 0]`.
187 197  
188 198 Wrong answers discount by default. If there are half-right answers, the discount values are calculated automatically. `discount: False` disables the discount calculation and the values are the ones defined in `correct`.
... ... @@ -191,20 +201,22 @@ Wrong answers discount by default. If there are half-right answers, the discount
191 201  
192 202 There can be several options correct. Each option is like answering an independent question.
193 203  
194   - -
195   - ref: some-key
196   - type: checkbox
197   - text: The horse is white. # optional (default: '')
198   - options:
199   - - The horse is white
200   - - The horse is not black
201   - - The horse is black
202   - correct: [1,1,-1] # optional (default: [0,0,0]).
203   - shuffle: True # optional (default: True)
204   - discount: True # optional (default: True)
205   -
  204 +```yaml
  205 +-
  206 + ref: some-key
  207 + type: checkbox
  208 + text: The horse is white. # optional (default: '')
  209 + options:
  210 + - The horse is white
  211 + - The horse is not black
  212 + - The horse is black
  213 + correct: [1,1,-1] # optional (default: [0,0,0]).
  214 + shuffle: True # optional (default: True)
  215 + discount: True # optional (default: True)
  216 +```
206 217 Wrong answers discount by default. The discount values are calculated automatically and are simply the symmetric of the correct value.
207 218 E.g. consider `correct: [1, 0.5, -1]`, then
  219 +
208 220 - if the first option is marked the value is 1, otherwise if it's unmarked the value is -1.
209 221 - if the second option is marked the value is 0.5, otherwise if it's unmarked the value is -0.5.
210 222 - if the third option is marked the value is -1, otherwise if it's unmarked the value is 1. (the student shouldn't have marked this one)
... ... @@ -216,26 +228,29 @@ E.g. consider `correct: [1, 0.5, -1]`, then
216 228 The answer is a line of text.
217 229 The server will check if the answer exactly matches the correct one.
218 230  
219   - -
220   - ref: some-key
221   - type: text
222   - text: What's your favorite color? # optional (default: '')
223   - correct: white
224   -
  231 +```yaml
  232 +-
  233 + ref: some-key
  234 + type: text
  235 + text: What's your favorite color? # optional (default: '')
  236 + correct: white
  237 +```
225 238 alternatively, we can give a list of acceptable answers
226   -
227   - correct: ['white', 'blue', 'red']
228   -
  239 +```yaml
  240 + correct: ['white', 'blue', 'red']
  241 +```
229 242 ### Regular expression
230 243  
231 244 The answer is a line of text.
232 245 The server will check if the answer matches a regular expression.
233 246  
234   - -
235   - ref: some-key
236   - type: text_regex
237   - text: What's your favorite color? # optional (default: '')
238   - correct: '[Ww]hite'
  247 +```yaml
  248 +-
  249 + ref: some-key
  250 + type: text_regex
  251 + text: What's your favorite color? # optional (default: '')
  252 + correct: '[Ww]hite'
  253 +```
239 254  
240 255 Careful: yaml does not support raw text. Some characters have to be escaped.
241 256  
... ... @@ -245,23 +260,27 @@ The answer is given in a textarea. The text (usually code) is sent to an externa
245 260 The external program should accept input from stdin, and print to stdout a single number in the interval 0.0 to 1.0 indicating the level of correctness.
246 261 The server will try to convert the printed message to a float, a failure will give 0.0.
247 262  
248   - -
249   - ref: some-key
250   - type: textarea
251   - text: write an expression to add x and y. # optional (default: '')
252   - correct: path/to/myscript
  263 +```yaml
  264 +-
  265 + ref: some-key
  266 + type: textarea
  267 + text: write an expression to add x and y. # optional (default: '')
  268 + correct: path/to/myscript
  269 +```
253 270  
254 271 An example of a script in python that validades an answer is
255 272  
256   - #!/usr/bin/env python3.4
  273 +```python
  274 +#!/usr/bin/env python3.4
257 275  
258   - import sys
259   - s = sys.stdin.read()
260   - if s == 'Alibaba':
261   - print(1.0)
262   - else:
263   - print(0.0)
264   - exit(0)
  276 +import sys
  277 +s = sys.stdin.read()
  278 +if s == 'Alibaba':
  279 + print(1.0)
  280 +else:
  281 + print(0.0)
  282 +exit(0)
  283 +```
265 284  
266 285 but any script language or executable program can be used for this purpose.
267 286  
... ... @@ -270,25 +289,38 @@ but any script language or executable program can be used for this purpose.
270 289  
271 290 A generator question will run an external program that is expected to print a question in yaml format to stdout. After running the generator, the question can be any of the other types (but not another generator!).
272 291  
273   - -
274   - ref: some-key
275   - type: generator
276   - script: path/to/generator_script
  292 +```yaml
  293 +-
  294 + ref: some-key
  295 + type: generator
  296 + script: path/to/generator_script
  297 + # arg: "optional string passed on to stdin of the script"
  298 +```
277 299  
278 300 An example of a question generator is the following
279 301  
280   - #!/usr/bin/env python3.4
281   - from random import randint
282   -
283   - x = randint(10,20)
284   - y = randint(10,20)
285   - s = '''
286   - ref: addition
287   - type: text
288   - text: How much is {0} plus {1}?
289   - correct: {2}
290   - '''.format(x, y, x + y)
291   - print(s)
  302 +```python
  303 +#!/usr/bin/env python3.4
  304 +from random import randint
  305 +import sys
  306 +
  307 +# read arguments from stdin and convert to integers
  308 +arg = sys.stdin.read()
  309 +a,b = (int(n) for n in arg.split(','))
  310 +
  311 +# generate question
  312 +x = randint(a, b)
  313 +y = randint(a, b)
  314 +s = '''
  315 +ref: addition
  316 +type: text
  317 +text: How much is {0} plus {1}?
  318 +correct: {2}
  319 +'''.format(x, y, x + y)
  320 +
  321 +# send question to stdout
  322 +print(s)
  323 +```
292 324  
293 325 ## Writing good looking questions
294 326  
... ... @@ -296,13 +328,16 @@ The text of the questions (and options in radio and checkbox type questios) is p
296 328  
297 329 A good way to define multiple lines of text in the questions is to use the bar |. Yaml will use all the text that is indented to the right of that column. Example
298 330  
299   - text: |
300   - Text is parsed as __markdown__. We can include equations $\sqrt{\pi}$ like in LaTeX
301   - and pretty code in several languages
  331 +```yaml
  332 + text: |
  333 + Text is parsed as __markdown__. We can include equations $\sqrt{\pi}$ like in LaTeX
  334 + and pretty code in several languages
  335 +
  336 + ```.C
  337 + int main(){
  338 + return 0;
  339 + }
  340 + ```
  341 + # this line stops the text because it is not indented
  342 +```
302 343  
303   - ```.C
304   - int main(){
305   - return 0;
306   - }
307   - ```
308   - # this line does stops the text because it is not indented
... ...
demo/generate-question.py
1 1 #!/usr/bin/env python3.4
2 2  
3 3 from random import randint
  4 +import sys
4 5  
  6 +arg = sys.stdin.read() # read arguments
  7 +
  8 +a,b = (int(n) for n in arg.split(','))
5 9  
6 10 q = '''
7 11 type: checkbox
8   -text: 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.
  12 +text: |
  13 + 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.
  14 +
  15 + Os números foram gerados aleatoriamente no intervalo de {0} a {1}.
9 16 options:
10   -'''
  17 +'''.format(a,b)
11 18  
12 19 correct = []
13 20 for i in range(5):
14   - x = randint(11,120)
15   - y = randint(11,120)
  21 + x = randint(a, b)
  22 + y = randint(a, b)
16 23 q += '- "`{} + {}`"\n'.format(x, y)
17 24 correct.append(1 if x + y > 127 else -1)
18 25  
... ...
demo/questions.yaml
... ... @@ -51,6 +51,7 @@
51 51 ref: question-whatever
52 52 type: generator
53 53 script: demo/generate-question.py
  54 + arg: "11,120"
54 55 # the script should print a question in yaml format like the ones above.
55 56 # Print only the dictionary, not the list (hiffen).
56 57 # ---------------------------------------------------------------------------
... ...
questions.py
... ... @@ -114,7 +114,7 @@ def question_generator(q):
114 114 '''Run an external script that will generate a question in yaml format.
115 115 This function will return the yaml converted back to a dict.'''
116 116  
117   - q['stdin'] = q.get('stdin', '')
  117 + q['arg'] = q.get('arg', '') # send this string to stdin
118 118  
119 119 try:
120 120 p = subprocess.Popen([q['script']], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT)
... ... @@ -122,7 +122,7 @@ def question_generator(q):
122 122 print(' * Script "{0}" defined in question "{1}" of file "{2}" could not be found'.format(q['script'], q['ref'], q['filename']))
123 123  
124 124 try:
125   - qyaml = p.communicate(input=q['stdin'].encode('utf-8'), timeout=5)[0].decode('utf-8')
  125 + qyaml = p.communicate(input=q['arg'].encode('utf-8'), timeout=5)[0].decode('utf-8')
126 126 except subprocess.TimeoutExpired:
127 127 p.kill()
128 128  
... ...