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 | # BUGS | 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 | - permitir adicionar imagens nas perguntas. | 6 | - permitir adicionar imagens nas perguntas. |
6 | - debug mode: log levels not working | 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 | # TODO | 10 | # TODO |
11 | 11 | ||
12 | - implementar practice mode. | 12 | - implementar practice mode. |
13 | -- SQLAlchemy em vez da classe database. | ||
14 | - single page web no teste/correcçao. Página construída em javascript, obter perguntas com ajax (para practice?). | 13 | - single page web no teste/correcçao. Página construída em javascript, obter perguntas com ajax (para practice?). |
15 | - aviso na pagina principal para quem usa browser da treta | 14 | - aviso na pagina principal para quem usa browser da treta |
16 | - permitir varios testes, aluno escolhe qual o teste que quer fazer. | 15 | - permitir varios testes, aluno escolhe qual o teste que quer fazer. |
@@ -24,6 +23,10 @@ | @@ -24,6 +23,10 @@ | ||
24 | 23 | ||
25 | # FIXED | 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 | - argumentos da linha de comando a funcionar. | 30 | - argumentos da linha de comando a funcionar. |
28 | - configuracao dos logs cherrypy para se darem bem com os outros | 31 | - configuracao dos logs cherrypy para se darem bem com os outros |
29 | - browser e ip usados gravado no test. | 32 | - browser e ip usados gravado no test. |
app.py
@@ -83,7 +83,7 @@ class App(object): | @@ -83,7 +83,7 @@ class App(object): | ||
83 | return False | 83 | return False |
84 | else: | 84 | else: |
85 | logger.info('Student {}: logged out.'.format(uid)) | 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 | return True | 87 | return True |
88 | 88 | ||
89 | def generate_test(self, uid): | 89 | def generate_test(self, uid): |
@@ -180,7 +180,7 @@ class App(object): | @@ -180,7 +180,7 @@ class App(object): | ||
180 | logger.info('Student {}: denied to login'.format(uid)) | 180 | logger.info('Student {}: denied to login'.format(uid)) |
181 | 181 | ||
182 | def reset_password(self, uid): | 182 | def reset_password(self, uid): |
183 | - self.db.reset_password(uid) | 183 | + self.db.update_password(uid, pw='') |
184 | logger.info('Student {}: password reset to ""'.format(uid)) | 184 | logger.info('Student {}: password reset to ""'.format(uid)) |
185 | 185 | ||
186 | def set_user_agent(self, uid, user_agent=''): | 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 | class Database(object): | 10 | class Database(object): |
5 | def __init__(self, db): | 11 | def __init__(self, db): |
6 | self.db = db # sqlite3 filename | 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 | # saves pw as is (should be already hashed) | 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 | def get_student(self, uid): | 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 | def get_all_students(self): | 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 | # get all results for a particular test. If a student has submited more than | 69 | # get all results for a particular test. If a student has submited more than |
41 | # one test, returns the highest grade FIXME not tested, not used | 70 | # one test, returns the highest grade FIXME not tested, not used |
@@ -44,30 +73,64 @@ class Database(object): | @@ -44,30 +73,64 @@ class Database(object): | ||
44 | # grades = c.execute('SELECT student_id,MAX(grade) FROM tests WHERE test_id==?', [testid]) | 73 | # grades = c.execute('SELECT student_id,MAX(grade) FROM tests WHERE test_id==?', [testid]) |
45 | # return grades.fetchall() | 74 | # return grades.fetchall() |
46 | 75 | ||
76 | + #------------------------------------------------------------------------- | ||
47 | # get results from previous tests of a student | 77 | # get results from previous tests of a student |
48 | def get_student_grades_from_all_tests(self, uid): | 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 | def get_student_grades_from_test(self, uid, testid): | 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 @@ | @@ -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 @@ | @@ -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,7 +6,6 @@ from os import path | ||
6 | import sys | 6 | import sys |
7 | import argparse | 7 | import argparse |
8 | import logging.config | 8 | import logging.config |
9 | -import html | ||
10 | import json | 9 | import json |
11 | 10 | ||
12 | try: | 11 | try: |
@@ -241,9 +240,6 @@ if __name__ == '__main__': | @@ -241,9 +240,6 @@ if __name__ == '__main__': | ||
241 | # --- Setup logging | 240 | # --- Setup logging |
242 | logging.config.dictConfig(load_yaml(LOGGER_CONF)) | 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 | # --- start application | 243 | # --- start application |
248 | from app import App | 244 | from app import App |
249 | 245 |