test.py 4.1 KB
'''
Test - instances of this class are individual tests
'''

# python standard library
from datetime import datetime
import json
import logging
from math import nan
from os import path

# Logger configuration
logger = logging.getLogger(__name__)


# ============================================================================
class Test(dict):
    '''
    Each instance Test() is a concrete test of a single student.
    '''

    # ------------------------------------------------------------------------
    def __init__(self, d):
        super().__init__(d)
        self['grade'] = nan
        self['comment'] = ''

    # ------------------------------------------------------------------------
    def start(self, student: dict) -> None:
        '''
        Write student id in the test and register start time
        '''
        self['student'] = student
        self['start_time'] = datetime.now()
        self['finish_time'] = None
        self['state'] = 'ACTIVE'

    # ------------------------------------------------------------------------
    def reset_answers(self) -> None:
        '''Removes all answers from the test (clean)'''
        for question in self['questions']:
            question['answer'] = None

    # ------------------------------------------------------------------------
    def update_answer(self, ref: str, ans) -> None:
        '''updates one answer in the test'''
        self['questions'][ref].set_answer(ans)

    # ------------------------------------------------------------------------
    def submit(self, answers_dict) -> None:
        '''
        Given a dictionary ans={'ref': 'some answer'} updates the answers of
        multiple questions in the test.
        Only affects the questions referred in the dictionary.
        '''
        self['finish_time'] = datetime.now()
        for ref, ans in answers_dict.items():
            self['questions'][ref].set_answer(ans)
        self['state'] = 'SUBMITTED'

    # ------------------------------------------------------------------------
    async def correct_async(self) -> None:
        '''Corrects all the answers of the test and computes the final grade'''
        grade = 0.0
        for question in self['questions']:
            await question.correct_async()
            grade += question['grade'] * question['points']
            logger.debug('Correcting %30s: %3g%%',
                         question['ref'], question['grade']*100)

        # truncate to avoid negative final grade and adjust scale
        self['grade'] = max(0.0, grade) + self['scale'][0]
        self['state'] = 'CORRECTED'

    # ------------------------------------------------------------------------
    def correct(self) -> None:
        '''Corrects all the answers of the test and computes the final grade'''
        grade = 0.0
        for question in self['questions']:
            question.correct()
            grade += question['grade'] * question['points']
            logger.debug('Correcting %30s: %3g%%',
                         question['ref'], question['grade']*100)

        # truncate to avoid negative final grade and adjust scale
        self['grade'] = max(0.0, grade) + self['scale'][0]
        self['state'] = 'CORRECTED'

    # ------------------------------------------------------------------------
    def giveup(self) -> None:
        '''Test is marqued as QUIT and is not corrected'''
        self['finish_time'] = datetime.now()
        self['state'] = 'QUIT'
        self['grade'] = 0.0

    # ------------------------------------------------------------------------
    def save_json(self, pathfile) -> None:
        '''save test in JSON format'''
        with open(pathfile, 'w') as file:
            json.dump(self, file, indent=2, default=str)  # str for datetime

    # ------------------------------------------------------------------------
    def __str__(self) -> str:
        return '\n'.join([f'{k}: {v}' for k,v in self.items()])
        # return ('Test:\n'
        #         f'  student:  {self.get("student", "--")}\n'
        #         f'  start_time:  {self.get("start_time", "--")}\n'
        #         f'  questions:  {", ".join(q["ref"] for q in self["questions"])}\n')