Commit feacfc1b015875d007a6455ba496f1d2e82ecc6c

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

- questions generated by an external script

- test information in the when in debug mode
@@ -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
@@ -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>