Commit 7e5841848b8ef20da03d2d383315327c8b25995d
1 parent
8005184e
Exists in
master
and in
1 other branch
- fixed bug where repeated questions with same reference would crash during correction.
- removed unused code.
Showing
5 changed files
with
37 additions
and
37 deletions
Show diff stats
BUGS.md
| 1 | 1 | |
| 2 | 2 | # BUGS |
| 3 | 3 | |
| 4 | -- se um teste tiver a mesma pergunta (ref igual) varias vezes, rebenta na correcçao. As respostas são agregadas numa lista para cada ref. Ex: | |
| 5 | - {'ref1': 'resposta1', 'ref2': ['resposta2a', 'resposta2b']} | |
| 6 | -possivelmente as referencias das perguntas deveriam ser o "testeRef:numPergunta"... é preciso ver como depois estao associadas às correcções. | |
| 7 | 4 | - se directorio logs não existir no directorio actual (não perguntations) rebenta. |
| 8 | 5 | - usar thread.Lock para aceder a variaveis de estado? |
| 9 | 6 | - servidor nao esta a lidar com eventos scroll/resize |
| ... | ... | @@ -27,6 +24,7 @@ possivelmente as referencias das perguntas deveriam ser o "testeRef:numPergunta" |
| 27 | 24 | |
| 28 | 25 | # FIXED |
| 29 | 26 | |
| 27 | +- se um teste tiver a mesma pergunta repetida (ref igual), rebenta na correcçao. As respostas são agregadas numa lista para cada ref. Ex: {'ref1': 'resposta1', 'ref2': ['resposta2a', 'resposta2b']} | |
| 30 | 28 | - usar http://fontawesome.io/examples/ em vez dos do bootstrap3 |
| 31 | 29 | - se pergunta tiver 'type:' errado, rebenta. |
| 32 | 30 | - se submeter um teste so com information, da divisao por zero. | ... | ... |
app.py
| ... | ... | @@ -124,6 +124,8 @@ class App(object): |
| 124 | 124 | return None |
| 125 | 125 | |
| 126 | 126 | # ----------------------------------------------------------------------- |
| 127 | + # ans is a dictionary {question_index: answer, ...} | |
| 128 | + # for example: {0:'hello', 1:[1,2]} | |
| 127 | 129 | def correct_test(self, uid, ans): |
| 128 | 130 | t = self.online[uid]['test'] |
| 129 | 131 | t.update_answers(ans) |
| ... | ... | @@ -161,7 +163,7 @@ class App(object): |
| 161 | 163 | # ----------------------------------------------------------------------- |
| 162 | 164 | def giveup_test(self, uid): |
| 163 | 165 | t = self.online[uid]['test'] |
| 164 | - grade = t.giveup() | |
| 166 | + t.giveup() | |
| 165 | 167 | |
| 166 | 168 | # save JSON with the test |
| 167 | 169 | fname = ' -- '.join((t['student']['number'], t['ref'], str(t['finish_time']))) + '.json' |
| ... | ... | @@ -194,8 +196,6 @@ class App(object): |
| 194 | 196 | return self.online[uid].get('test', default) |
| 195 | 197 | def get_questions_path(self): |
| 196 | 198 | return self.testfactory['questions_dir'] |
| 197 | - def get_test_qtypes(self, uid): | |
| 198 | - return {q['ref']:q['type'] for q in self.online[uid]['test']['questions']} | |
| 199 | 199 | def get_student_grades_from_all_tests(self, uid): |
| 200 | 200 | with self.db_session() as s: |
| 201 | 201 | r = s.query(Test).filter_by(student_id=uid).order_by(Test.finishtime).all() | ... | ... |
serve.py
| ... | ... | @@ -192,30 +192,35 @@ class Root(object): |
| 192 | 192 | @require() |
| 193 | 193 | def correct(self, **kwargs): |
| 194 | 194 | # receives dictionary with answers |
| 195 | - # kwargs = {'answered-xpto': 'on', 'xpto': '13.45', ...} | |
| 195 | + # kwargs = {'answered-0': 'on', '0': '13.45', ...} | |
| 196 | 196 | # Format: |
| 197 | 197 | # checkbox - all off -> no key, 1 on -> string '0', >1 on -> ['0', '1'] |
| 198 | 198 | # radio - all off -> no key, 1 on -> string '0' |
| 199 | 199 | # text - always returns string. no answer '', otherwise 'dskdjs' |
| 200 | 200 | uid = cherrypy.session.get(SESSION_KEY) |
| 201 | - name = self.app.get_student_name(uid) | |
| 202 | - qq = self.app.get_test_qtypes(uid) # {'q1_ref': 'checkbox', ...} | |
| 201 | + t = self.app.get_test(uid) | |
| 203 | 202 | |
| 204 | - # each question that is marked to be classified must have an answer. | |
| 205 | - # `ans` contains the answers to be corrected. The missing ones were | |
| 206 | - # disabled by the student | |
| 203 | + # build dictionary ans={0: 'answer0', 1:, 'answer1', ...} | |
| 204 | + # questions not answer are not included. | |
| 207 | 205 | ans = {} |
| 208 | - for qref, qtype in qq.items(): | |
| 209 | - if 'answered-' + qref in kwargs: | |
| 210 | - # HTML HACK: checkboxes in html return None instead of an empty list if none is selected. Also, if only one is selected returns string instead of list of strings. | |
| 211 | - default_ans = [] if qtype == 'checkbox' else None | |
| 212 | - a = kwargs.get(qref, default_ans) | |
| 213 | - if qtype == 'checkbox' and isinstance(a, str): | |
| 214 | - a = [a] | |
| 215 | - ans[qref] = a | |
| 216 | - | |
| 217 | - grade = self.app.correct_test(uid, ans) | |
| 218 | - t = self.app.get_test(uid) | |
| 206 | + for i, q in enumerate(t['questions']): | |
| 207 | + if 'answered-' + str(i) in kwargs: | |
| 208 | + ans[i] = kwargs.get(str(i), None) | |
| 209 | + | |
| 210 | + # Begin HACK | |
| 211 | + # checkboxes in html do not have a stable type: | |
| 212 | + # returns None instead of [], when no checkboxes are selected | |
| 213 | + # returns '5' instead of ['5'], when one checkbox is selected | |
| 214 | + # returns correctly ['1', '3'], on multiple selections | |
| 215 | + # we fix it to always return a list | |
| 216 | + if q['type'] == 'checkbox': | |
| 217 | + if ans[i] is None: | |
| 218 | + ans[i] = [] | |
| 219 | + elif isinstance(ans[i], str): | |
| 220 | + ans[i] = [ans[i]] | |
| 221 | + # end HACK | |
| 222 | + | |
| 223 | + self.app.correct_test(uid, ans) | |
| 219 | 224 | self.app.logout(uid) |
| 220 | 225 | |
| 221 | 226 | # --- Expire session | ... | ... |
templates/test.html
| ... | ... | @@ -130,7 +130,7 @@ |
| 130 | 130 | </h4> |
| 131 | 131 | <div class="pull-right"> |
| 132 | 132 | Classificar |
| 133 | - <input type="checkbox" class="question_disabler" data-size="mini" name="answered-${q['ref']}" id="answered-${q['ref']}" checked=""> | |
| 133 | + <input type="checkbox" class="question_disabler" data-size="mini" name="answered-${i}" id="answered-${i}" checked=""> | |
| 134 | 134 | </div> |
| 135 | 135 | </div> |
| 136 | 136 | <div class="panel-body" id="example${i}"> |
| ... | ... | @@ -143,7 +143,7 @@ |
| 143 | 143 | <div class="list-group"> |
| 144 | 144 | % for opt in q['options']: |
| 145 | 145 | <a class="list-group-item"> |
| 146 | - ${md_to_html('<input type="radio" name="{0}" id="{0}{1}" value="{1}" {2}/> '.format(q['ref'], loop.index, 'checked' if q['answer'] is not None and str(loop.index) == q['answer'] else '') + opt, q['ref'], q['files'])} | |
| 146 | + ${md_to_html('<input type="radio" name="{0}" id="{0}:{1}" value="{1}" {2}/> '.format(i, loop.index, 'checked' if q['answer'] is not None and str(loop.index) == q['answer'] else '') + opt, q['ref'], q['files'])} | |
| 147 | 147 | </a> |
| 148 | 148 | % endfor |
| 149 | 149 | </div> |
| ... | ... | @@ -152,25 +152,25 @@ |
| 152 | 152 | <div class="list-group"> |
| 153 | 153 | % for opt in q['options']: |
| 154 | 154 | <a class="list-group-item"> |
| 155 | - ${md_to_html('<input type="checkbox" name="{0}" id="{0}{1}" value="{1}" {2}/> {3}'.format(q['ref'], loop.index, 'checked' if q['answer'] is not None and str(loop.index) in q['answer'] else '', opt), q['ref'], q['files'])} | |
| 155 | + ${md_to_html('<input type="checkbox" name="{0}" id="{0}:{1}" value="{1}" {2}/> '.format(i, loop.index, 'checked' if q['answer'] is not None and str(loop.index) in q['answer'] else '') + opt, q['ref'], q['files'])} | |
| 156 | 156 | </a> |
| 157 | 157 | % endfor |
| 158 | 158 | </div> |
| 159 | 159 | % elif q['type'] in ('text', 'text_regex'): |
| 160 | - <input type="text" name="${q['ref']}" class="form-control" value="${q['answer'] if q['answer'] is not None else ''}"> | |
| 160 | + <input type="text" name="${i}" class="form-control" value="${q['answer'] if q['answer'] is not None else ''}"> | |
| 161 | 161 | % elif q['type'] == 'textarea': |
| 162 | - <textarea class="form-control" rows="${q['lines']}" name="${q['ref']}">${q['answer'] if q['answer'] is not None else ''}</textarea><br /> | |
| 162 | + <textarea class="form-control" rows="${q['lines']}" name="${i}">${q['answer'] if q['answer'] is not None else ''}</textarea><br /> | |
| 163 | 163 | % endif |
| 164 | 164 | </fieldset> |
| 165 | 165 | |
| 166 | 166 | % if t['show_hints']: |
| 167 | 167 | % if 'hint' in q: |
| 168 | 168 | <p> |
| 169 | - <button class="btn btn-sm btn-warning" type="button" data-toggle="collapse" data-target="#hint-${q['ref']}" aria-expanded="false" aria-controls="hint-${q['ref']}"> | |
| 169 | + <button class="btn btn-sm btn-warning" type="button" data-toggle="collapse" data-target="#hint-${i}" aria-expanded="false" aria-controls="hint-${i}"> | |
| 170 | 170 | Ajuda |
| 171 | 171 | </button> |
| 172 | 172 | </p> |
| 173 | - <div class="collapse" id="hint-${q['ref']}"> | |
| 173 | + <div class="collapse" id="hint-${i}"> | |
| 174 | 174 | <div class="well"> |
| 175 | 175 | ${md_to_html(q['hint'], q['ref'], q['files'])} |
| 176 | 176 | </div> | ... | ... |
test.py
| ... | ... | @@ -9,9 +9,7 @@ import logging |
| 9 | 9 | logger = logging.getLogger(__name__) |
| 10 | 10 | |
| 11 | 11 | try: |
| 12 | - # import yaml | |
| 13 | 12 | import json |
| 14 | - import markdown | |
| 15 | 13 | except ImportError: |
| 16 | 14 | logger.critical('Python package missing. See README.md for instructions.') |
| 17 | 15 | sys.exit(1) |
| ... | ... | @@ -213,13 +211,12 @@ class Test(dict): |
| 213 | 211 | logger.info('Student {}: all answers cleared.'.format(self['student']['number'])) |
| 214 | 212 | |
| 215 | 213 | # ----------------------------------------------------------------------- |
| 216 | - # Given a dictionary ans={'someref': 'some answer'} updates the | |
| 214 | + # Given a dictionary ans={index: 'some answer'} updates the | |
| 217 | 215 | # answers of the test. Only affects questions referred. |
| 218 | 216 | def update_answers(self, ans): |
| 219 | - for q in self['questions']: | |
| 220 | - if q['ref'] in ans: | |
| 221 | - q['answer'] = ans[q['ref']] | |
| 222 | - logger.info('Student {}: answers updated.'.format(self['student']['number'])) | |
| 217 | + for i in ans: | |
| 218 | + self['questions'][i]['answer'] = ans[i] | |
| 219 | + logger.info('Student {}: {} answers updated.'.format(self['student']['number'], len(ans))) | |
| 223 | 220 | |
| 224 | 221 | # ----------------------------------------------------------------------- |
| 225 | 222 | # Corrects all the answers and computes the final grade | ... | ... |