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