test.py
4.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import os
import random
import yaml
import json
import sqlite3
from datetime import datetime
import questions
import database
# ============================================================================
def read_configuration(filename, debug=False, show_points=False, show_hints=False, practice=False, save_answers=False):
# FIXME validar se ficheiros e directorios existem???
if not os.path.isfile(filename):
print('Cannot find file "%s"' % filename)
os.sys.exit(1)
with open(filename, 'r') as f:
test = yaml.load(f)
# defaults:
test['ref'] = str(test.get('ref', filename))
test['title'] = str(test.get('title', ''))
test['show_hints'] = bool(test.get('show_hints', show_hints))
test['show_points'] = bool(test.get('show_points', show_points))
test['practice'] = bool(test.get('practice', practice))
test['debug'] = bool(test.get('debug', debug))
test['save_answers'] = bool(test.get('save_answers', save_answers))
if test['save_answers']:
if 'answers_dir' not in test:
raise exception('Missing "answers_dir" in the test configuration.')
if not os.path.isdir(test['answers_dir']):
print(' * Directory "%s" does not exist. Creating...' % test['answers_dir'])
os.mkdir(test['answers_dir'])
if 'database' not in test:
print(' * Missing database in the test configuration.')
sys.exit(1)
if isinstance(test['files'], str):
test['files'] = [test['files']]
# replace ref,points by actual questions from pool
pool = questions.QuestionsPool()
pool.add_from_files(test['files'])
for i, q in enumerate(test['questions']):
# each question is a list of alternative versions, even if the list
# contains only one element
if isinstance(q, str):
# questions: some_ref --> questions: [{'ref':'some_ref', 'points':1.0, ...}]
test['questions'][i] = [pool[q]]
test['questions'][i][0]['points'] = 1.0
elif isinstance(q, dict):
if isinstance(q['ref'], str):
q['ref'] = [q['ref']]
p = float(q.get('points', 1.0))
l = []
for r in q['ref']:
qq = pool[r]
qq['points'] = p
l.append(qq)
test['questions'][i] = l
return test
# ============================================================================
class Test(dict):
# -----------------------------------------------------------------------
def __init__(self, d):
super().__init__(d)
qlist = []
for i, qq in enumerate(self['questions']):
q = random.choice(qq) # select from alternative versions
qlist.append(questions.create_question(q)) # create instance
self['questions'] = qlist
self['start_time'] = datetime.now()
# -----------------------------------------------------------------------
def update_answers(self, ans):
'''given a dictionary ans={'ref':'some answer'} updates the answers
of the test. FIXME: check if answer is to be corrected or not
'''
for i, q in enumerate(self['questions']):
if q['ref'] in ans:
if q['type'] == 'checkbox' and ans[q['ref']] == None:
# HACK: checkboxes in html return none instead of an empty list
# we have to manualy replace by []
q['answer'] = []
else:
q['answer'] = ans[q['ref']]
# -----------------------------------------------------------------------
def correct(self):
'''Corrects all the answers and computes the final grade.'''
self['finish_time'] = datetime.now()
total_points = 0.0
final_grade = 0.0
for q in self['questions']:
final_grade += q.correct() * q['points']
total_points += q['points']
final_grade = 20.0 * max(final_grade / total_points, 0.0)
self['grade'] = final_grade
return final_grade
# -----------------------------------------------------------------------
def save_json(self, path):
filename = ' -- '.join((str(self['number']), self['ref'],
str(self['finish_time']))) + '.json'
filepath = os.path.abspath(os.path.join(path, filename))
with open(filepath, 'w') as f:
json.dump(self, f, indent=2, default=str)
# HACK default=str is required for datetime objects