Commit 1edf739ffb890e486abf956410c0cfc1bc5c373a

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

- nova versão do initdb, igual à do aprendizations

Showing 2 changed files with 161 additions and 67 deletions   Show diff stats
1 1
2 # BUGS 2 # BUGS
3 3
  4 +- o eventloop está a bloquear. correção do teste é blocking. usar threadpoolexecutor?
4 - se submissão falhar (aluno desconectado da rede) nao pode sair da página para nao perder o teste. possiveis solucoes: 5 - se submissão falhar (aluno desconectado da rede) nao pode sair da página para nao perder o teste. possiveis solucoes:
5 - botao submeter valida se esta online com um post willing_to_submit, se estiver online, mostra mensagem de confirmacao, caso contrario avisa que nao esta online. 6 - botao submeter valida se esta online com um post willing_to_submit, se estiver online, mostra mensagem de confirmacao, caso contrario avisa que nao esta online.
6 - periodicamente, cada user faz um post keep-alive. warning nos logs se offline. 7 - periodicamente, cada user faz um post keep-alive. warning nos logs se offline.
1 -#!/usr/bin/env python3.6  
2 -# -*- coding: utf-8 -*- 1 +#!/usr/bin/env python3
3 2
  3 +# base
4 import csv 4 import csv
5 import argparse 5 import argparse
6 import re 6 import re
7 import string 7 import string
8 -import sys  
9 -from sqlalchemy import create_engine  
10 -from sqlalchemy.orm import sessionmaker 8 +from concurrent.futures import ThreadPoolExecutor
11 9
  10 +# installed packages
  11 +import bcrypt
  12 +import sqlalchemy as sa
  13 +
  14 +# this project
12 from models import Base, Student, Test, Question 15 from models import Base, Student, Test, Question
13 16
  17 +
  18 +# ===========================================================================
  19 +# Parse command line options
  20 +def parse_commandline_arguments():
  21 + argparser = argparse.ArgumentParser(
  22 + formatter_class=argparse.ArgumentDefaultsHelpFormatter,
  23 + 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.')
  24 +
  25 + argparser.add_argument('csvfile',
  26 + nargs='*',
  27 + type=str,
  28 + default='',
  29 + help='CSV file to import (SIIUE)')
  30 +
  31 + argparser.add_argument('--db',
  32 + default='students.db',
  33 + type=str,
  34 + help='database file')
  35 +
  36 + argparser.add_argument('-A', '--admin',
  37 + action='store_true',
  38 + help='insert the admin user')
  39 +
  40 + argparser.add_argument('-a', '--add',
  41 + nargs=2,
  42 + action='append',
  43 + metavar=('uid', 'name'),
  44 + help='add new user')
  45 +
  46 + argparser.add_argument('-u', '--update',
  47 + nargs='+',
  48 + metavar='uid',
  49 + default=[],
  50 + help='users to update')
  51 +
  52 + argparser.add_argument('--pw',
  53 + default=None,
  54 + type=str,
  55 + help='set password for new and updated users')
  56 +
  57 + argparser.add_argument('-V', '--verbose',
  58 + action='store_true',
  59 + help='show all students in database')
  60 +
  61 + return argparser.parse_args()
  62 +
  63 +
  64 +# ===========================================================================
14 # SIIUE names have alien strings like "(TE)" and are sometimes capitalized 65 # SIIUE names have alien strings like "(TE)" and are sometimes capitalized
15 # We remove them so that students dont keep asking what it means 66 # We remove them so that students dont keep asking what it means
16 -def fix(name):  
17 - return string.capwords(re.sub('\(.*\)', '', name).strip()) 67 +def get_students_from_csv(filename):
  68 + try:
  69 + csvreader = csv.DictReader(open(filename, encoding='iso-8859-1'), delimiter=';', quotechar='"', skipinitialspace=True)
  70 + except EnvironmentError:
  71 + print(f'!!! Error. File "{filename}" not found !!!')
  72 + students = []
  73 + else:
  74 + students = [{
  75 + 'uid': s['N.º'],
  76 + 'name': string.capwords(re.sub('\(.*\)', '', s['Nome']).strip())
  77 + } for s in csvreader]
  78 +
  79 + return students
  80 +
18 81
19 # =========================================================================== 82 # ===========================================================================
20 -# Parse command line options  
21 -argparser = argparse.ArgumentParser(description='Create new database from a CSV file (SIIUE format)')  
22 -argparser.add_argument('--db', default='students.db', type=str, help='database filename')  
23 -argparser.add_argument('--demo', action='store_true', help='initialize database with a few fake students')  
24 -argparser.add_argument('csvfile', nargs='?', type=str, default='', help='CSV filename')  
25 -args = argparser.parse_args() 83 +# replace password by hash for a single student
  84 +def hashpw(student, pw=None):
  85 + print('.', end='', flush=True)
  86 + pw = (pw or student.get('pw', None) or student['uid']).encode('utf-8')
  87 + student['pw'] = bcrypt.hashpw(pw, bcrypt.gensalt())
  88 +
26 89
27 # =========================================================================== 90 # ===========================================================================
28 -engine = create_engine('sqlite:///{}'.format(args.db), echo=False) 91 +def insert_students_into_db(session, students):
  92 + try:
  93 + # --- start db session ---
  94 + session.add_all([Student(id=s['uid'], name=s['name'], password=s['pw'])
  95 + for s in students])
29 96
30 -# Criate schema if needed  
31 -Base.metadata.create_all(engine) 97 + session.commit()
32 98
33 -Session = sessionmaker(bind=engine) 99 + except sa.exc.IntegrityError:
  100 + print('!!! Integrity error. User(s) already in database. None inserted !!!\n')
  101 + session.rollback()
34 102
35 -# --- start session ---  
36 -try:  
37 - session = Session()  
38 103
39 - # add administrator  
40 - session.add(Student(id='0', name='Professor', password=''))  
41 -  
42 - # add students  
43 - if args.csvfile:  
44 - # from csv file if available  
45 - try:  
46 - csvreader = csv.DictReader(open(args.csvfile, encoding='iso-8859-1'), delimiter=';', quotechar='"', skipinitialspace=True)  
47 - except OSError:  
48 - print(f'Error: Can\'t open CSV file "{args.csvfile}".')  
49 - session.rollback()  
50 - sys.exit(1) 104 +# ===========================================================================
  105 +def show_students_in_database(session, verbose=False):
  106 + try:
  107 + users = session.query(Student).order_by(Student.id).all()
  108 + except:
  109 + raise
  110 + else:
  111 + n = len(users)
  112 + print(f'Registered users:')
  113 + if n == 0:
  114 + print(' -- none --')
51 else: 115 else:
52 - session.add_all([Student(id=r['N.º'], name=fix(r['Nome']), password='') for r in csvreader])  
53 - elif args.demo:  
54 - # add a few fake students  
55 - fakes = [  
56 - ['1915', 'Alan Turing'],  
57 - ['1938', 'Donald Knuth'],  
58 - ['1815', 'Ada Lovelace'],  
59 - ['1969', 'Linus Torvalds'],  
60 - ['1955', 'Tim Burners-Lee'],  
61 - ['1916', 'Claude Shannon'],  
62 - ['1903', 'John von Neumann'],  
63 - ]  
64 - session.add_all([Student(id=i, name=name, password='') for i,name in fakes])  
65 -  
66 - session.commit()  
67 -  
68 -except Exception:  
69 - print('Error: Database already exists.')  
70 - session.rollback()  
71 - sys.exit(1)  
72 -  
73 -else:  
74 - n = session.query(Student).count()  
75 - print(f'New database created: {args.db}\n{n} user(s) inserted:')  
76 -  
77 - users = session.query(Student).order_by(Student.id).all()  
78 - print(f' {users[0].id:8} - {users[0].name} (administrator)')  
79 - if n > 1:  
80 - print(f' {users[1].id:8} - {users[1].name}')  
81 - if n > 3:  
82 - print(' ... ...')  
83 - if n > 2:  
84 - print(f' {users[-1].id:8} - {users[-1].name}')  
85 -  
86 -# --- end session --- 116 + if verbose:
  117 + for u in users:
  118 + print(f'{u.id:>12} {u.name}')
  119 + else:
  120 + print(f'{users[0].id:>12} {users[0].name}')
  121 + if n > 1:
  122 + print(f'{users[1].id:>12} {users[1].name}')
  123 + if n > 3:
  124 + print(' | |')
  125 + if n > 2:
  126 + print(f'{users[-1].id:>12} {users[-1].name}')
  127 + print(f'Total: {n}.')
  128 +
  129 +
  130 +# ===========================================================================
  131 +if __name__=='__main__':
  132 + args = parse_commandline_arguments()
  133 +
  134 + # --- make list of students to insert/update
  135 + students = []
  136 +
  137 + for csvfile in args.csvfile:
  138 + print('Adding users from:', csvfile)
  139 + students.extend(get_students_from_csv(csvfile))
  140 +
  141 + if args.admin:
  142 + print('Adding user: 0, Admin.')
  143 + students.append({'uid': '0', 'name': 'Admin'})
  144 +
  145 + if args.add:
  146 + for uid, name in args.add:
  147 + print(f'Adding user: {uid}, {name}.')
  148 + students.append({'uid': uid, 'name': name})
  149 +
  150 + # --- password hashing
  151 + if students:
  152 + print(f'Generating password hashes (bcrypt).')
  153 + hash_func = lambda s: hashpw(s, args.pw)
  154 + with ThreadPoolExecutor() as executor:
  155 + executor.map(hash_func, students) # hashing in parallel
  156 +
  157 + print()
  158 +
  159 + # --- database stuff
  160 + print(f'Using database: ', args.db)
  161 + engine = sa.create_engine(f'sqlite:///{args.db}', echo=False)
  162 + Base.metadata.create_all(engine) # Criate schema if needed
  163 + Session = sa.orm.sessionmaker(bind=engine)
  164 + session = Session()
  165 +
  166 + if students:
  167 + print(f'Inserting {len(students)}')
  168 + insert_students_into_db(session, students)
  169 +
  170 + for s in args.update:
  171 + print(f'Updating password of: {s}')
  172 + u = session.query(Student).get(s)
  173 + pw =(args.pw or s).encode('utf-8')
  174 + u.password = bcrypt.hashpw(pw, bcrypt.gensalt())
  175 + session.commit()
  176 +
  177 + show_students_in_database(session, args.verbose)
  178 +
  179 + session.close()