342 lines
14 KiB
Python
342 lines
14 KiB
Python
"""DRF-serializers voor het kerndomein."""
|
|
from rest_framework import serializers
|
|
|
|
from .models import (
|
|
Activiteit, Afwezigheid, BlokUitzondering, Functie, Groep, KalenderDag,
|
|
Locatie, Lokaal, Lokaalinstelling, Persoon, Roosterblok, Schooljaar,
|
|
Subgroep, Tijdslot,
|
|
)
|
|
|
|
|
|
class FunctieSerializer(serializers.ModelSerializer):
|
|
aantal_personen = serializers.IntegerField(source="personen.count", read_only=True)
|
|
|
|
class Meta:
|
|
model = Functie
|
|
fields = ["id", "naam", "omschrijving", "actief", "aantal_personen", "aangemaakt_op"]
|
|
read_only_fields = ["aangemaakt_op"]
|
|
|
|
|
|
class GroepSerializer(serializers.ModelSerializer):
|
|
aantal_leerlingen = serializers.IntegerField(source="leerlingen.count", read_only=True)
|
|
|
|
class Meta:
|
|
model = Groep
|
|
fields = ["id", "naam", "omschrijving", "actief", "aantal_leerlingen", "aangemaakt_op"]
|
|
read_only_fields = ["aangemaakt_op"]
|
|
|
|
|
|
class PersoonSerializer(serializers.ModelSerializer):
|
|
volledige_naam = serializers.CharField(read_only=True)
|
|
rol_label = serializers.CharField(source="get_rol_display", read_only=True)
|
|
groep_naam = serializers.CharField(source="groep.naam", read_only=True, default=None)
|
|
stamgroep_naam = serializers.CharField(source="stamgroep.naam", read_only=True, default=None)
|
|
functies = serializers.PrimaryKeyRelatedField(
|
|
many=True, queryset=Functie.objects.all(), required=False
|
|
)
|
|
functies_namen = serializers.SerializerMethodField()
|
|
vakken = serializers.PrimaryKeyRelatedField(
|
|
many=True, queryset=Activiteit.objects.all(), required=False
|
|
)
|
|
vakken_namen = serializers.SerializerMethodField()
|
|
begeleide_subgroepen = serializers.PrimaryKeyRelatedField(
|
|
many=True, queryset=Subgroep.objects.all(), required=False
|
|
)
|
|
begeleide_subgroepen_namen = serializers.SerializerMethodField()
|
|
|
|
class Meta:
|
|
model = Persoon
|
|
fields = [
|
|
"id", "voornaam", "achternaam", "volledige_naam",
|
|
"rol", "rol_label", "groep", "groep_naam",
|
|
"stamgroep", "stamgroep_naam",
|
|
"functies", "functies_namen", "vakken", "vakken_namen",
|
|
"begeleide_subgroepen", "begeleide_subgroepen_namen",
|
|
"geboortedatum", "actief", "opmerkingen", "aangemaakt_op",
|
|
]
|
|
read_only_fields = ["aangemaakt_op"]
|
|
|
|
def get_functies_namen(self, obj):
|
|
return [{"id": f.id, "naam": f.naam} for f in obj.functies.all()]
|
|
|
|
def get_vakken_namen(self, obj):
|
|
return [{"id": a.id, "naam": a.naam} for a in obj.vakken.all()]
|
|
|
|
def get_begeleide_subgroepen_namen(self, obj):
|
|
return [{"id": s.id, "naam": s.naam} for s in obj.begeleide_subgroepen.all()]
|
|
|
|
def validate(self, data):
|
|
# Functies/vakken/subgroep-begeleiding horen alleen bij medewerkers.
|
|
# (De M2M wordt door DRF ná save() gezet, dus hier afdwingen.)
|
|
rol = data.get("rol", getattr(self.instance, "rol", None))
|
|
if rol == "leerling":
|
|
data["functies"] = []
|
|
data["vakken"] = []
|
|
data["begeleide_subgroepen"] = []
|
|
return data
|
|
|
|
@staticmethod
|
|
def _met_theorievakken(gekozen):
|
|
"""Voeg de theorievakken toe aan de gekozen vakken (zonder dubbelen)."""
|
|
theorie = list(Activiteit.objects.filter(theorievak=True, actief=True))
|
|
ids = {v.id for v in gekozen}
|
|
return gekozen + [a for a in theorie if a.id not in ids]
|
|
|
|
def create(self, validated_data):
|
|
# begeleide_subgroepen is een reverse-M2M: apart afhandelen na save.
|
|
subg = validated_data.pop("begeleide_subgroepen", None)
|
|
if validated_data.get("rol") == "leerkracht":
|
|
validated_data["vakken"] = self._met_theorievakken(validated_data.get("vakken", []))
|
|
instance = super().create(validated_data)
|
|
if subg is not None:
|
|
instance.begeleide_subgroepen.set(subg)
|
|
return instance
|
|
|
|
def update(self, instance, validated_data):
|
|
subg = validated_data.pop("begeleide_subgroepen", "__keep__")
|
|
rol = validated_data.get("rol", instance.rol)
|
|
if rol == "leerkracht" and "vakken" in validated_data:
|
|
validated_data["vakken"] = self._met_theorievakken(validated_data["vakken"])
|
|
instance = super().update(instance, validated_data)
|
|
if subg != "__keep__":
|
|
instance.begeleide_subgroepen.set(subg)
|
|
return instance
|
|
|
|
|
|
class SubgroepSerializer(serializers.ModelSerializer):
|
|
leden = serializers.PrimaryKeyRelatedField(
|
|
many=True, queryset=Persoon.objects.filter(rol="leerling"), required=False
|
|
)
|
|
leden_namen = serializers.SerializerMethodField()
|
|
aantal_leden = serializers.SerializerMethodField()
|
|
groep_naam = serializers.CharField(source="groep.naam", read_only=True, default=None)
|
|
begeleiders = serializers.PrimaryKeyRelatedField(
|
|
many=True, queryset=Persoon.objects.exclude(rol="leerling"), required=False
|
|
)
|
|
begeleiders_namen = serializers.SerializerMethodField()
|
|
|
|
class Meta:
|
|
model = Subgroep
|
|
fields = [
|
|
"id", "naam", "omschrijving", "actief",
|
|
"groep", "groep_naam",
|
|
"leden", "leden_namen", "aantal_leden",
|
|
"begeleiders", "begeleiders_namen", "aangemaakt_op",
|
|
]
|
|
read_only_fields = ["aangemaakt_op"]
|
|
|
|
def get_leden_namen(self, obj):
|
|
return [{"id": p.id, "naam": p.volledige_naam} for p in obj.leden.all()]
|
|
|
|
def get_aantal_leden(self, obj):
|
|
return obj.leden.count()
|
|
|
|
def get_begeleiders_namen(self, obj):
|
|
return [{"id": p.id, "naam": p.volledige_naam} for p in obj.begeleiders.all()]
|
|
|
|
|
|
# --- Fase 2 ---------------------------------------------------------------
|
|
|
|
class SchooljaarSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = Schooljaar
|
|
fields = ["id", "naam", "start_datum", "eind_datum", "actief"]
|
|
|
|
def validate(self, data):
|
|
start = data.get("start_datum", getattr(self.instance, "start_datum", None))
|
|
eind = data.get("eind_datum", getattr(self.instance, "eind_datum", None))
|
|
if start and eind and eind <= start:
|
|
raise serializers.ValidationError("De einddatum moet na de startdatum liggen.")
|
|
return data
|
|
|
|
|
|
class KalenderDagSerializer(serializers.ModelSerializer):
|
|
type_label = serializers.CharField(source="get_type_display", read_only=True)
|
|
|
|
class Meta:
|
|
model = KalenderDag
|
|
fields = ["id", "schooljaar", "datum", "type", "type_label", "omschrijving"]
|
|
|
|
|
|
class TijdslotSerializer(serializers.ModelSerializer):
|
|
dag_label = serializers.CharField(source="get_dag_display", read_only=True)
|
|
|
|
groep_naam = serializers.CharField(source="groep.naam", read_only=True, default=None)
|
|
|
|
class Meta:
|
|
model = Tijdslot
|
|
fields = ["id", "naam", "dag", "dag_label", "start_tijd", "eind_tijd", "volgorde", "groep", "groep_naam", "actief"]
|
|
|
|
def validate(self, data):
|
|
start = data.get("start_tijd", getattr(self.instance, "start_tijd", None))
|
|
eind = data.get("eind_tijd", getattr(self.instance, "eind_tijd", None))
|
|
if start and eind and eind <= start:
|
|
raise serializers.ValidationError("De eindtijd moet na de starttijd liggen.")
|
|
return data
|
|
|
|
|
|
class ActiviteitSerializer(serializers.ModelSerializer):
|
|
kleur = serializers.RegexField(
|
|
regex=r"^#[0-9a-fA-F]{6}$",
|
|
error_messages={"invalid": "Kleur moet een hex-code zijn, bv. #2563eb."},
|
|
)
|
|
|
|
class Meta:
|
|
model = Activiteit
|
|
fields = ["id", "naam", "kleur", "omschrijving", "theorievak", "actief"]
|
|
|
|
|
|
class LocatieSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = Locatie
|
|
fields = ["id", "naam", "omschrijving", "actief"]
|
|
|
|
|
|
class LokaalSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = Lokaal
|
|
fields = ["id", "naam", "omschrijving", "capaciteit", "actief"]
|
|
|
|
|
|
class LokaalinstellingSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = Lokaalinstelling
|
|
fields = ["dubbel_blokkeren"]
|
|
|
|
|
|
class RoosterblokSerializer(serializers.ModelSerializer):
|
|
activiteit_naam = serializers.CharField(source="activiteit.naam", read_only=True)
|
|
activiteit_kleur = serializers.CharField(source="activiteit.kleur", read_only=True)
|
|
tijdslot_label = serializers.CharField(source="tijdslot.__str__", read_only=True)
|
|
locatie_naam = serializers.CharField(source="locatie.naam", read_only=True, default=None)
|
|
lokaal_naam = serializers.CharField(source="lokaal.naam", read_only=True, default=None)
|
|
doel_type = serializers.CharField(read_only=True)
|
|
doel_naam = serializers.SerializerMethodField()
|
|
begeleiders_namen = serializers.SerializerMethodField()
|
|
|
|
class Meta:
|
|
model = Roosterblok
|
|
fields = [
|
|
"id", "schooljaar", "tijdslot", "tijdslot_label",
|
|
"activiteit", "activiteit_naam", "activiteit_kleur",
|
|
"locatie", "locatie_naam", "lokaal", "lokaal_naam",
|
|
"groep", "subgroep", "leerling", "doel_type", "doel_naam",
|
|
"begeleiders", "begeleiders_namen", "opmerkingen", "aangemaakt_op",
|
|
]
|
|
read_only_fields = ["aangemaakt_op"]
|
|
|
|
def get_doel_naam(self, obj):
|
|
if obj.groep_id:
|
|
return obj.groep.naam
|
|
if obj.subgroep_id:
|
|
return obj.subgroep.naam
|
|
if obj.leerling_id:
|
|
return obj.leerling.volledige_naam
|
|
return None
|
|
|
|
def get_begeleiders_namen(self, obj):
|
|
return [{"id": p.id, "naam": p.volledige_naam} for p in obj.begeleiders.all()]
|
|
|
|
def validate(self, data):
|
|
# Precies één doelgroep (groep / subgroep / leerling).
|
|
groep = data.get("groep", getattr(self.instance, "groep", None))
|
|
subgroep = data.get("subgroep", getattr(self.instance, "subgroep", None))
|
|
leerling = data.get("leerling", getattr(self.instance, "leerling", None))
|
|
gevuld = [x for x in (groep, subgroep, leerling) if x]
|
|
if len(gevuld) != 1:
|
|
raise serializers.ValidationError(
|
|
"Kies precies één doelgroep: groep, subgroep of leerling."
|
|
)
|
|
# Inzetregels voor begeleiders t.o.v. het vak (de activiteit):
|
|
# - een leerling kan geen begeleider zijn;
|
|
# - een klassenondersteuner kan overal worden ingezet;
|
|
# - een leerkracht mag op theorievakken én op de aan hem/haar gekoppelde
|
|
# vakken; een praktijkondersteuner alleen op de gekoppelde vakken.
|
|
activiteit = data.get("activiteit", getattr(self.instance, "activiteit", None))
|
|
begeleiders = data.get("begeleiders", None)
|
|
if begeleiders is None and self.instance is not None:
|
|
begeleiders = list(self.instance.begeleiders.all())
|
|
for p in (begeleiders or []):
|
|
if p.rol == "leerling":
|
|
raise serializers.ValidationError(
|
|
f"'{p.volledige_naam}' is een leerling en kan geen begeleider zijn."
|
|
)
|
|
if p.rol == "klassenondersteuner":
|
|
continue
|
|
if activiteit is None:
|
|
continue
|
|
mag = p.vakken.filter(pk=activiteit.pk).exists()
|
|
if p.rol == "leerkracht" and getattr(activiteit, "theorievak", False):
|
|
mag = True
|
|
if not mag:
|
|
raise serializers.ValidationError(
|
|
f"'{p.volledige_naam}' is niet gekoppeld aan het vak '{activiteit.naam}'."
|
|
)
|
|
|
|
# Dubbele-lokaalboeking: bij harde modus weigeren we opslaan.
|
|
lokaal = data.get("lokaal", getattr(self.instance, "lokaal", None))
|
|
tijdslot = data.get("tijdslot", getattr(self.instance, "tijdslot", None))
|
|
schooljaar = data.get("schooljaar", getattr(self.instance, "schooljaar", None))
|
|
if lokaal is not None and tijdslot is not None and Lokaalinstelling.actueel().dubbel_blokkeren:
|
|
botsing = Roosterblok.objects.filter(
|
|
lokaal=lokaal, tijdslot=tijdslot, schooljaar=schooljaar,
|
|
)
|
|
if self.instance is not None:
|
|
botsing = botsing.exclude(pk=self.instance.pk)
|
|
if botsing.exists():
|
|
raise serializers.ValidationError(
|
|
f"Lokaal '{lokaal.naam}' is op dit tijdslot al bezet."
|
|
)
|
|
try:
|
|
from modules.stage.models import Stage as _Stage
|
|
stage_bezet = any(
|
|
st.dekt_tijdslot(tijdslot)
|
|
for st in _Stage.objects.filter(actief=True, lokaal=lokaal)
|
|
)
|
|
except Exception:
|
|
stage_bezet = False
|
|
if stage_bezet:
|
|
raise serializers.ValidationError(
|
|
f"Lokaal '{lokaal.naam}' is op dit tijdslot al door een stage bezet."
|
|
)
|
|
return data
|
|
|
|
|
|
class AfwezigheidSerializer(serializers.ModelSerializer):
|
|
persoon_naam = serializers.CharField(source="persoon.volledige_naam", read_only=True)
|
|
persoon_rol = serializers.CharField(source="persoon.rol", read_only=True)
|
|
|
|
class Meta:
|
|
model = Afwezigheid
|
|
fields = [
|
|
"id", "persoon", "persoon_naam", "persoon_rol",
|
|
"start_datum", "eind_datum", "reden", "aangemaakt_op",
|
|
]
|
|
read_only_fields = ["aangemaakt_op"]
|
|
|
|
def validate(self, data):
|
|
start = data.get("start_datum", getattr(self.instance, "start_datum", None))
|
|
eind = data.get("eind_datum", getattr(self.instance, "eind_datum", None))
|
|
if start and eind and eind < start:
|
|
raise serializers.ValidationError("De einddatum ligt vóór de startdatum.")
|
|
return data
|
|
|
|
|
|
class BlokUitzonderingSerializer(serializers.ModelSerializer):
|
|
vervanger_naam = serializers.CharField(
|
|
source="vervanger.volledige_naam", read_only=True, default=None
|
|
)
|
|
|
|
class Meta:
|
|
model = BlokUitzondering
|
|
fields = [
|
|
"id", "roosterblok", "datum", "vervalt",
|
|
"vervanger", "vervanger_naam", "opmerking",
|
|
]
|
|
|
|
def validate_vervanger(self, value):
|
|
if value is not None and value.rol == "leerling":
|
|
raise serializers.ValidationError(
|
|
"Een leerling kan geen vervanger zijn."
|
|
)
|
|
return value
|