PIVOT 1.1.0

This commit is contained in:
marijn committed 2026-10-14 13:34:37 +02:00
commit 8a53234eb6
161 files changed
+31109

No files matched your search

+291
View File
@@ -0,0 +1,291 @@
"""
Gebruikers & rechten.
- Administrators: alle gebruikers, alle scholen, rol (Administrator/Gebruiker) wijzigen.
- Gebruikers met het recht 'gebruikersbeheer' in een school: gebruikers aanmaken en hun
rechten in DIE school(en) instellen. Ze kunnen geen administrators wijzigen.
Gebruikers staan in de centrale database (één account voor alle scholen); de rechten per
school in Gebruiker_rechten.
"""
from collections import OrderedDict
from flask import Blueprint, render_template, request, session, redirect, url_for, flash
from werkzeug.security import generate_password_hash
import rechten
from rechten import ADMIN, GEBRUIKER, VEILIGHEIDSGROEPEN, ONDERDELEN, is_admin
from database import central_query, central_insert
from tenancy import alle_scholen, school_op_code
admin_bp = Blueprint('admin', __name__)
MIN_WACHTWOORD = 8
# ------------------------------------------------------------------
# Hulpfuncties
# ------------------------------------------------------------------
def beheerbare_scholen():
"""Scholen waarvan de ingelogde gebruiker de rechten mag instellen."""
scholen = alle_scholen(ook_inactief=True)
if is_admin():
return scholen
gid = session.get('gebruiker_id')
if not gid:
return []
rows = central_query(
"SELECT r.School_ID FROM Gebruiker_rechten r JOIN Onderdelen o ON o.Onderdeel_ID = r.Onderdeel_ID "
"WHERE r.Gebruiker_ID = %s AND o.Code = 'gebruikersbeheer'", (gid,))
ids = {r['School_ID'] for r in rows}
return [s for s in scholen if s.id in ids]
def _toegang():
"""None als de gebruiker hier mag komen, anders een redirect."""
if not session.get('auth') or not session.get('school'):
return redirect(url_for('auth.login'))
if not is_admin() and not rechten.heeft_recht('gebruikersbeheer'):
flash("Je hebt geen rechten om gebruikers te beheren.", "error")
return redirect(url_for('main.dashboard'))
return None
def _aantal_admins(behalve=None):
rows = central_query("SELECT COUNT(*) AS n FROM Gebruikers WHERE Veiligheidsgroep = %s AND Actief = 1 "
"AND Gebruiker_ID <> %s", (ADMIN, behalve or 0))
return rows[0]['n']
def _onderdelen_per_categorie():
groepen = OrderedDict()
for code, naam, cat in ONDERDELEN:
groepen.setdefault(cat, []).append((code, naam))
return groepen
def _wachtwoord_ok(pw, pw2):
if pw != pw2:
return "De wachtwoorden komen niet overeen."
if len(pw) < MIN_WACHTWOORD:
return f"Het wachtwoord moet minstens {MIN_WACHTWOORD} tekens hebben."
return None
# ------------------------------------------------------------------
# Overzicht + nieuwe gebruiker
# ------------------------------------------------------------------
@admin_bp.route("/settings", methods=["GET"])
def settings():
weg = _toegang()
if weg:
return weg
users = central_query(
"SELECT g.Gebruiker_ID, g.Gebruikersnaam, g.Emailadres, g.Veiligheidsgroep, g.Actief, g.Last_login, "
" v.Naam AS Rol "
"FROM Gebruikers g JOIN Veiligheidsgroepen v ON v.Veiligheidsgroep = g.Veiligheidsgroep "
"ORDER BY g.Veiligheidsgroep, g.Gebruikersnaam")
if not is_admin():
users = [u for u in users if int(u['Veiligheidsgroep']) != ADMIN]
# Aantal rechten per gebruiker per school (voor het overzicht)
per = {}
for r in central_query("SELECT Gebruiker_ID, School_ID, COUNT(*) AS n FROM Gebruiker_rechten "
"GROUP BY Gebruiker_ID, School_ID"):
per.setdefault(r['Gebruiker_ID'], {})[r['School_ID']] = r['n']
scholen = {s.id: s for s in alle_scholen(ook_inactief=True)}
for u in users:
u['scholen'] = sorted(((scholen[sid].naam, n) for sid, n in per.get(u['Gebruiker_ID'], {}).items()
if sid in scholen), key=lambda x: (-x[1], x[0].lower()))
return render_template("admin_accounts.html", users=users, groepen=VEILIGHEIDSGROEPEN,
ADMIN=ADMIN, totaal_onderdelen=len(ONDERDELEN))
@admin_bp.route("/gebruiker/nieuw", methods=["POST"])
def nieuw():
weg = _toegang()
if weg:
return weg
naam = (request.form.get('username') or '').strip()
email = (request.form.get('email') or '').strip().lower() or None
pw = request.form.get('password') or ''
pw2 = request.form.get('password_confirm') or ''
groep = int(request.form.get('veiligheidsgroep') or GEBRUIKER)
if groep not in VEILIGHEIDSGROEPEN or (groep == ADMIN and not is_admin()):
groep = GEBRUIKER
fout = None
if not naam:
fout = "Vul een gebruikersnaam in."
elif not pw and not email:
fout = "Geef een wachtwoord, of een e-mailadres om met Google in te loggen."
elif pw:
fout = _wachtwoord_ok(pw, pw2)
if not fout and rechten.zoek_gebruiker(naam):
fout = f"Er bestaat al een gebruiker '{naam}'."
if not fout and email and rechten.zoek_gebruiker(email):
fout = f"Het e-mailadres {email} is al in gebruik."
if fout:
flash(fout, "error")
return redirect(url_for('admin.settings', nieuw=1))
nieuw_id = central_insert(
"INSERT INTO Gebruikers (Gebruikersnaam, Veiligheidsgroep, Passwoordhash, Emailadres, Actief) "
"VALUES (%s, %s, %s, %s, 1)",
(naam, groep, generate_password_hash(pw) if pw else None, email))
rechten.log(actie=f"Gebruiker {naam} aangemaakt")
if groep == ADMIN:
flash(f"Administrator '{naam}' aangemaakt.", "success")
return redirect(url_for('admin.settings'))
flash(f"Gebruiker '{naam}' aangemaakt. Vink nu aan wat hij per school mag.", "success")
return redirect(url_for('admin.gebruiker_rechten', gid=nieuw_id))
# ------------------------------------------------------------------
# Eén gebruiker: account (gegevens, rol, wachtwoord)
# ------------------------------------------------------------------
def _gebruiker_of_terug(gid):
"""(gebruiker, None) of (None, redirect)."""
u = rechten.gebruiker_op_id(gid)
if not u:
flash("Onbekende gebruiker.", "error")
return None, redirect(url_for('admin.settings'))
if int(u['Veiligheidsgroep']) == ADMIN and not is_admin():
flash("Enkel een administrator kan een administrator wijzigen.", "error")
return None, redirect(url_for('admin.settings'))
return u, None
@admin_bp.route("/gebruiker/<int:gid>", methods=["GET", "POST"])
def gebruiker(gid):
weg = _toegang()
if weg:
return weg
u, weg = _gebruiker_of_terug(gid)
if weg:
return weg
zelf = gid == session.get('gebruiker_id')
if request.method == 'POST':
return _bewaar_account(u, zelf)
return render_template("admin_gebruiker.html", u=u, groepen=VEILIGHEIDSGROEPEN, ADMIN=ADMIN, zelf=zelf,
min_wachtwoord=MIN_WACHTWOORD)
# ------------------------------------------------------------------
# Eén gebruiker: rechten, één school tegelijk
# ------------------------------------------------------------------
@admin_bp.route("/gebruiker/<int:gid>/rechten", methods=["GET", "POST"])
def gebruiker_rechten(gid):
weg = _toegang()
if weg:
return weg
u, weg = _gebruiker_of_terug(gid)
if weg:
return weg
scholen = beheerbare_scholen()
per_id = {s.id: s for s in scholen}
aantallen = {}
for r in central_query("SELECT School_ID, COUNT(*) AS n FROM Gebruiker_rechten WHERE Gebruiker_ID = %s "
"GROUP BY School_ID", (gid,)):
aantallen[r['School_ID']] = r['n']
if request.method == 'POST':
sid = request.form.get('school_id', type=int)
if sid not in per_id:
flash("Je mag de rechten in deze school niet instellen.", "error")
return redirect(url_for('admin.gebruiker_rechten', gid=gid))
codes = [c for c in rechten.CODES if request.form.get(f"r|{c}")]
doel = [sid]
if request.form.get('ook_alle'):
doel = list(per_id)
else:
doel += [int(x) for x in request.form.getlist('ook') if x.isdigit() and int(x) in per_id]
doel = list(dict.fromkeys(doel))
door = session.get('user')
for school_id in doel:
rechten.zet_rechten(gid, school_id, codes, door=door)
if len(doel) == 1:
rechten.log(actie=f"Rechten van {u['Gebruikersnaam']} in {per_id[sid].naam} gewijzigd")
flash(f"Rechten van {u['Gebruikersnaam']} in {per_id[sid].naam} opgeslagen.", "success")
else:
rechten.log(actie=f"Rechten van {u['Gebruikersnaam']} in {len(doel)} scholen gewijzigd")
flash(f"Rechten van {u['Gebruikersnaam']} opgeslagen in {len(doel)} scholen.", "success")
return redirect(url_for('admin.gebruiker_rechten', gid=gid, school=sid))
# Welke school tonen: gevraagd > huidige school > eerste school met rechten > eerste school
gekozen = request.args.get('school', type=int)
if gekozen not in per_id:
huidig = school_op_code(session.get('school') or '')
huidig_id = huidig.id if huidig else None
if huidig_id in per_id and (aantallen.get(huidig_id) or not aantallen):
gekozen = huidig_id
else:
met = [s.id for s in scholen if aantallen.get(s.id)]
gekozen = met[0] if met else (scholen[0].id if scholen else None)
lijst = sorted(scholen, key=lambda s: (not aantallen.get(s.id), s.naam.lower()))
return render_template("admin_rechten.html", u=u, scholen=lijst, gekozen=per_id.get(gekozen),
aantallen=aantallen, huidig=rechten.rechten_van(gid, gekozen) if gekozen else set(),
categorieen=_onderdelen_per_categorie(), ADMIN=ADMIN, totaal=len(ONDERDELEN))
def _bewaar_account(u, zelf):
gid = u['Gebruiker_ID']
email = (request.form.get('email') or '').strip().lower() or None
pw = request.form.get('new_password') or ''
pw2 = request.form.get('new_password_confirm') or ''
actief = 1 if request.form.get('actief') else 0
groep = int(u['Veiligheidsgroep'])
if is_admin() and request.form.get('veiligheidsgroep'):
groep = int(request.form['veiligheidsgroep'])
if groep not in VEILIGHEIDSGROEPEN:
groep = int(u['Veiligheidsgroep'])
terug = redirect(url_for('admin.gebruiker', gid=gid))
if zelf and (not actief or groep != int(u['Veiligheidsgroep'])):
flash("Je kan je eigen rol niet wijzigen en jezelf niet uitschakelen.", "error")
return terug
if int(u['Veiligheidsgroep']) == ADMIN and (groep != ADMIN or not actief) and _aantal_admins(behalve=gid) == 0:
flash("Er moet minstens één actieve administrator overblijven.", "error")
return terug
if email:
ander = rechten.zoek_gebruiker(email)
if ander and ander['Gebruiker_ID'] != gid:
flash(f"Het e-mailadres {email} is al in gebruik.", "error")
return terug
if pw:
fout = _wachtwoord_ok(pw, pw2)
if fout:
flash(fout, "error")
return terug
central_query("UPDATE Gebruikers SET Passwoordhash = %s WHERE Gebruiker_ID = %s",
(generate_password_hash(pw), gid), commit=True)
central_query("UPDATE Gebruikers SET Emailadres = %s, Veiligheidsgroep = %s, Actief = %s WHERE Gebruiker_ID = %s",
(email, groep, actief, gid), commit=True)
rechten.log(actie=f"Account {u['Gebruikersnaam']} gewijzigd")
flash(f"Account van {u['Gebruikersnaam']} opgeslagen.", "success")
return terug
@admin_bp.route("/gebruiker/<int:gid>/verwijderen", methods=["POST"])
def verwijderen(gid):
weg = _toegang()
if weg:
return weg
u = rechten.gebruiker_op_id(gid)
if not u:
return redirect(url_for('admin.settings'))
if not is_admin():
flash("Enkel een administrator kan gebruikers verwijderen.", "error")
elif gid == session.get('gebruiker_id'):
flash("Je kan jezelf niet verwijderen.", "error")
elif int(u['Veiligheidsgroep']) == ADMIN and _aantal_admins(behalve=gid) == 0:
flash("Er moet minstens één actieve administrator overblijven.", "error")
else:
central_query("DELETE FROM Gebruikers WHERE Gebruiker_ID = %s", (gid,), commit=True)
rechten.log(actie=f"Gebruiker {u['Gebruikersnaam']} verwijderd")
flash(f"Gebruiker '{u['Gebruikersnaam']}' is verwijderd.", "success")
return redirect(url_for('admin.settings'))
return redirect(url_for('admin.gebruiker', gid=gid))