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> |