Initial commit

This commit is contained in:
Kristof committed 2026-10-14 12:29:29 +02:00
commit 536ff43ce4
158 files changed
+30355

No files matched your search

+301
View File
@@ -0,0 +1,301 @@
import os
from flask import Flask, render_template, redirect, url_for
from config import Config
import tenancy
import rechten
from tenancy import school_op_code, school_voor_host, GeenSchoolFout, KoppelingOntbreekt
#-----------------------------------------------------
# Blueprints importeren
#-----------------------------------------------------
from blueprints.devices import devices_bp
from blueprints.students import students_bp
from blueprints.repairs import repairs_bp
from blueprints.auth import auth_bp, oauth # OAuth toegevoegd aan import
from blueprints.dashboard_bp import main_bp
from blueprints.google_admin import google_bp
from blueprints.admin import admin_bp
from blueprints.tools import tools_bp
from blueprints.co2_sensor import co2_sensor_bp
from blueprints.smartschool_bp import smartschool_bp
from blueprints.leerlingenaantal_bp import leerlingenaantal_bp
from blueprints.handleidingen_bp import handleidingen_bp, lijst as handleidingen_lijst, mag_lezen as handleidingen_mag_lezen
from blueprints.nieuw_schooljaar_accounts_bp import account_verplaatsen_bp
from blueprints.nieuw_schooljaar_toestellen_bp import toestellen_verplaatsen_bp
from blueprints.qr_code_bp import qr_code_bp
from blueprints.smartschool_activiteit_bp import ss_bp
from services.smartschool_api import SmartschoolService
from blueprints.bugreport_bp import bugreport_bp
from blueprints.bugreport_overzicht_bp import bugreport_overzicht_bp
from blueprints.signpost_devices_bp import signpost_devices_bp
from blueprints.google_licenties_bp import google_licenties_bp
from blueprints.gmail_delegatie_bp import gmail_delegatie_bp
from blueprints.afwezigheden_bp import afwezigheden_bp
from services.afwezigheden_service import AfwezighedenService
from blueprints.tweestaps_bp import tweestaps_bp
from services.tweestaps_service import TweestapsService
from blueprints.personeel_bp import personeel_bp
from blueprints.scholen_bp import scholen_bp
from blueprints.skore_bp import skore_bp
from services import skore_service
from services import personeel_service
from flask import session, request, flash, g
#------------------------------------------------------
# Services importeren
#------------------------------------------------------
from services.co2_service import CO2Scanner
def create_app():
# Maak de app aan
app = Flask(__name__)
app.config.from_object(Config)
if Config.PROXY_AANTAL:
# Achter een reverse proxy: het echte IP-adres komt uit X-Forwarded-For (enkel van die proxy's vertrouwd)
from werkzeug.middleware.proxy_fix import ProxyFix
n = Config.PROXY_AANTAL
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=n, x_proto=n, x_host=n)
# Handleidingen bevatten schermafbeeldingen (base64); Flask laat standaard maar 500 kB formulierdata toe
app.config['MAX_FORM_MEMORY_SIZE'] = 30 * 1024 * 1024
#------------------------------------------------------
# OAuth Initialisatie
#------------------------------------------------------
oauth.init_app(app)
# Google-login: clients worden per school (of gedeeld) aangemaakt in blueprints/auth.py
# Centrale tabellen (Scholen, Gebruikers, rechten, Logs, Bugs) aanmaken indien nodig
try:
tenancy.ensure_schema()
except Exception as e:
print(f"PIVOT: centrale database niet bereikbaar bij opstarten: {e}", flush=True)
# Databases van de scholen: tabelnamen met hoofdletters herstellen (Windows → Linux), zie services/tabelnamen.py
try:
from services.tabelnamen import herstel_alle_scholen
herstel_alle_scholen()
except Exception as e:
print(f"PIVOT: tabelnamen van de scholen nakijken mislukt: {e}", flush=True)
#------------------------------------------------------
# Blueprints registreren
#------------------------------------------------------
# Registreert alle url prefixen voor zichtbaarheid en om naamconflicten te vermijden
app.register_blueprint(auth_bp)
app.register_blueprint(main_bp)
app.register_blueprint(devices_bp, url_prefix='/devices')
app.register_blueprint(students_bp, url_prefix='/leerlingen')
app.register_blueprint(repairs_bp, url_prefix='/herstellingen')
app.register_blueprint(google_bp, url_prefix='/google')
app.register_blueprint(admin_bp, url_prefix='/admin')
app.register_blueprint(tools_bp, url_prefix='/tools')
app.register_blueprint(co2_sensor_bp, url_prefix='/co2')
app.register_blueprint(smartschool_bp, url_prefix='/ss')
#-------------------------
app.register_blueprint(leerlingenaantal_bp, url_prefix='/scholengroep')
from blueprints.gopro_bp import gopro_bp
app.register_blueprint(gopro_bp, url_prefix='/scholengroep/gopro')
from blueprints.facturatie_bp import facturatie_bp
app.register_blueprint(facturatie_bp, url_prefix='/leerlingfacturatie')
from blueprints.gmail_labels_bp import gmail_labels_bp
app.register_blueprint(gmail_labels_bp, url_prefix='/google/labels-verplaatsen')
from blueprints.handtekeningen_bp import handtekeningen_bp, handtekening_logo_bp
app.register_blueprint(handtekeningen_bp, url_prefix='/google/handtekeningen')
app.register_blueprint(handtekening_logo_bp)
from blueprints.contacten_bp import contacten_bp
from blueprints.gedeelde_drives_bp import gedeelde_drives_bp
app.register_blueprint(gedeelde_drives_bp, url_prefix='/google/gedeelde-drives')
from blueprints.basisonderwijs_bp import basisonderwijs_bp
app.register_blueprint(basisonderwijs_bp, url_prefix='/basisonderwijs')
app.register_blueprint(contacten_bp, url_prefix='/google/contacten')
from blueprints.internaat_bp import internaat_bp
app.register_blueprint(internaat_bp, url_prefix='/internaat')
from blueprints.sgr_groepen_bp import sgr_groepen_bp
app.register_blueprint(sgr_groepen_bp, url_prefix='/smartschool/sgr-groepen')
from blueprints.personeelsaantal_bp import personeelsaantal_bp
app.register_blueprint(personeelsaantal_bp, url_prefix='/personeelsaantallen')
#---------------------------
#-------------------------
app.register_blueprint(account_verplaatsen_bp, url_prefix='/nieuw_schooljaar')
#---------------------------
#----------------------------
app.register_blueprint(toestellen_verplaatsen_bp, url_prefix='/nieuw_schooljaar_toestellen')
#---------------------------
#---------------------------
app.register_blueprint(qr_code_bp, url_prefix='/qr_code')
#----------------------------
#-----------------------------
app.register_blueprint(ss_bp,url_prefix='/activiteit')
#------------------------------
#-----------------------------
app.register_blueprint(bugreport_bp,url_prefix='/bugreport')
app.register_blueprint(bugreport_overzicht_bp,url_prefix='/bugreport_overzicht')
#------------------------------
app.register_blueprint(signpost_devices_bp,url_prefix='/signpost_devices')
app.register_blueprint(google_licenties_bp, url_prefix='/google_licenties')
app.register_blueprint(gmail_delegatie_bp, url_prefix='/gmail_delegatie')
app.register_blueprint(afwezigheden_bp, url_prefix='/afwezigheden')
app.register_blueprint(tweestaps_bp, url_prefix='/2fa')
app.register_blueprint(personeel_bp, url_prefix='/personeel')
app.register_blueprint(scholen_bp, url_prefix='/scholen')
app.register_blueprint(skore_bp, url_prefix='/skore')
app.register_blueprint(handleidingen_bp, url_prefix='/handleidingen')
from blueprints.verzendlijsten_bp import verzendlijsten_bp
app.register_blueprint(verzendlijsten_bp, url_prefix='/google/verzendlijsten')
from blueprints.ipblokkade_bp import ipblokkade_bp
app.register_blueprint(ipblokkade_bp, url_prefix='/beheer/ip-blokkades')
#------------------------------------------------------
# Welke school? (per request)
#------------------------------------------------------
# Endpoints die zonder gekozen school werken
ZONDER_SCHOOL = ('static', 'index', 'auth.', 'scholen.')
# Publieke pagina's (zonder login): school via hostnaam of ?school=code
PUBLIEK = ('tools.',)
# IP-blokkades: als eerste controle, voor elke aanvraag (ook de loginpagina en statische bestanden)
@app.before_request
def ip_blokkade():
from services import ipblokkade
if request.endpoint == 'handtekening_logo.logo':
return None # het logo van de Gmail-handtekening moet voor iedereen (ook Google) bereikbaar zijn
if ipblokkade.controleer(request.remote_addr):
return ("<!doctype html><meta charset='utf-8'><title>Toegang geweigerd</title>"
"<div style='font-family:system-ui,sans-serif;max-width:480px;margin:15vh auto;text-align:center'>"
"<h1>Toegang geweigerd</h1><p>Je hebt geen toegang tot deze site.</p></div>", 403)
return None
@app.before_request
def bepaal_school():
ep = request.endpoint or ''
host_school = school_voor_host(request.host)
g.school = None
if session.get('auth'):
code = session.get('school')
school = school_op_code(code) if code else None
if code and school is None:
session.clear()
flash("Je school is niet (meer) actief in PIVOT. Log opnieuw in.", "error")
return redirect(url_for('auth.login'))
if host_school and school and host_school.code != school.code and not session.get('superbeheerder'):
# Sessie van een andere school op het adres van deze school → opnieuw inloggen
session.clear()
return redirect(url_for('auth.login'))
g.school = school
if school is None and not ep.startswith(ZONDER_SCHOOL):
# Superbeheerder zonder gekozen school
return redirect(url_for('scholen.overzicht'))
else:
g.school = host_school
if g.school is None and ep.startswith(PUBLIEK):
g.school = school_op_code(request.args.get('school'))
if g.school is None and ep.startswith(PUBLIEK):
flash("Kies eerst een school.", "error")
return redirect(url_for('auth.login'))
return None
@app.errorhandler(GeenSchoolFout)
def geen_school(e):
session.clear()
flash(str(e), "error")
return redirect(url_for('auth.login'))
@app.errorhandler(KoppelingOntbreekt)
def koppeling_ontbreekt(e):
flash(str(e), "error")
return redirect(url_for('main.dashboard'))
# Leerkrachten die via Google inloggen (groep 5) hebben enkel toegang tot de personeelsmodule
@app.before_request
def beperk_leerkrachten():
if session.get('security_group') != personeel_service.LEERKRACHT_GROEP:
return None
ep = request.endpoint or ''
if ep == 'static' or ep.startswith('personeel.') or ep.startswith('auth.'):
return None
if ep not in ('main.dashboard', 'index'):
flash("Je hebt enkel toegang tot de module afwezigheden en activiteiten.", "error")
return redirect(url_for('personeel.mijn'))
# Dagelijkse synchronisatie van afwezigheden (B-codes) uit Smartschool.
# In debugmodus start Flask twee processen; enkel het 'echte' proces start de taak.
# Eén achtergrondtaak per soort, die telkens alle scholen overloopt.
if not app.debug or os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
AfwezighedenService.start_scheduler()
TweestapsService.start_scheduler() # verlopen 2FA-uitzonderingen automatisch beëindigen
skore_service.start_scheduler() # geplande Skore-synchronisatie per school
from services import gopro_service
gopro_service.start_scheduler() # GO! Pro: 's nachts de groene naar de verzameltabel (als de schakelaar aan staat)
from services import handtekening_service
handtekening_service.start_scheduler() # Gmail-handtekeningen 's nachts opnieuw zetten (per school, schakelaar)
#------------------------------
@app.context_processor
def inject_school_info():
school = g.get('school')
info = {
'school': school,
'version': app.config.get('APP_VERSION'),
'is_superbeheerder': bool(session.get('superbeheerder')),
'school_name': school.naam if school else '',
'school_name_short': school.code if school else '',
'contact_email': school.get('CONTACT_EMAIL') if school else '',
'personeel_rollen': set(),
# Rechten: {% if mag('herstellingen') %} ... {% endif %}
'mag': rechten.heeft_recht,
'is_admin': rechten.is_admin(),
'andere_scholen': [],
'handleidingen_lijst': handleidingen_lijst,
'mag_handleidingen': handleidingen_mag_lezen, # functie: enkel opgehaald waar gebruikt (dashboard)
}
if school and session.get('auth'):
info['personeel_rollen'] = personeel_service.rollen(session)
info['andere_scholen'] = mijn_andere_scholen(school)
return info
def mijn_andere_scholen(school):
"""Andere scholen waarin de ingelogde gebruiker rechten heeft (om te wisselen)."""
try:
if rechten.is_admin():
return [s for s in tenancy.alle_scholen() if s.code != school.code]
gid = session.get('gebruiker_id')
if not gid:
return []
ids = rechten.scholen_met_rechten(gid)
return [s for s in tenancy.alle_scholen() if s.id in ids and s.code != school.code]
except Exception:
return []
# Logboek: elke bezochte pagina van een ingelogde gebruiker → tabel Logs (centrale database)
LOG_OVERSLAAN = ('static', 'co2_sensor.get_scan_status', 'co2_sensor.get_cleanup_status',
'afwezigheden.status')
LOGBOEK = Config.LOGBOEK
@app.after_request
def logboek(response):
if LOGBOEK and session.get('auth') and request.endpoint and request.endpoint not in LOG_OVERSLAAN \
and response.status_code < 500:
rechten.log(actie=request.method if request.method != 'GET' else None)
return response
# Definieert de root, wordt uitgevoerd zodra iemand op de startpagina komt
@app.route('/')
def index():
return redirect(url_for('auth.login'))
return app
# Start de applicatie
if __name__ == "__main__":
app = create_app()
# Debug aanzetten via config.ini › [algemeen] › debug = ja
app.run(debug=Config.DEBUG, host='0.0.0.0', port=5001)