app.py 4.97 KB

import random
from contextlib import contextmanager  # `with` statement in db sessions
from  datetime import datetime
import logging

# user installed libraries
try:
    import bcrypt
    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker
except ImportError:
    logger.critical('Python package missing. See README.md for instructions.')
    sys.exit(1)

# this project
import questions
from models import Student, Answer

# setup logger for this module
logger = logging.getLogger(__name__)

# ============================================================================
# LearnApp - application logic
# ============================================================================
class LearnApp(object):
    def __init__(self):
        self.factory = questions.QuestionFactory()
        self.factory.load_files(['questions.yaml'], 'demo') # FIXME
        self.online = {}

        # connect to database and check registered students
        db = 'students.db'  # FIXME
        engine = create_engine(f'sqlite:///{db}', echo=False)
        # self.Session = scoped_session(sessionmaker(bind=engine))
        self.Session = sessionmaker(bind=engine)
        try:
            with self.db_session() as s:
                n = s.query(Student).count() # filter(Student.id != '0').
        except Exception as e:
            logger.critical('Database not usable.')
            raise e
        else:
            logger.info(f'Database has {n} students registered.')

    # ------------------------------------------------------------------------
    def login(self, uid, try_pw):
        with self.db_session() as s:
            student =  s.query(Student).filter(Student.id == uid).one_or_none()

            if student is None:
                logger.info(f'User "{uid}" does not exist.')
                return False    # student does not exist or already loggeg in

            hashedtry = bcrypt.hashpw(try_pw.encode('utf-8'), student.password)
            if hashedtry != student.password:
                logger.info(f'User "{uid}" wrong password.')
                return False    # wrong password

            # success
            self.online[uid] = {
                'name': student.name,
                'number': student.id,
                'current': None,
            }
            print(self.online)
            return True

    # ------------------------------------------------------------------------
    # logout
    def logout(self, uid):
        del self.online[uid]  # FIXME save current question?

    # ------------------------------------------------------------------------
    def get_current_question(self, uid):
        return self.online[uid].get('current', None)

    # ------------------------------------------------------------------------
    def get_student_name(self, uid):
        return self.online[uid].get('name', '')

    # ------------------------------------------------------------------------
    # given the currect state, generates a new question for the student
    def new_question_for(self, uid):
        # FIXME
        questions = list(self.factory)
        nextquestion = self.factory.generate(random.choice(questions))
        print(nextquestion)
        self.online[uid]['current'] = nextquestion
        return nextquestion

    # ------------------------------------------------------------------------
    # check answer and if correct returns new question, otherise returns None
    def check_answer(self, uid, answer):
        question = self.get_current_question(uid)
        print(question)
        print(answer)

        if question is not None:
            question['finish_time'] = datetime.now()
            grade = question.correct(answer)       # correct answer

            with self.db_session() as s:
                s.add(Answer(
                    ref=question['ref'],
                    grade=question['grade'],
                    starttime=str(question['start_time']),
                    finishtime=str(question['finish_time']),
                    student_id=uid))
                s.commit()

                correct = grade > 0.99999
                if correct:
                    print('CORRECT')
                    question = self.new_question_for(uid)
                    question['start_time'] = datetime.now()
                    return question
                else:
                    print('WRONG')
                    return None
        else:
            print('FIRST QUESTION')
            question = self.new_question_for(uid)
            question['start_time'] = datetime.now()
            return question

    # ------------------------------------------------------------------------
    # helper to manage db sessions using the `with` statement, for example
    #   with self.db_session() as s:  s.query(...)
    @contextmanager
    def db_session(self):
        session = self.Session()
        try:
            yield session
            session.commit()
        except:
            session.rollback()
            raise
        finally:
            session.close()