Commit 1c00dfe6dc95c9382c8994292d5c6d133b6d8bfc

Authored by Miguel Barão
1 parent 2f37bba0
Exists in master and in 1 other branch dev

- 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 401 '''An instance of QuestionInformation will always have the keys:
402 402 type (str)
403 403 text (str)
404   - answer (None or an actual answer)
405 404 points (0.0)
406 405 '''
407 406 #------------------------------------------------------------------------
... ... @@ -409,8 +408,7 @@ class QuestionInformation(Question):
409 408 # create key/values as given in q
410 409 super().__init__(q)
411 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 414 # can return negative values for wrong answers
... ...
serve.py
... ... @@ -106,22 +106,22 @@ class Root(object):
106 106 ans = {}
107 107 for q in t['questions']:
108 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 114 # store the answers in the Test, correct it, save JSON and
112 115 # store results in the database
113 116 t.update_answers(ans)
114   - # try:
115 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 119 if t['save_answers']:
121 120 t.save_json(self.testconf['answers_dir'])
122 121 self.database.save_test(t)
123 122  
124 123 if t['practice']:
  124 + # ---- Repeat the test ----
125 125 raise cherrypy.HTTPRedirect('/test')
126 126  
127 127 else:
... ...
test.py
1 1  
2   -import os
  2 +import os, sys
3 3 import random
4 4 import yaml
5 5 import json
6 6 import sqlite3
7 7 from datetime import datetime
  8 +
  9 +# my code
8 10 import questions
9 11 import database
10 12  
... ... @@ -13,10 +15,14 @@ def read_configuration(filename, debug=False, show_points=False, show_hints=Fals
13 15 # FIXME validar se ficheiros e directorios existem???
14 16 if not os.path.isfile(filename):
15 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 27 # defaults:
22 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 41  
36 42 if 'database' not in test:
37 43 print(' * Missing database in the test configuration.')
38   - os.sys.exit(1)
  44 + sys.exit(1)
39 45  
40 46 if isinstance(test['files'], str):
41 47 test['files'] = [test['files']]
... ... @@ -48,21 +54,31 @@ def read_configuration(filename, debug=False, show_points=False, show_hints=Fals
48 54 # each question is a list of alternative versions, even if the list
49 55 # contains only one element
50 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 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 69 elif isinstance(q, dict):
56 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 75 l = []
62 76 for r in q['ref']:
63 77 qq = pool[r]
64 78 qq['points'] = p
65 79 l.append(qq)
  80 +
  81 + # add question (i.e. list of alternatives) to the test
66 82 test['questions'][i] = l
67 83  
68 84 return test
... ... @@ -85,14 +101,8 @@ class Test(dict):
85 101 '''given a dictionary ans={'ref':'some answer'} updates the answers
86 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 108 def correct(self):
... ...