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 | ... | ... |