Commit 981734c09fbc38fd4e4e826596724810b0ba4db4

Authored by Miguel Barão
1 parent 7c49db42
Exists in master and in 1 other branch dev

irrelevant, for testing

1 [mypy] 1 [mypy]
2 python_version = 3.8 2 python_version = 3.8
3 -# warn_return_any = True  
4 -# warn_unused_configs = True  
5 3
6 [mypy-setuptools.*] 4 [mypy-setuptools.*]
7 ignore_missing_imports = True 5 ignore_missing_imports = True
perguntations/app.py
@@ -6,7 +6,6 @@ Description: Main application logic. @@ -6,7 +6,6 @@ Description: Main application logic.
6 6
7 # python standard libraries 7 # python standard libraries
8 import asyncio 8 import asyncio
9 -# from contextlib import contextmanager # `with` statement in db sessions  
10 import csv 9 import csv
11 import io 10 import io
12 import json 11 import json
@@ -17,8 +16,7 @@ from os import path @@ -17,8 +16,7 @@ from os import path
17 import bcrypt 16 import bcrypt
18 from sqlalchemy import create_engine, select, func 17 from sqlalchemy import create_engine, select, func
19 from sqlalchemy.orm import Session 18 from sqlalchemy.orm import Session
20 -from sqlalchemy.exc import NoResultFound  
21 -# from sqlalchemy.orm import sessionmaker 19 +# from sqlalchemy.exc import NoResultFound
22 20
23 # this project 21 # this project
24 from perguntations.models import Student, Test, Question 22 from perguntations.models import Student, Test, Question
@@ -113,8 +111,6 @@ class App(): @@ -113,8 +111,6 @@ class App():
113 logger.info('Generating %d tests. May take awhile...', 111 logger.info('Generating %d tests. May take awhile...',
114 len(self.allowed)) 112 len(self.allowed))
115 self._pregenerate_tests(len(self.allowed)) 113 self._pregenerate_tests(len(self.allowed))
116 - else:  
117 - logger.info('No tests generated yet.')  
118 114
119 if conf['correct']: 115 if conf['correct']:
120 self._correct_tests() 116 self._correct_tests()
@@ -139,94 +135,91 @@ class App(): @@ -139,94 +135,91 @@ class App():
139 135
140 logger.info('Database "%s" has %s students.', dbfile, num) 136 logger.info('Database "%s" has %s students.', dbfile, num)
141 137
142 - # ------------------------------------------------------------------------  
143 - def _correct_tests(self):  
144 - with Session(self._engine, future=True) as session:  
145 - # Find which tests have to be corrected  
146 - dbtests = session.execute(  
147 - select(Test).  
148 - where(Test.ref == self.testfactory['ref']).  
149 - where(Test.state == "SUBMITTED")  
150 - ).all()  
151 - # dbtests = session.query(Test)\  
152 - # .filter(Test.ref == self.testfactory['ref'])\  
153 - # .filter(Test.state == "SUBMITTED")\  
154 - # .all()  
155 -  
156 - logger.info('Correcting %d tests...', len(dbtests))  
157 - for dbtest in dbtests:  
158 - try:  
159 - with open(dbtest.filename) as file:  
160 - testdict = json.load(file)  
161 - except FileNotFoundError:  
162 - logger.error('File not found: %s', dbtest.filename)  
163 - continue  
164 -  
165 - # creates a class Test with the methods to correct it  
166 - # the questions are still dictionaries, so we have to call  
167 - # question_from() to produce Question() instances that can be  
168 - # corrected. Finally the test can be corrected.  
169 - test = perguntations.test.Test(testdict)  
170 - test['questions'] = [question_from(q) for q in test['questions']]  
171 - test.correct()  
172 - logger.info('Student %s: grade = %f', test['student']['number'], test['grade'])  
173 -  
174 - # save JSON file (overwriting the old one)  
175 - uid = test['student']['number']  
176 - ref = test['ref']  
177 - finish_time = test['finish_time']  
178 - answers_dir = test['answers_dir']  
179 - fname = f'{uid}--{ref}--{finish_time}.json'  
180 - fpath = path.join(answers_dir, fname)  
181 - test.save_json(fpath)  
182 - logger.info('%s saved JSON file.', uid)  
183 -  
184 - # update database  
185 - dbtest.grade = test['grade']  
186 - dbtest.state = test['state']  
187 - dbtest.questions = [  
188 - Question(  
189 - number=n,  
190 - ref=q['ref'],  
191 - grade=q['grade'],  
192 - comment=q.get('comment', ''),  
193 - starttime=str(test['start_time']),  
194 - finishtime=str(test['finish_time']),  
195 - test_id=test['ref']  
196 - )  
197 - for n, q in enumerate(test['questions'])  
198 - ]  
199 - logger.info('%s database updated.', uid) 138 +# # ------------------------------------------------------------------------
  139 +# # FIXME not working
  140 +# def _correct_tests(self):
  141 +# with Session(self._engine, future=True) as session:
  142 +# # Find which tests have to be corrected
  143 +# dbtests = session.execute(
  144 +# select(Test).
  145 +# where(Test.ref == self.testfactory['ref']).
  146 +# where(Test.state == "SUBMITTED")
  147 +# ).all()
  148 +# # dbtests = session.query(Test)\
  149 +# # .filter(Test.ref == self.testfactory['ref'])\
  150 +# # .filter(Test.state == "SUBMITTED")\
  151 +# # .all()
  152 +
  153 +# logger.info('Correcting %d tests...', len(dbtests))
  154 +# for dbtest in dbtests:
  155 +# try:
  156 +# with open(dbtest.filename) as file:
  157 +# testdict = json.load(file)
  158 +# except FileNotFoundError:
  159 +# logger.error('File not found: %s', dbtest.filename)
  160 +# continue
  161 +
  162 +# # creates a class Test with the methods to correct it
  163 +# # the questions are still dictionaries, so we have to call
  164 +# # question_from() to produce Question() instances that can be
  165 +# # corrected. Finally the test can be corrected.
  166 +# test = perguntations.test.Test(testdict)
  167 +# test['questions'] = [question_from(q) for q in test['questions']]
  168 +# test.correct()
  169 +# logger.info('Student %s: grade = %f', test['student']['number'], test['grade'])
  170 +
  171 +# # save JSON file (overwriting the old one)
  172 +# uid = test['student']['number']
  173 +# ref = test['ref']
  174 +# finish_time = test['finish_time']
  175 +# answers_dir = test['answers_dir']
  176 +# fname = f'{uid}--{ref}--{finish_time}.json'
  177 +# fpath = path.join(answers_dir, fname)
  178 +# test.save_json(fpath)
  179 +# logger.info('%s saved JSON file.', uid)
  180 +
  181 +# # update database
  182 +# dbtest.grade = test['grade']
  183 +# dbtest.state = test['state']
  184 +# dbtest.questions = [
  185 +# Question(
  186 +# number=n,
  187 +# ref=q['ref'],
  188 +# grade=q['grade'],
  189 +# comment=q.get('comment', ''),
  190 +# starttime=str(test['start_time']),
  191 +# finishtime=str(test['finish_time']),
  192 +# test_id=test['ref']
  193 +# )
  194 +# for n, q in enumerate(test['questions'])
  195 +# ]
  196 +# logger.info('%s database updated.', uid)
200 197
201 # ------------------------------------------------------------------------ 198 # ------------------------------------------------------------------------
202 - async def login(self, uid, try_pw, headers=None): 199 + async def login(self, uid, password, headers=None):
203 '''login authentication''' 200 '''login authentication'''
204 - if uid not in self.allowed and uid != '0': # not allowed 201 + if uid != '0' and uid not in self.allowed: # not allowed
205 logger.warning('"%s" unauthorized.', uid) 202 logger.warning('"%s" unauthorized.', uid)
206 return 'unauthorized' 203 return 'unauthorized'
207 204
208 with Session(self._engine, future=True) as session: 205 with Session(self._engine, future=True) as session:
209 - # with self._db_session() as sess:  
210 - name, hashed_pw = session.execute( 206 + name, hashed = session.execute(
211 select(Student.name, Student.password). 207 select(Student.name, Student.password).
212 - where(id == uid) 208 + where(Student.id == uid)
213 ).one() 209 ).one()
214 - # name, hashed_pw = sess.query(Student.name, Student.password)\  
215 - # .filter_by(id=uid)\  
216 - # .one()  
217 210
218 - if hashed_pw == '': # update password on first login  
219 - await self.update_student_password(uid, try_pw)  
220 - pw_ok = True 211 + if hashed == '': # update password on first login
  212 + logger.info('First login "%s"', name)
  213 + await self.update_password(uid, password)
  214 + ok = True
221 else: # check password 215 else: # check password
222 loop = asyncio.get_running_loop() 216 loop = asyncio.get_running_loop()
223 - pw_ok = await loop.run_in_executor(None,  
224 - bcrypt.checkpw,  
225 - try_pw.encode('utf-8'),  
226 - hashed_pw.password)  
227 - # pw_ok = await check_password(try_pw, hashed_pw) # async bcrypt 217 + ok = await loop.run_in_executor(None,
  218 + bcrypt.checkpw,
  219 + password.encode('utf-8'),
  220 + hashed)
228 221
229 - if not pw_ok: # wrong password 222 + if not ok:
230 logger.info('"%s" wrong password.', uid) 223 logger.info('"%s" wrong password.', uid)
231 return 'wrong_password' 224 return 'wrong_password'
232 225
@@ -238,6 +231,7 @@ class App(): @@ -238,6 +231,7 @@ class App():
238 uid, headers['remote_ip']) 231 uid, headers['remote_ip'])
239 # FIXME invalidate previous login 232 # FIXME invalidate previous login
240 else: 233 else:
  234 + # first login
241 self.online[uid] = {'student': { 235 self.online[uid] = {'student': {
242 'name': name, 236 'name': name,
243 'number': uid, 237 'number': uid,
@@ -375,8 +369,9 @@ class App(): @@ -375,8 +369,9 @@ class App():
375 for n, q in enumerate(test['questions']) 369 for n, q in enumerate(test['questions'])
376 ] 370 ]
377 371
378 - with self._db_session() as sess:  
379 - sess.add(test_row) 372 + with Session(self._engine, future=True) as session:
  373 + session.add(test_row)
  374 + session.commit()
380 logger.info('"%s" database updated.', uid) 375 logger.info('"%s" database updated.', uid)
381 376
382 # ------------------------------------------------------------------------ 377 # ------------------------------------------------------------------------
@@ -432,12 +427,22 @@ class App(): @@ -432,12 +427,22 @@ class App():
432 def get_questions_csv(self): 427 def get_questions_csv(self):
433 '''generates a CSV with the grades of the test''' 428 '''generates a CSV with the grades of the test'''
434 test_ref = self.testfactory['ref'] 429 test_ref = self.testfactory['ref']
435 - with self._db_session() as sess:  
436 - questions = sess.query(Test.id, Test.student_id, Test.starttime,  
437 - Question.number, Question.grade)\  
438 - .join(Question)\  
439 - .filter(Test.ref == test_ref)\  
440 - .all() 430 + with Session(self._engine, future=True) as session:
  431 + questions = session.execute(
  432 + select(Test.id, Test.student_id, Test.starttime,
  433 + Question.number, Question.grade).
  434 + join(Question).
  435 + where(Test.ref == test_ref)
  436 + ).all()
  437 + print(questions)
  438 +
  439 +
  440 +
  441 + # questions = sess.query(Test.id, Test.student_id, Test.starttime,
  442 + # Question.number, Question.grade)\
  443 + # .join(Question)\
  444 + # .filter(Test.ref == test_ref)\
  445 + # .all()
441 446
442 qnums = set() # keeps track of all the questions in the test 447 qnums = set() # keeps track of all the questions in the test
443 tests = {} # {test_id: {student_id, starttime, 0: grade, ...}} 448 tests = {} # {test_id: {student_id, starttime, 0: grade, ...}}
@@ -463,14 +468,21 @@ class App(): @@ -463,14 +468,21 @@ class App():
463 def get_test_csv(self): 468 def get_test_csv(self):
464 '''generates a CSV with the grades of the test currently running''' 469 '''generates a CSV with the grades of the test currently running'''
465 test_ref = self.testfactory['ref'] 470 test_ref = self.testfactory['ref']
466 - with self._db_session() as sess:  
467 - tests = sess.query(Test.student_id,  
468 - Test.grade,  
469 - Test.starttime, Test.finishtime)\  
470 - .filter(Test.ref == test_ref)\  
471 - .order_by(Test.student_id)\  
472 - .all()  
473 - 471 + with Session(self._engine, future=True) as session:
  472 + tests = session.execute(
  473 + select(Test.student_id, Test.grade, Test.starttime, Test.finishtime).
  474 + where(Test.ref == test_ref).
  475 + order_by(Test.student_id)
  476 + ).all()
  477 + # with self._db_session() as sess:
  478 + # tests = sess.query(Test.student_id,
  479 + # Test.grade,
  480 + # Test.starttime, Test.finishtime)\
  481 + # .filter(Test.ref == test_ref)\
  482 + # .order_by(Test.student_id)\
  483 + # .all()
  484 +
  485 + print(tests)
474 if not tests: 486 if not tests:
475 logger.warning('Empty CSV: there are no tests!') 487 logger.warning('Empty CSV: there are no tests!')
476 return test_ref, '' 488 return test_ref, ''
@@ -603,21 +615,40 @@ class App(): @@ -603,21 +615,40 @@ class App():
603 logger.info('"%s" area=%g%%, window=%dx%d, screen=%dx%d', 615 logger.info('"%s" area=%g%%, window=%dx%d, screen=%dx%d',
604 uid, area, win_x, win_y, scr_x, scr_y) 616 uid, area, win_x, win_y, scr_x, scr_y)
605 617
606 - async def update_student_password(self, uid, password=''): 618 + async def update_password(self, uid, password=''):
607 '''change password on the database''' 619 '''change password on the database'''
608 if password: 620 if password:
609 - password = await hash_password(password)  
610 - with self._db_session() as sess:  
611 - student = sess.query(Student).filter_by(id=uid).one() 621 + # password = await hash_password(password)
  622 + loop = asyncio.get_running_loop()
  623 + password = await loop.run_in_executor(None,
  624 + bcrypt.hashpw,
  625 + password.encode('utf-8'),
  626 + bcrypt.gensalt())
  627 +
  628 + # with self._db_session() as sess:
  629 + # student = sess.query(Student).filter_by(id=uid).one()
  630 + with Session(self._engine, future=True) as session:
  631 + student = session.execute(
  632 + select(Student).
  633 + where(Student.id == uid)
  634 + ).scalar_one()
612 student.password = password 635 student.password = password
613 logger.info('"%s" password updated.', uid) 636 logger.info('"%s" password updated.', uid)
614 637
615 def insert_new_student(self, uid, name): 638 def insert_new_student(self, uid, name):
616 '''insert new student into the database''' 639 '''insert new student into the database'''
617 - try:  
618 - with self._db_session() as sess:  
619 - sess.add(Student(id=uid, name=name, password=''))  
620 - except exc.SQLAlchemyError:  
621 - logger.error('Insert failed: student %s already exists?', uid)  
622 - else:  
623 - logger.info('New student: "%s", "%s"', uid, name) 640 + with Session(self._engine, future=True) as session:
  641 + session.add(
  642 + Student(id=uid, name=name, password='')
  643 + )
  644 + session.commit()
  645 + # try:
  646 + # with Session(self._engine, future=True) as session:
  647 + # session.add(
  648 + # Student(id=uid, name=name, password='')
  649 + # )
  650 + # session.commit()
  651 + # except Exception:
  652 + # logger.error('Insert failed: student %s already exists?', uid)
  653 + # else:
  654 + # logger.info('New student: "%s", "%s"', uid, name)
perguntations/initdb.py
@@ -194,7 +194,7 @@ def main(): @@ -194,7 +194,7 @@ def main():
194 if args.update_all: 194 if args.update_all:
195 all_students = session.execute( 195 all_students = session.execute(
196 select(Student).where(Student.id != '0') 196 select(Student).where(Student.id != '0')
197 - ).all() 197 + ).scalars().all()
198 198
199 print(f'Updating password of {len(all_students)} users', end='') 199 print(f'Updating password of {len(all_students)} users', end='')
200 for student in all_students: 200 for student in all_students:
@@ -208,9 +208,12 @@ def main(): @@ -208,9 +208,12 @@ def main():
208 else: 208 else:
209 for student_id in args.update: 209 for student_id in args.update:
210 print(f'Updating password of {student_id}') 210 print(f'Updating password of {student_id}')
211 - student = session.execute(select(Student.id))  
212 - password = (args.pw or student_id).encode('utf-8')  
213 - student.password = bcrypt.hashpw(password, bcrypt.gensalt()) 211 + student = session.execute(
  212 + select(Student).
  213 + where(Student.id == student_id)
  214 + ).scalar_one()
  215 + new_password = (args.pw or student_id).encode('utf-8')
  216 + student.password = bcrypt.hashpw(new_password, bcrypt.gensalt())
214 session.commit() 217 session.commit()
215 218
216 show_students_in_database(session, args.verbose) 219 show_students_in_database(session, args.verbose)
@@ -3,4 +3,3 @@ @@ -3,4 +3,3 @@
3 git pull 3 git pull
4 npm update 4 npm update
5 pip install -U . 5 pip install -U .
6 -