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,17 +8,19 @@ | ||
8 | 8 | ||
9 | # TODO | 9 | # TODO |
10 | 10 | ||
11 | +- testar envio de parametros para stdin para perguntas tipo generator | ||
11 | - permitir enviar varios testes, aluno escolhe qual o teste que quer fazer. | 12 | - permitir enviar varios testes, aluno escolhe qual o teste que quer fazer. |
12 | - alterar o script json2md.py em conformidade | 13 | - alterar o script json2md.py em conformidade |
13 | - Menu para professor com link para /results e /students | 14 | - Menu para professor com link para /results e /students |
14 | - implementar singlepage/multipage. Fazer uma class para single page que trate de andar gerir o avanco e correcao das perguntas | 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 | - permitir adicionar imagens nas perguntas | 16 | - permitir adicionar imagens nas perguntas |
17 | - criar perguntas de outros tipos, e.g. associação, ordenação, varios textinput | 17 | - criar perguntas de outros tipos, e.g. associação, ordenação, varios textinput |
18 | 18 | ||
19 | 19 | ||
20 | # FIXED | 20 | # FIXED |
21 | 21 | ||
22 | +- criar pergunta gerada por script externo. | ||
23 | +- debug mode | ||
22 | - in the train_mode, there is no way to logout. Add logout option in the menu. | 24 | - in the train_mode, there is no way to logout. Add logout option in the menu. |
23 | - simplificar a gravacao do teste em json. | 25 | - simplificar a gravacao do teste em json. |
24 | - mostrar numero ordem em /results | 26 | - mostrar numero ordem em /results |
questions.py
@@ -55,7 +55,7 @@ class QuestionsPool(dict): | @@ -55,7 +55,7 @@ class QuestionsPool(dict): | ||
55 | 55 | ||
56 | #============================================================================ | 56 | #============================================================================ |
57 | # Question Factory | 57 | # Question Factory |
58 | -# given a dictionary returns a question instance. | 58 | +# Given a dictionary returns a question instance. |
59 | def create_question(q): | 59 | def create_question(q): |
60 | '''To create a question, q must be a dictionary with at least the | 60 | '''To create a question, q must be a dictionary with at least the |
61 | following keys defined: | 61 | following keys defined: |
@@ -65,15 +65,20 @@ def create_question(q): | @@ -65,15 +65,20 @@ def create_question(q): | ||
65 | The remaing keys depend on the type of question. | 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 | types = { | 72 | types = { |
69 | - 'information': QuestionInformation, | ||
70 | 'radio' : QuestionRadio, | 73 | 'radio' : QuestionRadio, |
71 | 'checkbox' : QuestionCheckbox, | 74 | 'checkbox' : QuestionCheckbox, |
72 | 'text' : QuestionText, | 75 | 'text' : QuestionText, |
73 | 'text_regex': QuestionTextRegex, | 76 | 'text_regex': QuestionTextRegex, |
74 | 'textarea' : QuestionTextArea, | 77 | 'textarea' : QuestionTextArea, |
78 | + 'information': QuestionInformation, | ||
75 | '' : QuestionInformation, # default | 79 | '' : QuestionInformation, # default |
76 | } | 80 | } |
81 | + | ||
77 | # create instance of given type | 82 | # create instance of given type |
78 | try: | 83 | try: |
79 | questiontype = types[q['type']] | 84 | questiontype = types[q['type']] |
@@ -84,6 +89,22 @@ def create_question(q): | @@ -84,6 +89,22 @@ def create_question(q): | ||
84 | # create question instance and return | 89 | # create question instance and return |
85 | return questiontype(q) | 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 | class Question(dict): | 109 | class Question(dict): |
89 | ''' | 110 | ''' |
@@ -93,6 +114,7 @@ class Question(dict): | @@ -93,6 +114,7 @@ class Question(dict): | ||
93 | ''' | 114 | ''' |
94 | pass | 115 | pass |
95 | 116 | ||
117 | + | ||
96 | # =========================================================================== | 118 | # =========================================================================== |
97 | class QuestionRadio(Question): | 119 | class QuestionRadio(Question): |
98 | '''An instance of QuestionRadio will always have the keys: | 120 | '''An instance of QuestionRadio will always have the keys: |
@@ -165,6 +187,7 @@ class QuestionRadio(Question): | @@ -165,6 +187,7 @@ class QuestionRadio(Question): | ||
165 | self['grade'] = x | 187 | self['grade'] = x |
166 | return x | 188 | return x |
167 | 189 | ||
190 | + | ||
168 | # =========================================================================== | 191 | # =========================================================================== |
169 | class QuestionCheckbox(Question): | 192 | class QuestionCheckbox(Question): |
170 | '''An instance of QuestionCheckbox will always have the keys: | 193 | '''An instance of QuestionCheckbox will always have the keys: |
@@ -236,6 +259,7 @@ class QuestionCheckbox(Question): | @@ -236,6 +259,7 @@ class QuestionCheckbox(Question): | ||
236 | 259 | ||
237 | return self['grade'] | 260 | return self['grade'] |
238 | 261 | ||
262 | + | ||
239 | # =========================================================================== | 263 | # =========================================================================== |
240 | class QuestionText(Question): | 264 | class QuestionText(Question): |
241 | '''An instance of QuestionCheckbox will always have the keys: | 265 | '''An instance of QuestionCheckbox will always have the keys: |
@@ -253,9 +277,13 @@ class QuestionText(Question): | @@ -253,9 +277,13 @@ class QuestionText(Question): | ||
253 | self['text'] = self.get('text', '') | 277 | self['text'] = self.get('text', '') |
254 | 278 | ||
255 | # make sure its always a list of possible correct answers | 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 | self['correct'] = [self['correct']] | 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 | self['answer'] = None | 287 | self['answer'] = None |
260 | 288 | ||
261 | #------------------------------------------------------------------------ | 289 | #------------------------------------------------------------------------ |
@@ -270,6 +298,7 @@ class QuestionText(Question): | @@ -270,6 +298,7 @@ class QuestionText(Question): | ||
270 | 298 | ||
271 | return self['grade'] | 299 | return self['grade'] |
272 | 300 | ||
301 | + | ||
273 | # =========================================================================== | 302 | # =========================================================================== |
274 | class QuestionTextRegex(Question): | 303 | class QuestionTextRegex(Question): |
275 | '''An instance of QuestionCheckbox will always have the keys: | 304 | '''An instance of QuestionCheckbox will always have the keys: |
@@ -298,6 +327,7 @@ class QuestionTextRegex(Question): | @@ -298,6 +327,7 @@ class QuestionTextRegex(Question): | ||
298 | 327 | ||
299 | return self['grade'] | 328 | return self['grade'] |
300 | 329 | ||
330 | + | ||
301 | # =========================================================================== | 331 | # =========================================================================== |
302 | class QuestionTextArea(Question): | 332 | class QuestionTextArea(Question): |
303 | '''An instance of QuestionCheckbox will always have the keys: | 333 | '''An instance of QuestionCheckbox will always have the keys: |
@@ -353,6 +383,7 @@ class QuestionTextArea(Question): | @@ -353,6 +383,7 @@ class QuestionTextArea(Question): | ||
353 | 383 | ||
354 | return self['grade'] | 384 | return self['grade'] |
355 | 385 | ||
386 | + | ||
356 | # =========================================================================== | 387 | # =========================================================================== |
357 | class QuestionInformation(Question): | 388 | class QuestionInformation(Question): |
358 | '''An instance of QuestionCheckbox will always have the keys: | 389 | '''An instance of QuestionCheckbox will always have the keys: |
@@ -374,3 +405,4 @@ class QuestionInformation(Question): | @@ -374,3 +405,4 @@ class QuestionInformation(Question): | ||
374 | def correct(self): | 405 | def correct(self): |
375 | self['grade'] = 1.0 # always "correct" but points should be zero! | 406 | self['grade'] = 1.0 # always "correct" but points should be zero! |
376 | return self['grade'] | 407 | return self['grade'] |
408 | + |
static/.DS_Store
No preview for this file type
templates/test.html
@@ -69,7 +69,7 @@ | @@ -69,7 +69,7 @@ | ||
69 | </a> | 69 | </a> |
70 | </div> | 70 | </div> |
71 | 71 | ||
72 | - <p class="navbar-text"> ${t['title']} </p> | 72 | + <!-- <p class="navbar-text"> ${t['title']} </p> --> |
73 | 73 | ||
74 | <div class="collapse navbar-collapse" id="myNavbar"> | 74 | <div class="collapse navbar-collapse" id="myNavbar"> |
75 | <!-- <ul class="nav navbar-nav"> | 75 | <!-- <ul class="nav navbar-nav"> |
@@ -113,6 +113,11 @@ | @@ -113,6 +113,11 @@ | ||
113 | <% | 113 | <% |
114 | total_points = sum(q['points'] for q in questions) | 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 | % if t['train_mode'] and 'grade' in t: | 122 | % if t['train_mode'] and 'grade' in t: |
118 | <div class="jumbotron drop-shadow"> | 123 | <div class="jumbotron drop-shadow"> |
@@ -215,7 +220,7 @@ | @@ -215,7 +220,7 @@ | ||
215 | <span class="glyphicon glyphicon-ok" aria-hidden="true"></span> | 220 | <span class="glyphicon glyphicon-ok" aria-hidden="true"></span> |
216 | ${round(q['grade'] * q['points'] / total_points * 20.0, 1)} pontos | 221 | ${round(q['grade'] * q['points'] / total_points * 20.0, 1)} pontos |
217 | </div> | 222 | </div> |
218 | - % elif q['grade'] > 0.5: | 223 | + % elif q['grade'] > 0.49: |
219 | <div class="alert alert-warning" role="alert"> | 224 | <div class="alert alert-warning" role="alert"> |
220 | <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> | 225 | <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> |
221 | ${round(q['grade'] * q['points'] / total_points * 20.0, 1)} pontos | 226 | ${round(q['grade'] * q['points'] / total_points * 20.0, 1)} pontos |
@@ -268,7 +273,7 @@ | @@ -268,7 +273,7 @@ | ||
268 | </div> | 273 | </div> |
269 | <div class="modal-footer"> | 274 | <div class="modal-footer"> |
270 | <button type="button" class="btn btn-success btn-lg" data-dismiss="modal">Não!</button> | 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 | </div> | 277 | </div> |
273 | </div> | 278 | </div> |
274 | </div> | 279 | </div> |