test.py
4.88 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
import os, sys
import random
import yaml
import json
import sqlite3
from datetime import datetime
# my code
import questions
import database
# ===========================================================================
def read_configuration(filename, debug=False, show_points=False, show_hints=False, practice=False, save_answers=False, show_ref=False):
# FIXME validar se ficheiros e directorios existem???
if not os.path.isfile(filename):
print('Cannot find file "%s"' % filename)
sys.exit(1)
try:
with open(filename, 'r') as f:
test = yaml.load(f)
except:
print('Error reading yaml file "{0}".'.format(filename))
raise
# 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['show_ref'] = bool(test.get('show_ref', show_ref))
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):
# normalize question to a dict
# - some_ref
# becomes
# - ref: some_ref
# points: 1.0
test['questions'][i] = [pool[q]] # list with just one question
test['questions'][i][0]['points'] = 1.0
# Note: at this moment we do not know the questions types.
# Some questions, like information, should have default points
# set to 0. That must be done later when the question is
# instantiated.
elif isinstance(q, dict):
if isinstance(q['ref'], str):
q['ref'] = [q['ref']] # ref is always a list
p = float(q.get('points', 1.0)) # default points is 1.0
# create list of alternatives, normalized
l = []
for r in q['ref']:
qq = pool[r]
qq['points'] = p
l.append(qq)
# add question (i.e. list of alternatives) to the test
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 q in self['questions']:
q['answer'] = ans[q['ref']] if q['ref'] in ans else None
# -----------------------------------------------------------------------
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