Commit 981734c09fbc38fd4e4e826596724810b0ba4db4
1 parent
7c49db42
Exists in
master
and in
1 other branch
irrelevant, for testing
Showing
4 changed files
with
145 additions
and
114 deletions
Show diff stats
mypy.ini
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