app.py 6.88 KB


import logging
from os import path
import sqlite3

import bcrypt

import test
import database

# ============================================================================
#   Application
# ============================================================================
class App(object):
    def __init__(self, filename, conf):
        # online = {
        #   uid1: {
        #     'student': {'number': 123, 'name': john},
        #     'test': ...
        #   }
        #   uid2: {...}
        # }
        logger.info('============= Running perguntations =============')
        self.online = dict()               # {uid: {'student':{}}}
        self.allowed = set([])  # '0' is hardcoded to allowed elsewhere FIXME
        self.testfactory = test.TestFactory(filename, conf=conf)
        self.db = database.Database(self.testfactory['database']) # FIXME
        try:
            n = self.db.get_count_students()
        except sqlite3.OperationalError as e:
            logger.critical('Database not usable {}.'.format(self.db.db))
            raise e
        else:
            logger.info('Database has {} students registered.'.format(n))


    def exit(self):
        logger.critical('----------- !!! Server terminated !!! -----------')


    def login(self, uid, try_pw):
        if uid not in self.allowed and uid != '0':
            # not allowed
            logger.warning('Student {}:  not allowed to login.'.format(uid))
            return False

        student = self.db.get_student(uid)
        if student is None:
            # not found
            logger.warning('Student {}:  not found in database.'.format(uid))
            return False

        # uid found in database
        name, pw = student

        if pw == '':
            # update password on first login
            hashed_pw = bcrypt.hashpw(try_pw.encode('utf-8'), bcrypt.gensalt())
            self.db.update_password(uid, hashed_pw)
            logger.warning('Student {}:  first login, password updated.'.format(uid))
        elif bcrypt.hashpw(try_pw.encode('utf-8'), pw) != pw:
            # wrong password
            logger.info('Student {}:  wrong password.'.format(uid))
            return False

        # success
        self.allowed.discard(uid)
        if uid in self.online:
            logger.warning('Student {}:  already logged in.'.format(uid))
        else:
            self.online[uid] = {'student': {'name': name, 'number': uid}}
            logger.info('Student {}:  logged in.'.format(uid))
        return True


    def logout(self, uid):
        if uid not in self.online:
            # this should never happen
            logger.error('Student {}:  tried to logout, but is not logged in.'.format(uid))
            return False
        else:
            logger.info('Student {}:  logged out.'.format(uid))
            del self.online[uid]
            return True

    def generate_test(self, uid):
        if uid in self.online:
            logger.info('Student {}:  generating new test.'.format(uid))
            student_id = self.online[uid]['student']
            self.online[uid]['test'] = self.testfactory.generate(student_id)
            return self.online[uid]['test']
        else:
            logger.error('Student {}:  offline, can''t generate test'.format(uid))
            return None

    def correct_test(self, uid, ans):
        t = self.online[uid]['test']
        t.update_answers(ans)
        grade = t.correct()
        logger.info('Student {0}:  finished with {1} points.'.format(uid, grade))

        if t['save_answers']:
            fname = ' -- '.join((t['student']['number'], t['ref'], str(t['finish_time']))) + '.json'
            fpath = path.abspath(path.join(t['answers_dir'], fname))
            t.save_json(fpath)

        self.db.save_test(t)
        self.db.save_questions(t)
        return grade


    # --- helpers (getters)
    def get_student_name(self, uid):
        return self.online[uid]['student']['name']
    def get_test(self, uid, default=None):
        return self.online[uid].get('test', default)
    def get_test_qtypes(self, uid):
        return {q['ref']:q['type'] for q in self.online[uid]['test']['questions']}
    def get_student_grades_from_all_tests(self, uid):
        return self.db.get_student_grades_from_all_tests(uid)

    # def get_student_grades_from_test(self, uid, testid):
    #     return self.db.get_student_grades_from_test(uid, testid)


    # def get_online_students(self):
    #     # list of ('uid', 'name', 'start_time') sorted by start time
    #     return sorted(
    #         ((k, v['student']['name'], str(v.get('test', {}).get('start_time', '---'))) for k,v in self.online.items() if k != '0'),
    #         key=lambda k: k[2]   # sort key
    #     )

    def get_online_students(self):
        # {'123': '2016-12-02 12:04:12.344243', ...}
        return [(k, v['student']['name'], str(v.get('test', {}).get('start_time', '---'))) for k,v in self.online.items() if k != '0']

    def get_offline_students(self):
        # list of ('uid', 'name') sorted by number
        return sorted((s[:2] for s in self.db.get_all_students() if s[0] not in self.online), key=lambda k: k[0])

    def get_all_students(self):
        # list of ('uid', 'name') sorted by number
        return sorted((s[:2] for s in self.db.get_all_students() if s[0] != '0'), key=lambda k: k[0])

    def get_students_state(self):
        # {'123': {'name': 'John', 'start_time':'', 'grades':[10.2, 13.1], ...}}
        d = {}
        for s in self.db.get_all_students():
            uid, name, pw = s
            if uid == '0':
                continue
            d[uid] = {'name': name}
            d[uid]['allowed'] = uid in self.allowed
            d[uid]['online'] = uid in self.online
            d[uid]['start_time'] = self.online.get(uid, {}).get('test', {}).get('start_time','')
            d[uid]['password_defined'] = pw != ''
            d[uid]['grades'] = self.db.get_student_grades_from_test(uid, self.testfactory['ref'])
        return d

    # def get_this_students_grades(self):
    #     # list of ('uid', 'name') sorted by number
    #     return self.db.get_students_grades(self.testfactory['ref'])

    def get_allowed_students(self):
        # set of 'uid' allowed to login
        return self.allowed

    # --- helpers (change state)
    def allow_student(self, uid):
        self.allowed.add(uid)
        logger.info('Student {}:  allowed to login.'.format(uid))

    def deny_student(self, uid):
        self.allowed.discard(uid)
        logger.info('Student {}:  denied to login'.format(uid))

    def reset_password(self, uid):
        self.db.reset_password(uid)
        logger.info('Student {}:  password reset to ""'.format(uid))

# ============================================================================
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
ch.setFormatter(logging.Formatter('%(asctime)s | %(name)-10s | %(levelname)-8s | %(message)s'))

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
logger.addHandler(ch)