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