Commit 981734c09fbc38fd4e4e826596724810b0ba4db4

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

irrelevant, for testing

mypy.ini
1 1 [mypy]
2 2 python_version = 3.8
3   -# warn_return_any = True
4   -# warn_unused_configs = True
5 3  
6 4 [mypy-setuptools.*]
7 5 ignore_missing_imports = True
... ...
perguntations/app.py
... ... @@ -6,7 +6,6 @@ Description: Main application logic.
6 6  
7 7 # python standard libraries
8 8 import asyncio
9   -# from contextlib import contextmanager # `with` statement in db sessions
10 9 import csv
11 10 import io
12 11 import json
... ... @@ -17,8 +16,7 @@ from os import path
17 16 import bcrypt
18 17 from sqlalchemy import create_engine, select, func
19 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 21 # this project
24 22 from perguntations.models import Student, Test, Question
... ... @@ -113,8 +111,6 @@ class App():
113 111 logger.info('Generating %d tests. May take awhile...',
114 112 len(self.allowed))
115 113 self._pregenerate_tests(len(self.allowed))
116   - else:
117   - logger.info('No tests generated yet.')
118 114  
119 115 if conf['correct']:
120 116 self._correct_tests()
... ... @@ -139,94 +135,91 @@ class App():
139 135  
140 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 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 202 logger.warning('"%s" unauthorized.', uid)
206 203 return 'unauthorized'
207 204  
208 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 207 select(Student.name, Student.password).
212   - where(id == uid)
  208 + where(Student.id == uid)
213 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 215 else: # check password
222 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 223 logger.info('"%s" wrong password.', uid)
231 224 return 'wrong_password'
232 225  
... ... @@ -238,6 +231,7 @@ class App():
238 231 uid, headers['remote_ip'])
239 232 # FIXME invalidate previous login
240 233 else:
  234 + # first login
241 235 self.online[uid] = {'student': {
242 236 'name': name,
243 237 'number': uid,
... ... @@ -375,8 +369,9 @@ class App():
375 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 375 logger.info('"%s" database updated.', uid)
381 376  
382 377 # ------------------------------------------------------------------------
... ... @@ -432,12 +427,22 @@ class App():
432 427 def get_questions_csv(self):
433 428 '''generates a CSV with the grades of the test'''
434 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 447 qnums = set() # keeps track of all the questions in the test
443 448 tests = {} # {test_id: {student_id, starttime, 0: grade, ...}}
... ... @@ -463,14 +468,21 @@ class App():
463 468 def get_test_csv(self):
464 469 '''generates a CSV with the grades of the test currently running'''
465 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 486 if not tests:
475 487 logger.warning('Empty CSV: there are no tests!')
476 488 return test_ref, ''
... ... @@ -603,21 +615,40 @@ class App():
603 615 logger.info('"%s" area=%g%%, window=%dx%d, screen=%dx%d',
604 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 619 '''change password on the database'''
608 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 635 student.password = password
613 636 logger.info('"%s" password updated.', uid)
614 637  
615 638 def insert_new_student(self, uid, name):
616 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 194 if args.update_all:
195 195 all_students = session.execute(
196 196 select(Student).where(Student.id != '0')
197   - ).all()
  197 + ).scalars().all()
198 198  
199 199 print(f'Updating password of {len(all_students)} users', end='')
200 200 for student in all_students:
... ... @@ -208,9 +208,12 @@ def main():
208 208 else:
209 209 for student_id in args.update:
210 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 217 session.commit()
215 218  
216 219 show_students_in_database(session, args.verbose)
... ...
update.sh
... ... @@ -3,4 +3,3 @@
3 3 git pull
4 4 npm update
5 5 pip install -U .
6   -
... ...