Einstieg Schritt 6: Unterrichtssteuerung (Veyon) am Standort/Zweck einschalten
Raum bleibt der Standort/Zweck (Schlüssel, Raumliste je OE). Auf der Einstellungsseite des Standorts: Veyon einschalten (ohne Häkchen) und Lehrergerät(e) ankreuzen; die kommen in die Gruppe "Lehrergeräte" (eine je Organisation, Häkchen veyon_lehrer gesetzt), alle anderen dort sind Schülergeräte. Ausschalten nimmt die Lehrergeräte aus der Gruppe. Hinweis auf die alte Falle (Häkchen am Standort), Speichern behebt sie. Geräteseite: "Als Lehrergerät festlegen" / "Kein Lehrergerät mehr". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fed0e9e48a
commit
2d1b45e973
6 files changed
+234
-2
No files matched your search
@@ -9,6 +9,7 @@ from einladungen import PERSONALISIERUNG_STATUS, personalisierung_einschalten, p
|
||||
from routers.gruppen import fetch_gruppen
|
||||
from routers.personalisierung import _passwort_hash, einladung_senden
|
||||
from templating import templates
|
||||
from unterrichtssteuerung import lehrer_ids, lehrergeraet_setzen
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
@@ -215,6 +216,12 @@ def geraete_info(
|
||||
"email_domaenen": org_result["organization"].get("email_domaenen", []) if org_result.get("success") else [],
|
||||
"einladung": einladung,
|
||||
"neues_passwort": neues_passwort,
|
||||
# Schritt 6 (25.10.2026): Lehrergerät direkt schaltbar.
|
||||
"unterricht": {
|
||||
"lehrer": device["id"] in lehrer_ids(user.organization_id, [device]),
|
||||
"veyon_an": any(e.get("merkmal") == "veyon" and e.get("state") == "present" for e in katalog),
|
||||
} if any(e.get("merkmal") == "veyon" for e in katalog) else None,
|
||||
"unterricht_fehler": request.query_params.get("unterricht_fehler"),
|
||||
},
|
||||
status_code=status_code,
|
||||
)
|
||||
@@ -366,6 +373,18 @@ def personalisierung_zurueckziehen(device_id: str, user: dict = Depends(get_curr
|
||||
return RedirectResponse(f"/geraete/{device_id}#personalisierung", status_code=303)
|
||||
|
||||
|
||||
@router.post("/geraete/{device_id}/lehrergeraet")
|
||||
def lehrergeraet(device_id: str, lehrer: str = Form(""), user: dict = Depends(get_current_user)):
|
||||
if find_device_in_organization(device_id, user.organization_id) is None:
|
||||
raise HTTPException(status_code=404, detail="Gerät wurde nicht gefunden.")
|
||||
|
||||
fehler = lehrergeraet_setzen(str(user.organization_id), device_id, lehrer == "1")
|
||||
ziel = f"/geraete/{device_id}"
|
||||
if fehler:
|
||||
ziel += "?" + urlencode({"unterricht_fehler": fehler})
|
||||
return RedirectResponse(ziel + "#unterricht", status_code=303)
|
||||
|
||||
|
||||
@router.post("/geraete/{device_id}/eigene-gruppe")
|
||||
def eigene_gruppe_anlegen(device_id: str, user: dict = Depends(get_current_user)):
|
||||
"""
|
||||
|
||||
+77
-2
@@ -8,6 +8,7 @@ from einladungen import PERSONALISIERUNG_MERKMAL, PERSONALISIERUNG_STATUS, einla
|
||||
from optionen_felder import OPTIONEN_FELDER, parse_optionen_form
|
||||
from routers.workspace import creator_frei, zuweisbare_workspaces
|
||||
from templating import templates
|
||||
from unterrichtssteuerung import lehrer_gruppe, lehrer_ids, lehrergeraet_setzen, veyon_eintrag
|
||||
import workspace_creator
|
||||
|
||||
|
||||
@@ -175,6 +176,24 @@ def oe_merkmale_ansicht(request: Request, oe_id: str, user=Depends(get_current_u
|
||||
kategorie_name = eintrag["kategorie"]["name"] if eintrag["kategorie"] else "Ohne Kategorie"
|
||||
kategorien.setdefault(kategorie_name, []).append(eintrag)
|
||||
|
||||
devices_result = anode_request("GET", f"/api/v1/organizations/{user.organization_id}/devices")
|
||||
devices = devices_result.get("devices", []) if devices_result.get("success") else []
|
||||
|
||||
# Schritt 6 (25.10.2026): Unterrichtssteuerung für die Geräte genau
|
||||
# dieses Standorts/Zwecks (Schlüssel und Raumliste gelten je OE, nicht
|
||||
# für die darunter).
|
||||
veyon = veyon_eintrag(merkmale)
|
||||
unterricht = None
|
||||
if veyon is not None:
|
||||
hier = [d for d in devices if d.get("oe_id") == oe_id]
|
||||
unterricht = {
|
||||
"an": veyon.get("aktiv") is True,
|
||||
"devices": hier,
|
||||
"lehrer": lehrer_ids(user.organization_id, hier),
|
||||
# Alte Falle (B18): Häkchen am Standort macht jedes Gerät zum Lehrer.
|
||||
"falle": veyon.get("aktiv") is True and bool((veyon.get("optionen") or {}).get("veyon_lehrer")),
|
||||
}
|
||||
|
||||
# Schritt 7 (25.10.2026): wie bei Gruppen, für die Geräte hier und
|
||||
# in den Standorten/Zwecken darunter.
|
||||
einladungen = None
|
||||
@@ -186,8 +205,6 @@ def oe_merkmale_ansicht(request: Request, oe_id: str, user=Depends(get_current_u
|
||||
if not neu:
|
||||
break
|
||||
unten |= neu
|
||||
devices_result = anode_request("GET", f"/api/v1/organizations/{user.organization_id}/devices")
|
||||
devices = devices_result.get("devices", []) if devices_result.get("success") else []
|
||||
einladungen = einladungs_zeilen([d for d in devices if d.get("oe_id") in unten])
|
||||
|
||||
return templates.TemplateResponse(
|
||||
@@ -202,10 +219,68 @@ def oe_merkmale_ansicht(request: Request, oe_id: str, user=Depends(get_current_u
|
||||
"email_domaenen": email_domaenen(user.organization_id) if einladungen is not None else [],
|
||||
"personalisierung_status": PERSONALISIERUNG_STATUS,
|
||||
"einladung_zurueck": f"/struktur/{oe_id}/merkmale",
|
||||
"unterricht": unterricht,
|
||||
"hinweis": request.query_params.get("hinweis"),
|
||||
"fehler": request.query_params.get("fehler"),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _unterricht_zurueck(oe_id: str, **meldung) -> RedirectResponse:
|
||||
return RedirectResponse(f"/struktur/{oe_id}/merkmale?{urlencode(meldung)}#unterricht", status_code=303)
|
||||
|
||||
|
||||
@router.post("/struktur/{oe_id}/unterrichtssteuerung")
|
||||
async def unterrichtssteuerung_speichern(request: Request, oe_id: str, user=Depends(get_current_user)):
|
||||
"""Schritt 6: Veyon am Standort/Zweck einschalten (ohne Häkchen) und die
|
||||
gewählten Geräte hier zu Lehrergeräten machen, die übrigen hier nicht."""
|
||||
|
||||
organization_id = str(user.organization_id)
|
||||
if find_oe_in_organization(oe_id, organization_id) is None:
|
||||
raise HTTPException(status_code=404, detail="Standort/Zweck wurde nicht gefunden.")
|
||||
form = await request.form()
|
||||
gewaehlt = set(form.getlist("lehrer"))
|
||||
|
||||
result = anode_request(
|
||||
"POST", f"/api/v1/organisationseinheiten/{oe_id}/merkmale/veyon/select",
|
||||
json={"optionen": {"veyon_lehrer": False}},
|
||||
)
|
||||
if not result.get("success"):
|
||||
return _unterricht_zurueck(oe_id, fehler=result.get("message", "Veyon ließ sich nicht einschalten."))
|
||||
|
||||
devices_result = anode_request("GET", f"/api/v1/organizations/{organization_id}/devices")
|
||||
hier = [d for d in devices_result.get("devices", []) if d.get("oe_id") == oe_id]
|
||||
if gewaehlt:
|
||||
_, fehler = lehrer_gruppe(organization_id)
|
||||
if fehler:
|
||||
return _unterricht_zurueck(oe_id, fehler=fehler)
|
||||
for d in hier:
|
||||
fehler = lehrergeraet_setzen(organization_id, d["id"], d["id"] in gewaehlt)
|
||||
if fehler:
|
||||
return _unterricht_zurueck(oe_id, fehler=fehler)
|
||||
|
||||
hinweis = "Unterrichtssteuerung gespeichert."
|
||||
if not gewaehlt:
|
||||
hinweis += " Noch kein Lehrergerät gewählt."
|
||||
return _unterricht_zurueck(oe_id, hinweis=hinweis)
|
||||
|
||||
|
||||
@router.post("/struktur/{oe_id}/unterrichtssteuerung/aus")
|
||||
def unterrichtssteuerung_aus(oe_id: str, user=Depends(get_current_user)):
|
||||
"""Veyon am Standort/Zweck zurücksetzen und die Lehrergeräte hier aus der
|
||||
Gruppe nehmen; sonst behielten sie Veyon über die Gruppe."""
|
||||
|
||||
organization_id = str(user.organization_id)
|
||||
if find_oe_in_organization(oe_id, organization_id) is None:
|
||||
raise HTTPException(status_code=404, detail="Standort/Zweck wurde nicht gefunden.")
|
||||
anode_request("POST", f"/api/v1/organisationseinheiten/{oe_id}/merkmale/veyon/unset")
|
||||
devices_result = anode_request("GET", f"/api/v1/organizations/{organization_id}/devices")
|
||||
for d in devices_result.get("devices", []):
|
||||
if d.get("oe_id") == oe_id:
|
||||
lehrergeraet_setzen(organization_id, d["id"], False)
|
||||
return _unterricht_zurueck(oe_id, hinweis="Unterrichtssteuerung ausgeschaltet.")
|
||||
|
||||
|
||||
@router.post("/struktur/{oe_id}/merkmale/{merkmal_key}/select")
|
||||
async def oe_merkmal_select(request: Request, oe_id: str, merkmal_key: str, user=Depends(get_current_user)):
|
||||
if find_oe_in_organization(oe_id, user.organization_id) is None:
|
||||
|
||||
@@ -47,6 +47,24 @@
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
{% if unterricht %}
|
||||
<section id="unterricht">
|
||||
<h3>Unterrichtssteuerung (Veyon)</h3>
|
||||
{% if unterricht_fehler %}<p><mark>{{ unterricht_fehler }}</mark></p>{% endif %}
|
||||
{% if unterricht.lehrer %}
|
||||
<p>Lehrergerät: Hier läuft der Veyon Master für alle Geräte von „{{ device.oe_name }}“.</p>
|
||||
{% elif unterricht.veyon_an %}
|
||||
<p>Schülergerät in „{{ device.oe_name }}“.</p>
|
||||
{% else %}
|
||||
<p><small>Für „{{ device.oe_name }}“ ist die Unterrichtssteuerung nicht eingeschaltet (<a href="/struktur/{{ device.oe_id }}/merkmale#unterricht">einschalten</a>).</small></p>
|
||||
{% endif %}
|
||||
<form method="post" action="/geraete/{{ device.id }}/lehrergeraet">
|
||||
<input type="hidden" name="lehrer" value="{{ '0' if unterricht.lehrer else '1' }}">
|
||||
<button type="submit" class="outline">{% if unterricht.lehrer %}Kein Lehrergerät mehr{% else %}Als Lehrergerät festlegen{% endif %}</button>
|
||||
</form>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{% if personalisierungen is defined %}
|
||||
<section id="personalisierung">
|
||||
<h3>Personalisierung</h3>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
{% if oe.anzahl_geraete is defined %}<small> · {{ oe.anzahl_geraete }} Gerät(e){% if oe.distros %}: {{ oe.distros | join(", ") }}{% endif %}</small>{% endif %}
|
||||
<small>
|
||||
· <a href="/struktur/{{ oe.id }}/merkmale">Einstellungen</a>
|
||||
· <a href="/struktur/{{ oe.id }}/merkmale#unterricht">Unterrichtssteuerung</a>
|
||||
{% if not oe.ist_standard %}
|
||||
· <a href="/struktur/{{ oe.id }}/umbenennen">Umbenennen</a>
|
||||
· <a href="/struktur/{{ oe.id }}/loeschen">Löschen</a>
|
||||
|
||||
@@ -5,6 +5,46 @@
|
||||
<h2>Merkmale: {{ oe.name }}</h2>
|
||||
<p>Legt fest, was Geräte hier (und darunter, sofern dort nichts anderes gilt) standardmäßig bekommen. "Nicht festgelegt" überlässt die Entscheidung der nächsten Schicht (Gruppen, dann Workspace-Standard).</p>
|
||||
|
||||
{% if fehler %}<p><mark>{{ fehler }}</mark></p>{% endif %}
|
||||
{% if hinweis %}<p><ins>{{ hinweis }}</ins></p>{% endif %}
|
||||
|
||||
{% if unterricht %}
|
||||
<section id="unterricht">
|
||||
<h3>Unterrichtssteuerung (Veyon)</h3>
|
||||
<p><small>Am Lehrergerät sieht die Lehrkraft mit dem Veyon Master die Bildschirme aller anderen Geräte von „{{ oe.name }}“ und kann sie steuern. Alle Geräte müssen im selben Netz sein. Standorte/Zwecke darunter bekommen Veyon ebenfalls, sind aber jeweils ein eigener Raum mit eigenem Lehrergerät.</small></p>
|
||||
{% if unterricht.falle %}
|
||||
<p><mark>Hier ist „Lehrergerät“ für den ganzen Standort/Zweck angehakt, damit ist jedes Gerät Lehrergerät. Speichern unten behebt das.</mark></p>
|
||||
{% endif %}
|
||||
{% if not unterricht.devices %}
|
||||
<p>Hier sind noch keine Geräte. Verschiebe sie auf der Seite des Geräts hierher.</p>
|
||||
{% endif %}
|
||||
{% if unterricht.an or unterricht.devices %}
|
||||
<form method="post" action="/struktur/{{ oe.id }}/unterrichtssteuerung">
|
||||
{% if unterricht.devices %}
|
||||
<fieldset>
|
||||
<legend>{% if unterricht.an %}Lehrergerät(e){% else %}Welches ist das Lehrergerät?{% endif %}</legend>
|
||||
{% for d in unterricht.devices %}
|
||||
<label>
|
||||
<input type="checkbox" name="lehrer" value="{{ d.id }}" {% if d.id in unterricht.lehrer %}checked{% endif %}>
|
||||
{{ d.hostname or d.device_fingerprint }}
|
||||
<small>{% if unterricht.an %}{{ "Lehrergerät" if d.id in unterricht.lehrer else "Schülergerät" }}{% endif %}</small>
|
||||
</label>
|
||||
{% endfor %}
|
||||
<small>Alle nicht angehakten Geräte hier sind Schülergeräte.</small>
|
||||
</fieldset>
|
||||
{% endif %}
|
||||
<button type="submit">{% if unterricht.an %}Speichern{% else %}Unterrichtssteuerung einschalten{% endif %}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if unterricht.an %}
|
||||
<form method="post" action="/struktur/{{ oe.id }}/unterrichtssteuerung/aus">
|
||||
<button type="submit" class="secondary outline">Unterrichtssteuerung ausschalten</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<h3>Alle Merkmale</h3>
|
||||
{% if not kategorien %}
|
||||
<p>Keine katalogfähigen Merkmale vorhanden.</p>
|
||||
{% else %}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
"""
|
||||
Unterrichtssteuerung (Veyon) ohne Gruppen-Handarbeit (Einstieg vereinfachen,
|
||||
Schritt 6, 25.10.2026). Der Raum bleibt der Standort/Zweck: dort hängen
|
||||
Schlüssel und Raumliste (provisioning-server, VEYON_MERKMAL). Die Rolle
|
||||
Lehrergerät kommt aus einer gemeinsamen Gruppe "Lehrergeräte" je
|
||||
Organisation mit gesetztem Häkchen veyon_lehrer; alle anderen Geräte am
|
||||
Standort sind Schülergeräte.
|
||||
"""
|
||||
|
||||
from anode_client import anode_request
|
||||
|
||||
VEYON_MERKMAL = "veyon"
|
||||
LEHRER_GRUPPE = "Lehrergeräte"
|
||||
|
||||
|
||||
def _gruppe_finden(organization_id: str) -> dict | None:
|
||||
result = anode_request("GET", f"/api/v1/organizations/{organization_id}/gruppen")
|
||||
gruppen = result.get("gruppen", []) if result.get("success") else []
|
||||
return next((g for g in gruppen if g["name"] == LEHRER_GRUPPE), None)
|
||||
|
||||
|
||||
def lehrer_gruppe(organization_id: str) -> tuple[dict | None, str | None]:
|
||||
"""Gruppe "Lehrergeräte", bei Bedarf angelegt; das Häkchen wird jedes
|
||||
Mal gesetzt, falls jemand es an der Gruppe zurückgenommen hat.
|
||||
Rückgabe: (gruppe, fehlermeldung)."""
|
||||
|
||||
gruppe = _gruppe_finden(organization_id)
|
||||
if gruppe is None:
|
||||
result = anode_request(
|
||||
"POST", f"/api/v1/organizations/{organization_id}/gruppen", json={"name": LEHRER_GRUPPE}
|
||||
)
|
||||
if not result.get("success"):
|
||||
return None, result.get("message") or "Die Gruppe „Lehrergeräte“ ließ sich nicht anlegen."
|
||||
gruppe = result["gruppe"]
|
||||
|
||||
result = anode_request(
|
||||
"POST", f"/api/v1/gruppen/{gruppe['id']}/merkmale/{VEYON_MERKMAL}/select",
|
||||
json={"optionen": {"veyon_lehrer": True}},
|
||||
)
|
||||
if not result.get("success"):
|
||||
return None, result.get("message") or "Veyon ließ sich an der Gruppe „Lehrergeräte“ nicht setzen."
|
||||
return gruppe, None
|
||||
|
||||
|
||||
def lehrer_ids(organization_id: str, devices: list[dict]) -> set[str]:
|
||||
"""Welche dieser Geräte in der Gruppe "Lehrergeräte" sind."""
|
||||
|
||||
gruppe = _gruppe_finden(organization_id)
|
||||
if gruppe is None:
|
||||
return set()
|
||||
ids = set()
|
||||
for device in devices:
|
||||
result = anode_request("GET", f"/api/v1/devices/{device['id']}/gruppen")
|
||||
if any(g["id"] == gruppe["id"] for g in result.get("gruppen", [])):
|
||||
ids.add(device["id"])
|
||||
return ids
|
||||
|
||||
|
||||
def lehrergeraet_setzen(organization_id: str, device_id: str, lehrer: bool) -> str | None:
|
||||
"""Gerät in die Gruppe "Lehrergeräte" aufnehmen oder herausnehmen.
|
||||
Rückgabe: Fehlermeldung oder None."""
|
||||
|
||||
if lehrer:
|
||||
gruppe, fehler = lehrer_gruppe(organization_id)
|
||||
if fehler:
|
||||
return fehler
|
||||
result = anode_request("POST", f"/api/v1/devices/{device_id}/gruppen/{gruppe['id']}")
|
||||
else:
|
||||
gruppe = _gruppe_finden(organization_id)
|
||||
if gruppe is None:
|
||||
return None
|
||||
result = anode_request("DELETE", f"/api/v1/devices/{device_id}/gruppen/{gruppe['id']}")
|
||||
if not result.get("success"):
|
||||
return result.get("message") or "Das Lehrergerät ließ sich nicht ändern."
|
||||
return None
|
||||
|
||||
|
||||
def veyon_eintrag(merkmale: list[dict]) -> dict | None:
|
||||
return next((e for e in merkmale if e.get("merkmal") == VEYON_MERKMAL), None)
|
||||
Reference in new issue
Block a user