Commit fac28ea70d8bc4e99b371dc073b7e3c93a990b6b
1 parent
01b86197
Exists in
master
and in
1 other branch
- fixed threading issue with sqlalchemy.
- removed all sqlite3 code. - fixed sorting students in admin. - added logger configuration files.
Showing
12 changed files
with
374 additions
and
443 deletions
Show diff stats
BUGS.md
| 1 | 1 | |
| 2 | 2 | # BUGS |
| 3 | 3 | |
| 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 | 4 | - usar thread.Lock para aceder a variaveis de estado? |
| 6 | 5 | - permitir adicionar imagens nas perguntas. |
| 7 | 6 | - debug mode: log levels not working |
| ... | ... | @@ -23,6 +22,7 @@ |
| 23 | 22 | |
| 24 | 23 | # FIXED |
| 25 | 24 | |
| 25 | +- 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. | |
| 26 | 26 | - sqlalchemy queixa-se de threads. |
| 27 | 27 | - SQLAlchemy em vez da classe database. |
| 28 | 28 | - replace sys.exit calls | ... | ... |
app.py
| 1 | 1 | |
| 2 | 2 | |
| 3 | -import logging | |
| 4 | 3 | from os import path |
| 5 | -import sqlite3 | |
| 4 | +import logging | |
| 6 | 5 | import bcrypt |
| 6 | +from sqlalchemy import create_engine | |
| 7 | +from sqlalchemy.orm import sessionmaker, scoped_session | |
| 8 | +from models import Base, Student, Test, Question | |
| 9 | +from contextlib import contextmanager # to create `with` statement for db sessions | |
| 7 | 10 | |
| 8 | 11 | import test |
| 9 | -import database | |
| 12 | +import threading | |
| 10 | 13 | |
| 11 | 14 | logger = logging.getLogger(__name__) |
| 12 | 15 | |
| ... | ... | @@ -18,64 +21,85 @@ class App(object): |
| 18 | 21 | def __init__(self, filename, conf): |
| 19 | 22 | # online = { |
| 20 | 23 | # uid1: { |
| 21 | - # 'student': {'number': 123, 'name': john}, | |
| 22 | - # 'test': ... | |
| 24 | + # 'student': {'number': 123, 'name': john, ...}, | |
| 25 | + # 'test': {...} | |
| 23 | 26 | # } |
| 24 | 27 | # uid2: {...} |
| 25 | 28 | # } |
| 26 | 29 | logger.info('============= Running perguntations =============') |
| 27 | 30 | self.online = dict() # {uid: {'student':{}}} |
| 28 | 31 | self.allowed = set([]) # '0' is hardcoded to allowed elsewhere |
| 32 | + | |
| 29 | 33 | self.testfactory = test.TestFactory(filename, conf=conf) |
| 30 | - self.db = database.Database(self.testfactory['database']) # FIXME | |
| 34 | + | |
| 35 | + # database | |
| 36 | + engine = create_engine('sqlite:///{}'.format(self.testfactory['database']), echo=False) | |
| 37 | + Base.metadata.create_all(engine) # Criate schema if needed FIXME no student '0' | |
| 38 | + self.Session = scoped_session(sessionmaker(bind=engine)) | |
| 39 | + | |
| 31 | 40 | try: |
| 32 | - n = self.db.get_count_students() | |
| 33 | - except sqlite3.OperationalError as e: | |
| 34 | - logger.critical('Database not usable {}.'.format(self.db.db)) | |
| 41 | + with self.db_session() as s: | |
| 42 | + n = s.query(Student).filter(Student.id != '0').count() | |
| 43 | + except Exception as e: | |
| 44 | + logger.critical('Database not usable {}.'.format(self.testfactory['database'])) | |
| 35 | 45 | raise e |
| 36 | 46 | else: |
| 37 | 47 | logger.info('Database has {} students registered.'.format(n)) |
| 38 | 48 | |
| 39 | 49 | |
| 50 | + # ----------------------------------------------------------------------- | |
| 51 | + # helper to manage db sessions using the `with` statement, for example | |
| 52 | + # with self.db_session() as s: ... | |
| 53 | + @contextmanager | |
| 54 | + def db_session(self): | |
| 55 | + try: | |
| 56 | + yield self.Session() | |
| 57 | + finally: | |
| 58 | + self.Session.remove() | |
| 59 | + | |
| 60 | + # ----------------------------------------------------------------------- | |
| 40 | 61 | def exit(self): |
| 62 | + # FIXME what if there are online students? | |
| 41 | 63 | logger.critical('----------- !!! Server terminated !!! -----------') |
| 42 | 64 | |
| 43 | - | |
| 65 | + # ----------------------------------------------------------------------- | |
| 44 | 66 | def login(self, uid, try_pw): |
| 45 | 67 | if uid not in self.allowed and uid != '0': |
| 46 | 68 | # not allowed |
| 47 | 69 | logger.warning('Student {}: not allowed to login.'.format(uid)) |
| 48 | 70 | return False |
| 49 | 71 | |
| 50 | - student = self.db.get_student(uid) | |
| 51 | - if student is None: | |
| 52 | - # not found | |
| 53 | - logger.warning('Student {}: not found in database.'.format(uid)) | |
| 54 | - return False | |
| 55 | - | |
| 56 | - # uid found in database | |
| 57 | - name, pw = student | |
| 58 | - | |
| 59 | - if pw == '': | |
| 60 | - # update password on first login | |
| 61 | - hashed_pw = bcrypt.hashpw(try_pw.encode('utf-8'), bcrypt.gensalt()) | |
| 62 | - self.db.update_password(uid, hashed_pw) | |
| 63 | - logger.warning('Student {}: first login, password updated.'.format(uid)) | |
| 64 | - elif bcrypt.hashpw(try_pw.encode('utf-8'), pw) != pw: | |
| 65 | - # wrong password | |
| 66 | - logger.info('Student {}: wrong password.'.format(uid)) | |
| 67 | - return False | |
| 72 | + with self.db_session() as s: | |
| 73 | + student = s.query(Student).filter(Student.id == uid).one_or_none() | |
| 74 | + | |
| 75 | + if student is None: | |
| 76 | + # not found | |
| 77 | + logger.warning('Student {}: not found in database.'.format(uid)) | |
| 78 | + return False | |
| 79 | + | |
| 80 | + if student.password == '': | |
| 81 | + # update password on first login | |
| 82 | + hashed_pw = bcrypt.hashpw(try_pw.encode('utf-8'), bcrypt.gensalt()) | |
| 83 | + student.password = hashed_pw | |
| 84 | + s.commit() | |
| 85 | + logger.warning('Student {}: first login, password updated.'.format(uid)) | |
| 86 | + | |
| 87 | + elif bcrypt.hashpw(try_pw.encode('utf-8'), student.password) != student.password: | |
| 88 | + # wrong password | |
| 89 | + logger.info('Student {}: wrong password.'.format(uid)) | |
| 90 | + return False | |
| 91 | + | |
| 92 | + # success | |
| 93 | + self.allowed.discard(uid) | |
| 94 | + if uid in self.online: | |
| 95 | + logger.warning('Student {}: already logged in.'.format(uid)) | |
| 96 | + else: | |
| 97 | + self.online[uid] = {'student': {'name': student.name, 'number': uid}} | |
| 98 | + logger.info('Student {}: logged in.'.format(uid)) | |
| 68 | 99 | |
| 69 | - # success | |
| 70 | - self.allowed.discard(uid) | |
| 71 | - if uid in self.online: | |
| 72 | - logger.warning('Student {}: already logged in.'.format(uid)) | |
| 73 | - else: | |
| 74 | - self.online[uid] = {'student': {'name': name, 'number': uid}} | |
| 75 | - logger.info('Student {}: logged in.'.format(uid)) | |
| 76 | 100 | return True |
| 77 | 101 | |
| 78 | - | |
| 102 | + # ----------------------------------------------------------------------- | |
| 79 | 103 | def logout(self, uid): |
| 80 | 104 | if uid not in self.online: |
| 81 | 105 | # this should never happen |
| ... | ... | @@ -83,9 +107,10 @@ class App(object): |
| 83 | 107 | return False |
| 84 | 108 | else: |
| 85 | 109 | logger.info('Student {}: logged out.'.format(uid)) |
| 86 | - del self.online[uid] # Nao está a gravar o teste como desistencia... | |
| 110 | + del self.online[uid] # FIXME Nao está a gravar o teste como desistencia... | |
| 87 | 111 | return True |
| 88 | 112 | |
| 113 | + # ----------------------------------------------------------------------- | |
| 89 | 114 | def generate_test(self, uid): |
| 90 | 115 | if uid in self.online: |
| 91 | 116 | logger.info('Student {}: generating new test.'.format(uid)) |
| ... | ... | @@ -96,6 +121,7 @@ class App(object): |
| 96 | 121 | logger.error('Student {}: offline, can''t generate test'.format(uid)) |
| 97 | 122 | return None |
| 98 | 123 | |
| 124 | + # ----------------------------------------------------------------------- | |
| 99 | 125 | def correct_test(self, uid, ans): |
| 100 | 126 | t = self.online[uid]['test'] |
| 101 | 127 | t.update_answers(ans) |
| ... | ... | @@ -107,10 +133,25 @@ class App(object): |
| 107 | 133 | fpath = path.abspath(path.join(t['answers_dir'], fname)) |
| 108 | 134 | t.save_json(fpath) |
| 109 | 135 | |
| 110 | - self.db.save_test(t) | |
| 111 | - self.db.save_questions(t) | |
| 136 | + with self.db_session() as s: | |
| 137 | + s.add(Test( | |
| 138 | + ref=t['ref'], | |
| 139 | + grade=t['grade'], | |
| 140 | + starttime=str(t['start_time']), | |
| 141 | + finishtime=str(t['finish_time']), | |
| 142 | + student_id=t['student']['number'])) | |
| 143 | + s.add_all([Question( | |
| 144 | + ref=q['ref'], | |
| 145 | + grade=q['grade'], | |
| 146 | + starttime='', | |
| 147 | + finishtime=str(t['finish_time']), | |
| 148 | + student_id=t['student']['number'], | |
| 149 | + test_id=t['ref']) for q in t['questions'] if 'grade' in q]) | |
| 150 | + s.commit() | |
| 151 | + | |
| 112 | 152 | return grade |
| 113 | 153 | |
| 154 | + # ----------------------------------------------------------------------- | |
| 114 | 155 | |
| 115 | 156 | # --- helpers (getters) |
| 116 | 157 | def get_student_name(self, uid): |
| ... | ... | @@ -120,51 +161,52 @@ class App(object): |
| 120 | 161 | def get_test_qtypes(self, uid): |
| 121 | 162 | return {q['ref']:q['type'] for q in self.online[uid]['test']['questions']} |
| 122 | 163 | def get_student_grades_from_all_tests(self, uid): |
| 123 | - return self.db.get_student_grades_from_all_tests(uid) | |
| 124 | - | |
| 125 | - # def get_student_grades_from_test(self, uid, testid): | |
| 126 | - # return self.db.get_student_grades_from_test(uid, testid) | |
| 127 | - | |
| 128 | - | |
| 129 | - # def get_online_students(self): | |
| 130 | - # # list of ('uid', 'name', 'start_time') sorted by start time | |
| 131 | - # return sorted( | |
| 132 | - # ((k, v['student']['name'], str(v.get('test', {}).get('start_time', '---'))) for k,v in self.online.items() if k != '0'), | |
| 133 | - # key=lambda k: k[2] # sort key | |
| 134 | - # ) | |
| 164 | + with self.db_session() as s: | |
| 165 | + r = s.query(Test).filter(Student.id == uid).all() | |
| 166 | + return [(t.id, t.grade, t.finishtime) for t in r] | |
| 135 | 167 | |
| 136 | 168 | def get_online_students(self): |
| 137 | - # {'123': '2016-12-02 12:04:12.344243', ...} | |
| 169 | + # [('uid', 'name', 'starttime')] | |
| 138 | 170 | return [(k, v['student']['name'], str(v.get('test', {}).get('start_time', '---'))) for k,v in self.online.items() if k != '0'] |
| 139 | 171 | |
| 140 | 172 | def get_offline_students(self): |
| 141 | - # list of ('uid', 'name') sorted by number | |
| 142 | - return sorted((s[:2] for s in self.db.get_all_students() if s[0] not in self.online), key=lambda k: k[0]) | |
| 173 | + # list of ('uid', 'name') sorted by uid | |
| 174 | + return [u[:2] for u in self.get_all_students() if u[0] not in self.online] | |
| 143 | 175 | |
| 144 | 176 | def get_all_students(self): |
| 145 | - # list of ('uid', 'name') sorted by number | |
| 146 | - return sorted((s[:2] for s in self.db.get_all_students() if s[0] != '0'), key=lambda k: k[0]) | |
| 177 | + # list of all ('uid', 'name', 'password') sorted by uid | |
| 178 | + with self.db_session() as s: | |
| 179 | + r = s.query(Student).all() | |
| 180 | + return sorted(((u.id, u.name, u.password) for u in r if u.id != '0'), key=lambda k: k[0]) | |
| 181 | + | |
| 182 | + def get_student_grades_from_test(self, uid, testid): | |
| 183 | + with self.db_session() as s: | |
| 184 | + r = s.query(Test).filter(Test.student_id==uid and Test.id==testid).all() | |
| 185 | + return [(u.grade, u.finishtime) for u in r] | |
| 147 | 186 | |
| 148 | 187 | def get_students_state(self): |
| 149 | - # {'123': {'name': 'John', 'start_time':'', 'grades':[10.2, 13.1], ...}} | |
| 150 | - d = {} | |
| 151 | - for s in self.db.get_all_students(): | |
| 152 | - uid, name, pw = s | |
| 153 | - if uid == '0': | |
| 154 | - continue | |
| 155 | - d[uid] = {'name': name} | |
| 156 | - d[uid]['allowed'] = uid in self.allowed | |
| 157 | - d[uid]['online'] = uid in self.online | |
| 158 | - d[uid]['start_time'] = self.online.get(uid, {}).get('test', {}).get('start_time','') | |
| 159 | - d[uid]['password_defined'] = pw != '' | |
| 160 | - d[uid]['grades'] = self.db.get_student_grades_from_test(uid, self.testfactory['ref']) | |
| 161 | - d[uid]['ip_address'] = self.online.get(uid, {}).get('student', {}).get('ip_address','') | |
| 162 | - d[uid]['user_agent'] = self.online.get(uid, {}).get('student', {}).get('user_agent','') | |
| 163 | - return d | |
| 164 | - | |
| 165 | - # def get_this_students_grades(self): | |
| 166 | - # # list of ('uid', 'name') sorted by number | |
| 167 | - # return self.db.get_students_grades(self.testfactory['ref']) | |
| 188 | + # [{ | |
| 189 | + # 'uid' : '12345' | |
| 190 | + # 'name' : 'John Smith', | |
| 191 | + # 'start_time': '', | |
| 192 | + # 'grades' : [10.2, 13.1], | |
| 193 | + # ... | |
| 194 | + # }] | |
| 195 | + l = [] | |
| 196 | + for u in self.get_all_students(): | |
| 197 | + uid, name, pw = u | |
| 198 | + l.append({ | |
| 199 | + 'uid': uid, | |
| 200 | + 'name': name, | |
| 201 | + 'allowed': uid in self.allowed, | |
| 202 | + 'online': uid in self.online, | |
| 203 | + 'start_time': self.online.get(uid, {}).get('test', {}).get('start_time',''), | |
| 204 | + 'password_defined': pw != '', | |
| 205 | + 'grades': self.get_student_grades_from_test(uid, self.testfactory['ref']), | |
| 206 | + 'ip_address': self.online.get(uid, {}).get('student', {}).get('ip_address',''), | |
| 207 | + 'user_agent': self.online.get(uid, {}).get('student', {}).get('user_agent','') | |
| 208 | + }) | |
| 209 | + return l | |
| 168 | 210 | |
| 169 | 211 | def get_allowed_students(self): |
| 170 | 212 | # set of 'uid' allowed to login |
| ... | ... | @@ -180,7 +222,9 @@ class App(object): |
| 180 | 222 | logger.info('Student {}: denied to login'.format(uid)) |
| 181 | 223 | |
| 182 | 224 | def reset_password(self, uid): |
| 183 | - self.db.update_password(uid, pw='') | |
| 225 | + with self.db_session() as s: | |
| 226 | + u = s.query(Student).filter(Student.id == uid).update({'password': ''}) | |
| 227 | + s.commit() | |
| 184 | 228 | logger.info('Student {}: password reset to ""'.format(uid)) |
| 185 | 229 | |
| 186 | 230 | def set_user_agent(self, uid, user_agent=''): | ... | ... |
| ... | ... | @@ -0,0 +1,75 @@ |
| 1 | + | |
| 2 | +version: 1 | |
| 3 | + | |
| 4 | +formatters: | |
| 5 | + void: | |
| 6 | + format: '' | |
| 7 | + standard: | |
| 8 | + format: '%(asctime)s | %(levelname)-8s | %(name)-14s | %(message)s' | |
| 9 | + | |
| 10 | +handlers: | |
| 11 | + default: | |
| 12 | + level: 'INFO' | |
| 13 | + class: 'logging.StreamHandler' | |
| 14 | + formatter: 'standard' | |
| 15 | + stream: 'ext://sys.stdout' | |
| 16 | + | |
| 17 | + cherrypy_console: | |
| 18 | + level: 'INFO' | |
| 19 | + class: 'logging.StreamHandler' | |
| 20 | + formatter: 'standard' | |
| 21 | + stream: 'ext://sys.stdout' | |
| 22 | + | |
| 23 | + cherrypy_access: | |
| 24 | + level: 'INFO' | |
| 25 | + class: 'logging.handlers.RotatingFileHandler' | |
| 26 | + formatter: 'void' | |
| 27 | + filename: 'logs/access.log' | |
| 28 | + maxBytes: 10485760 | |
| 29 | + backupCount: 20 | |
| 30 | + encoding: 'utf8' | |
| 31 | + | |
| 32 | + cherrypy_error: | |
| 33 | + level: 'INFO' | |
| 34 | + class: 'logging.handlers.RotatingFileHandler' | |
| 35 | + formatter: 'void' | |
| 36 | + filename: 'logs/errors.log' | |
| 37 | + maxBytes: 10485760 | |
| 38 | + backupCount: 20 | |
| 39 | + encoding: 'utf8' | |
| 40 | + | |
| 41 | +loggers: | |
| 42 | + '': | |
| 43 | + handlers: ['default'] | |
| 44 | + level: 'DEBUG' | |
| 45 | + | |
| 46 | + 'cherrypy.access': | |
| 47 | + handlers: ['cherrypy_access'] | |
| 48 | + level: 'DEBUG' | |
| 49 | + propagate: False | |
| 50 | + | |
| 51 | + 'cherrypy.error': | |
| 52 | + handlers: ['cherrypy_console', 'cherrypy_error'] | |
| 53 | + level: 'DEBUG' | |
| 54 | + propagate: False | |
| 55 | + | |
| 56 | + 'app': | |
| 57 | + handlers: ['default'] | |
| 58 | + level: 'DEBUG' | |
| 59 | + propagate: False | |
| 60 | + | |
| 61 | + 'test': | |
| 62 | + handlers: ['default'] | |
| 63 | + level: 'DEBUG' | |
| 64 | + propagate: False | |
| 65 | + | |
| 66 | + 'questions': | |
| 67 | + handlers: ['default'] | |
| 68 | + level: 'DEBUG' | |
| 69 | + propagate: False | |
| 70 | + | |
| 71 | + 'tools': | |
| 72 | + handlers: ['default'] | |
| 73 | + level: 'DEBUG' | |
| 74 | + propagate: False | |
| 75 | + | ... | ... |
| ... | ... | @@ -0,0 +1,75 @@ |
| 1 | + | |
| 2 | +version: 1 | |
| 3 | + | |
| 4 | +formatters: | |
| 5 | + void: | |
| 6 | + format: '' | |
| 7 | + standard: | |
| 8 | + format: '%(asctime)s | %(levelname)-8s | %(name)-14s | %(message)s' | |
| 9 | + | |
| 10 | +handlers: | |
| 11 | + default: | |
| 12 | + level: 'INFO' | |
| 13 | + class: 'logging.StreamHandler' | |
| 14 | + formatter: 'standard' | |
| 15 | + stream: 'ext://sys.stdout' | |
| 16 | + | |
| 17 | + cherrypy_console: | |
| 18 | + level: 'INFO' | |
| 19 | + class: 'logging.StreamHandler' | |
| 20 | + formatter: 'standard' | |
| 21 | + stream: 'ext://sys.stdout' | |
| 22 | + | |
| 23 | + cherrypy_access: | |
| 24 | + level: 'INFO' | |
| 25 | + class: 'logging.handlers.RotatingFileHandler' | |
| 26 | + formatter: 'void' | |
| 27 | + filename: 'logs/access.log' | |
| 28 | + maxBytes: 10485760 | |
| 29 | + backupCount: 20 | |
| 30 | + encoding: 'utf8' | |
| 31 | + | |
| 32 | + cherrypy_error: | |
| 33 | + level: 'INFO' | |
| 34 | + class: 'logging.handlers.RotatingFileHandler' | |
| 35 | + formatter: 'void' | |
| 36 | + filename: 'logs/errors.log' | |
| 37 | + maxBytes: 10485760 | |
| 38 | + backupCount: 20 | |
| 39 | + encoding: 'utf8' | |
| 40 | + | |
| 41 | +loggers: | |
| 42 | + '': | |
| 43 | + handlers: ['default'] | |
| 44 | + level: 'INFO' | |
| 45 | + | |
| 46 | + 'cherrypy.access': | |
| 47 | + handlers: ['cherrypy_access'] | |
| 48 | + level: 'INFO' | |
| 49 | + propagate: False | |
| 50 | + | |
| 51 | + 'cherrypy.error': | |
| 52 | + handlers: ['cherrypy_console', 'cherrypy_error'] | |
| 53 | + level: 'INFO' | |
| 54 | + propagate: False | |
| 55 | + | |
| 56 | + 'app': | |
| 57 | + handlers: ['default'] | |
| 58 | + level: 'INFO' | |
| 59 | + propagate: False | |
| 60 | + | |
| 61 | + 'test': | |
| 62 | + handlers: ['default'] | |
| 63 | + level: 'INFO' | |
| 64 | + propagate: False | |
| 65 | + | |
| 66 | + 'questions': | |
| 67 | + handlers: ['default'] | |
| 68 | + level: 'INFO' | |
| 69 | + propagate: False | |
| 70 | + | |
| 71 | + 'tools': | |
| 72 | + handlers: ['default'] | |
| 73 | + level: 'INFO' | |
| 74 | + propagate: False | |
| 75 | + | ... | ... |
database.py
| ... | ... | @@ -1,181 +0,0 @@ |
| 1 | - | |
| 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 | |
| 6 | - | |
| 7 | -logger = logging.getLogger(__name__) | |
| 8 | - | |
| 9 | -#---------------------------------------------------------------------------- | |
| 10 | -class Database(object): | |
| 11 | - def __init__(self, db): | |
| 12 | - self.db = db # sqlite3 filename | |
| 13 | - | |
| 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)) | |
| 17 | - | |
| 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() | |
| 37 | - | |
| 38 | - # saves pw as is (should be already hashed) | |
| 39 | - # with sqlite3.connect(self.db) as c: | |
| 40 | - # sql = 'UPDATE students SET password=? WHERE id=?' | |
| 41 | - # c.execute(sql, (pw, uid)) | |
| 42 | - | |
| 43 | - #------------------------------------------------------------------------- | |
| 44 | - def get_student(self, uid): | |
| 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 | - #------------------------------------------------------------------------- | |
| 59 | - def get_all_students(self): | |
| 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 | |
| 68 | - | |
| 69 | - # get all results for a particular test. If a student has submited more than | |
| 70 | - # one test, returns the highest grade FIXME not tested, not used | |
| 71 | - # def get_students_grades(self, testid): | |
| 72 | - # with sqlite3.connect(self.db) as c: | |
| 73 | - # grades = c.execute('SELECT student_id,MAX(grade) FROM tests WHERE test_id==?', [testid]) | |
| 74 | - # return grades.fetchall() | |
| 75 | - | |
| 76 | - #------------------------------------------------------------------------- | |
| 77 | - # get results from previous tests of a student | |
| 78 | - def get_student_grades_from_all_tests(self, uid): | |
| 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() | |
| 86 | - | |
| 87 | - #------------------------------------------------------------------------- | |
| 88 | - def get_student_grades_from_test(self, uid, testid): | |
| 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) | |
| 134 | - | |
| 135 | - | |
| 136 | - | |
| 137 | - | |
| 138 | - # def insert_student(self, number, name, password=''): # FIXME testar... | |
| 139 | - # with sqlite3.connect(self.db) as c: | |
| 140 | - # if password != '': | |
| 141 | - # password = sha256(password.encode('utf-8')).hexdigest() # FIXME bcrypt | |
| 142 | - # cmd = 'INSERT INTO students VALUES (?, ?, ?);' | |
| 143 | - # c.execute(cmd, number, name, password) | |
| 144 | - | |
| 145 | - | |
| 146 | - | |
| 147 | - | |
| 148 | - # # return list of students and their results for a given test | |
| 149 | - # def get_test_grades(self, test_id): | |
| 150 | - # with sqlite3.connect(self.db) as c: | |
| 151 | - # # with all tests done by each student: | |
| 152 | - # # cmd = 'SELECT student_id,name,grade FROM students INNER JOIN tests ON students.number=tests.student_id WHERE test_id==? ORDER BY grade DESC;' | |
| 153 | - | |
| 154 | - # # only the best result for each student | |
| 155 | - # cmd = ''' | |
| 156 | - # SELECT student_id, name, MAX(grade), finish_time | |
| 157 | - # FROM students INNER JOIN tests | |
| 158 | - # ON students.number=tests.student_id | |
| 159 | - # WHERE test_id==? AND student_id!=0 | |
| 160 | - # GROUP BY student_id | |
| 161 | - # ORDER BY grade DESC, finish_time DESC;''' | |
| 162 | - # return c.execute(cmd, [test_id]).fetchall() | |
| 163 | - | |
| 164 | - # # return list of students and their results for a given test | |
| 165 | - # def test_grades2(self, test_id): | |
| 166 | - # with sqlite3.connect(self.db) as c: | |
| 167 | - # # with all tests done by each student: | |
| 168 | - # # cmd = 'SELECT student_id,name,grade FROM students INNER JOIN tests ON students.number=tests.student_id WHERE test_id==? ORDER BY grade DESC;' | |
| 169 | - | |
| 170 | - # # only the best result for each student | |
| 171 | - # cmd = ''' | |
| 172 | - # SELECT student_id, name, grade, start_time, finish_time | |
| 173 | - # FROM students INNER JOIN tests | |
| 174 | - # ON students.number=tests.student_id | |
| 175 | - # WHERE test_id==? | |
| 176 | - # ORDER BY finish_time ASC;''' | |
| 177 | - # return c.execute(cmd, [test_id]).fetchall() | |
| 178 | - | |
| 179 | - | |
| 180 | - # the following methods update de database data | |
| 181 | - |
initdb.py
| ... | ... | @@ -9,7 +9,7 @@ import sys |
| 9 | 9 | from sqlalchemy import create_engine |
| 10 | 10 | from sqlalchemy.orm import sessionmaker |
| 11 | 11 | |
| 12 | -from orm import Base, Student, Test, Question | |
| 12 | +from models import Base, Student, Test, Question | |
| 13 | 13 | |
| 14 | 14 | # SIIUE names have alien strings like "(TE)" and are sometimes capitalized |
| 15 | 15 | # We remove them so that students dont keep asking what it means |
| ... | ... | @@ -55,8 +55,14 @@ try: |
| 55 | 55 | session.add_all([Student(id=r['N.º'], name=fix(r['Nome']), password='') for r in csvreader]) |
| 56 | 56 | |
| 57 | 57 | session.commit() |
| 58 | + | |
| 58 | 59 | except Exception: |
| 59 | 60 | session.rollback() |
| 60 | 61 | print('Erro: Dados já existentes na base de dados?') |
| 61 | 62 | sys.exit(1) |
| 63 | + | |
| 64 | +else: | |
| 65 | + n = session.query(Student).count() | |
| 66 | + print('Base de dados inicializada com {} utilizadores.'.format(n)) | |
| 67 | + | |
| 62 | 68 | # --- end session --- | ... | ... |
initdb_from_csv.py
| ... | ... | @@ -1,95 +0,0 @@ |
| 1 | -#!/usr/bin/env python3 | |
| 2 | -# -*- coding: utf-8 -*- | |
| 3 | - | |
| 4 | -import sys | |
| 5 | -import sqlite3 | |
| 6 | -import csv | |
| 7 | -import argparse | |
| 8 | -import bcrypt | |
| 9 | -import os | |
| 10 | -import string | |
| 11 | -import re | |
| 12 | - | |
| 13 | -# SIIUE names have alien strings like "(TE)" and are sometimes capitalized | |
| 14 | -# We remove them so that students dont keep asking what it means | |
| 15 | -def fixname(s): | |
| 16 | - return string.capwords(re.sub('\(.*\)', '', s).strip()) | |
| 17 | - | |
| 18 | -def genstudent(reader, pw=''): | |
| 19 | - for i, r in enumerate(reader): | |
| 20 | - num = r['N.º'] | |
| 21 | - name = fixname(r['Nome']) | |
| 22 | - yield (r['N.º'], fixname(r['Nome']), '') | |
| 23 | - print('{} students inserted.'.format(i+1)) | |
| 24 | - | |
| 25 | -# ---- DATABASE SCHEMA ---- | |
| 26 | -sql_cmd = '''PRAGMA foreign_keys = ON; | |
| 27 | - CREATE TABLE students ( | |
| 28 | - number TEXT PRIMARY KEY, | |
| 29 | - name TEXT, | |
| 30 | - password TEXT | |
| 31 | - ); | |
| 32 | - CREATE TABLE tests ( | |
| 33 | - test_id TEXT NOT NULL, | |
| 34 | - student_id TEXT NOT NULL, | |
| 35 | - grade REAL, | |
| 36 | - start_time TEXT, | |
| 37 | - finish_time TEXT, | |
| 38 | - FOREIGN KEY(student_id) REFERENCES students(number) | |
| 39 | - ); | |
| 40 | - CREATE TABLE questions ( | |
| 41 | - test_id TEXT NOT NULL, | |
| 42 | - question_id TEXT NOT NULL, | |
| 43 | - student_id TEXT NOT NULL, | |
| 44 | - grade REAL, | |
| 45 | - time TEXT, | |
| 46 | - FOREIGN KEY(student_id) REFERENCES students(number) | |
| 47 | - );''' | |
| 48 | - | |
| 49 | -# --------- Parse command line options ----------- | |
| 50 | -argparser = argparse.ArgumentParser(description='Create new database from a CSV file (SIIUE format)') | |
| 51 | -argparser.add_argument('--db', default='students.db', type=str, help='database filename') | |
| 52 | -argparser.add_argument('csvfile', nargs='?', type=str, default='', help='CSV filename') | |
| 53 | -args = argparser.parse_args() | |
| 54 | - | |
| 55 | -db_exists = os.path.exists(args.db) | |
| 56 | - | |
| 57 | -with sqlite3.connect(args.db) as c: | |
| 58 | - # use existing or create new database schema | |
| 59 | - if db_exists: | |
| 60 | - print('-> Using previous database "{}"...'.format(args.db)) | |
| 61 | - else: | |
| 62 | - print('-> Creating new database "{}"...'.format(args.db)) | |
| 63 | - c.executescript(sql_cmd) | |
| 64 | - | |
| 65 | - # get students | |
| 66 | - if args.csvfile: | |
| 67 | - csvfile = open(args.csvfile, encoding='iso-8859-1') | |
| 68 | - print('-> Using students from CSV file "{}"...'.format(args.csvfile)) | |
| 69 | - students = genstudent(csv.DictReader(csvfile, delimiter=';', quotechar='"')) | |
| 70 | - else: | |
| 71 | - print('-> Creating fake students numbered 1 to 5...'.format(args.csvfile)) | |
| 72 | - students = [ | |
| 73 | - ('1', 'Student1', ''), | |
| 74 | - ('2', 'Student2', ''), | |
| 75 | - ('3', 'Student3', ''), | |
| 76 | - ('4', 'Student4', ''), | |
| 77 | - ('5', 'Student5', '') | |
| 78 | - ] | |
| 79 | - | |
| 80 | - # insert students into database | |
| 81 | - print('-> Inserting students into database... ') | |
| 82 | - try: | |
| 83 | - c.executemany('INSERT INTO students VALUES (?,?,?)', students) | |
| 84 | - except sqlite3.IntegrityError: | |
| 85 | - print('** ERROR ** Students already exist. Aborted!') | |
| 86 | - sys.exit(1) | |
| 87 | - | |
| 88 | - # insert professor into database | |
| 89 | - print('-> Inserting professor (id=0)...') | |
| 90 | - try: | |
| 91 | - c.execute('INSERT INTO students VALUES (?,?,?)', ('0', 'Professor', '')) | |
| 92 | - except sqlite3.IntegrityError: | |
| 93 | - print('** WARNING ** Professor already exists.') | |
| 94 | - | |
| 95 | -print('Done.') |
| ... | ... | @@ -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 | +# --------------------------------------------------------------------------- | ... | ... |
orm.py
| ... | ... | @@ -1,73 +0,0 @@ |
| 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
| ... | ... | @@ -83,7 +83,7 @@ class AdminWebService(object): |
| 83 | 83 | @cherrypy.tools.accept(media='application/json') # FIXME |
| 84 | 84 | def GET(self): |
| 85 | 85 | data = { |
| 86 | - 'students': list(self.app.get_students_state().items()), | |
| 86 | + 'students': self.app.get_students_state(), | |
| 87 | 87 | 'test': self.app.testfactory |
| 88 | 88 | } |
| 89 | 89 | return json.dumps(data, default=str) |
| ... | ... | @@ -246,7 +246,7 @@ if __name__ == '__main__': |
| 246 | 246 | try: |
| 247 | 247 | app = App(filename, vars(arg)) |
| 248 | 248 | except Exception as e: |
| 249 | - logging.critical('Cannot start application.') | |
| 249 | + logging.critical('Can\'t start application.') | |
| 250 | 250 | raise e # FIXME just for testing |
| 251 | 251 | sys.exit(1) |
| 252 | 252 | ... | ... |
static/js/admin.js
| ... | ... | @@ -28,6 +28,7 @@ $(document).ready(function() { |
| 28 | 28 | ); |
| 29 | 29 | } |
| 30 | 30 | |
| 31 | + // ---------------------------------------------------------------------- | |
| 31 | 32 | // checkbox handler to allow/deny students individually |
| 32 | 33 | function autorizeStudent(e) { |
| 33 | 34 | $.ajax({ |
| ... | ... | @@ -41,12 +42,14 @@ $(document).ready(function() { |
| 41 | 42 | $(this).parent().parent().removeClass("active"); |
| 42 | 43 | } |
| 43 | 44 | |
| 45 | + // ---------------------------------------------------------------------- | |
| 44 | 46 | function populateOnlineTable(students) { |
| 45 | - var active = []; | |
| 46 | 47 | var rows = ""; |
| 48 | + // make list of online students | |
| 49 | + var active = []; | |
| 47 | 50 | $.each(students, function(i, r) { |
| 48 | - if (r[1]['start_time'] != '') { | |
| 49 | - active.push([r[0], r[1]['name'], r[1]['start_time'], r[1]['ip_address'], r[1]['user_agent']]); | |
| 51 | + if (r['start_time'] != '') { | |
| 52 | + active.push([r['uid'], r['name'], r['start_time'], r['ip_address'], r['user_agent']]); | |
| 50 | 53 | } |
| 51 | 54 | }); |
| 52 | 55 | // sort by start time |
| ... | ... | @@ -65,6 +68,7 @@ $(document).ready(function() { |
| 65 | 68 | $("#online-header").html(n + " Activo(s)"); |
| 66 | 69 | } |
| 67 | 70 | |
| 71 | + // ---------------------------------------------------------------------- | |
| 68 | 72 | function generate_grade_bar(grade) { |
| 69 | 73 | var barcolor; |
| 70 | 74 | if (grade < 10) { |
| ... | ... | @@ -81,13 +85,13 @@ $(document).ready(function() { |
| 81 | 85 | return bar |
| 82 | 86 | } |
| 83 | 87 | |
| 88 | + // ---------------------------------------------------------------------- | |
| 84 | 89 | function populateStudentsTable(students) { |
| 85 | - $("#students-header").html(students.length + " Alunos") | |
| 90 | + var n = students.length; | |
| 91 | + $("#students-header").html(n + " Alunos") | |
| 86 | 92 | var rows = ""; |
| 87 | - students.sort(function(a,b){return a[0] - b[0]}); | |
| 88 | - $.each(students, function(i, r) { | |
| 89 | - var uid = r[0]; | |
| 90 | - var d = r[1]; // dictionary | |
| 93 | + $.each(students, function(i, d) { | |
| 94 | + var uid = d['uid']; | |
| 91 | 95 | |
| 92 | 96 | if (d['start_time'] != '') // test |
| 93 | 97 | rows += '<tr id="' + uid + '" + class="success">'; |
| ... | ... | @@ -119,15 +123,20 @@ $(document).ready(function() { |
| 119 | 123 | $('[data-toggle="tooltip"]').tooltip(); |
| 120 | 124 | } |
| 121 | 125 | |
| 126 | + // ---------------------------------------------------------------------- | |
| 122 | 127 | function populate() { |
| 123 | 128 | $.ajax({ |
| 124 | 129 | url: "/adminwebservice", |
| 125 | 130 | dataType: "json", |
| 126 | 131 | success: function(data) { |
| 132 | + // show clock on upper left corner | |
| 127 | 133 | var t = new Date(); |
| 128 | 134 | $('#currenttime').html(t.getHours() + (t.getMinutes() < 10 ? ':0' : ':') + t.getMinutes()); |
| 135 | + | |
| 136 | + // fill jumbotron data | |
| 129 | 137 | $("#title").html(data['test']['title']); |
| 130 | 138 | $("#ref").html(data['test']['ref']); |
| 139 | + $("#filename").html(data['test']['filename']); | |
| 131 | 140 | $("#database").html(data['test']['database']); |
| 132 | 141 | if (data['test']['save_answers']) { |
| 133 | 142 | $("#answers_dir").html(data['test']['answers_dir']); |
| ... | ... | @@ -135,8 +144,8 @@ $(document).ready(function() { |
| 135 | 144 | else { |
| 136 | 145 | $("#answers_dir").html('--- not being saved ---'); |
| 137 | 146 | } |
| 138 | - $("#filename").html(data['test']['filename']); | |
| 139 | 147 | |
| 148 | + // fill online and student tables | |
| 140 | 149 | populateOnlineTable(data["students"]); |
| 141 | 150 | populateStudentsTable(data["students"]) |
| 142 | 151 | ... | ... |
test.py
| ... | ... | @@ -3,14 +3,13 @@ from os import path, listdir |
| 3 | 3 | import sys, fnmatch |
| 4 | 4 | import random |
| 5 | 5 | from datetime import datetime |
| 6 | -import sqlite3 | |
| 7 | 6 | import logging |
| 8 | 7 | |
| 9 | 8 | # Logger configuration |
| 10 | 9 | logger = logging.getLogger(__name__) |
| 11 | 10 | |
| 12 | 11 | try: |
| 13 | - import yaml | |
| 12 | + # import yaml | |
| 14 | 13 | import json |
| 15 | 14 | except ImportError: |
| 16 | 15 | logger.critical('Python package missing. See README.md for instructions.') |
| ... | ... | @@ -18,7 +17,6 @@ except ImportError: |
| 18 | 17 | |
| 19 | 18 | # my code |
| 20 | 19 | import questions |
| 21 | -import database | |
| 22 | 20 | from tools import load_yaml |
| 23 | 21 | |
| 24 | 22 | # =========================================================================== |
| ... | ... | @@ -100,11 +98,11 @@ class TestFactory(dict): |
| 100 | 98 | self['answers_dir'] = path.abspath(path.expanduser(self['answers_dir'])) |
| 101 | 99 | |
| 102 | 100 | if not path.isfile(self['database']): |
| 103 | - logger.critical('Cannot find database "{}"'.format(self['database'])) | |
| 101 | + logger.critical('Can\'t find database "{}"'.format(self['database'])) | |
| 104 | 102 | raise TestFactoryException() |
| 105 | 103 | |
| 106 | 104 | if not path.isdir(self['questions_dir']): |
| 107 | - logger.critical('Cannot find questions directory "{}"'.format(self['questions_dir'])) | |
| 105 | + logger.critical('Can\'t find questions directory "{}"'.format(self['questions_dir'])) | |
| 108 | 106 | raise TestFactoryException() |
| 109 | 107 | |
| 110 | 108 | # make sure we have a list of question files. | ... | ... |