diff --git a/BUGS.md b/BUGS.md index 2b3e2cf..3e22340 100644 --- a/BUGS.md +++ b/BUGS.md @@ -1,7 +1,6 @@ # BUGS -- criar sqlalchemy sessions dentro de app de modo a estarem associadas a requests. ver se é facil usar with db:(...) para criar e fechar sessão. - usar thread.Lock para aceder a variaveis de estado? - permitir adicionar imagens nas perguntas. - debug mode: log levels not working @@ -23,6 +22,7 @@ # FIXED +- criar sqlalchemy sessions dentro de app de modo a estarem associadas a requests. ver se é facil usar with db:(...) para criar e fechar sessão. - sqlalchemy queixa-se de threads. - SQLAlchemy em vez da classe database. - replace sys.exit calls diff --git a/app.py b/app.py index f5cd037..5c5073f 100644 --- a/app.py +++ b/app.py @@ -1,12 +1,15 @@ -import logging from os import path -import sqlite3 +import logging import bcrypt +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, scoped_session +from models import Base, Student, Test, Question +from contextlib import contextmanager # to create `with` statement for db sessions import test -import database +import threading logger = logging.getLogger(__name__) @@ -18,64 +21,85 @@ class App(object): def __init__(self, filename, conf): # online = { # uid1: { - # 'student': {'number': 123, 'name': john}, - # 'test': ... + # '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 + self.testfactory = test.TestFactory(filename, conf=conf) - self.db = database.Database(self.testfactory['database']) # FIXME + + # database + engine = create_engine('sqlite:///{}'.format(self.testfactory['database']), echo=False) + Base.metadata.create_all(engine) # Criate schema if needed FIXME no student '0' + self.Session = scoped_session(sessionmaker(bind=engine)) + try: - n = self.db.get_count_students() - except sqlite3.OperationalError as e: - logger.critical('Database not usable {}.'.format(self.db.db)) + with self.db_session() as s: + n = s.query(Student).filter(Student.id != '0').count() + except Exception as e: + logger.critical('Database not usable {}.'.format(self.testfactory['database'])) raise e else: logger.info('Database has {} students registered.'.format(n)) + # ----------------------------------------------------------------------- + # helper to manage db sessions using the `with` statement, for example + # with self.db_session() as s: ... + @contextmanager + def db_session(self): + try: + yield self.Session() + finally: + self.Session.remove() + + # ----------------------------------------------------------------------- def exit(self): + # FIXME what if there are online students? 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 + with self.db_session() as s: + student = s.query(Student).filter(Student.id == uid).one_or_none() + + if student is None: + # not found + logger.warning('Student {}: not found in database.'.format(uid)) + return False + + if student.password == '': + # update password on first login + hashed_pw = bcrypt.hashpw(try_pw.encode('utf-8'), bcrypt.gensalt()) + student.password = hashed_pw + s.commit() + logger.warning('Student {}: first login, password updated.'.format(uid)) + + elif bcrypt.hashpw(try_pw.encode('utf-8'), student.password) != student.password: + # 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': student.name, 'number': uid}} + logger.info('Student {}: logged in.'.format(uid)) - # 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 @@ -83,9 +107,10 @@ class App(object): return False else: logger.info('Student {}: logged out.'.format(uid)) - del self.online[uid] # Nao está a gravar o teste como desistencia... + del self.online[uid] # FIXME Nao está a gravar o teste como desistencia... return True + # ----------------------------------------------------------------------- def generate_test(self, uid): if uid in self.online: logger.info('Student {}: generating new test.'.format(uid)) @@ -96,6 +121,7 @@ class App(object): 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) @@ -107,10 +133,25 @@ class App(object): fpath = path.abspath(path.join(t['answers_dir'], fname)) t.save_json(fpath) - self.db.save_test(t) - self.db.save_questions(t) + with self.db_session() as s: + s.add(Test( + ref=t['ref'], + grade=t['grade'], + starttime=str(t['start_time']), + finishtime=str(t['finish_time']), + student_id=t['student']['number'])) + s.add_all([Question( + ref=q['ref'], + grade=q['grade'], + starttime='', + finishtime=str(t['finish_time']), + student_id=t['student']['number'], + test_id=t['ref']) for q in t['questions'] if 'grade' in q]) + s.commit() + return grade + # ----------------------------------------------------------------------- # --- helpers (getters) def get_student_name(self, uid): @@ -120,51 +161,52 @@ class App(object): 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 - # ) + with self.db_session() as s: + r = s.query(Test).filter(Student.id == uid).all() + return [(t.id, t.grade, t.finishtime) for t in r] def get_online_students(self): - # {'123': '2016-12-02 12:04:12.344243', ...} + # [('uid', 'name', 'starttime')] 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]) + # list of ('uid', 'name') sorted by uid + return [u[:2] for u in self.get_all_students() if u[0] not in self.online] 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]) + # list of all ('uid', 'name', 'password') sorted by uid + with self.db_session() as s: + r = s.query(Student).all() + return sorted(((u.id, u.name, u.password) for u in r if u.id != '0'), key=lambda k: k[0]) + + def get_student_grades_from_test(self, uid, testid): + with self.db_session() as s: + r = s.query(Test).filter(Test.student_id==uid and Test.id==testid).all() + return [(u.grade, u.finishtime) for u in r] 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']) - d[uid]['ip_address'] = self.online.get(uid, {}).get('student', {}).get('ip_address','') - d[uid]['user_agent'] = self.online.get(uid, {}).get('student', {}).get('user_agent','') - return d - - # def get_this_students_grades(self): - # # list of ('uid', 'name') sorted by number - # return self.db.get_students_grades(self.testfactory['ref']) + # [{ + # 'uid' : '12345' + # 'name' : 'John Smith', + # 'start_time': '', + # 'grades' : [10.2, 13.1], + # ... + # }] + l = [] + for u in self.get_all_students(): + uid, name, pw = u + l.append({ + 'uid': uid, + 'name': name, + 'allowed': uid in self.allowed, + 'online': uid in self.online, + 'start_time': self.online.get(uid, {}).get('test', {}).get('start_time',''), + 'password_defined': pw != '', + 'grades': self.get_student_grades_from_test(uid, self.testfactory['ref']), + 'ip_address': self.online.get(uid, {}).get('student', {}).get('ip_address',''), + 'user_agent': self.online.get(uid, {}).get('student', {}).get('user_agent','') + }) + return l def get_allowed_students(self): # set of 'uid' allowed to login @@ -180,7 +222,9 @@ class App(object): logger.info('Student {}: denied to login'.format(uid)) def reset_password(self, uid): - self.db.update_password(uid, pw='') + with self.db_session() as s: + u = s.query(Student).filter(Student.id == uid).update({'password': ''}) + s.commit() logger.info('Student {}: password reset to ""'.format(uid)) def set_user_agent(self, uid, user_agent=''): diff --git a/config/logger-debug.yaml b/config/logger-debug.yaml new file mode 100644 index 0000000..c8f9dcf --- /dev/null +++ b/config/logger-debug.yaml @@ -0,0 +1,75 @@ + +version: 1 + +formatters: + void: + format: '' + standard: + format: '%(asctime)s | %(levelname)-8s | %(name)-14s | %(message)s' + +handlers: + default: + level: 'INFO' + class: 'logging.StreamHandler' + formatter: 'standard' + stream: 'ext://sys.stdout' + + cherrypy_console: + level: 'INFO' + class: 'logging.StreamHandler' + formatter: 'standard' + stream: 'ext://sys.stdout' + + cherrypy_access: + level: 'INFO' + class: 'logging.handlers.RotatingFileHandler' + formatter: 'void' + filename: 'logs/access.log' + maxBytes: 10485760 + backupCount: 20 + encoding: 'utf8' + + cherrypy_error: + level: 'INFO' + class: 'logging.handlers.RotatingFileHandler' + formatter: 'void' + filename: 'logs/errors.log' + maxBytes: 10485760 + backupCount: 20 + encoding: 'utf8' + +loggers: + '': + handlers: ['default'] + level: 'DEBUG' + + 'cherrypy.access': + handlers: ['cherrypy_access'] + level: 'DEBUG' + propagate: False + + 'cherrypy.error': + handlers: ['cherrypy_console', 'cherrypy_error'] + level: 'DEBUG' + propagate: False + + 'app': + handlers: ['default'] + level: 'DEBUG' + propagate: False + + 'test': + handlers: ['default'] + level: 'DEBUG' + propagate: False + + 'questions': + handlers: ['default'] + level: 'DEBUG' + propagate: False + + 'tools': + handlers: ['default'] + level: 'DEBUG' + propagate: False + diff --git a/config/logger.yaml b/config/logger.yaml new file mode 100644 index 0000000..1ab0219 --- /dev/null +++ b/config/logger.yaml @@ -0,0 +1,75 @@ + +version: 1 + +formatters: + void: + format: '' + standard: + format: '%(asctime)s | %(levelname)-8s | %(name)-14s | %(message)s' + +handlers: + default: + level: 'INFO' + class: 'logging.StreamHandler' + formatter: 'standard' + stream: 'ext://sys.stdout' + + cherrypy_console: + level: 'INFO' + class: 'logging.StreamHandler' + formatter: 'standard' + stream: 'ext://sys.stdout' + + cherrypy_access: + level: 'INFO' + class: 'logging.handlers.RotatingFileHandler' + formatter: 'void' + filename: 'logs/access.log' + maxBytes: 10485760 + backupCount: 20 + encoding: 'utf8' + + cherrypy_error: + level: 'INFO' + class: 'logging.handlers.RotatingFileHandler' + formatter: 'void' + filename: 'logs/errors.log' + maxBytes: 10485760 + backupCount: 20 + encoding: 'utf8' + +loggers: + '': + handlers: ['default'] + level: 'INFO' + + 'cherrypy.access': + handlers: ['cherrypy_access'] + level: 'INFO' + propagate: False + + 'cherrypy.error': + handlers: ['cherrypy_console', 'cherrypy_error'] + level: 'INFO' + propagate: False + + 'app': + handlers: ['default'] + level: 'INFO' + propagate: False + + 'test': + handlers: ['default'] + level: 'INFO' + propagate: False + + 'questions': + handlers: ['default'] + level: 'INFO' + propagate: False + + 'tools': + handlers: ['default'] + level: 'INFO' + propagate: False + diff --git a/database.py b/database.py deleted file mode 100644 index 88057ae..0000000 --- a/database.py +++ /dev/null @@ -1,181 +0,0 @@ - -import logging -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker, scoped_session -from orm import Base, Student, Test, Question - -logger = logging.getLogger(__name__) - -#---------------------------------------------------------------------------- -class Database(object): - def __init__(self, db): - self.db = db # sqlite3 filename - - engine = create_engine('sqlite:///{}'.format(db), echo=False) - Base.metadata.create_all(engine) # Criate schema if needed - self.Session = scoped_session(sessionmaker(bind=engine)) - - #------------------------------------------------------------------------- - def get_count_students(self): - s = self.Session() - return s.query(Student).filter(Student.id != '0').count() - - # with sqlite3.connect(self.db) as c: - # sql = 'SELECT COUNT(*) FROM students' - # return c.execute(sql).fetchone()[0] - - #------------------------------------------------------------------------- - def update_password(self, uid, pw=''): - s = self.Session() - try: - u = s.query(Student).filter(Student.id == uid).one() - except: - pass - else: - u.password = pw - s.commit() - - # saves pw as is (should be already hashed) - # with sqlite3.connect(self.db) as c: - # sql = 'UPDATE students SET password=? WHERE id=?' - # c.execute(sql, (pw, uid)) - - #------------------------------------------------------------------------- - def get_student(self, uid): - s = self.Session() - r = s.query(Student).filter(Student.id == uid).one_or_none() - return r.name, r.password - - # with sqlite3.connect(self.db) as c: - # sql = 'SELECT name,password FROM students WHERE id=?' - # try: - # name, pw = c.execute(sql, [uid]).fetchone() - # except: - # return None - # else: - # return (name, pw) - - #------------------------------------------------------------------------- - def get_all_students(self): - s = self.Session() - r = s.query(Student).all() - return [(x.id, x.name, x.password) for x in r] - - # with sqlite3.connect(self.db) as c: - # sql = 'SELECT id,name,password FROM students ORDER BY id ASC' - # students = c.execute(sql).fetchall() - # return students - - # get all results for a particular test. If a student has submited more than - # one test, returns the highest grade FIXME not tested, not used - # def get_students_grades(self, testid): - # with sqlite3.connect(self.db) as c: - # grades = c.execute('SELECT student_id,MAX(grade) FROM tests WHERE test_id==?', [testid]) - # return grades.fetchall() - - #------------------------------------------------------------------------- - # get results from previous tests of a student - def get_student_grades_from_all_tests(self, uid): - s = self.Session() - r = s.query(Test).filter(Student.id == uid).all() - return [(x.id, x.grade, x.finishtime) for x in r] - - # with sqlite3.connect(self.db) as c: - # grades = c.execute('SELECT id,grade,finishtime FROM tests WHERE student_id==?', [uid]) - # return grades.fetchall() - - #------------------------------------------------------------------------- - def get_student_grades_from_test(self, uid, testid): - s = self.Session() - r = s.query(Test).filter(Test.student_id==uid and Test.id==testid).all() - return [(x.grade, x.finishtime) for x in r] - - # with sqlite3.connect(self.db) as c: - # grades = c.execute('SELECT grade,finishtime FROM tests WHERE student_id==? and id==?', [uid, testid]) - # return grades.fetchall() - - #------------------------------------------------------------------------- - def save_test(self, test): - t = Test( - ref=test['ref'], - grade=test['grade'], - starttime=str(test['start_time']), - finishtime=str(test['finish_time']), - student_id=test['student']['number'] - ) - s = self.Session() - s.add(t) - s.commit() - - # with sqlite3.connect(self.db) as c: - # # save final grade of the test - # sql = 'INSERT INTO tests VALUES (?,?,?,?,?)' - # test = (t['ref'], t['student']['number'], t['grade'], str(t['start_time']), str(t['finish_time'])) - # c.execute(sql, test) - - #------------------------------------------------------------------------- - def save_questions(self, test): - s = self.Session() - questions = [Question( - ref=q['ref'], - grade=q['grade'], - starttime='', - finishtime=str(test['finish_time']), - student_id=test['student']['number'], - test_id=test['ref']) for q in test['questions'] if 'grade' in q] - s.add_all(questions) - s.commit() - - # with sqlite3.connect(self.db) as c: - # # save grades of all the questions (omits questions without grade) - # sql = 'INSERT INTO questions VALUES (?,?,?,?,?)' - # questions = [(t['ref'], q['ref'], t['student']['number'], q['grade'], str(t['finish_time'])) for q in t['questions'] if 'grade' in q] - # c.executemany(sql, questions) - - - - - # def insert_student(self, number, name, password=''): # FIXME testar... - # with sqlite3.connect(self.db) as c: - # if password != '': - # password = sha256(password.encode('utf-8')).hexdigest() # FIXME bcrypt - # cmd = 'INSERT INTO students VALUES (?, ?, ?);' - # c.execute(cmd, number, name, password) - - - - - # # return list of students and their results for a given test - # def get_test_grades(self, test_id): - # with sqlite3.connect(self.db) as c: - # # with all tests done by each student: - # # cmd = 'SELECT student_id,name,grade FROM students INNER JOIN tests ON students.number=tests.student_id WHERE test_id==? ORDER BY grade DESC;' - - # # only the best result for each student - # cmd = ''' - # SELECT student_id, name, MAX(grade), finish_time - # FROM students INNER JOIN tests - # ON students.number=tests.student_id - # WHERE test_id==? AND student_id!=0 - # GROUP BY student_id - # ORDER BY grade DESC, finish_time DESC;''' - # return c.execute(cmd, [test_id]).fetchall() - - # # return list of students and their results for a given test - # def test_grades2(self, test_id): - # with sqlite3.connect(self.db) as c: - # # with all tests done by each student: - # # cmd = 'SELECT student_id,name,grade FROM students INNER JOIN tests ON students.number=tests.student_id WHERE test_id==? ORDER BY grade DESC;' - - # # only the best result for each student - # cmd = ''' - # SELECT student_id, name, grade, start_time, finish_time - # FROM students INNER JOIN tests - # ON students.number=tests.student_id - # WHERE test_id==? - # ORDER BY finish_time ASC;''' - # return c.execute(cmd, [test_id]).fetchall() - - - # the following methods update de database data - diff --git a/initdb.py b/initdb.py index 28bd5e9..4449a07 100755 --- a/initdb.py +++ b/initdb.py @@ -9,7 +9,7 @@ import sys from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker -from orm import Base, Student, Test, Question +from models import Base, Student, Test, Question # SIIUE names have alien strings like "(TE)" and are sometimes capitalized # We remove them so that students dont keep asking what it means @@ -55,8 +55,14 @@ try: session.add_all([Student(id=r['N.º'], name=fix(r['Nome']), password='') for r in csvreader]) session.commit() + except Exception: session.rollback() print('Erro: Dados já existentes na base de dados?') sys.exit(1) + +else: + n = session.query(Student).count() + print('Base de dados inicializada com {} utilizadores.'.format(n)) + # --- end session --- diff --git a/initdb_from_csv.py b/initdb_from_csv.py deleted file mode 100755 index 124fbb5..0000000 --- a/initdb_from_csv.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import sys -import sqlite3 -import csv -import argparse -import bcrypt -import os -import string -import re - -# SIIUE names have alien strings like "(TE)" and are sometimes capitalized -# We remove them so that students dont keep asking what it means -def fixname(s): - return string.capwords(re.sub('\(.*\)', '', s).strip()) - -def genstudent(reader, pw=''): - for i, r in enumerate(reader): - num = r['N.º'] - name = fixname(r['Nome']) - yield (r['N.º'], fixname(r['Nome']), '') - print('{} students inserted.'.format(i+1)) - -# ---- DATABASE SCHEMA ---- -sql_cmd = '''PRAGMA foreign_keys = ON; - CREATE TABLE students ( - number TEXT PRIMARY KEY, - name TEXT, - password TEXT - ); - CREATE TABLE tests ( - test_id TEXT NOT NULL, - student_id TEXT NOT NULL, - grade REAL, - start_time TEXT, - finish_time TEXT, - FOREIGN KEY(student_id) REFERENCES students(number) - ); - CREATE TABLE questions ( - test_id TEXT NOT NULL, - question_id TEXT NOT NULL, - student_id TEXT NOT NULL, - grade REAL, - time TEXT, - FOREIGN KEY(student_id) REFERENCES students(number) - );''' - -# --------- Parse command line options ----------- -argparser = argparse.ArgumentParser(description='Create new database from a CSV file (SIIUE format)') -argparser.add_argument('--db', default='students.db', type=str, help='database filename') -argparser.add_argument('csvfile', nargs='?', type=str, default='', help='CSV filename') -args = argparser.parse_args() - -db_exists = os.path.exists(args.db) - -with sqlite3.connect(args.db) as c: - # use existing or create new database schema - if db_exists: - print('-> Using previous database "{}"...'.format(args.db)) - else: - print('-> Creating new database "{}"...'.format(args.db)) - c.executescript(sql_cmd) - - # get students - if args.csvfile: - csvfile = open(args.csvfile, encoding='iso-8859-1') - print('-> Using students from CSV file "{}"...'.format(args.csvfile)) - students = genstudent(csv.DictReader(csvfile, delimiter=';', quotechar='"')) - else: - print('-> Creating fake students numbered 1 to 5...'.format(args.csvfile)) - students = [ - ('1', 'Student1', ''), - ('2', 'Student2', ''), - ('3', 'Student3', ''), - ('4', 'Student4', ''), - ('5', 'Student5', '') - ] - - # insert students into database - print('-> Inserting students into database... ') - try: - c.executemany('INSERT INTO students VALUES (?,?,?)', students) - except sqlite3.IntegrityError: - print('** ERROR ** Students already exist. Aborted!') - sys.exit(1) - - # insert professor into database - print('-> Inserting professor (id=0)...') - try: - c.execute('INSERT INTO students VALUES (?,?,?)', ('0', 'Professor', '')) - except sqlite3.IntegrityError: - print('** WARNING ** Professor already exists.') - -print('Done.') diff --git a/models.py b/models.py new file mode 100644 index 0000000..4dbaa89 --- /dev/null +++ b/models.py @@ -0,0 +1,73 @@ + + +from sqlalchemy import Table, Column, ForeignKey, Integer, Float, String, DateTime +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import relationship + + +# =========================================================================== +# Declare ORM +Base = declarative_base() + +# --------------------------------------------------------------------------- +class Student(Base): + __tablename__ = 'students' + id = Column(String, primary_key=True) + name = Column(String) + password = Column(String) + + # --- + tests = relationship('Test', back_populates='student') + questions = relationship('Question', back_populates='student') + + # def __repr__(self): + # return 'Student:\n id: "{0}"\n name: "{1}"\n password: "{2}"'.format(self.id, self.name, self.password) + + +# --------------------------------------------------------------------------- +class Test(Base): + __tablename__ = 'tests' + id = Column(Integer, primary_key=True) # auto_increment + ref = Column(String) + grade = Column(Float) + starttime = Column(String) + finishtime = Column(String) + student_id = Column(String, ForeignKey('students.id')) + + # --- + student = relationship('Student', back_populates='tests') + questions = relationship('Question', back_populates='test') + + # def __repr__(self): + # return 'Test:\n id: "{0}"\n ref="{1}"\n grade="{2}"\n starttime="{3}"\n finishtime="{4}"\n student_id="{5}"'.format(self.id, self.ref, self.grade, self.starttime, self.finishtime, self.student_id) + + +# --------------------------------------------------------------------------- +class Question(Base): + __tablename__ = 'questions' + id = Column(Integer, primary_key=True) # auto_increment + ref = Column(String) + grade = Column(Float) + starttime = Column(String) + finishtime = Column(String) + student_id = Column(String, ForeignKey('students.id')) + test_id = Column(String, ForeignKey('tests.id')) + + # --- + student = relationship('Student', back_populates='questions') + test = relationship('Test', back_populates='questions') + +# def __repr__(self): +# return ''' +# Question: +# id: "{0}" +# ref: "{1}" +# grade: "{2}" +# starttime: "{3}" +# finishtime: "{4}" +# student_id: "{5}" +# test_id: "{6}" +# '''.fotmat(self.id, self.ref, self.grade, self.starttime, self.finishtime, self.student_id, self.test_id) + + +# --------------------------------------------------------------------------- diff --git a/orm.py b/orm.py deleted file mode 100644 index 4dbaa89..0000000 --- a/orm.py +++ /dev/null @@ -1,73 +0,0 @@ - - -from sqlalchemy import Table, Column, ForeignKey, Integer, Float, String, DateTime -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import relationship - - -# =========================================================================== -# Declare ORM -Base = declarative_base() - -# --------------------------------------------------------------------------- -class Student(Base): - __tablename__ = 'students' - id = Column(String, primary_key=True) - name = Column(String) - password = Column(String) - - # --- - tests = relationship('Test', back_populates='student') - questions = relationship('Question', back_populates='student') - - # def __repr__(self): - # return 'Student:\n id: "{0}"\n name: "{1}"\n password: "{2}"'.format(self.id, self.name, self.password) - - -# --------------------------------------------------------------------------- -class Test(Base): - __tablename__ = 'tests' - id = Column(Integer, primary_key=True) # auto_increment - ref = Column(String) - grade = Column(Float) - starttime = Column(String) - finishtime = Column(String) - student_id = Column(String, ForeignKey('students.id')) - - # --- - student = relationship('Student', back_populates='tests') - questions = relationship('Question', back_populates='test') - - # def __repr__(self): - # return 'Test:\n id: "{0}"\n ref="{1}"\n grade="{2}"\n starttime="{3}"\n finishtime="{4}"\n student_id="{5}"'.format(self.id, self.ref, self.grade, self.starttime, self.finishtime, self.student_id) - - -# --------------------------------------------------------------------------- -class Question(Base): - __tablename__ = 'questions' - id = Column(Integer, primary_key=True) # auto_increment - ref = Column(String) - grade = Column(Float) - starttime = Column(String) - finishtime = Column(String) - student_id = Column(String, ForeignKey('students.id')) - test_id = Column(String, ForeignKey('tests.id')) - - # --- - student = relationship('Student', back_populates='questions') - test = relationship('Test', back_populates='questions') - -# def __repr__(self): -# return ''' -# Question: -# id: "{0}" -# ref: "{1}" -# grade: "{2}" -# starttime: "{3}" -# finishtime: "{4}" -# student_id: "{5}" -# test_id: "{6}" -# '''.fotmat(self.id, self.ref, self.grade, self.starttime, self.finishtime, self.student_id, self.test_id) - - -# --------------------------------------------------------------------------- diff --git a/serve.py b/serve.py index 641a403..7f36009 100755 --- a/serve.py +++ b/serve.py @@ -83,7 +83,7 @@ class AdminWebService(object): @cherrypy.tools.accept(media='application/json') # FIXME def GET(self): data = { - 'students': list(self.app.get_students_state().items()), + 'students': self.app.get_students_state(), 'test': self.app.testfactory } return json.dumps(data, default=str) @@ -246,7 +246,7 @@ if __name__ == '__main__': try: app = App(filename, vars(arg)) except Exception as e: - logging.critical('Cannot start application.') + logging.critical('Can\'t start application.') raise e # FIXME just for testing sys.exit(1) diff --git a/static/js/admin.js b/static/js/admin.js index 5778b5b..2722fe5 100644 --- a/static/js/admin.js +++ b/static/js/admin.js @@ -28,6 +28,7 @@ $(document).ready(function() { ); } + // ---------------------------------------------------------------------- // checkbox handler to allow/deny students individually function autorizeStudent(e) { $.ajax({ @@ -41,12 +42,14 @@ $(document).ready(function() { $(this).parent().parent().removeClass("active"); } + // ---------------------------------------------------------------------- function populateOnlineTable(students) { - var active = []; var rows = ""; + // make list of online students + var active = []; $.each(students, function(i, r) { - if (r[1]['start_time'] != '') { - active.push([r[0], r[1]['name'], r[1]['start_time'], r[1]['ip_address'], r[1]['user_agent']]); + if (r['start_time'] != '') { + active.push([r['uid'], r['name'], r['start_time'], r['ip_address'], r['user_agent']]); } }); // sort by start time @@ -65,6 +68,7 @@ $(document).ready(function() { $("#online-header").html(n + " Activo(s)"); } + // ---------------------------------------------------------------------- function generate_grade_bar(grade) { var barcolor; if (grade < 10) { @@ -81,13 +85,13 @@ $(document).ready(function() { return bar } + // ---------------------------------------------------------------------- function populateStudentsTable(students) { - $("#students-header").html(students.length + " Alunos") + var n = students.length; + $("#students-header").html(n + " Alunos") var rows = ""; - students.sort(function(a,b){return a[0] - b[0]}); - $.each(students, function(i, r) { - var uid = r[0]; - var d = r[1]; // dictionary + $.each(students, function(i, d) { + var uid = d['uid']; if (d['start_time'] != '') // test rows += '