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