Commit 1323bb4d0f868538ee9229ec405321deefc3a12c
1 parent
1db56dc6
Exists in
master
and in
1 other branch
fix pylint warnings
Showing
3 changed files
with
179 additions
and
153 deletions
Show diff stats
perguntations/app.py
| 1 | +''' | ||
| 2 | +Main application module | ||
| 3 | +''' | ||
| 4 | + | ||
| 1 | 5 | ||
| 2 | # python standard libraries | 6 | # python standard libraries |
| 3 | -from os import path | ||
| 4 | -import logging | ||
| 5 | -from contextlib import contextmanager # `with` statement in db sessions | ||
| 6 | import asyncio | 7 | import asyncio |
| 8 | +from contextlib import contextmanager # `with` statement in db sessions | ||
| 9 | +import json | ||
| 10 | +import logging | ||
| 11 | +from os import path | ||
| 7 | 12 | ||
| 8 | # user installed packages | 13 | # user installed packages |
| 9 | import bcrypt | 14 | import bcrypt |
| 10 | -import json | ||
| 11 | from sqlalchemy import create_engine | 15 | from sqlalchemy import create_engine |
| 12 | from sqlalchemy.orm import sessionmaker | 16 | from sqlalchemy.orm import sessionmaker |
| 13 | 17 | ||
| @@ -21,43 +25,50 @@ logger = logging.getLogger(__name__) | @@ -21,43 +25,50 @@ logger = logging.getLogger(__name__) | ||
| 21 | 25 | ||
| 22 | # ============================================================================ | 26 | # ============================================================================ |
| 23 | class AppException(Exception): | 27 | class AppException(Exception): |
| 24 | - pass | 28 | + '''Exception raised in this module''' |
| 25 | 29 | ||
| 26 | 30 | ||
| 27 | # ============================================================================ | 31 | # ============================================================================ |
| 28 | # helper functions | 32 | # helper functions |
| 29 | # ============================================================================ | 33 | # ============================================================================ |
| 30 | async def check_password(try_pw, password): | 34 | async def check_password(try_pw, password): |
| 35 | + '''check password in executor''' | ||
| 31 | try_pw = try_pw.encode('utf-8') | 36 | try_pw = try_pw.encode('utf-8') |
| 32 | loop = asyncio.get_running_loop() | 37 | loop = asyncio.get_running_loop() |
| 33 | hashed = await loop.run_in_executor(None, bcrypt.hashpw, try_pw, password) | 38 | hashed = await loop.run_in_executor(None, bcrypt.hashpw, try_pw, password) |
| 34 | return password == hashed | 39 | return password == hashed |
| 35 | 40 | ||
| 36 | 41 | ||
| 37 | -async def hash_password(pw): | ||
| 38 | - pw = pw.encode('utf-8') | 42 | +async def hash_password(password): |
| 43 | + '''hash password in executor''' | ||
| 39 | loop = asyncio.get_running_loop() | 44 | loop = asyncio.get_running_loop() |
| 40 | - r = await loop.run_in_executor(None, bcrypt.hashpw, pw, bcrypt.gensalt()) | ||
| 41 | - return r | 45 | + return await loop.run_in_executor(None, bcrypt.hashpw, |
| 46 | + password.encode('utf-8'), | ||
| 47 | + bcrypt.gensalt()) | ||
| 42 | 48 | ||
| 43 | 49 | ||
| 44 | # ============================================================================ | 50 | # ============================================================================ |
| 45 | -# Application | ||
| 46 | -# state: | ||
| 47 | -# self.Session | ||
| 48 | -# self.online - {uid: | ||
| 49 | -# {'student':{...}, 'test': {...}}, | ||
| 50 | -# ... | ||
| 51 | -# } | ||
| 52 | -# self.allowd - {'123', '124', ...} | ||
| 53 | -# self.testfactory - TestFactory | ||
| 54 | # ============================================================================ | 51 | # ============================================================================ |
| 55 | -class App(object): | 52 | +class App(): |
| 53 | + ''' | ||
| 54 | + This is the main application | ||
| 55 | + state: | ||
| 56 | + self.Session | ||
| 57 | + self.online - {uid: | ||
| 58 | + {'student':{...}, 'test': {...}}, | ||
| 59 | + ... | ||
| 60 | + } | ||
| 61 | + self.allowd - {'123', '124', ...} | ||
| 62 | + self.testfactory - TestFactory | ||
| 63 | + ''' | ||
| 64 | + | ||
| 56 | # ------------------------------------------------------------------------ | 65 | # ------------------------------------------------------------------------ |
| 57 | - # helper to manage db sessions using the `with` statement, for example | ||
| 58 | - # with self.db_session() as s: s.query(...) | ||
| 59 | @contextmanager | 66 | @contextmanager |
| 60 | def db_session(self): | 67 | def db_session(self): |
| 68 | + ''' | ||
| 69 | + helper to manage db sessions using the `with` statement, for example: | ||
| 70 | + with self.db_session() as s: s.query(...) | ||
| 71 | + ''' | ||
| 61 | session = self.Session() | 72 | session = self.Session() |
| 62 | try: | 73 | try: |
| 63 | yield session | 74 | yield session |
| @@ -73,15 +84,15 @@ class App(object): | @@ -73,15 +84,15 @@ class App(object): | ||
| 73 | self.online = dict() # {uid: {'student':{...}, 'test': {...}}, ...} | 84 | self.online = dict() # {uid: {'student':{...}, 'test': {...}}, ...} |
| 74 | self.allowed = set([]) # '0' is hardcoded to allowed elsewhere | 85 | self.allowed = set([]) # '0' is hardcoded to allowed elsewhere |
| 75 | 86 | ||
| 76 | - logger.info(f'Loading test configuration "{conf["testfile"]}".') | 87 | + logger.info('Loading test configuration "%s".', conf["testfile"]) |
| 77 | testconf = load_yaml(conf['testfile']) | 88 | testconf = load_yaml(conf['testfile']) |
| 78 | testconf.update(conf) # command line options override configuration | 89 | testconf.update(conf) # command line options override configuration |
| 79 | 90 | ||
| 80 | # start test factory | 91 | # start test factory |
| 81 | try: | 92 | try: |
| 82 | self.testfactory = TestFactory(testconf) | 93 | self.testfactory = TestFactory(testconf) |
| 83 | - except TestFactoryException as e: | ||
| 84 | - logger.critical(e) | 94 | + except TestFactoryException as exc: |
| 95 | + logger.critical(exc) | ||
| 85 | raise AppException('Failed to create test factory!') | 96 | raise AppException('Failed to create test factory!') |
| 86 | else: | 97 | else: |
| 87 | logger.info('No errors found. Test factory ready.') | 98 | logger.info('No errors found. Test factory ready.') |
| @@ -92,12 +103,12 @@ class App(object): | @@ -92,12 +103,12 @@ class App(object): | ||
| 92 | engine = create_engine(database, echo=False) | 103 | engine = create_engine(database, echo=False) |
| 93 | self.Session = sessionmaker(bind=engine) | 104 | self.Session = sessionmaker(bind=engine) |
| 94 | try: | 105 | try: |
| 95 | - with self.db_session() as s: | ||
| 96 | - n = s.query(Student).filter(Student.id != '0').count() | 106 | + with self.db_session() as sess: |
| 107 | + num = sess.query(Student).filter(Student.id != '0').count() | ||
| 97 | except Exception: | 108 | except Exception: |
| 98 | raise AppException(f'Database unusable {dbfile}.') | 109 | raise AppException(f'Database unusable {dbfile}.') |
| 99 | else: | 110 | else: |
| 100 | - logger.info(f'Database "{dbfile}" has {n} students.') | 111 | + logger.info('Database "%s" has %s students.', dbfile, num) |
| 101 | 112 | ||
| 102 | # command line option --allow-all | 113 | # command line option --allow-all |
| 103 | if conf['allow_all']: | 114 | if conf['allow_all']: |
| @@ -117,15 +128,16 @@ class App(object): | @@ -117,15 +128,16 @@ class App(object): | ||
| 117 | 128 | ||
| 118 | # ------------------------------------------------------------------------ | 129 | # ------------------------------------------------------------------------ |
| 119 | async def login(self, uid, try_pw): | 130 | async def login(self, uid, try_pw): |
| 131 | + '''login authentication''' | ||
| 120 | if uid not in self.allowed and uid != '0': # not allowed | 132 | if uid not in self.allowed and uid != '0': # not allowed |
| 121 | - logger.warning(f'Student {uid}: not allowed to login.') | 133 | + logger.warning('Student %s: not allowed to login.', uid) |
| 122 | return False | 134 | return False |
| 123 | 135 | ||
| 124 | # get name+password from db | 136 | # get name+password from db |
| 125 | - with self.db_session() as s: | ||
| 126 | - name, password = s.query(Student.name, Student.password)\ | ||
| 127 | - .filter_by(id=uid)\ | ||
| 128 | - .one() | 137 | + with self.db_session() as sess: |
| 138 | + name, password = sess.query(Student.name, Student.password)\ | ||
| 139 | + .filter_by(id=uid)\ | ||
| 140 | + .one() | ||
| 129 | 141 | ||
| 130 | # first login updates the password | 142 | # first login updates the password |
| 131 | if password == '': # update password on first login | 143 | if password == '': # update password on first login |
| @@ -137,104 +149,112 @@ class App(object): | @@ -137,104 +149,112 @@ class App(object): | ||
| 137 | if pw_ok: # success | 149 | if pw_ok: # success |
| 138 | self.allowed.discard(uid) # remove from set of allowed students | 150 | self.allowed.discard(uid) # remove from set of allowed students |
| 139 | if uid in self.online: | 151 | if uid in self.online: |
| 140 | - logger.warning(f'Student {uid}: already logged in.') | 152 | + logger.warning('Student %s: already logged in.', uid) |
| 141 | else: # make student online | 153 | else: # make student online |
| 142 | self.online[uid] = {'student': {'name': name, 'number': uid}} | 154 | self.online[uid] = {'student': {'name': name, 'number': uid}} |
| 143 | - logger.info(f'Student {uid}: logged in.') | 155 | + logger.info('Student %s: logged in.', uid) |
| 144 | return True | 156 | return True |
| 145 | - else: # wrong password | ||
| 146 | - logger.info(f'Student {uid}: wrong password.') | ||
| 147 | - return False | 157 | + # wrong password |
| 158 | + logger.info('Student %s: wrong password.', uid) | ||
| 159 | + return False | ||
| 148 | 160 | ||
| 149 | # ------------------------------------------------------------------------ | 161 | # ------------------------------------------------------------------------ |
| 150 | def logout(self, uid): | 162 | def logout(self, uid): |
| 163 | + '''student logout''' | ||
| 151 | self.online.pop(uid, None) # remove from dict if exists | 164 | self.online.pop(uid, None) # remove from dict if exists |
| 152 | - logger.info(f'Student {uid}: logged out.') | 165 | + logger.info('Student %s: logged out.', uid) |
| 153 | 166 | ||
| 154 | # ------------------------------------------------------------------------ | 167 | # ------------------------------------------------------------------------ |
| 155 | async def generate_test(self, uid): | 168 | async def generate_test(self, uid): |
| 169 | + '''generate a test for a given student''' | ||
| 156 | if uid in self.online: | 170 | if uid in self.online: |
| 157 | - logger.info(f'Student {uid}: generating new test.') | 171 | + logger.info('Student %s: generating new test.', uid) |
| 158 | student_id = self.online[uid]['student'] # {number, name} | 172 | student_id = self.online[uid]['student'] # {number, name} |
| 159 | test = await self.testfactory.generate(student_id) | 173 | test = await self.testfactory.generate(student_id) |
| 160 | self.online[uid]['test'] = test | 174 | self.online[uid]['test'] = test |
| 161 | - logger.info(f'Student {uid}: test is ready.') | 175 | + logger.info('Student %s: test is ready.', uid) |
| 162 | return self.online[uid]['test'] | 176 | return self.online[uid]['test'] |
| 163 | - else: | ||
| 164 | - # this implies an error in the code. should never be here! | ||
| 165 | - logger.critical(f'Student {uid}: offline, can\'t generate test') | 177 | + |
| 178 | + # this implies an error in the code. should never be here! | ||
| 179 | + logger.critical('Student %s: offline, can\'t generate test', uid) | ||
| 166 | 180 | ||
| 167 | # ------------------------------------------------------------------------ | 181 | # ------------------------------------------------------------------------ |
| 168 | - # ans is a dictionary {question_index: answer, ...} | ||
| 169 | - # for example: {0:'hello', 1:[1,2]} | ||
| 170 | async def correct_test(self, uid, ans): | 182 | async def correct_test(self, uid, ans): |
| 171 | - t = self.online[uid]['test'] | 183 | + ''' |
| 184 | + Corrects test | ||
| 185 | + | ||
| 186 | + ans is a dictionary {question_index: answer, ...} | ||
| 187 | + for example: {0:'hello', 1:[1,2]} | ||
| 188 | + ''' | ||
| 189 | + test = self.online[uid]['test'] | ||
| 172 | 190 | ||
| 173 | # --- submit answers and correct test | 191 | # --- submit answers and correct test |
| 174 | - t.update_answers(ans) | ||
| 175 | - logger.info(f'Student {uid}: {len(ans)} answers submitted.') | 192 | + test.update_answers(ans) |
| 193 | + logger.info('Student %s: %d answers submitted.', uid, len(ans)) | ||
| 176 | 194 | ||
| 177 | - grade = await t.correct() | ||
| 178 | - logger.info(f'Student {uid}: grade = {grade:5.3} points.') | 195 | + grade = await test.correct() |
| 196 | + logger.info('Student %s: grade = %.1g points.', uid, grade) | ||
| 179 | 197 | ||
| 180 | # --- save test in JSON format | 198 | # --- save test in JSON format |
| 181 | - fields = (uid, t['ref'], str(t['finish_time'])) | ||
| 182 | - fname = ' -- '.join(fields) + '.json' | ||
| 183 | - fpath = path.join(t['answers_dir'], fname) | ||
| 184 | - with open(path.expanduser(fpath), 'w') as f: | 199 | + fields = (uid, test['ref'], str(test['finish_time'])) |
| 200 | + fname = '--'.join(fields) + '.json' | ||
| 201 | + fpath = path.join(test['answers_dir'], fname) | ||
| 202 | + with open(path.expanduser(fpath), 'w') as file: | ||
| 185 | # default=str required for datetime objects | 203 | # default=str required for datetime objects |
| 186 | - json.dump(t, f, indent=2, default=str) | ||
| 187 | - logger.info(f'Student {uid}: saved JSON.') | 204 | + json.dump(test, file, indent=2, default=str) |
| 205 | + logger.info('Student %s: saved JSON.', uid) | ||
| 188 | 206 | ||
| 189 | # --- insert test and questions into database | 207 | # --- insert test and questions into database |
| 190 | - with self.db_session() as s: | ||
| 191 | - s.add(Test( | ||
| 192 | - ref=t['ref'], | ||
| 193 | - title=t['title'], | ||
| 194 | - grade=t['grade'], | ||
| 195 | - starttime=str(t['start_time']), | ||
| 196 | - finishtime=str(t['finish_time']), | 208 | + with self.db_session() as sess: |
| 209 | + sess.add(Test( | ||
| 210 | + ref=test['ref'], | ||
| 211 | + title=test['title'], | ||
| 212 | + grade=test['grade'], | ||
| 213 | + starttime=str(test['start_time']), | ||
| 214 | + finishtime=str(test['finish_time']), | ||
| 197 | filename=fpath, | 215 | filename=fpath, |
| 198 | student_id=uid, | 216 | student_id=uid, |
| 199 | - state=t['state'], | 217 | + state=test['state'], |
| 200 | comment='')) | 218 | comment='')) |
| 201 | - s.add_all([Question( | 219 | + sess.add_all([Question( |
| 202 | ref=q['ref'], | 220 | ref=q['ref'], |
| 203 | grade=q['grade'], | 221 | grade=q['grade'], |
| 204 | - starttime=str(t['start_time']), | ||
| 205 | - finishtime=str(t['finish_time']), | 222 | + starttime=str(test['start_time']), |
| 223 | + finishtime=str(test['finish_time']), | ||
| 206 | student_id=uid, | 224 | student_id=uid, |
| 207 | - test_id=t['ref']) for q in t['questions'] if 'grade' in q]) | 225 | + test_id=test['ref']) |
| 226 | + for q in test['questions'] if 'grade' in q]) | ||
| 208 | 227 | ||
| 209 | - logger.info(f'Student {uid}: database updated.') | 228 | + logger.info('Student %s: database updated.', uid) |
| 210 | return grade | 229 | return grade |
| 211 | 230 | ||
| 212 | # ------------------------------------------------------------------------ | 231 | # ------------------------------------------------------------------------ |
| 213 | def giveup_test(self, uid): | 232 | def giveup_test(self, uid): |
| 214 | - t = self.online[uid]['test'] | ||
| 215 | - t.giveup() | 233 | + '''giveup test - not used??''' |
| 234 | + test = self.online[uid]['test'] | ||
| 235 | + test.giveup() | ||
| 216 | 236 | ||
| 217 | # save JSON with the test | 237 | # save JSON with the test |
| 218 | - fields = (t['student']['number'], t['ref'], str(t['finish_time'])) | ||
| 219 | - fname = ' -- '.join(fields) + '.json' | ||
| 220 | - fpath = path.join(t['answers_dir'], fname) | ||
| 221 | - t.save_json(fpath) | 238 | + fields = (test['student']['number'], test['ref'], |
| 239 | + str(test['finish_time'])) | ||
| 240 | + fname = '--'.join(fields) + '.json' | ||
| 241 | + fpath = path.join(test['answers_dir'], fname) | ||
| 242 | + test.save_json(fpath) | ||
| 222 | 243 | ||
| 223 | # insert test into database | 244 | # insert test into database |
| 224 | - with self.db_session() as s: | ||
| 225 | - s.add(Test( | ||
| 226 | - ref=t['ref'], | ||
| 227 | - title=t['title'], | ||
| 228 | - grade=t['grade'], | ||
| 229 | - starttime=str(t['start_time']), | ||
| 230 | - finishtime=str(t['finish_time']), | ||
| 231 | - filename=fpath, | ||
| 232 | - student_id=t['student']['number'], | ||
| 233 | - state=t['state'], | ||
| 234 | - comment='')) | ||
| 235 | - | ||
| 236 | - logger.info(f'Student {uid}: gave up.') | ||
| 237 | - return t | 245 | + with self.db_session() as sess: |
| 246 | + sess.add(Test(ref=test['ref'], | ||
| 247 | + title=test['title'], | ||
| 248 | + grade=test['grade'], | ||
| 249 | + starttime=str(test['start_time']), | ||
| 250 | + finishtime=str(test['finish_time']), | ||
| 251 | + filename=fpath, | ||
| 252 | + student_id=test['student']['number'], | ||
| 253 | + state=test['state'], | ||
| 254 | + comment='')) | ||
| 255 | + | ||
| 256 | + logger.info('Student %s: gave up.', uid) | ||
| 257 | + return test | ||
| 238 | 258 | ||
| 239 | # ------------------------------------------------------------------------ | 259 | # ------------------------------------------------------------------------ |
| 240 | 260 | ||
| @@ -243,52 +263,58 @@ class App(object): | @@ -243,52 +263,58 @@ class App(object): | ||
| 243 | # return self.online[uid]['student']['name'] | 263 | # return self.online[uid]['student']['name'] |
| 244 | 264 | ||
| 245 | def get_student_test(self, uid, default=None): | 265 | def get_student_test(self, uid, default=None): |
| 266 | + '''get test from online student''' | ||
| 246 | return self.online[uid].get('test', default) | 267 | return self.online[uid].get('test', default) |
| 247 | 268 | ||
| 248 | # def get_questions_dir(self): | 269 | # def get_questions_dir(self): |
| 249 | # return self.testfactory['questions_dir'] | 270 | # return self.testfactory['questions_dir'] |
| 250 | 271 | ||
| 251 | def get_student_grades_from_all_tests(self, uid): | 272 | def get_student_grades_from_all_tests(self, uid): |
| 252 | - with self.db_session() as s: | ||
| 253 | - return s.query(Test.title, Test.grade, Test.finishtime)\ | ||
| 254 | - .filter_by(student_id=uid)\ | ||
| 255 | - .order_by(Test.finishtime) | 273 | + '''get grades of student from all tests''' |
| 274 | + with self.db_session() as sess: | ||
| 275 | + return sess.query(Test.title, Test.grade, Test.finishtime)\ | ||
| 276 | + .filter_by(student_id=uid)\ | ||
| 277 | + .order_by(Test.finishtime) | ||
| 256 | 278 | ||
| 257 | def get_json_filename_of_test(self, test_id): | 279 | def get_json_filename_of_test(self, test_id): |
| 258 | - with self.db_session() as s: | ||
| 259 | - return s.query(Test.filename)\ | ||
| 260 | - .filter_by(id=test_id)\ | ||
| 261 | - .scalar() | 280 | + '''get JSON filename from database given the test_id''' |
| 281 | + with self.db_session() as sess: | ||
| 282 | + return sess.query(Test.filename)\ | ||
| 283 | + .filter_by(id=test_id)\ | ||
| 284 | + .scalar() | ||
| 262 | 285 | ||
| 263 | def get_all_students(self): | 286 | def get_all_students(self): |
| 264 | - with self.db_session() as s: | ||
| 265 | - return s.query(Student.id, Student.name, Student.password)\ | ||
| 266 | - .filter(Student.id != '0')\ | ||
| 267 | - .order_by(Student.id) | 287 | + '''get all students from database''' |
| 288 | + with self.db_session() as sess: | ||
| 289 | + return sess.query(Student.id, Student.name, Student.password)\ | ||
| 290 | + .filter(Student.id != '0')\ | ||
| 291 | + .order_by(Student.id) | ||
| 268 | 292 | ||
| 269 | def get_student_grades_from_test(self, uid, testid): | 293 | def get_student_grades_from_test(self, uid, testid): |
| 270 | - with self.db_session() as s: | ||
| 271 | - return s.query(Test.grade, Test.finishtime, Test.id)\ | ||
| 272 | - .filter_by(student_id=uid)\ | ||
| 273 | - .filter_by(ref=testid)\ | ||
| 274 | - .all() | 294 | + '''get grades of student for a given testid''' |
| 295 | + with self.db_session() as sess: | ||
| 296 | + return sess.query(Test.grade, Test.finishtime, Test.id)\ | ||
| 297 | + .filter_by(student_id=uid)\ | ||
| 298 | + .filter_by(ref=testid)\ | ||
| 299 | + .all() | ||
| 275 | 300 | ||
| 276 | def get_students_state(self): | 301 | def get_students_state(self): |
| 302 | + '''get list of states of every student''' | ||
| 277 | return [{ | 303 | return [{ |
| 278 | - 'uid': uid, | ||
| 279 | - 'name': name, | ||
| 280 | - 'allowed': uid in self.allowed, | ||
| 281 | - 'online': uid in self.online, | ||
| 282 | - 'start_time': self.online.get(uid, {}).get('test', {}) | ||
| 283 | - .get('start_time', ''), | ||
| 284 | - 'password_defined': pw != '', | ||
| 285 | - 'grades': self.get_student_grades_from_test( | ||
| 286 | - uid, | ||
| 287 | - self.testfactory['ref'] | ||
| 288 | - ), | ||
| 289 | - | ||
| 290 | - # 'focus': self.online.get(uid, {}).get('student', {}).get('focus', True), # FIXME | ||
| 291 | - } for uid, name, pw in self.get_all_students()] | 304 | + 'uid': uid, |
| 305 | + 'name': name, | ||
| 306 | + 'allowed': uid in self.allowed, | ||
| 307 | + 'online': uid in self.online, | ||
| 308 | + 'start_time': self.online.get(uid, {}).get('test', {}) | ||
| 309 | + .get('start_time', ''), | ||
| 310 | + 'password_defined': pw != '', | ||
| 311 | + 'grades': self.get_student_grades_from_test( | ||
| 312 | + uid, | ||
| 313 | + self.testfactory['ref'] | ||
| 314 | + ), | ||
| 315 | + | ||
| 316 | + # 'focus': self.online.get(uid, {}).get('student', {}).get('focus', True), # FIXME | ||
| 317 | + } for uid, name, pw in self.get_all_students()] | ||
| 292 | 318 | ||
| 293 | # def get_allowed_students(self): | 319 | # def get_allowed_students(self): |
| 294 | # # set of 'uid' allowed to login | 320 | # # set of 'uid' allowed to login |
| @@ -303,26 +329,30 @@ class App(object): | @@ -303,26 +329,30 @@ class App(object): | ||
| 303 | 329 | ||
| 304 | # --- helpers (change state) | 330 | # --- helpers (change state) |
| 305 | def allow_student(self, uid): | 331 | def allow_student(self, uid): |
| 332 | + '''allow a single student to login''' | ||
| 306 | self.allowed.add(uid) | 333 | self.allowed.add(uid) |
| 307 | - logger.info(f'Student {uid}: allowed to login.') | 334 | + logger.info('Student %s: allowed to login.', uid) |
| 308 | 335 | ||
| 309 | def deny_student(self, uid): | 336 | def deny_student(self, uid): |
| 337 | + '''deny a single student to login''' | ||
| 310 | self.allowed.discard(uid) | 338 | self.allowed.discard(uid) |
| 311 | - logger.info(f'Student {uid}: denied to login') | 339 | + logger.info('Student %s: denied to login', uid) |
| 312 | 340 | ||
| 313 | - async def update_student_password(self, uid, pw=''): | ||
| 314 | - if pw: | ||
| 315 | - pw = await hash_password(pw) | ||
| 316 | - with self.db_session() as s: | ||
| 317 | - student = s.query(Student).filter_by(id=uid).one() | ||
| 318 | - student.password = pw | ||
| 319 | - logger.info(f'Student {uid}: password updated.') | 341 | + async def update_student_password(self, uid, password=''): |
| 342 | + '''change password on the database''' | ||
| 343 | + if password: | ||
| 344 | + password = await hash_password(password) | ||
| 345 | + with self.db_session() as sess: | ||
| 346 | + student = sess.query(Student).filter_by(id=uid).one() | ||
| 347 | + student.password = password | ||
| 348 | + logger.info('Student %s: password updated.', uid) | ||
| 320 | 349 | ||
| 321 | def insert_new_student(self, uid, name): | 350 | def insert_new_student(self, uid, name): |
| 351 | + '''insert new student into the database''' | ||
| 322 | try: | 352 | try: |
| 323 | - with self.db_session() as s: | ||
| 324 | - s.add(Student(id=uid, name=name, password='')) | 353 | + with self.db_session() as sess: |
| 354 | + sess.add(Student(id=uid, name=name, password='')) | ||
| 325 | except Exception: | 355 | except Exception: |
| 326 | - logger.error(f'Insert failed: student {uid} already exists.') | 356 | + logger.error('Insert failed: student %s already exists.', uid) |
| 327 | else: | 357 | else: |
| 328 | - logger.info(f'New student inserted: {uid}, {name}') | 358 | + logger.info('New student inserted: %s, %s', uid, name) |
perguntations/main.py
| 1 | #!/usr/bin/env python3 | 1 | #!/usr/bin/env python3 |
| 2 | 2 | ||
| 3 | +''' | ||
| 4 | +Main file that starts the application and the web server | ||
| 5 | +''' | ||
| 6 | + | ||
| 7 | + | ||
| 3 | # python standard library | 8 | # python standard library |
| 4 | import argparse | 9 | import argparse |
| 5 | import logging | 10 | import logging |
| @@ -10,7 +15,7 @@ import sys | @@ -10,7 +15,7 @@ import sys | ||
| 10 | # from typing import Any, Dict | 15 | # from typing import Any, Dict |
| 11 | 16 | ||
| 12 | # this project | 17 | # this project |
| 13 | -from .app import App | 18 | +from .app import App, AppException |
| 14 | from .serve import run_webserver | 19 | from .serve import run_webserver |
| 15 | from .tools import load_yaml | 20 | from .tools import load_yaml |
| 16 | from . import APP_NAME, APP_VERSION | 21 | from . import APP_NAME, APP_VERSION |
| @@ -125,7 +130,7 @@ def main(): | @@ -125,7 +130,7 @@ def main(): | ||
| 125 | # testapp = App(config) | 130 | # testapp = App(config) |
| 126 | try: | 131 | try: |
| 127 | testapp = App(config) | 132 | testapp = App(config) |
| 128 | - except Exception: | 133 | + except AppException: |
| 129 | logging.critical('Failed to start application.') | 134 | logging.critical('Failed to start application.') |
| 130 | sys.exit(-1) | 135 | sys.exit(-1) |
| 131 | 136 |
perguntations/test.py
| @@ -267,23 +267,16 @@ class TestFactory(dict): | @@ -267,23 +267,16 @@ class TestFactory(dict): | ||
| 267 | if nerr > 0: | 267 | if nerr > 0: |
| 268 | logger.error('%s errors found!', nerr) | 268 | logger.error('%s errors found!', nerr) |
| 269 | 269 | ||
| 270 | + inherit = {'ref', 'title', 'database', 'answers_dir', | ||
| 271 | + 'questions_dir', 'files', | ||
| 272 | + 'duration', 'autosubmit', | ||
| 273 | + 'scale_min', 'scale_max', 'show_points', | ||
| 274 | + 'show_ref', 'debug', } | ||
| 275 | + # NOT INCLUDED: scale_points, testfile, allow_all, review | ||
| 276 | + | ||
| 270 | return Test({ | 277 | return Test({ |
| 271 | - 'ref': self['ref'], | ||
| 272 | - 'title': self['title'], # title of the test | ||
| 273 | - 'student': student, # student id | ||
| 274 | - 'questions': test, # list of Question instances | ||
| 275 | - 'answers_dir': self['answers_dir'], | ||
| 276 | - 'duration': self['duration'], | ||
| 277 | - 'autosubmit': self['autosubmit'], | ||
| 278 | - 'scale_min': self['scale_min'], | ||
| 279 | - 'scale_max': self['scale_max'], | ||
| 280 | - 'show_points': self['show_points'], | ||
| 281 | - 'show_ref': self['show_ref'], | ||
| 282 | - 'debug': self['debug'], # required by template test.html | ||
| 283 | - 'database': self['database'], | ||
| 284 | - 'questions_dir': self['questions_dir'], | ||
| 285 | - 'files': self['files'], | ||
| 286 | - }) | 278 | + **{'student': student, 'questions': test}, |
| 279 | + **{k:self[k] for k in inherit}}) | ||
| 287 | 280 | ||
| 288 | # ------------------------------------------------------------------------ | 281 | # ------------------------------------------------------------------------ |
| 289 | def __repr__(self): | 282 | def __repr__(self): |
| @@ -323,14 +316,12 @@ class Test(dict): | @@ -323,14 +316,12 @@ class Test(dict): | ||
| 323 | # ------------------------------------------------------------------------ | 316 | # ------------------------------------------------------------------------ |
| 324 | async def correct(self): | 317 | async def correct(self): |
| 325 | '''Corrects all the answers of the test and computes the final grade''' | 318 | '''Corrects all the answers of the test and computes the final grade''' |
| 326 | - | ||
| 327 | self['finish_time'] = datetime.now() | 319 | self['finish_time'] = datetime.now() |
| 328 | self['state'] = 'FINISHED' | 320 | self['state'] = 'FINISHED' |
| 329 | grade = 0.0 | 321 | grade = 0.0 |
| 330 | for question in self['questions']: | 322 | for question in self['questions']: |
| 331 | await question.correct_async() | 323 | await question.correct_async() |
| 332 | grade += question['grade'] * question['points'] | 324 | grade += question['grade'] * question['points'] |
| 333 | - # logger.debug(f'Correcting {q["ref"]:>30}: {q["grade"]*100:4.0f}%') | ||
| 334 | logger.debug('Correcting %30s: %3g%%', | 325 | logger.debug('Correcting %30s: %3g%%', |
| 335 | question["ref"], question["grade"]*100) | 326 | question["ref"], question["grade"]*100) |
| 336 | 327 |