Commit 01b8619759789cd614bade0b9ebdd5cffc26e466
1 parent
a4e0e360
Exists in
master
and in
1 other branch
- using sqlalchemy
Showing
6 changed files
with
253 additions
and
56 deletions
Show diff stats
BUGS.md
| 1 | 1 | |
| 2 | 2 | # BUGS |
| 3 | 3 | |
| 4 | -- usar thread.Lock para aceder a variaveis de estado. | |
| 4 | +- 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. | |
| 5 | +- usar thread.Lock para aceder a variaveis de estado? | |
| 5 | 6 | - permitir adicionar imagens nas perguntas. |
| 6 | 7 | - debug mode: log levels not working |
| 7 | -- replace sys.exit calls | |
| 8 | -- if does not find questions, aborts silently | |
| 8 | +- Se aluno fizer logout, o teste não é gravado e ficamos sem registo do teste que o aluno viu. | |
| 9 | 9 | |
| 10 | 10 | # TODO |
| 11 | 11 | |
| 12 | 12 | - implementar practice mode. |
| 13 | -- SQLAlchemy em vez da classe database. | |
| 14 | 13 | - single page web no teste/correcçao. Página construída em javascript, obter perguntas com ajax (para practice?). |
| 15 | 14 | - aviso na pagina principal para quem usa browser da treta |
| 16 | 15 | - permitir varios testes, aluno escolhe qual o teste que quer fazer. |
| ... | ... | @@ -24,6 +23,10 @@ |
| 24 | 23 | |
| 25 | 24 | # FIXED |
| 26 | 25 | |
| 26 | +- sqlalchemy queixa-se de threads. | |
| 27 | +- SQLAlchemy em vez da classe database. | |
| 28 | +- replace sys.exit calls | |
| 29 | +- if does not find questions, aborts silently | |
| 27 | 30 | - argumentos da linha de comando a funcionar. |
| 28 | 31 | - configuracao dos logs cherrypy para se darem bem com os outros |
| 29 | 32 | - browser e ip usados gravado no test. | ... | ... |
app.py
| ... | ... | @@ -83,7 +83,7 @@ class App(object): |
| 83 | 83 | return False |
| 84 | 84 | else: |
| 85 | 85 | logger.info('Student {}: logged out.'.format(uid)) |
| 86 | - del self.online[uid] | |
| 86 | + del self.online[uid] # Nao está a gravar o teste como desistencia... | |
| 87 | 87 | return True |
| 88 | 88 | |
| 89 | 89 | def generate_test(self, uid): |
| ... | ... | @@ -180,7 +180,7 @@ class App(object): |
| 180 | 180 | logger.info('Student {}: denied to login'.format(uid)) |
| 181 | 181 | |
| 182 | 182 | def reset_password(self, uid): |
| 183 | - self.db.reset_password(uid) | |
| 183 | + self.db.update_password(uid, pw='') | |
| 184 | 184 | logger.info('Student {}: password reset to ""'.format(uid)) |
| 185 | 185 | |
| 186 | 186 | def set_user_agent(self, uid, user_agent=''): | ... | ... |
database.py
| 1 | 1 | |
| 2 | -import sqlite3 | |
| 2 | +import logging | |
| 3 | +from sqlalchemy import create_engine | |
| 4 | +from sqlalchemy.orm import sessionmaker, scoped_session | |
| 5 | +from orm import Base, Student, Test, Question | |
| 3 | 6 | |
| 7 | +logger = logging.getLogger(__name__) | |
| 8 | + | |
| 9 | +#---------------------------------------------------------------------------- | |
| 4 | 10 | class Database(object): |
| 5 | 11 | def __init__(self, db): |
| 6 | 12 | self.db = db # sqlite3 filename |
| 7 | 13 | |
| 8 | - def get_count_students(self): | |
| 9 | - with sqlite3.connect(self.db) as c: | |
| 10 | - sql = 'SELECT COUNT(*) FROM students' | |
| 11 | - return c.execute(sql).fetchone()[0] | |
| 14 | + engine = create_engine('sqlite:///{}'.format(db), echo=False) | |
| 15 | + Base.metadata.create_all(engine) # Criate schema if needed | |
| 16 | + self.Session = scoped_session(sessionmaker(bind=engine)) | |
| 12 | 17 | |
| 13 | - def reset_password(self, uid): | |
| 14 | - with sqlite3.connect(self.db) as c: | |
| 15 | - sql = 'UPDATE students SET password="" WHERE number=?' | |
| 16 | - c.execute(sql, [uid]) | |
| 18 | + #------------------------------------------------------------------------- | |
| 19 | + def get_count_students(self): | |
| 20 | + s = self.Session() | |
| 21 | + return s.query(Student).filter(Student.id != '0').count() | |
| 22 | + | |
| 23 | + # with sqlite3.connect(self.db) as c: | |
| 24 | + # sql = 'SELECT COUNT(*) FROM students' | |
| 25 | + # return c.execute(sql).fetchone()[0] | |
| 26 | + | |
| 27 | + #------------------------------------------------------------------------- | |
| 28 | + def update_password(self, uid, pw=''): | |
| 29 | + s = self.Session() | |
| 30 | + try: | |
| 31 | + u = s.query(Student).filter(Student.id == uid).one() | |
| 32 | + except: | |
| 33 | + pass | |
| 34 | + else: | |
| 35 | + u.password = pw | |
| 36 | + s.commit() | |
| 17 | 37 | |
| 18 | - def update_password(self, uid, pw): | |
| 19 | 38 | # saves pw as is (should be already hashed) |
| 20 | - with sqlite3.connect(self.db) as c: | |
| 21 | - sql = 'UPDATE students SET password=? WHERE number=?' | |
| 22 | - c.execute(sql, (pw, uid)) | |
| 39 | + # with sqlite3.connect(self.db) as c: | |
| 40 | + # sql = 'UPDATE students SET password=? WHERE id=?' | |
| 41 | + # c.execute(sql, (pw, uid)) | |
| 23 | 42 | |
| 43 | + #------------------------------------------------------------------------- | |
| 24 | 44 | def get_student(self, uid): |
| 25 | - with sqlite3.connect(self.db) as c: | |
| 26 | - sql = 'SELECT name,password FROM students WHERE number=?' | |
| 27 | - try: | |
| 28 | - name, pw = c.execute(sql, [uid]).fetchone() | |
| 29 | - except: | |
| 30 | - return None | |
| 31 | - else: | |
| 32 | - return (name, pw) | |
| 33 | - | |
| 45 | + s = self.Session() | |
| 46 | + r = s.query(Student).filter(Student.id == uid).one_or_none() | |
| 47 | + return r.name, r.password | |
| 48 | + | |
| 49 | + # with sqlite3.connect(self.db) as c: | |
| 50 | + # sql = 'SELECT name,password FROM students WHERE id=?' | |
| 51 | + # try: | |
| 52 | + # name, pw = c.execute(sql, [uid]).fetchone() | |
| 53 | + # except: | |
| 54 | + # return None | |
| 55 | + # else: | |
| 56 | + # return (name, pw) | |
| 57 | + | |
| 58 | + #------------------------------------------------------------------------- | |
| 34 | 59 | def get_all_students(self): |
| 35 | - with sqlite3.connect(self.db) as c: | |
| 36 | - sql = 'SELECT number,name,password FROM students ORDER BY number ASC' | |
| 37 | - students = c.execute(sql).fetchall() | |
| 38 | - return students | |
| 60 | + s = self.Session() | |
| 61 | + r = s.query(Student).all() | |
| 62 | + return [(x.id, x.name, x.password) for x in r] | |
| 63 | + | |
| 64 | + # with sqlite3.connect(self.db) as c: | |
| 65 | + # sql = 'SELECT id,name,password FROM students ORDER BY id ASC' | |
| 66 | + # students = c.execute(sql).fetchall() | |
| 67 | + # return students | |
| 39 | 68 | |
| 40 | 69 | # get all results for a particular test. If a student has submited more than |
| 41 | 70 | # one test, returns the highest grade FIXME not tested, not used |
| ... | ... | @@ -44,30 +73,64 @@ class Database(object): |
| 44 | 73 | # grades = c.execute('SELECT student_id,MAX(grade) FROM tests WHERE test_id==?', [testid]) |
| 45 | 74 | # return grades.fetchall() |
| 46 | 75 | |
| 76 | + #------------------------------------------------------------------------- | |
| 47 | 77 | # get results from previous tests of a student |
| 48 | 78 | def get_student_grades_from_all_tests(self, uid): |
| 49 | - with sqlite3.connect(self.db) as c: | |
| 50 | - grades = c.execute('SELECT test_id,grade,finish_time FROM tests WHERE student_id==?', [uid]) | |
| 51 | - return grades.fetchall() | |
| 79 | + s = self.Session() | |
| 80 | + r = s.query(Test).filter(Student.id == uid).all() | |
| 81 | + return [(x.id, x.grade, x.finishtime) for x in r] | |
| 82 | + | |
| 83 | + # with sqlite3.connect(self.db) as c: | |
| 84 | + # grades = c.execute('SELECT id,grade,finishtime FROM tests WHERE student_id==?', [uid]) | |
| 85 | + # return grades.fetchall() | |
| 52 | 86 | |
| 87 | + #------------------------------------------------------------------------- | |
| 53 | 88 | def get_student_grades_from_test(self, uid, testid): |
| 54 | - with sqlite3.connect(self.db) as c: | |
| 55 | - grades = c.execute('SELECT grade,finish_time FROM tests WHERE student_id==? and test_id==?', [uid, testid]) | |
| 56 | - return grades.fetchall() | |
| 57 | - | |
| 58 | - def save_test(self, t): | |
| 59 | - with sqlite3.connect(self.db) as c: | |
| 60 | - # save final grade of the test | |
| 61 | - sql = 'INSERT INTO tests VALUES (?,?,?,?,?)' | |
| 62 | - test = (t['ref'], t['student']['number'], t['grade'], str(t['start_time']), str(t['finish_time'])) | |
| 63 | - c.execute(sql, test) | |
| 64 | - | |
| 65 | - def save_questions(self, t): | |
| 66 | - with sqlite3.connect(self.db) as c: | |
| 67 | - # save grades of all the questions (omits questions without grade) | |
| 68 | - sql = 'INSERT INTO questions VALUES (?,?,?,?,?)' | |
| 69 | - questions = [(t['ref'], q['ref'], t['student']['number'], q['grade'], str(t['finish_time'])) for q in t['questions'] if 'grade' in q] | |
| 70 | - c.executemany(sql, questions) | |
| 89 | + s = self.Session() | |
| 90 | + r = s.query(Test).filter(Test.student_id==uid and Test.id==testid).all() | |
| 91 | + return [(x.grade, x.finishtime) for x in r] | |
| 92 | + | |
| 93 | + # with sqlite3.connect(self.db) as c: | |
| 94 | + # grades = c.execute('SELECT grade,finishtime FROM tests WHERE student_id==? and id==?', [uid, testid]) | |
| 95 | + # return grades.fetchall() | |
| 96 | + | |
| 97 | + #------------------------------------------------------------------------- | |
| 98 | + def save_test(self, test): | |
| 99 | + t = Test( | |
| 100 | + ref=test['ref'], | |
| 101 | + grade=test['grade'], | |
| 102 | + starttime=str(test['start_time']), | |
| 103 | + finishtime=str(test['finish_time']), | |
| 104 | + student_id=test['student']['number'] | |
| 105 | + ) | |
| 106 | + s = self.Session() | |
| 107 | + s.add(t) | |
| 108 | + s.commit() | |
| 109 | + | |
| 110 | + # with sqlite3.connect(self.db) as c: | |
| 111 | + # # save final grade of the test | |
| 112 | + # sql = 'INSERT INTO tests VALUES (?,?,?,?,?)' | |
| 113 | + # test = (t['ref'], t['student']['number'], t['grade'], str(t['start_time']), str(t['finish_time'])) | |
| 114 | + # c.execute(sql, test) | |
| 115 | + | |
| 116 | + #------------------------------------------------------------------------- | |
| 117 | + def save_questions(self, test): | |
| 118 | + s = self.Session() | |
| 119 | + questions = [Question( | |
| 120 | + ref=q['ref'], | |
| 121 | + grade=q['grade'], | |
| 122 | + starttime='', | |
| 123 | + finishtime=str(test['finish_time']), | |
| 124 | + student_id=test['student']['number'], | |
| 125 | + test_id=test['ref']) for q in test['questions'] if 'grade' in q] | |
| 126 | + s.add_all(questions) | |
| 127 | + s.commit() | |
| 128 | + | |
| 129 | + # with sqlite3.connect(self.db) as c: | |
| 130 | + # # save grades of all the questions (omits questions without grade) | |
| 131 | + # sql = 'INSERT INTO questions VALUES (?,?,?,?,?)' | |
| 132 | + # questions = [(t['ref'], q['ref'], t['student']['number'], q['grade'], str(t['finish_time'])) for q in t['questions'] if 'grade' in q] | |
| 133 | + # c.executemany(sql, questions) | |
| 71 | 134 | |
| 72 | 135 | |
| 73 | 136 | ... | ... |
| ... | ... | @@ -0,0 +1,62 @@ |
| 1 | +#!/usr/bin/env python3 | |
| 2 | +# -*- coding: utf-8 -*- | |
| 3 | + | |
| 4 | +import csv | |
| 5 | +import argparse | |
| 6 | +import re | |
| 7 | +import string | |
| 8 | +import sys | |
| 9 | +from sqlalchemy import create_engine | |
| 10 | +from sqlalchemy.orm import sessionmaker | |
| 11 | + | |
| 12 | +from orm import Base, Student, Test, Question | |
| 13 | + | |
| 14 | +# SIIUE names have alien strings like "(TE)" and are sometimes capitalized | |
| 15 | +# We remove them so that students dont keep asking what it means | |
| 16 | +def fix(name): | |
| 17 | + return string.capwords(re.sub('\(.*\)', '', name).strip()) | |
| 18 | + | |
| 19 | +# =========================================================================== | |
| 20 | +# Parse command line options | |
| 21 | +argparser = argparse.ArgumentParser(description='Create new database from a CSV file (SIIUE format)') | |
| 22 | +argparser.add_argument('--db', default='students.db', type=str, help='database filename') | |
| 23 | +argparser.add_argument('csvfile', nargs='?', type=str, default='', help='CSV filename') | |
| 24 | +args = argparser.parse_args() | |
| 25 | + | |
| 26 | +# =========================================================================== | |
| 27 | +engine = create_engine('sqlite:///{}'.format(args.db), echo=False) | |
| 28 | + | |
| 29 | +# Criate schema if needed | |
| 30 | +Base.metadata.create_all(engine) | |
| 31 | + | |
| 32 | +Session = sessionmaker(bind=engine) | |
| 33 | + | |
| 34 | +# --- start session --- | |
| 35 | +try: | |
| 36 | + session = Session() | |
| 37 | + | |
| 38 | + # add administrator and fake student accounts | |
| 39 | + session.add_all([ | |
| 40 | + Student(id='0', name='Professor', password=''), | |
| 41 | + Student(id='student1', name='Student1', password=''), | |
| 42 | + Student(id='student2', name='Student2', password=''), | |
| 43 | + Student(id='student3', name='Student3', password=''), | |
| 44 | + Student(id='student4', name='Student4', password=''), | |
| 45 | + Student(id='student5', name='Student5', password=''), | |
| 46 | + ]) | |
| 47 | + | |
| 48 | + # add enrolled students from csv file | |
| 49 | + if args.csvfile: | |
| 50 | + try: | |
| 51 | + csvreader = csv.DictReader(open(args.csvfile, encoding='iso-8859-1'), delimiter=';', quotechar='"') | |
| 52 | + except EnvironmentError: | |
| 53 | + print('CSV file "{0}" not found!'.format(args.csvfile)) | |
| 54 | + else: | |
| 55 | + session.add_all([Student(id=r['N.º'], name=fix(r['Nome']), password='') for r in csvreader]) | |
| 56 | + | |
| 57 | + session.commit() | |
| 58 | +except Exception: | |
| 59 | + session.rollback() | |
| 60 | + print('Erro: Dados já existentes na base de dados?') | |
| 61 | + sys.exit(1) | |
| 62 | +# --- end session --- | ... | ... |
| ... | ... | @@ -0,0 +1,73 @@ |
| 1 | + | |
| 2 | + | |
| 3 | +from sqlalchemy import Table, Column, ForeignKey, Integer, Float, String, DateTime | |
| 4 | +from sqlalchemy.ext.declarative import declarative_base | |
| 5 | +from sqlalchemy.orm import relationship | |
| 6 | + | |
| 7 | + | |
| 8 | +# =========================================================================== | |
| 9 | +# Declare ORM | |
| 10 | +Base = declarative_base() | |
| 11 | + | |
| 12 | +# --------------------------------------------------------------------------- | |
| 13 | +class Student(Base): | |
| 14 | + __tablename__ = 'students' | |
| 15 | + id = Column(String, primary_key=True) | |
| 16 | + name = Column(String) | |
| 17 | + password = Column(String) | |
| 18 | + | |
| 19 | + # --- | |
| 20 | + tests = relationship('Test', back_populates='student') | |
| 21 | + questions = relationship('Question', back_populates='student') | |
| 22 | + | |
| 23 | + # def __repr__(self): | |
| 24 | + # return 'Student:\n id: "{0}"\n name: "{1}"\n password: "{2}"'.format(self.id, self.name, self.password) | |
| 25 | + | |
| 26 | + | |
| 27 | +# --------------------------------------------------------------------------- | |
| 28 | +class Test(Base): | |
| 29 | + __tablename__ = 'tests' | |
| 30 | + id = Column(Integer, primary_key=True) # auto_increment | |
| 31 | + ref = Column(String) | |
| 32 | + grade = Column(Float) | |
| 33 | + starttime = Column(String) | |
| 34 | + finishtime = Column(String) | |
| 35 | + student_id = Column(String, ForeignKey('students.id')) | |
| 36 | + | |
| 37 | + # --- | |
| 38 | + student = relationship('Student', back_populates='tests') | |
| 39 | + questions = relationship('Question', back_populates='test') | |
| 40 | + | |
| 41 | + # def __repr__(self): | |
| 42 | + # 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) | |
| 43 | + | |
| 44 | + | |
| 45 | +# --------------------------------------------------------------------------- | |
| 46 | +class Question(Base): | |
| 47 | + __tablename__ = 'questions' | |
| 48 | + id = Column(Integer, primary_key=True) # auto_increment | |
| 49 | + ref = Column(String) | |
| 50 | + grade = Column(Float) | |
| 51 | + starttime = Column(String) | |
| 52 | + finishtime = Column(String) | |
| 53 | + student_id = Column(String, ForeignKey('students.id')) | |
| 54 | + test_id = Column(String, ForeignKey('tests.id')) | |
| 55 | + | |
| 56 | + # --- | |
| 57 | + student = relationship('Student', back_populates='questions') | |
| 58 | + test = relationship('Test', back_populates='questions') | |
| 59 | + | |
| 60 | +# def __repr__(self): | |
| 61 | +# return ''' | |
| 62 | +# Question: | |
| 63 | +# id: "{0}" | |
| 64 | +# ref: "{1}" | |
| 65 | +# grade: "{2}" | |
| 66 | +# starttime: "{3}" | |
| 67 | +# finishtime: "{4}" | |
| 68 | +# student_id: "{5}" | |
| 69 | +# test_id: "{6}" | |
| 70 | +# '''.fotmat(self.id, self.ref, self.grade, self.starttime, self.finishtime, self.student_id, self.test_id) | |
| 71 | + | |
| 72 | + | |
| 73 | +# --------------------------------------------------------------------------- | ... | ... |
serve.py
| ... | ... | @@ -6,7 +6,6 @@ from os import path |
| 6 | 6 | import sys |
| 7 | 7 | import argparse |
| 8 | 8 | import logging.config |
| 9 | -import html | |
| 10 | 9 | import json |
| 11 | 10 | |
| 12 | 11 | try: |
| ... | ... | @@ -241,9 +240,6 @@ if __name__ == '__main__': |
| 241 | 240 | # --- Setup logging |
| 242 | 241 | logging.config.dictConfig(load_yaml(LOGGER_CONF)) |
| 243 | 242 | |
| 244 | - # with open(LOGGER_CONF,'r') as f: | |
| 245 | - # logging.config.dictConfig(yaml.load(f)) | |
| 246 | - | |
| 247 | 243 | # --- start application |
| 248 | 244 | from app import App |
| 249 | 245 | ... | ... |