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) | ... | ... |