PIVOT 1.1.0
This commit is contained in:
commit
8a53234eb6
161 files changed
+31109
No files matched your search
@@ -0,0 +1,292 @@
|
||||
"""
|
||||
Beheer van PIVOT vanaf de opdrachtregel.
|
||||
|
||||
python beheer.py init
|
||||
Maakt de tabellen van de centrale database aan (Scholen, Gebruikers, rechten, Logs, Bugs).
|
||||
|
||||
python beheer.py nieuwe-schooldatabase DATABASE
|
||||
Maakt een lege schooldatabase met de standaardstructuur (sql/school.sql).
|
||||
|
||||
python beheer.py gebruiker NAAM [--wachtwoord W] [--email E] [--admin]
|
||||
Maakt een PIVOT-gebruiker aan, of zet een nieuw wachtwoord / maakt hem administrator.
|
||||
|
||||
python beheer.py rechten NAAM SCHOOLCODE [onderdeel ...|alles|geen]
|
||||
Toont of zet de rechten van een gebruiker in een school.
|
||||
|
||||
python beheer.py importeer-config OUDE_CONFIG.ini [--google-sleutel sleutel.json] [--code kamxm] [--db Pivot_26-27]
|
||||
Zet een school uit een config.ini van PIVOT 1.0.x in het scholenregister
|
||||
(alle wachtwoorden en koppelingen worden versleuteld overgenomen).
|
||||
|
||||
python beheer.py lijst
|
||||
Toont alle scholen.
|
||||
|
||||
python beheer.py kopieer-school BRONCODE NIEUWE_DATABASE
|
||||
Maakt een nieuwe database als volledige kopie (tabellen + gegevens) van de database van BRONCODE.
|
||||
Registreer ze daarna als school via PIVOT (Nieuwe school › Database aanmaken? › Nee, bestaat al).
|
||||
|
||||
python beheer.py nieuwe-sleutel
|
||||
Toont een nieuwe versleutelingssleutel (voor PIVOT_SLEUTEL).
|
||||
"""
|
||||
import argparse
|
||||
import configparser
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from cryptography.fernet import Fernet
|
||||
|
||||
# Oude config.ini → velden in het scholenregister
|
||||
MAPPING = {
|
||||
('schoolgegevens', 'admin_email'): 'ADMIN_EMAIL',
|
||||
('schooljaar', 'huidig_schooljaar'): 'SCHOOLYEAR',
|
||||
('google', 'ou_nieuwe_lln'): 'STUDENT_OU',
|
||||
('google', 'google_sso_clientid'): 'GOOGLE_SSO_CLIENTID',
|
||||
('google', 'google_sso_clientsecret'): 'GOOGLE_SSO_CLIENTSECRET',
|
||||
('google', 'google_smtp_server'): 'GOOGLE_SMTP_SERVER',
|
||||
('google', 'google_smtp_port'): 'GOOGLE_SMTP_PORT',
|
||||
('google', 'google_sender_adres'): 'GOOGLE_SMTP_SENDER_adres',
|
||||
('google', 'google_email_passsword'): 'GOOGLE_SMTP_EMAIL_PASSWORD',
|
||||
('google', 'google_api_scopes'): 'GOOGLE_API_SCOPES_RAW',
|
||||
('google', 'tweestaps_uitzondering_groep'): 'TWEESTAPS_UITZONDERING_GROEP',
|
||||
('google', 'tweestaps_ous'): 'TWEESTAPS_OUS',
|
||||
('google', 'tweestaps_groepen'): 'TWEESTAPS_GROEPEN_RAW',
|
||||
('informat', 'client_id'): 'INFORMAT_ID',
|
||||
('informat', 'client_secret'): 'INFORMAT_SECRET',
|
||||
('informat', 'intitute_number'): 'INFORMAT_INST_NUMBER',
|
||||
('informat', 'autho_url'): 'INFORMAT_AUTH_URL',
|
||||
('informat', 'api_base'): 'INFORMAT_API_BASE',
|
||||
('informat', 'scope'): 'INFORMAT_SCOPE',
|
||||
('smartschool', 'ss_client_id'): 'SS_CLIENT_ID',
|
||||
('smartschool', 'ss_client_secret'): 'SS_CLIENT_SECRET',
|
||||
('smartschool', 'ss_api_pw'): 'SS_API_PW',
|
||||
('smartschool', 'ss_api_platform'): 'SS_API_PLATFORM',
|
||||
('smartschool', 'afwezigheid_codes'): 'AFWEZIGHEID_CODES',
|
||||
('smartschool', 'afwezigheid_sync_uur'): 'AFWEZIGHEID_SYNC_UUR',
|
||||
('CO2_sensor', 'co2_netwerk'): 'CO2_NETWORK',
|
||||
('CO2_sensor', 'co2_beginadres'): 'CO2_START_ADDR',
|
||||
('CO2_sensor', 'co2_eindadres'): 'CO2_END_ADDR',
|
||||
('CO2_sensor', 'co2_waardedrempel'): 'CO2_THRESHOLD',
|
||||
('CO2_sensor', 'co2_scan_interval'): 'CO2_INTERVAL',
|
||||
('personeel', 'personeel_ous'): 'PERSONEEL_OUS',
|
||||
('personeel', 'directie'): 'PERSONEEL_DIRECTIE',
|
||||
('personeel', 'secretariaat'): 'PERSONEEL_SECRETARIAAT',
|
||||
('personeel', 'secretariaat_mail'): 'PERSONEEL_SECRETARIAAT_MAIL',
|
||||
('personeel', 'mails_versturen'): 'PERSONEEL_MAILS',
|
||||
('m4s', 'm4s_base_data_url'): 'M4S_BASE_URL',
|
||||
('m4s', 'm4s_auth_url'): 'M4S_AUTH_URL',
|
||||
('m4s', 'm4s_clientid'): 'M4S_CLIENT_ID',
|
||||
('m4s', 'm4s_clientsecret'): 'M4S_CLIENT_SECRET',
|
||||
('m4s', 'm4s_institutionguid'): 'M4S_INSTITUTIONGUID',
|
||||
('m4s', 'm4s_repair_location_type'): 'M4S_REPAIR_LOCATION_TYPE',
|
||||
('m4s', 'm4s_repair_status'): 'M4S_REPAIR_STATUS',
|
||||
('m4s', 'm4s_truth_statement'): 'M4S_TRUTH_STATEMENT',
|
||||
('m4s', 'm4s_repair_at_institution'): 'M4S_REPAIR_AT_INTITUTION',
|
||||
('m4s', 'm4s_contact_first_name'): 'M4S_CONTACT_FIRST_NAME',
|
||||
('m4s', 'm4s_contact_last_name'): 'M4S_CONTACT_LAST_NAME',
|
||||
('m4s', 'm4s_contact_email'): 'M4S_CONTACT_EMAIL',
|
||||
('m4s', 'm4s_contact_phone'): 'M4S_CONTACT_PHONE',
|
||||
('m4s', 'm4s_repair_street'): 'M4S_REPAIR_STREET',
|
||||
('m4s', 'm4s_repair_streetnumber'): 'M4S_REPAIR_STREETNUMBER',
|
||||
('m4s', 'm4s_repair_zipcode'): 'M4S_REPAIR_ZIPCODE',
|
||||
('m4s', 'm4s_repair_city'): 'M4S_REPAIIR_CITY',
|
||||
('m4s', 'm4s_repair_countrycode'): 'M4S_REPAIR_COUNTRYCODE',
|
||||
('m4s', 'm4s_language_code'): 'M4S_LANGUAGE_CODE',
|
||||
('m4s', 'm4s_billing_first_name'): 'M4S_BILLING_FIRST_NAME',
|
||||
('m4s', 'm4s_billing_last_name'): 'M4S_BILLING_LAST_NAME',
|
||||
('m4s', 'm4s_billing_email'): 'M4S_BILLING_EMAIL',
|
||||
('m4s', 'm4s_billing_phone'): 'M4S_BILLING_PHONE',
|
||||
}
|
||||
|
||||
|
||||
def lees_oude_config(pad):
|
||||
cp = configparser.ConfigParser()
|
||||
if not cp.read(pad, encoding='utf-8'):
|
||||
sys.exit(f"Kan {pad} niet lezen.")
|
||||
waarden = {}
|
||||
for (sectie, key), veld in MAPPING.items():
|
||||
if cp.has_option(sectie, key):
|
||||
v = cp.get(sectie, key).strip()
|
||||
if v and v != 'None':
|
||||
waarden[veld] = v
|
||||
# Smartschool-platform zonder https:// en pad
|
||||
if 'SS_API_PLATFORM' in waarden:
|
||||
waarden['SS_API_PLATFORM'] = waarden['SS_API_PLATFORM'].replace('https://', '').replace('http://', '').split('/')[0]
|
||||
return cp, waarden
|
||||
|
||||
|
||||
def cmd_importeer(args):
|
||||
import tenancy
|
||||
from config import Config
|
||||
cp, waarden = lees_oude_config(args.config)
|
||||
code = (args.code or cp.get('schoolgegevens', 'school_afkorting', fallback='')).strip().lower()
|
||||
naam = cp.get('schoolgegevens', 'schoolnaam', fallback=code).strip()
|
||||
domein = cp.get('schoolgegevens', 'schooldomein', fallback='').strip().lower()
|
||||
db_naam = args.db or cp.get('database', 'database_name', fallback='').strip()
|
||||
if not code or not domein or not db_naam:
|
||||
sys.exit("Code, schooldomein of databasenaam ontbreekt (gebruik --code / --db).")
|
||||
|
||||
# Eigen databaseserver/gebruiker enkel overnemen als die verschilt van de nieuwe config.ini
|
||||
for key, veld, standaard in [('host', 'DB_HOST', Config.DB_HOST), ('user', 'DB_USER', Config.DB_USER),
|
||||
('password', 'DB_PASSWORD', Config.DB_PASSWORD)]:
|
||||
v = cp.get('database', key, fallback='').strip()
|
||||
if v and v != standaard:
|
||||
waarden[veld] = v
|
||||
|
||||
# Google-serviceaccount
|
||||
sleutel = None
|
||||
kandidaten = [args.google_sleutel] if args.google_sleutel else [
|
||||
cp.get('google', 'location_linux', fallback=''), cp.get('google', 'location', fallback='')]
|
||||
for pad in kandidaten:
|
||||
if pad and os.path.exists(pad):
|
||||
with open(pad, encoding='utf-8') as f:
|
||||
sleutel = json.load(f)
|
||||
print(f"Google-sleutel gelezen uit {pad}")
|
||||
break
|
||||
if not sleutel:
|
||||
print("⚠ Geen Google-sleutel gevonden; upload hem later via /scholen (of gebruik --google-sleutel).")
|
||||
|
||||
tenancy.ensure_schema()
|
||||
bestaand = tenancy.school_op_code(code, ook_inactief=True)
|
||||
if bestaand and not args.overschrijven:
|
||||
sys.exit(f"School '{code}' bestaat al. Gebruik --overschrijven om bij te werken.")
|
||||
tenancy.opslaan(code, naam, domein, '', db_naam, True, waarden, sleutel,
|
||||
gebruiker='beheer.py', nieuw=not bestaand)
|
||||
print(f"✓ School {naam} ({code}) opgeslagen: domein {domein}, database {db_naam}, "
|
||||
f"{len(waarden)} instellingen{', Google-sleutel' if sleutel else ''}.")
|
||||
|
||||
|
||||
def cmd_lijst(args):
|
||||
import tenancy
|
||||
tenancy.ensure_schema()
|
||||
scholen = tenancy.alle_scholen(ook_inactief=True)
|
||||
if not scholen:
|
||||
print("Nog geen scholen.")
|
||||
for s in scholen:
|
||||
print(f"{s.code:<12} {s.naam:<40} {','.join(s.domeinen):<30} db={s.db_naam:<20} "
|
||||
f"{'actief' if s.actief else 'INACTIEF'}{' google✓' if s.google_sleutel else ''}")
|
||||
|
||||
|
||||
def cmd_kopieer(args):
|
||||
import tenancy
|
||||
from blueprints.scholen_bp import kopieer_school_db
|
||||
bron = tenancy.school_op_code(args.bron, ook_inactief=True)
|
||||
if not bron:
|
||||
sys.exit(f"Onbekende school {args.bron}")
|
||||
ok, msg = kopieer_school_db(bron, args.database, {})
|
||||
print(msg)
|
||||
sys.exit(0 if ok else 1)
|
||||
|
||||
|
||||
def cmd_nieuwe_schooldatabase(args):
|
||||
from blueprints.scholen_bp import maak_schooldatabase
|
||||
ok, msg = maak_schooldatabase(args.database)
|
||||
print(msg)
|
||||
sys.exit(0 if ok else 1)
|
||||
|
||||
|
||||
def cmd_gebruiker(args):
|
||||
import getpass
|
||||
import tenancy
|
||||
import rechten
|
||||
from database import central_query
|
||||
from werkzeug.security import generate_password_hash
|
||||
tenancy.ensure_schema()
|
||||
pw = args.wachtwoord
|
||||
bestaand = rechten.zoek_gebruiker(args.naam)
|
||||
if pw is None and not bestaand:
|
||||
pw = getpass.getpass("Wachtwoord: ")
|
||||
groep = rechten.ADMIN if args.admin else None
|
||||
if bestaand:
|
||||
gid = bestaand['Gebruiker_ID']
|
||||
if pw:
|
||||
central_query("UPDATE Gebruikers SET Passwoordhash = %s WHERE Gebruiker_ID = %s",
|
||||
(generate_password_hash(pw), gid), commit=True)
|
||||
if args.email:
|
||||
central_query("UPDATE Gebruikers SET Emailadres = %s WHERE Gebruiker_ID = %s",
|
||||
(args.email.lower(), gid), commit=True)
|
||||
if groep:
|
||||
central_query("UPDATE Gebruikers SET Veiligheidsgroep = %s, Actief = 1 WHERE Gebruiker_ID = %s",
|
||||
(groep, gid), commit=True)
|
||||
print(f"✓ Gebruiker {bestaand['Gebruikersnaam']} bijgewerkt.")
|
||||
else:
|
||||
central_query("INSERT INTO Gebruikers (Gebruikersnaam, Veiligheidsgroep, Passwoordhash, Emailadres) "
|
||||
"VALUES (%s, %s, %s, %s)",
|
||||
(args.naam, groep or rechten.GEBRUIKER, generate_password_hash(pw) if pw else None,
|
||||
args.email.lower() if args.email else None), commit=True)
|
||||
print(f"✓ Gebruiker {args.naam} aangemaakt ({'Administrator' if groep else 'Gebruiker'}).")
|
||||
|
||||
|
||||
def cmd_rechten(args):
|
||||
import tenancy
|
||||
import rechten
|
||||
g = rechten.zoek_gebruiker(args.naam)
|
||||
school = tenancy.school_op_code(args.school, ook_inactief=True)
|
||||
if not g or not school:
|
||||
sys.exit("Onbekende gebruiker of school.")
|
||||
if args.onderdelen:
|
||||
if args.onderdelen == ['alles']:
|
||||
codes = rechten.CODES
|
||||
elif args.onderdelen == ['geen']:
|
||||
codes = []
|
||||
else:
|
||||
onbekend = [c for c in args.onderdelen if c not in rechten.CODES]
|
||||
if onbekend:
|
||||
sys.exit(f"Onbekende onderdelen: {', '.join(onbekend)}\nMogelijk: {', '.join(rechten.CODES)}")
|
||||
codes = args.onderdelen
|
||||
rechten.zet_rechten(g['Gebruiker_ID'], school.id, codes, door='beheer.py')
|
||||
huidig = rechten.rechten_van(g['Gebruiker_ID'], school.id)
|
||||
print(f"{g['Gebruikersnaam']} in {school.naam}:")
|
||||
for code, naam, cat in rechten.ONDERDELEN:
|
||||
print(f" [{'x' if code in huidig else ' '}] {code:<26} {naam}")
|
||||
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser(description="PIVOT beheer")
|
||||
sub = p.add_subparsers(dest='cmd', required=True)
|
||||
sub.add_parser('init')
|
||||
i = sub.add_parser('importeer-config')
|
||||
i.add_argument('config')
|
||||
i.add_argument('--code')
|
||||
i.add_argument('--db')
|
||||
i.add_argument('--google-sleutel')
|
||||
i.add_argument('--overschrijven', action='store_true')
|
||||
sub.add_parser('lijst')
|
||||
k = sub.add_parser('kopieer-school', aliases=['kopieer-schema'])
|
||||
k.add_argument('bron')
|
||||
k.add_argument('database')
|
||||
sub.add_parser('nieuwe-sleutel')
|
||||
n = sub.add_parser('nieuwe-schooldatabase')
|
||||
n.add_argument('database')
|
||||
gb = sub.add_parser('gebruiker')
|
||||
gb.add_argument('naam')
|
||||
gb.add_argument('--wachtwoord')
|
||||
gb.add_argument('--email')
|
||||
gb.add_argument('--admin', action='store_true')
|
||||
r = sub.add_parser('rechten')
|
||||
r.add_argument('naam')
|
||||
r.add_argument('school')
|
||||
r.add_argument('onderdelen', nargs='*')
|
||||
args = p.parse_args()
|
||||
|
||||
if args.cmd == 'nieuwe-sleutel':
|
||||
print(Fernet.generate_key().decode())
|
||||
elif args.cmd == 'init':
|
||||
import tenancy
|
||||
tenancy.ensure_schema()
|
||||
print("✓ Centrale tabellen aangemaakt (of bestonden al).")
|
||||
elif args.cmd == 'importeer-config':
|
||||
cmd_importeer(args)
|
||||
elif args.cmd == 'lijst':
|
||||
cmd_lijst(args)
|
||||
elif args.cmd in ('kopieer-school', 'kopieer-schema'):
|
||||
cmd_kopieer(args)
|
||||
elif args.cmd == 'nieuwe-schooldatabase':
|
||||
cmd_nieuwe_schooldatabase(args)
|
||||
elif args.cmd == 'gebruiker':
|
||||
cmd_gebruiker(args)
|
||||
elif args.cmd == 'rechten':
|
||||
cmd_rechten(args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in new issue
Block a user