Migrate assessments to class-based model
All checks were successful
Build & Push / Build & Push image (push) Successful in 42s
All checks were successful
Build & Push / Build & Push image (push) Successful in 42s
- Dropped the existing assessments table to remove user_id association. - Created a new assessments table linked to classes (class_id) allowing multiple teachers to share assessments. - Added necessary indexes for performance optimization. - Downgrade functionality to revert back to user-based assessments if needed.
This commit is contained in:
60
backend/migrations/versions/0004_class_based_assessments.py
Normal file
60
backend/migrations/versions/0004_class_based_assessments.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""assessments: herstructureer naar klasgebonden model
|
||||
|
||||
Revision ID: 0004
|
||||
Revises: 0003
|
||||
Create Date: 2026-03-05
|
||||
|
||||
Wijziging: assessments zijn niet langer gekoppeld aan een individuele
|
||||
leerkracht (user_id) maar aan een klas (class_id). Meerdere leerkrachten
|
||||
van dezelfde klas delen één set beoordelingen.
|
||||
|
||||
OPGELET: dit dropt de bestaande assessments tabel — testdata gaat verloren.
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = '0004'
|
||||
down_revision = '0003'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# Drop oude tabel volledig (testomgeving — geen productiedata)
|
||||
op.execute("DROP TABLE IF EXISTS assessments CASCADE")
|
||||
|
||||
# Nieuwe tabel: klasgebonden, geen user_id
|
||||
op.execute("""
|
||||
CREATE TABLE assessments (
|
||||
id SERIAL PRIMARY KEY,
|
||||
class_id INTEGER NOT NULL REFERENCES classes(id) ON DELETE CASCADE,
|
||||
school_year_id INTEGER NOT NULL REFERENCES school_years(id) ON DELETE CASCADE,
|
||||
vak_id VARCHAR(50) NOT NULL,
|
||||
goal_id VARCHAR(50) NOT NULL,
|
||||
status VARCHAR(10) NOT NULL,
|
||||
opmerking VARCHAR(500),
|
||||
updated_at TIMESTAMP DEFAULT NOW(),
|
||||
UNIQUE(class_id, school_year_id, vak_id, goal_id)
|
||||
)
|
||||
""")
|
||||
op.execute("CREATE INDEX IF NOT EXISTS ix_assessments_class_year ON assessments(class_id, school_year_id)")
|
||||
op.execute("CREATE INDEX IF NOT EXISTS ix_assessments_vak ON assessments(vak_id)")
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.execute("DROP TABLE IF EXISTS assessments CASCADE")
|
||||
# Zet terug naar user-gebaseerde tabel (zonder data)
|
||||
op.execute("""
|
||||
CREATE TABLE assessments (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
school_id INTEGER NOT NULL REFERENCES schools(id) ON DELETE CASCADE,
|
||||
school_year_id INTEGER NOT NULL REFERENCES school_years(id) ON DELETE CASCADE,
|
||||
vak_id VARCHAR(50) NOT NULL,
|
||||
goal_id VARCHAR(50) NOT NULL,
|
||||
status VARCHAR(10) NOT NULL,
|
||||
opmerking VARCHAR(500),
|
||||
updated_at TIMESTAMP DEFAULT NOW(),
|
||||
UNIQUE(user_id, school_year_id, vak_id, goal_id)
|
||||
)
|
||||
""")
|
||||
@@ -151,8 +151,8 @@ class Assessment(db.Model):
|
||||
__tablename__ = 'assessments'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('users.id', ondelete='CASCADE'), nullable=False)
|
||||
school_id = db.Column(db.Integer, db.ForeignKey('schools.id', ondelete='CASCADE'), nullable=False)
|
||||
# Klasgebonden — geen koppeling aan individuele leerkracht
|
||||
class_id = db.Column(db.Integer, db.ForeignKey('classes.id', ondelete='CASCADE'), nullable=False)
|
||||
school_year_id = db.Column(db.Integer, db.ForeignKey('school_years.id', ondelete='CASCADE'), nullable=False)
|
||||
vak_id = db.Column(db.String(50), nullable=False)
|
||||
goal_id = db.Column(db.String(50), nullable=False)
|
||||
@@ -160,17 +160,18 @@ class Assessment(db.Model):
|
||||
opmerking = db.Column(db.String(500), nullable=True)
|
||||
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
user = db.relationship('User')
|
||||
school = db.relationship('School')
|
||||
klas = db.relationship('Class')
|
||||
school_year = db.relationship('SchoolYear', back_populates='assessments')
|
||||
|
||||
__table_args__ = (
|
||||
db.UniqueConstraint('user_id', 'school_year_id', 'vak_id', 'goal_id'),
|
||||
db.UniqueConstraint('class_id', 'school_year_id', 'vak_id', 'goal_id',
|
||||
name='uq_assessment_class_year_vak_goal'),
|
||||
)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'class_id': self.class_id,
|
||||
'vak_id': self.vak_id,
|
||||
'goal_id': self.goal_id,
|
||||
'status': self.status,
|
||||
|
||||
@@ -19,18 +19,36 @@ def director_required(f):
|
||||
return decorated
|
||||
|
||||
|
||||
def get_active_year(school_id=None):
|
||||
"""Geeft het globaal actief schooljaar terug (school_id wordt genegeerd)."""
|
||||
def get_active_year():
|
||||
"""Geeft het globaal actief schooljaar terug."""
|
||||
return SchoolYear.query.filter_by(school_id=None, is_active=True).first()
|
||||
|
||||
|
||||
def check_class_access(class_id):
|
||||
"""
|
||||
Geeft de klas terug als de huidige gebruiker er toegang toe heeft.
|
||||
- Leerkrachten: enkel hun eigen klassen (via teacher_classes).
|
||||
- Directeur en hoger: alle klassen van hun school.
|
||||
- Geeft False terug als de klas niet bestaat.
|
||||
- Geeft None terug als de gebruiker geen toegang heeft.
|
||||
"""
|
||||
klas = Class.query.filter_by(id=class_id).first()
|
||||
if not klas:
|
||||
return False
|
||||
if klas.school_id != current_user.school_id:
|
||||
return None
|
||||
if current_user.is_teacher:
|
||||
if not any(c.id == class_id for c in current_user.classes):
|
||||
return None
|
||||
return klas
|
||||
|
||||
|
||||
# ── Doelen (statische JSON bestanden) ─────────────────────────────────────────
|
||||
|
||||
@api_bp.route('/doelen/index')
|
||||
@login_required
|
||||
def doelen_index():
|
||||
data = load_index()
|
||||
# Altijd een geldig object teruggeven — lege vakkenlijst is geen fout
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@@ -50,16 +68,30 @@ def doelen_vak(vak_id):
|
||||
@api_bp.route('/assessments', methods=['GET'])
|
||||
@login_required
|
||||
def get_assessments():
|
||||
if not current_user.school_id:
|
||||
"""Haal beoordelingen op voor een klas (en optioneel een vak)."""
|
||||
class_id_str = request.args.get('class_id')
|
||||
if not class_id_str:
|
||||
return jsonify({'assessments': []})
|
||||
school_year = get_active_year(current_user.school_id)
|
||||
|
||||
try:
|
||||
class_id = int(class_id_str)
|
||||
except ValueError:
|
||||
return jsonify({'error': 'Ongeldig class_id'}), 400
|
||||
|
||||
klas = check_class_access(class_id)
|
||||
if klas is False:
|
||||
return jsonify({'error': 'Klas niet gevonden'}), 404
|
||||
if klas is None:
|
||||
return jsonify({'error': 'Geen toegang tot deze klas'}), 403
|
||||
|
||||
school_year = get_active_year()
|
||||
if not school_year:
|
||||
return jsonify({'assessments': []})
|
||||
|
||||
year_id = request.args.get('year_id', school_year.id)
|
||||
vak_id = request.args.get('vak_id')
|
||||
|
||||
query = Assessment.query.filter_by(user_id=current_user.id, school_year_id=year_id)
|
||||
query = Assessment.query.filter_by(class_id=class_id, school_year_id=year_id)
|
||||
if vak_id:
|
||||
query = query.filter_by(vak_id=vak_id)
|
||||
|
||||
@@ -68,30 +100,39 @@ def get_assessments():
|
||||
|
||||
@api_bp.route('/assessments', methods=['POST'])
|
||||
@login_required
|
||||
@limiter.limit('120 per minute') # max 2 per seconde per gebruiker
|
||||
@limiter.limit('120 per minute')
|
||||
def save_assessment():
|
||||
data = request.get_json() or {}
|
||||
class_id = data.get('class_id')
|
||||
vak_id = (data.get('vak_id') or '').strip()
|
||||
goal_id = (data.get('goal_id') or '').strip()
|
||||
status = (data.get('status') or '').strip()
|
||||
opmerking = (data.get('opmerking') or '').strip()[:500]
|
||||
|
||||
if not vak_id or not goal_id:
|
||||
return jsonify({'error': 'vak_id en goal_id zijn verplicht'}), 400
|
||||
if not class_id or not vak_id or not goal_id:
|
||||
return jsonify({'error': 'class_id, vak_id en goal_id zijn verplicht'}), 400
|
||||
if status not in ('groen', 'oranje', 'roze', ''):
|
||||
return jsonify({'error': 'Ongeldige status — gebruik groen, oranje, roze of leeg'}), 400
|
||||
# Sanitiseer input — voorkomt oversized data in DB
|
||||
return jsonify({'error': 'Ongeldige status'}), 400
|
||||
if len(vak_id) > 100 or len(goal_id) > 50:
|
||||
return jsonify({'error': 'Ongeldige invoer'}), 400
|
||||
if not current_user.school_id:
|
||||
return jsonify({'error': 'Account is nog niet gekoppeld aan een school'}), 400
|
||||
|
||||
school_year = get_active_year(current_user.school_id)
|
||||
try:
|
||||
class_id = int(class_id)
|
||||
except (ValueError, TypeError):
|
||||
return jsonify({'error': 'Ongeldig class_id'}), 400
|
||||
|
||||
klas = check_class_access(class_id)
|
||||
if klas is False:
|
||||
return jsonify({'error': 'Klas niet gevonden'}), 404
|
||||
if klas is None:
|
||||
return jsonify({'error': 'Geen toegang tot deze klas'}), 403
|
||||
|
||||
school_year = get_active_year()
|
||||
if not school_year:
|
||||
return jsonify({'error': 'Geen actief schooljaar gevonden'}), 400
|
||||
|
||||
assessment = Assessment.query.filter_by(
|
||||
user_id=current_user.id,
|
||||
class_id=class_id,
|
||||
school_year_id=school_year.id,
|
||||
vak_id=vak_id,
|
||||
goal_id=goal_id,
|
||||
@@ -109,8 +150,7 @@ def save_assessment():
|
||||
assessment.updated_at = datetime.utcnow()
|
||||
else:
|
||||
assessment = Assessment(
|
||||
user_id=current_user.id,
|
||||
school_id=current_user.school_id,
|
||||
class_id=class_id,
|
||||
school_year_id=school_year.id,
|
||||
vak_id=vak_id,
|
||||
goal_id=goal_id,
|
||||
@@ -120,38 +160,44 @@ def save_assessment():
|
||||
db.session.add(assessment)
|
||||
|
||||
db.session.commit()
|
||||
# Auditlog enkel bij statuswijziging (niet bij elke klik)
|
||||
audit_log('assessment.save', 'assessment',
|
||||
target_type='goal', target_id=f'{vak_id}:{goal_id}',
|
||||
detail={'status': status})
|
||||
target_type='class', target_id=str(class_id),
|
||||
detail={'status': status, 'vak': vak_id, 'goal': goal_id})
|
||||
return jsonify({'assessment': assessment.to_dict()})
|
||||
|
||||
|
||||
|
||||
|
||||
@api_bp.route('/assessments/opmerking', methods=['POST'])
|
||||
@login_required
|
||||
@limiter.limit('120 per minute')
|
||||
def save_opmerking():
|
||||
"""Sla enkel een opmerking op bij een bestaand of nieuw assessment record."""
|
||||
data = request.get_json() or {}
|
||||
class_id = data.get('class_id')
|
||||
vak_id = (data.get('vak_id') or '').strip()
|
||||
goal_id = (data.get('goal_id') or '').strip()
|
||||
opmerking = (data.get('opmerking') or '').strip()[:500]
|
||||
|
||||
if not vak_id or not goal_id:
|
||||
return jsonify({'error': 'vak_id en goal_id zijn verplicht'}), 400
|
||||
if not class_id or not vak_id or not goal_id:
|
||||
return jsonify({'error': 'class_id, vak_id en goal_id zijn verplicht'}), 400
|
||||
if len(vak_id) > 100 or len(goal_id) > 50:
|
||||
return jsonify({'error': 'Ongeldige invoer'}), 400
|
||||
if not current_user.school_id:
|
||||
return jsonify({'error': 'Account niet gekoppeld aan een school'}), 400
|
||||
|
||||
school_year = get_active_year(current_user.school_id)
|
||||
try:
|
||||
class_id = int(class_id)
|
||||
except (ValueError, TypeError):
|
||||
return jsonify({'error': 'Ongeldig class_id'}), 400
|
||||
|
||||
klas = check_class_access(class_id)
|
||||
if klas is False:
|
||||
return jsonify({'error': 'Klas niet gevonden'}), 404
|
||||
if klas is None:
|
||||
return jsonify({'error': 'Geen toegang tot deze klas'}), 403
|
||||
|
||||
school_year = get_active_year()
|
||||
if not school_year:
|
||||
return jsonify({'error': 'Geen actief schooljaar'}), 400
|
||||
|
||||
assessment = Assessment.query.filter_by(
|
||||
user_id=current_user.id,
|
||||
class_id=class_id,
|
||||
school_year_id=school_year.id,
|
||||
vak_id=vak_id,
|
||||
goal_id=goal_id,
|
||||
@@ -161,10 +207,8 @@ def save_opmerking():
|
||||
assessment.opmerking = opmerking or None
|
||||
assessment.updated_at = datetime.utcnow()
|
||||
else:
|
||||
# Maak een record aan zonder status voor de opmerking
|
||||
assessment = Assessment(
|
||||
user_id=current_user.id,
|
||||
school_id=current_user.school_id,
|
||||
class_id=class_id,
|
||||
school_year_id=school_year.id,
|
||||
vak_id=vak_id,
|
||||
goal_id=goal_id,
|
||||
@@ -176,32 +220,43 @@ def save_opmerking():
|
||||
db.session.commit()
|
||||
return jsonify({'ok': True})
|
||||
|
||||
|
||||
@api_bp.route('/assessments/bulk-import', methods=['POST'])
|
||||
@login_required
|
||||
@limiter.limit('5 per minute')
|
||||
def bulk_import_assessments():
|
||||
"""
|
||||
Importeer beoordelingen vanuit de legacy standalone JSON export.
|
||||
Body: { "vakken": { "vak_id": { "goal_id": "status", ... }, ... } }
|
||||
of v4 formaat: { "vakken": { "vak_id": { "statussen": { "goal_id": "status" } } } }
|
||||
Importeer beoordelingen vanuit legacy standalone JSON export.
|
||||
Body: { "class_id": 1, "vakken": { "vak_id": { "goal_id": "status" } } }
|
||||
"""
|
||||
if not current_user.school_id:
|
||||
return jsonify({'error': 'Account niet gekoppeld aan een school'}), 400
|
||||
|
||||
school_year = get_active_year(current_user.school_id)
|
||||
if not school_year:
|
||||
return jsonify({'error': 'Geen actief schooljaar'}), 400
|
||||
|
||||
data = request.get_json() or {}
|
||||
class_id = data.get('class_id')
|
||||
vakken = data.get('vakken', {})
|
||||
|
||||
if not class_id:
|
||||
return jsonify({'error': 'class_id is verplicht'}), 400
|
||||
if not vakken:
|
||||
return jsonify({'error': 'Geen vakken gevonden in payload'}), 400
|
||||
|
||||
try:
|
||||
class_id = int(class_id)
|
||||
except (ValueError, TypeError):
|
||||
return jsonify({'error': 'Ongeldig class_id'}), 400
|
||||
|
||||
klas = check_class_access(class_id)
|
||||
if klas is False:
|
||||
return jsonify({'error': 'Klas niet gevonden'}), 404
|
||||
if klas is None:
|
||||
return jsonify({'error': 'Geen toegang tot deze klas'}), 403
|
||||
|
||||
school_year = get_active_year()
|
||||
if not school_year:
|
||||
return jsonify({'error': 'Geen actief schooljaar'}), 400
|
||||
|
||||
totaal = 0
|
||||
fouten = 0
|
||||
|
||||
for vak_id, vak_data in vakken.items():
|
||||
# Sanitiseer vak_id
|
||||
if not isinstance(vak_id, str) or len(vak_id) > 100:
|
||||
fouten += 1
|
||||
continue
|
||||
@@ -224,7 +279,7 @@ def bulk_import_assessments():
|
||||
|
||||
try:
|
||||
assessment = Assessment.query.filter_by(
|
||||
user_id=current_user.id,
|
||||
class_id=class_id,
|
||||
school_year_id=school_year.id,
|
||||
vak_id=vak_id,
|
||||
goal_id=goal_id,
|
||||
@@ -235,8 +290,7 @@ def bulk_import_assessments():
|
||||
assessment.updated_at = datetime.utcnow()
|
||||
else:
|
||||
db.session.add(Assessment(
|
||||
user_id=current_user.id,
|
||||
school_id=current_user.school_id,
|
||||
class_id=class_id,
|
||||
school_year_id=school_year.id,
|
||||
vak_id=vak_id,
|
||||
goal_id=goal_id,
|
||||
@@ -249,6 +303,7 @@ def bulk_import_assessments():
|
||||
|
||||
db.session.commit()
|
||||
audit_log('assessment.bulk_import', 'assessment',
|
||||
target_type='class', target_id=str(class_id),
|
||||
detail={'totaal': totaal, 'fouten': fouten})
|
||||
return jsonify({'totaal': totaal, 'fouten': fouten})
|
||||
|
||||
@@ -261,47 +316,44 @@ def bulk_import_assessments():
|
||||
def school_overview():
|
||||
if not current_user.school_id:
|
||||
return jsonify({'error': 'Geen school gekoppeld'}), 400
|
||||
school_year = get_active_year(current_user.school_id)
|
||||
|
||||
school_year = get_active_year()
|
||||
if not school_year:
|
||||
return jsonify({'error': 'Geen actief schooljaar'}), 400
|
||||
|
||||
# year_id param: directeur/admin kan wisselen, leerkracht zit vast aan actief jaar
|
||||
year_id_param = request.args.get('year_id')
|
||||
if year_id_param and current_user.is_director:
|
||||
year_id = int(year_id_param)
|
||||
selected_year = SchoolYear.query.filter_by(
|
||||
id=year_id, school_id=current_user.school_id
|
||||
).first() or school_year
|
||||
if year_id_param:
|
||||
selected_year = SchoolYear.query.get(int(year_id_param)) or school_year
|
||||
else:
|
||||
selected_year = school_year
|
||||
year_id = school_year.id
|
||||
|
||||
vak_id = request.args.get('vak_id')
|
||||
|
||||
teachers = User.query.filter_by(
|
||||
school_id=current_user.school_id, role='teacher', is_active=True
|
||||
).all()
|
||||
# Alle klassen van deze school
|
||||
klassen = Class.query.filter_by(school_id=current_user.school_id)\
|
||||
.order_by(Class.name).all()
|
||||
class_ids = [k.id for k in klassen]
|
||||
|
||||
query = Assessment.query.filter_by(
|
||||
school_id=current_user.school_id, school_year_id=year_id
|
||||
query = Assessment.query.filter(
|
||||
Assessment.class_id.in_(class_ids),
|
||||
Assessment.school_year_id == selected_year.id,
|
||||
)
|
||||
if vak_id:
|
||||
query = query.filter_by(vak_id=vak_id)
|
||||
|
||||
by_teacher = {t.id: {} for t in teachers}
|
||||
# Groepeer per klas → vak → goal
|
||||
by_class = {k.id: {} for k in klassen}
|
||||
for a in query.all():
|
||||
by_teacher.setdefault(a.user_id, {})
|
||||
by_teacher[a.user_id].setdefault(a.vak_id, {})
|
||||
by_teacher[a.user_id][a.vak_id][a.goal_id] = a.status
|
||||
by_class[a.class_id].setdefault(a.vak_id, {})[a.goal_id] = a.status
|
||||
|
||||
return jsonify({
|
||||
'school_year': selected_year.to_dict(),
|
||||
'teachers': [t.to_dict() for t in teachers],
|
||||
'assessments_by_teacher': by_teacher,
|
||||
'classes': [k.to_dict() for k in klassen],
|
||||
'assessments_by_class': by_class,
|
||||
})
|
||||
|
||||
|
||||
# ── Gebruikersbeheer (school_ict / directeur) ──────────────────────────────────
|
||||
# ── Gebruikersbeheer (director / school_ict) ───────────────────────────────────
|
||||
|
||||
@api_bp.route('/users', methods=['GET'])
|
||||
@login_required
|
||||
@@ -347,39 +399,39 @@ def delete_user(user_id):
|
||||
return jsonify({'deleted': True})
|
||||
|
||||
|
||||
|
||||
# ── Schooljaren (directeur/admin leesbaar) ────────────────────────────────────
|
||||
# ── Schooljaren ────────────────────────────────────────────────────────────────
|
||||
|
||||
@api_bp.route('/school/years')
|
||||
@login_required
|
||||
@director_required
|
||||
def get_school_years():
|
||||
"""Geeft alle globale schooljaren terug (voor jaarselectie in directeur dashboard)."""
|
||||
years = SchoolYear.query.filter_by(school_id=None) .order_by(SchoolYear.label.desc()).all()
|
||||
years = SchoolYear.query.filter_by(school_id=None)\
|
||||
.order_by(SchoolYear.label.desc()).all()
|
||||
return jsonify({'years': [y.to_dict() for y in years]})
|
||||
|
||||
|
||||
# ── Huidig ingelogde gebruiker ────────────────────────────────────────────────
|
||||
# ── Huidig ingelogde gebruiker ─────────────────────────────────────────────────
|
||||
|
||||
@api_bp.route('/me')
|
||||
@login_required
|
||||
def me():
|
||||
school_year = get_active_year(current_user.school_id) if current_user.school_id else None
|
||||
school_year = get_active_year() if current_user.school_id else None
|
||||
return jsonify({
|
||||
'user': current_user.to_dict(),
|
||||
'school_year': school_year.to_dict() if school_year else None,
|
||||
})
|
||||
|
||||
|
||||
# ── Klassen voor leerkracht (zelf instellen) ──────────────────────────────────
|
||||
# ── Klassen voor leerkracht ────────────────────────────────────────────────────
|
||||
|
||||
@api_bp.route('/my/classes', methods=['GET'])
|
||||
@login_required
|
||||
def my_classes():
|
||||
"""Geeft alle beschikbare klassen en eigen klassen terug."""
|
||||
"""Geeft alle klassen van de school en de eigen klassen van de leerkracht."""
|
||||
if not current_user.school_id:
|
||||
return jsonify({'all_classes': [], 'my_classes': []})
|
||||
all_cls = Class.query.filter_by(school_id=current_user.school_id) .order_by(Class.name).all()
|
||||
all_cls = Class.query.filter_by(school_id=current_user.school_id)\
|
||||
.order_by(Class.name).all()
|
||||
return jsonify({
|
||||
'all_classes': [{'id': c.id, 'name': c.name} for c in all_cls],
|
||||
'my_classes': [{'id': c.id, 'name': c.name} for c in current_user.classes],
|
||||
@@ -389,12 +441,12 @@ def my_classes():
|
||||
@api_bp.route('/my/classes', methods=['PUT'])
|
||||
@login_required
|
||||
def set_my_classes():
|
||||
"""Leerkracht stelt eigen klassen in."""
|
||||
"""Leerkracht stelt zijn eigen klassen in."""
|
||||
data = request.get_json() or {}
|
||||
class_ids = data.get('class_ids', [])
|
||||
classes = Class.query.filter(
|
||||
Class.id.in_(class_ids),
|
||||
Class.school_id == current_user.school_id
|
||||
Class.school_id == current_user.school_id,
|
||||
).all()
|
||||
current_user.classes = classes
|
||||
audit_log('class.user_assignment', 'class', target_type='user',
|
||||
@@ -404,8 +456,7 @@ def set_my_classes():
|
||||
return jsonify({'my_classes': [{'id': c.id, 'name': c.name} for c in current_user.classes]})
|
||||
|
||||
|
||||
|
||||
# ── Auditlog ──────────────────────────────────────────────────────────────────
|
||||
# ── Auditlog ───────────────────────────────────────────────────────────────────
|
||||
|
||||
@api_bp.route('/audit-log')
|
||||
@login_required
|
||||
@@ -414,16 +465,13 @@ def get_audit_log():
|
||||
return jsonify({'error': 'Geen toegang'}), 403
|
||||
|
||||
page = max(1, int(request.args.get('page', 1)))
|
||||
per_page = min(100, max(1, int(request.args.get('per_page', 50)))) # max 100 per pagina
|
||||
per_page = min(100, max(1, int(request.args.get('per_page', 50))))
|
||||
category = request.args.get('category')
|
||||
search = request.args.get('search', '').strip()
|
||||
|
||||
query = AuditLog.query
|
||||
|
||||
# School ICT ziet enkel eigen school
|
||||
if not current_user.is_scholengroep_ict:
|
||||
query = query.filter(AuditLog.school_id == current_user.school_id)
|
||||
|
||||
if category:
|
||||
query = query.filter(AuditLog.category == category)
|
||||
if search:
|
||||
@@ -435,7 +483,8 @@ def get_audit_log():
|
||||
)
|
||||
|
||||
total = query.count()
|
||||
entries = query.order_by(AuditLog.timestamp.desc()) .offset((page - 1) * per_page).limit(per_page).all()
|
||||
entries = query.order_by(AuditLog.timestamp.desc())\
|
||||
.offset((page - 1) * per_page).limit(per_page).all()
|
||||
|
||||
return jsonify({
|
||||
'total': total,
|
||||
@@ -445,17 +494,11 @@ def get_audit_log():
|
||||
})
|
||||
|
||||
|
||||
# ── SSO-lookup: welke loginmethodes heeft dit e-maildomein? ──────────────────
|
||||
# ── SSO-lookup ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@api_bp.route('/sso-lookup')
|
||||
def sso_lookup():
|
||||
"""
|
||||
Publieke endpoint — geen auth vereist.
|
||||
Geeft aan welke SSO-methodes beschikbaar zijn voor een e-maildomein.
|
||||
Legt NOOIT credentials bloot — enkel of Google geconfigureerd is.
|
||||
"""
|
||||
from flask import current_app
|
||||
from app import limiter
|
||||
|
||||
email = request.args.get('email', '').lower().strip()
|
||||
if not email or '@' not in email:
|
||||
@@ -474,11 +517,7 @@ def sso_lookup():
|
||||
)
|
||||
|
||||
if not school:
|
||||
return jsonify({
|
||||
'found': False,
|
||||
'microsoft': microsoft_available,
|
||||
'google': False,
|
||||
})
|
||||
return jsonify({'found': False, 'microsoft': microsoft_available, 'google': False})
|
||||
|
||||
return jsonify({
|
||||
'found': True,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user