Commit 1c00dfe6dc95c9382c8994292d5c6d133b6d8bfc
1 parent
2f37bba0
Exists in
master
and in
1 other branch
- improved separation between questions, tests and html server.
- code cleanup.
Showing
3 changed files
with
34 additions
and
26 deletions
Show diff stats
questions.py
| @@ -401,7 +401,6 @@ class QuestionInformation(Question): | @@ -401,7 +401,6 @@ class QuestionInformation(Question): | ||
| 401 | '''An instance of QuestionInformation will always have the keys: | 401 | '''An instance of QuestionInformation will always have the keys: |
| 402 | type (str) | 402 | type (str) |
| 403 | text (str) | 403 | text (str) |
| 404 | - answer (None or an actual answer) | ||
| 405 | points (0.0) | 404 | points (0.0) |
| 406 | ''' | 405 | ''' |
| 407 | #------------------------------------------------------------------------ | 406 | #------------------------------------------------------------------------ |
| @@ -409,8 +408,7 @@ class QuestionInformation(Question): | @@ -409,8 +408,7 @@ class QuestionInformation(Question): | ||
| 409 | # create key/values as given in q | 408 | # create key/values as given in q |
| 410 | super().__init__(q) | 409 | super().__init__(q) |
| 411 | self['text'] = self.get('text', '') | 410 | self['text'] = self.get('text', '') |
| 412 | - self['answer'] = None | ||
| 413 | - self['points'] = 0.0 # FIXME shouldnt be defined on the test??? | 411 | + self['points'] = 0.0 # always override the points |
| 414 | 412 | ||
| 415 | #------------------------------------------------------------------------ | 413 | #------------------------------------------------------------------------ |
| 416 | # can return negative values for wrong answers | 414 | # can return negative values for wrong answers |
serve.py
| @@ -106,22 +106,22 @@ class Root(object): | @@ -106,22 +106,22 @@ class Root(object): | ||
| 106 | ans = {} | 106 | ans = {} |
| 107 | for q in t['questions']: | 107 | for q in t['questions']: |
| 108 | if 'answered-' + q['ref'] in kwargs: | 108 | if 'answered-' + q['ref'] in kwargs: |
| 109 | - ans[q['ref']] = kwargs.get(q['ref'], None) | 109 | + # HACK: checkboxes in html return None instead of an empty list |
| 110 | + # we have to manualy replace by [] | ||
| 111 | + default_ans = [] if q['type'] == 'checkbox' else None | ||
| 112 | + ans[q['ref']] = kwargs.get(q['ref'], default_ans) | ||
| 110 | 113 | ||
| 111 | # store the answers in the Test, correct it, save JSON and | 114 | # store the answers in the Test, correct it, save JSON and |
| 112 | # store results in the database | 115 | # store results in the database |
| 113 | t.update_answers(ans) | 116 | t.update_answers(ans) |
| 114 | - # try: | ||
| 115 | t.correct() | 117 | t.correct() |
| 116 | - # except: | ||
| 117 | - # cherrypy.log.error('Failed to correct test of student %s' % t['number'], 'APPLICATION') | ||
| 118 | - # t['grade'] = None | ||
| 119 | 118 | ||
| 120 | if t['save_answers']: | 119 | if t['save_answers']: |
| 121 | t.save_json(self.testconf['answers_dir']) | 120 | t.save_json(self.testconf['answers_dir']) |
| 122 | self.database.save_test(t) | 121 | self.database.save_test(t) |
| 123 | 122 | ||
| 124 | if t['practice']: | 123 | if t['practice']: |
| 124 | + # ---- Repeat the test ---- | ||
| 125 | raise cherrypy.HTTPRedirect('/test') | 125 | raise cherrypy.HTTPRedirect('/test') |
| 126 | 126 | ||
| 127 | else: | 127 | else: |
test.py
| 1 | 1 | ||
| 2 | -import os | 2 | +import os, sys |
| 3 | import random | 3 | import random |
| 4 | import yaml | 4 | import yaml |
| 5 | import json | 5 | import json |
| 6 | import sqlite3 | 6 | import sqlite3 |
| 7 | from datetime import datetime | 7 | from datetime import datetime |
| 8 | + | ||
| 9 | +# my code | ||
| 8 | import questions | 10 | import questions |
| 9 | import database | 11 | import database |
| 10 | 12 | ||
| @@ -13,10 +15,14 @@ def read_configuration(filename, debug=False, show_points=False, show_hints=Fals | @@ -13,10 +15,14 @@ def read_configuration(filename, debug=False, show_points=False, show_hints=Fals | ||
| 13 | # FIXME validar se ficheiros e directorios existem??? | 15 | # FIXME validar se ficheiros e directorios existem??? |
| 14 | if not os.path.isfile(filename): | 16 | if not os.path.isfile(filename): |
| 15 | print('Cannot find file "%s"' % filename) | 17 | print('Cannot find file "%s"' % filename) |
| 16 | - os.sys.exit(1) | 18 | + sys.exit(1) |
| 17 | 19 | ||
| 18 | - with open(filename, 'r') as f: | ||
| 19 | - test = yaml.load(f) | 20 | + try: |
| 21 | + with open(filename, 'r') as f: | ||
| 22 | + test = yaml.load(f) | ||
| 23 | + except: | ||
| 24 | + print('Error reading yaml file "{0}".'.format(filename)) | ||
| 25 | + raise | ||
| 20 | 26 | ||
| 21 | # defaults: | 27 | # defaults: |
| 22 | test['ref'] = str(test.get('ref', filename)) | 28 | test['ref'] = str(test.get('ref', filename)) |
| @@ -35,7 +41,7 @@ def read_configuration(filename, debug=False, show_points=False, show_hints=Fals | @@ -35,7 +41,7 @@ def read_configuration(filename, debug=False, show_points=False, show_hints=Fals | ||
| 35 | 41 | ||
| 36 | if 'database' not in test: | 42 | if 'database' not in test: |
| 37 | print(' * Missing database in the test configuration.') | 43 | print(' * Missing database in the test configuration.') |
| 38 | - os.sys.exit(1) | 44 | + sys.exit(1) |
| 39 | 45 | ||
| 40 | if isinstance(test['files'], str): | 46 | if isinstance(test['files'], str): |
| 41 | test['files'] = [test['files']] | 47 | test['files'] = [test['files']] |
| @@ -48,21 +54,31 @@ def read_configuration(filename, debug=False, show_points=False, show_hints=Fals | @@ -48,21 +54,31 @@ def read_configuration(filename, debug=False, show_points=False, show_hints=Fals | ||
| 48 | # each question is a list of alternative versions, even if the list | 54 | # each question is a list of alternative versions, even if the list |
| 49 | # contains only one element | 55 | # contains only one element |
| 50 | if isinstance(q, str): | 56 | if isinstance(q, str): |
| 51 | - # questions: some_ref --> questions: [{'ref':'some_ref', 'points':1.0, ...}] | ||
| 52 | - test['questions'][i] = [pool[q]] | 57 | + # normalize question to a dict |
| 58 | + # - some_ref | ||
| 59 | + # becomes | ||
| 60 | + # - ref: some_ref | ||
| 61 | + # points: 1.0 | ||
| 62 | + test['questions'][i] = [pool[q]] # list with just one question | ||
| 53 | test['questions'][i][0]['points'] = 1.0 | 63 | test['questions'][i][0]['points'] = 1.0 |
| 64 | + # Note: at this moment we do not know the questions types. | ||
| 65 | + # Some questions, like information, should have default points | ||
| 66 | + # set to 0. That must be done later when the question is | ||
| 67 | + # instantiated. | ||
| 54 | 68 | ||
| 55 | elif isinstance(q, dict): | 69 | elif isinstance(q, dict): |
| 56 | if isinstance(q['ref'], str): | 70 | if isinstance(q['ref'], str): |
| 57 | - q['ref'] = [q['ref']] | ||
| 58 | - | ||
| 59 | - p = float(q.get('points', 1.0)) | 71 | + q['ref'] = [q['ref']] # ref is always a list |
| 72 | + p = float(q.get('points', 1.0)) # default points is 1.0 | ||
| 60 | 73 | ||
| 74 | + # create list of alternatives, normalized | ||
| 61 | l = [] | 75 | l = [] |
| 62 | for r in q['ref']: | 76 | for r in q['ref']: |
| 63 | qq = pool[r] | 77 | qq = pool[r] |
| 64 | qq['points'] = p | 78 | qq['points'] = p |
| 65 | l.append(qq) | 79 | l.append(qq) |
| 80 | + | ||
| 81 | + # add question (i.e. list of alternatives) to the test | ||
| 66 | test['questions'][i] = l | 82 | test['questions'][i] = l |
| 67 | 83 | ||
| 68 | return test | 84 | return test |
| @@ -85,14 +101,8 @@ class Test(dict): | @@ -85,14 +101,8 @@ class Test(dict): | ||
| 85 | '''given a dictionary ans={'ref':'some answer'} updates the answers | 101 | '''given a dictionary ans={'ref':'some answer'} updates the answers |
| 86 | of the test. FIXME: check if answer is to be corrected or not | 102 | of the test. FIXME: check if answer is to be corrected or not |
| 87 | ''' | 103 | ''' |
| 88 | - for i, q in enumerate(self['questions']): | ||
| 89 | - if q['ref'] in ans: | ||
| 90 | - if q['type'] == 'checkbox' and ans[q['ref']] == None: | ||
| 91 | - # HACK: checkboxes in html return none instead of an empty list | ||
| 92 | - # we have to manualy replace by [] | ||
| 93 | - q['answer'] = [] | ||
| 94 | - else: | ||
| 95 | - q['answer'] = ans[q['ref']] | 104 | + for q in self['questions']: |
| 105 | + q['answer'] = ans[q['ref']] if q['ref'] in ans else None | ||
| 96 | 106 | ||
| 97 | # ----------------------------------------------------------------------- | 107 | # ----------------------------------------------------------------------- |
| 98 | def correct(self): | 108 | def correct(self): |