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
@@ -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
@@ -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:
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):