Commit ff655aef8a5cf33a7915d6178981565d55756c15
1 parent
1cb1329f
Exists in
master
and in
1 other branch
- large rewrite of initdb, with new options
Showing
5 changed files
with
135 additions
and
132 deletions
Show diff stats
BUGS.md
| 1 | 1 | |
| 2 | 2 | # BUGS |
| 3 | 3 | |
| 4 | +- generators e correct scripts que durem muito tempo bloqueiam o eventloop do tornado. | |
| 4 | 5 | - change password modal nao aparece no ipad (safari e firefox) |
| 5 | 6 | - on start topic, logs show questionhandler.get() twice. |
| 6 | -- generators e correct scripts que durem muito tempo podem bloquear o loop do tornado? | |
| 7 | 7 | - detect questions in questions.yaml without ref -> error ou generate default. |
| 8 | 8 | - Criar outra estrutura organizada em capítulos (conjuntos de tópicos). Permitir capítulos de capítulos, etc. talvez usar grafos de grafos... |
| 9 | 9 | - session management. close after inactive time. | ... | ... |
addstudent.py
| ... | ... | @@ -1,53 +0,0 @@ |
| 1 | -#!/usr/bin/env python3 | |
| 2 | - | |
| 3 | -import csv | |
| 4 | -import argparse | |
| 5 | -import re | |
| 6 | -import string | |
| 7 | -from sys import exit | |
| 8 | - | |
| 9 | -import bcrypt | |
| 10 | -from sqlalchemy import create_engine | |
| 11 | -from sqlalchemy.orm import sessionmaker | |
| 12 | - | |
| 13 | -from models import Base, Student | |
| 14 | - | |
| 15 | -# =========================================================================== | |
| 16 | -# Parse command line options | |
| 17 | -# =========================================================================== | |
| 18 | -argparser = argparse.ArgumentParser(description='Add new student to database or update its password') | |
| 19 | -argparser.add_argument('--db', default='students.db', type=str, help='database filename') | |
| 20 | -argparser.add_argument('uid', type=str, default='', help='Student ID') | |
| 21 | -argparser.add_argument('password', type=str, default='', help='Student password') | |
| 22 | -argparser.add_argument('name', nargs='*', type=str, default='', help='Student name') | |
| 23 | -args = argparser.parse_args() | |
| 24 | - | |
| 25 | -# =======================================================x==================== | |
| 26 | -engine = create_engine(f'sqlite:///{args.db}', echo=False) | |
| 27 | -Base.metadata.create_all(engine) # Criate schema if needed | |
| 28 | -Session = sessionmaker(bind=engine) | |
| 29 | - | |
| 30 | -uid = args.uid | |
| 31 | -password = bcrypt.hashpw(args.password.encode('utf-8'), bcrypt.gensalt()) | |
| 32 | -name = ' '.join(args.name) | |
| 33 | - | |
| 34 | -# --- start db session --- | |
| 35 | -session = Session() | |
| 36 | - | |
| 37 | -u = session.query(Student).get(uid) | |
| 38 | - | |
| 39 | -if u is None: | |
| 40 | - session.add(Student(id=uid, name=name, password=password)) | |
| 41 | - print(f'New user added to "{args.db}".') | |
| 42 | - | |
| 43 | -else: | |
| 44 | - print('Student already exists.') | |
| 45 | - if input(' Update password? [y/n] ') in ('y', 'yes', 'Y', 'Yes', 'YES'): | |
| 46 | - u.password = password | |
| 47 | - print(' Password updated.') | |
| 48 | - else: | |
| 49 | - print(' Nothing done.') | |
| 50 | - | |
| 51 | -session.commit() | |
| 52 | -session.close() | |
| 53 | -# --- end db session --- |
demo/solar_system/questions.yaml
| ... | ... | @@ -40,8 +40,9 @@ |
| 40 | 40 | type: textarea |
| 41 | 41 | title: Sistema solar |
| 42 | 42 | text: Escreva o nome dos três planetas mais próximos do Sol. (Exemplo `A, B e C`) |
| 43 | - correct: correct-first_3_planets.py | |
| 43 | + # correct: correct-first_3_planets.py | |
| 44 | + correct: correct-timeout.py | |
| 44 | 45 | # opcional |
| 45 | 46 | answer: Vulcano, Krypton, Plutão |
| 46 | 47 | lines: 3 |
| 47 | - timeout: 5 | |
| 48 | + timeout: 50 | ... | ... |
initdb.py
| ... | ... | @@ -31,113 +31,166 @@ async def hash_all_passwords(executor, students): |
| 31 | 31 | await asyncio.wait(tasks) # block until all tasks are done |
| 32 | 32 | print() |
| 33 | 33 | |
| 34 | +# =========================================================================== | |
| 34 | 35 | # SIIUE names have alien strings like "(TE)" and are sometimes capitalized |
| 35 | 36 | # We remove them so that students dont keep asking what it means |
| 36 | 37 | def fix(name): |
| 37 | 38 | return string.capwords(re.sub('\(.*\)', '', name).strip()) |
| 38 | 39 | |
| 40 | + | |
| 39 | 41 | # =========================================================================== |
| 40 | 42 | # Parse command line options |
| 41 | 43 | def parse_commandline_arguments(): |
| 42 | 44 | argparser = argparse.ArgumentParser( |
| 43 | - description='Create new database from a CSV file (SIIUE format)') | |
| 45 | + formatter_class=argparse.ArgumentDefaultsHelpFormatter, | |
| 46 | + description='Insert new users into a database. Users can be imported from CSV files in the SIIUE format or defined in the command line. If the database does not exist, a new one is created.') | |
| 47 | + | |
| 48 | + argparser.add_argument('csvfile', | |
| 49 | + nargs='*', | |
| 50 | + type=str, | |
| 51 | + default='', | |
| 52 | + help='CSV file to import (SIIUE)') | |
| 44 | 53 | |
| 45 | 54 | argparser.add_argument('--db', |
| 46 | 55 | default='students.db', |
| 47 | 56 | type=str, |
| 48 | - help='database filename') | |
| 57 | + help='database file') | |
| 49 | 58 | |
| 50 | - argparser.add_argument('--demo', | |
| 59 | + argparser.add_argument('-A', '--admin', | |
| 51 | 60 | action='store_true', |
| 52 | - help='initialize database with a few fake students') | |
| 61 | + help='insert the admin user') | |
| 62 | + | |
| 63 | + argparser.add_argument('-a', '--add', | |
| 64 | + nargs=2, | |
| 65 | + action='append', | |
| 66 | + metavar=('uid', 'name'), | |
| 67 | + help='add new user') | |
| 68 | + | |
| 69 | + argparser.add_argument('-u', '--update', | |
| 70 | + nargs='+', | |
| 71 | + metavar='uid', | |
| 72 | + default=[], | |
| 73 | + help='users to update') | |
| 53 | 74 | |
| 54 | 75 | argparser.add_argument('--pw', |
| 55 | 76 | default='', |
| 56 | 77 | type=str, |
| 57 | - help='default password') | |
| 78 | + help='set password for new and updated users') | |
| 58 | 79 | |
| 59 | - argparser.add_argument('csvfile', | |
| 60 | - nargs='?', | |
| 61 | - type=str, | |
| 62 | - default='', | |
| 63 | - help='CSV filename') | |
| 80 | + argparser.add_argument('-V', '--verbose', | |
| 81 | + action='store_true', | |
| 82 | + help='show all students in database') | |
| 64 | 83 | |
| 65 | 84 | return argparser.parse_args() |
| 66 | 85 | |
| 86 | + | |
| 67 | 87 | # =========================================================================== |
| 68 | 88 | def get_students_from_csv(filename): |
| 69 | 89 | try: |
| 70 | 90 | csvreader = csv.DictReader(open(filename, encoding='iso-8859-1'), delimiter=';', quotechar='"', skipinitialspace=True) |
| 71 | 91 | except EnvironmentError: |
| 72 | - print(f'Error: CSV file "{filename}" not found.') | |
| 73 | - exit(1) | |
| 74 | - students = [{ | |
| 75 | - 'uid': s['N.º'], | |
| 76 | - 'name': fix(s['Nome']) | |
| 77 | - } for s in csvreader] | |
| 92 | + print(f'!!! Error. File "{filename}" not found !!!') | |
| 93 | + students = [] | |
| 94 | + else: | |
| 95 | + students = [{ | |
| 96 | + 'uid': s['N.º'], | |
| 97 | + 'name': fix(s['Nome']) | |
| 98 | + } for s in csvreader] | |
| 78 | 99 | |
| 79 | 100 | return students |
| 80 | 101 | |
| 102 | + | |
| 81 | 103 | # =========================================================================== |
| 82 | -args = parse_commandline_arguments() | |
| 83 | - | |
| 84 | -if args.csvfile: | |
| 85 | - students = get_students_from_csv(args.csvfile) | |
| 86 | -elif args.demo: | |
| 87 | - students = [ | |
| 88 | - {'uid': '1915', 'name': 'Alan Turing'}, | |
| 89 | - {'uid': '1938', 'name': 'Donald Knuth'}, | |
| 90 | - {'uid': '1815', 'name': 'Ada Lovelace'}, | |
| 91 | - {'uid': '1969', 'name': 'Linus Torvalds'}, | |
| 92 | - {'uid': '1955', 'name': 'Tim Burners-Lee'}, | |
| 93 | - {'uid': '1916', 'name': 'Claude Shannon'}, | |
| 94 | - {'uid': '1903', 'name': 'John von Neumann'}] | |
| 95 | -students.append({'uid': '0', 'name': 'Admin'}) | |
| 96 | - | |
| 97 | -if args.pw: | |
| 98 | - for s in students: | |
| 99 | - s['pw'] = args.pw | |
| 100 | - | |
| 101 | -print(f'Generating {len(students)} bcrypt password hashes.') | |
| 102 | -executor = ThreadPoolExecutor() | |
| 103 | -event_loop = asyncio.get_event_loop() | |
| 104 | -event_loop.run_until_complete(hash_all_passwords(executor, students)) | |
| 105 | -event_loop.close() | |
| 106 | - | |
| 107 | -print(f'Creating database: {args.db}') | |
| 108 | -engine = sa.create_engine(f'sqlite:///{args.db}', echo=False) | |
| 109 | -Base.metadata.create_all(engine) # Criate schema if needed | |
| 110 | -Session = sa.orm.sessionmaker(bind=engine) | |
| 111 | - | |
| 112 | -try: | |
| 113 | - # --- start db session --- | |
| 104 | +def insert_students_into_db(session, students): | |
| 105 | + try: | |
| 106 | + # --- start db session --- | |
| 107 | + session.add_all([Student(id=s['uid'], name=s['name'], password=s['pw']) | |
| 108 | + for s in students]) | |
| 109 | + | |
| 110 | + session.commit() | |
| 111 | + | |
| 112 | + except sa.exc.IntegrityError: | |
| 113 | + print('!!! Integrity error. User(s) already in database. None inserted !!!\n') | |
| 114 | + session.rollback() | |
| 115 | + | |
| 116 | + | |
| 117 | +# =========================================================================== | |
| 118 | +def show_students_in_database(session, verbose=False): | |
| 119 | + try: | |
| 120 | + users = session.query(Student).order_by(Student.id).all() | |
| 121 | + except: | |
| 122 | + raise | |
| 123 | + else: | |
| 124 | + n = len(users) | |
| 125 | + print(f'Users registered:') | |
| 126 | + if n == 0: | |
| 127 | + print(' -- none --') | |
| 128 | + else: | |
| 129 | + if verbose: | |
| 130 | + for u in users: | |
| 131 | + print(f'{u.id:>12} {u.name}') | |
| 132 | + else: | |
| 133 | + print(f'{users[0].id:>12} {users[0].name}') | |
| 134 | + if n > 1: | |
| 135 | + print(f'{users[1].id:>12} {users[1].name}') | |
| 136 | + if n > 3: | |
| 137 | + print(' | |') | |
| 138 | + if n > 2: | |
| 139 | + print(f'{users[-1].id:>12} {users[-1].name}') | |
| 140 | + print(f'Total: {n}.') | |
| 141 | + | |
| 142 | + | |
| 143 | +# =========================================================================== | |
| 144 | +if __name__=='__main__': | |
| 145 | + args = parse_commandline_arguments() | |
| 146 | + | |
| 147 | + # --- make list of students to insert/update | |
| 148 | + students = [] | |
| 149 | + | |
| 150 | + for csvfile in args.csvfile: | |
| 151 | + print('Adding users from:', csvfile) | |
| 152 | + students.extend(get_students_from_csv(csvfile)) | |
| 153 | + | |
| 154 | + if args.admin: | |
| 155 | + print('Adding user: 0, admin.') | |
| 156 | + students.append({'uid': '0', 'name': 'Admin'}) | |
| 157 | + | |
| 158 | + if args.add: | |
| 159 | + for uid, name in args.add: | |
| 160 | + print(f'Adding user: {uid}, {name}.') | |
| 161 | + students.append({'uid': uid, 'name': name}) | |
| 162 | + | |
| 163 | + # --- password hashing | |
| 164 | + if students: | |
| 165 | + print(f'Generating password hashes (bcrypt).') | |
| 166 | + if args.pw: # maybe set default password | |
| 167 | + for s in students: | |
| 168 | + s['pw'] = args.pw | |
| 169 | + | |
| 170 | + executor = ThreadPoolExecutor() | |
| 171 | + event_loop = asyncio.get_event_loop() | |
| 172 | + event_loop.run_until_complete(hash_all_passwords(executor, students)) | |
| 173 | + event_loop.close() | |
| 174 | + | |
| 175 | + # --- database stuff | |
| 176 | + print(f'Using database: ', args.db) | |
| 177 | + engine = sa.create_engine(f'sqlite:///{args.db}', echo=False) | |
| 178 | + Base.metadata.create_all(engine) # Criate schema if needed | |
| 179 | + Session = sa.orm.sessionmaker(bind=engine) | |
| 114 | 180 | session = Session() |
| 115 | 181 | |
| 116 | - session.add_all([Student(id=s['uid'], name=s['name'], password=s['pw']) | |
| 117 | - for s in students]) | |
| 118 | - | |
| 119 | - n = session.query(Student).count() | |
| 120 | - print(f'{n} user(s):') | |
| 121 | - | |
| 122 | - users = session.query(Student).order_by(Student.id).all() | |
| 123 | - print(f' {users[0].id:8} - {users[0].name} (administrator)') | |
| 124 | - if n > 1: | |
| 125 | - print(f' {users[1].id:8} - {users[1].name}') | |
| 126 | - if n > 3: | |
| 127 | - print(' ... ...') | |
| 128 | - if n > 2: | |
| 129 | - print(f' {users[-1].id:8} - {users[-1].name}') | |
| 130 | - | |
| 131 | -except sa.exc.IntegrityError: | |
| 132 | - print('!!! Integrity error !!!') | |
| 133 | - session.rollback() | |
| 134 | - | |
| 135 | -except Exception as e: | |
| 136 | - print(f'Error: Database "{args.db}" already exists?') | |
| 137 | - session.rollback() | |
| 138 | - raise e | |
| 139 | - # exit(1) | |
| 140 | - | |
| 141 | -else: | |
| 142 | - # --- end session --- | |
| 143 | - session.commit() | |
| 182 | + if students: | |
| 183 | + print(f'Inserting {len(students)}') | |
| 184 | + insert_students_into_db(session, students) | |
| 185 | + | |
| 186 | + # print(args.update) | |
| 187 | + for s in args.update: | |
| 188 | + print(f'Updating password of: {s}') | |
| 189 | + u = session.query(Student).get(s) | |
| 190 | + pw =(args.pw or s).encode('utf-8') | |
| 191 | + u.password = bcrypt.hashpw(pw, bcrypt.gensalt()) | |
| 192 | + session.commit() | |
| 193 | + | |
| 194 | + show_students_in_database(session, args.verbose) | |
| 195 | + | |
| 196 | + session.close() | ... | ... |
tools.py
| ... | ... | @@ -154,6 +154,8 @@ def load_yaml(filename, default=None): |
| 154 | 154 | |
| 155 | 155 | # --------------------------------------------------------------------------- |
| 156 | 156 | # Runs a script and returns its stdout parsed as yaml, or None on error. |
| 157 | +# The script is run in another process but this function blocks waiting | |
| 158 | +# for its termination. | |
| 157 | 159 | # --------------------------------------------------------------------------- |
| 158 | 160 | def run_script(script, stdin='', timeout=5): |
| 159 | 161 | script = path.expanduser(script) | ... | ... |