Commit feacfc1b015875d007a6455ba496f1d2e82ecc6c
1 parent
1b40ee75
Exists in
master
and in
1 other branch
- questions generated by an external script
- test information in the when in debug mode
Showing
4 changed files
with
46 additions
and
7 deletions
Show diff stats
BUGS.md
... | ... | @@ -8,17 +8,19 @@ |
8 | 8 | |
9 | 9 | # TODO |
10 | 10 | |
11 | +- testar envio de parametros para stdin para perguntas tipo generator | |
11 | 12 | - permitir enviar varios testes, aluno escolhe qual o teste que quer fazer. |
12 | 13 | - alterar o script json2md.py em conformidade |
13 | 14 | - Menu para professor com link para /results e /students |
14 | 15 | - implementar singlepage/multipage. Fazer uma class para single page que trate de andar gerir o avanco e correcao das perguntas |
15 | -- criar pergunta gerada por script externo. Na instanciacao QuestionScript() é corrido um script que devolve uma instancia de pergunta de qualquer tipo. | |
16 | 16 | - permitir adicionar imagens nas perguntas |
17 | 17 | - criar perguntas de outros tipos, e.g. associação, ordenação, varios textinput |
18 | 18 | |
19 | 19 | |
20 | 20 | # FIXED |
21 | 21 | |
22 | +- criar pergunta gerada por script externo. | |
23 | +- debug mode | |
22 | 24 | - in the train_mode, there is no way to logout. Add logout option in the menu. |
23 | 25 | - simplificar a gravacao do teste em json. |
24 | 26 | - mostrar numero ordem em /results | ... | ... |
questions.py
... | ... | @@ -55,7 +55,7 @@ class QuestionsPool(dict): |
55 | 55 | |
56 | 56 | #============================================================================ |
57 | 57 | # Question Factory |
58 | -# given a dictionary returns a question instance. | |
58 | +# Given a dictionary returns a question instance. | |
59 | 59 | def create_question(q): |
60 | 60 | '''To create a question, q must be a dictionary with at least the |
61 | 61 | following keys defined: |
... | ... | @@ -65,15 +65,20 @@ def create_question(q): |
65 | 65 | The remaing keys depend on the type of question. |
66 | 66 | ''' |
67 | 67 | |
68 | + if q['type'] == 'generator': | |
69 | + q.update(question_generator(q)) | |
70 | + # at this point the generator question was replaced by an actual question | |
71 | + | |
68 | 72 | types = { |
69 | - 'information': QuestionInformation, | |
70 | 73 | 'radio' : QuestionRadio, |
71 | 74 | 'checkbox' : QuestionCheckbox, |
72 | 75 | 'text' : QuestionText, |
73 | 76 | 'text_regex': QuestionTextRegex, |
74 | 77 | 'textarea' : QuestionTextArea, |
78 | + 'information': QuestionInformation, | |
75 | 79 | '' : QuestionInformation, # default |
76 | 80 | } |
81 | + | |
77 | 82 | # create instance of given type |
78 | 83 | try: |
79 | 84 | questiontype = types[q['type']] |
... | ... | @@ -84,6 +89,22 @@ def create_question(q): |
84 | 89 | # create question instance and return |
85 | 90 | return questiontype(q) |
86 | 91 | |
92 | + | |
93 | +# --------------------------------------------------------------------------- | |
94 | +def question_generator(q): | |
95 | + '''Run an external script that will generate a question in yaml format. | |
96 | + This function will return the yaml converted back to a dict.''' | |
97 | + # raise exception('question generation not yet implemented.') | |
98 | + q['stdin'] = q.get('stdin', '') | |
99 | + p = subprocess.Popen([q['script']], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) | |
100 | + try: | |
101 | + qyaml = p.communicate(input=q['stdin'].encode('utf-8'), timeout=5)[0].decode('utf-8') | |
102 | + except subprocess.TimeoutExpired: | |
103 | + p.kill() | |
104 | + | |
105 | + return yaml.load(qyaml) | |
106 | + | |
107 | + | |
87 | 108 | # =========================================================================== |
88 | 109 | class Question(dict): |
89 | 110 | ''' |
... | ... | @@ -93,6 +114,7 @@ class Question(dict): |
93 | 114 | ''' |
94 | 115 | pass |
95 | 116 | |
117 | + | |
96 | 118 | # =========================================================================== |
97 | 119 | class QuestionRadio(Question): |
98 | 120 | '''An instance of QuestionRadio will always have the keys: |
... | ... | @@ -165,6 +187,7 @@ class QuestionRadio(Question): |
165 | 187 | self['grade'] = x |
166 | 188 | return x |
167 | 189 | |
190 | + | |
168 | 191 | # =========================================================================== |
169 | 192 | class QuestionCheckbox(Question): |
170 | 193 | '''An instance of QuestionCheckbox will always have the keys: |
... | ... | @@ -236,6 +259,7 @@ class QuestionCheckbox(Question): |
236 | 259 | |
237 | 260 | return self['grade'] |
238 | 261 | |
262 | + | |
239 | 263 | # =========================================================================== |
240 | 264 | class QuestionText(Question): |
241 | 265 | '''An instance of QuestionCheckbox will always have the keys: |
... | ... | @@ -253,9 +277,13 @@ class QuestionText(Question): |
253 | 277 | self['text'] = self.get('text', '') |
254 | 278 | |
255 | 279 | # make sure its always a list of possible correct answers |
256 | - if isinstance(self['correct'], str): | |
280 | + if not isinstance(self['correct'], list): | |
257 | 281 | self['correct'] = [self['correct']] |
258 | 282 | |
283 | + # make sure the elements of the list are strings | |
284 | + for i, a in enumerate(self['correct']): | |
285 | + self['correct'][i] = str(a) | |
286 | + | |
259 | 287 | self['answer'] = None |
260 | 288 | |
261 | 289 | #------------------------------------------------------------------------ |
... | ... | @@ -270,6 +298,7 @@ class QuestionText(Question): |
270 | 298 | |
271 | 299 | return self['grade'] |
272 | 300 | |
301 | + | |
273 | 302 | # =========================================================================== |
274 | 303 | class QuestionTextRegex(Question): |
275 | 304 | '''An instance of QuestionCheckbox will always have the keys: |
... | ... | @@ -298,6 +327,7 @@ class QuestionTextRegex(Question): |
298 | 327 | |
299 | 328 | return self['grade'] |
300 | 329 | |
330 | + | |
301 | 331 | # =========================================================================== |
302 | 332 | class QuestionTextArea(Question): |
303 | 333 | '''An instance of QuestionCheckbox will always have the keys: |
... | ... | @@ -353,6 +383,7 @@ class QuestionTextArea(Question): |
353 | 383 | |
354 | 384 | return self['grade'] |
355 | 385 | |
386 | + | |
356 | 387 | # =========================================================================== |
357 | 388 | class QuestionInformation(Question): |
358 | 389 | '''An instance of QuestionCheckbox will always have the keys: |
... | ... | @@ -374,3 +405,4 @@ class QuestionInformation(Question): |
374 | 405 | def correct(self): |
375 | 406 | self['grade'] = 1.0 # always "correct" but points should be zero! |
376 | 407 | return self['grade'] |
408 | + | ... | ... |
static/.DS_Store
No preview for this file type
templates/test.html
... | ... | @@ -69,7 +69,7 @@ |
69 | 69 | </a> |
70 | 70 | </div> |
71 | 71 | |
72 | - <p class="navbar-text"> ${t['title']} </p> | |
72 | + <!-- <p class="navbar-text"> ${t['title']} </p> --> | |
73 | 73 | |
74 | 74 | <div class="collapse navbar-collapse" id="myNavbar"> |
75 | 75 | <!-- <ul class="nav navbar-nav"> |
... | ... | @@ -113,6 +113,11 @@ |
113 | 113 | <% |
114 | 114 | total_points = sum(q['points'] for q in questions) |
115 | 115 | %> |
116 | + % if t['debug']: | |
117 | + <pre> | |
118 | + ${yaml.dump({k:v for k,v in t.items() if k!='questions'})} | |
119 | + </pre> | |
120 | + % endif | |
116 | 121 | |
117 | 122 | % if t['train_mode'] and 'grade' in t: |
118 | 123 | <div class="jumbotron drop-shadow"> |
... | ... | @@ -215,7 +220,7 @@ |
215 | 220 | <span class="glyphicon glyphicon-ok" aria-hidden="true"></span> |
216 | 221 | ${round(q['grade'] * q['points'] / total_points * 20.0, 1)} pontos |
217 | 222 | </div> |
218 | - % elif q['grade'] > 0.5: | |
223 | + % elif q['grade'] > 0.49: | |
219 | 224 | <div class="alert alert-warning" role="alert"> |
220 | 225 | <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> |
221 | 226 | ${round(q['grade'] * q['points'] / total_points * 20.0, 1)} pontos |
... | ... | @@ -268,7 +273,7 @@ |
268 | 273 | </div> |
269 | 274 | <div class="modal-footer"> |
270 | 275 | <button type="button" class="btn btn-success btn-lg" data-dismiss="modal">Não!</button> |
271 | - <button form="test" type="submit" class="btn btn-danger">Sim, submeter</button> | |
276 | + <button form="test" type="submit" class="btn btn-danger btn-lg">Sim, submeter</button> | |
272 | 277 | </div> |
273 | 278 | </div> |
274 | 279 | </div> | ... | ... |