test.py 4.33 KB

import os
import random
import yaml
import json
import sqlite3
from datetime import datetime
import questions
import database

# ============================================================================
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['train_mode'] = bool(test.get('train_mode', False))
    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):
    # -----------------------------------------------------------------------
    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