test.py
5.5 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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import os
import random
import yaml
import json
import sqlite3
from datetime import datetime
import questions
# ============================================================================
def read_configuration(filename):
# 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', False))
test['show_points'] = bool(test.get('show_points', False))
test['singlepage'] = bool(test.get('singlepage', True))
test['save_answers'] = bool(test.get('save_answers', True))
if test['save_answers']:
if 'answers_dir' not in test:
print(' * Missing "answers_dir" in the test configuration.')
sys.exit(1)
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):
'''Creates an instance of a test'''
# -----------------------------------------------------------------------
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):
# ---- Saves test (JSON) ---- FIXME simplify and update json2md
header = {
'title': self['title'],
'number': self['number'],
'name': self['name'],
'testid': self['ref'],
'start_time': str(self['start_time']),
'finish_time': str(self['finish_time']),
'final_grade': str(self['grade'])
}
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:
test = {'header': header, 'answers': self['questions']}
json.dump(test, f, indent=4)
# -----------------------------------------------------------------------
def save_database(self, db):
# ---- Saves grade to database ---- FIXME SQLAlchemy??
t = self
ans = []
for q in t['questions']:
ans.append((t['ref'], q['ref'], t['number'], q['grade'], str(t['finish_time'])))
values = (t['ref'], t['number'], t['grade'], str(t['start_time']), str(t['finish_time']))
conn = sqlite3.connect(db)
conn.execute('INSERT INTO tests VALUES (?,?,?,?,?)', values)
conn.executemany('INSERT INTO questions VALUES (?,?,?,?,?)', ans)
conn.commit()
conn.close()
# -----------------------------------------------------------------------
def __str__(self):
return str(self)