Fassung 0.7.0: Landkreis-Linkliste, oFB liest nur, Untergruppen, 403 meldet nicht ab

- Auswahl über GET /tgen/auswahl (Manager seit 06.10.2026): oben die eigenen Landkreise
  („… — Linkliste des Landkreises“), dann die Verfahren; als oFB die Verfahren des Landkreises
  mit „(nur lesen)“. Älterer Server ohne die Route: Rückfall auf /tgen.
- Landkreis gewählt: „Liste holen“ lädt die Linkliste des ganzen Landkreises; „Layer laden“
  erklärt, dass es für einen Landkreis keine Verfahrenslayer gibt.
- 401 und 403 getrennt: nur 401 (AuthError) meldet ab, 403 (KeinZugriff) zeigt eine Meldung.
  Bis 0.6.0 verwarf jedes 403 den gespeicherten API-Key.
- OGC-Sammlungen mit vln:schreibbar: false werden schreibgeschützt geladen; Meldungen für Stil
  und Verkehrszeichen nennen „nur lesen“ als Grund.
- Linkliste: Gruppe › Untergruppe › Layer im Ebenenbaum (linkliste.zielgruppe), Untergruppe
  auch in der Dienstliste.
- README: Abschnitt „Geodienste aus der Linkliste“; Tests ohne Netz:
  tools/headless_linkliste_test.py (neu), tools/headless_panel_test.py erweitert — beide grün
  mit QGIS 4.2 offscreen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
KAI
2026-10-06 22:44:36 +02:00
co-authored by Claude Opus 5.5
parent c84bd6e378
commit a56bf7c397
10 changed files with 331 additions and 69 deletions
+98
View File
@@ -0,0 +1,98 @@
# -*- coding: utf-8 -*-
"""Plugin 0.7.0 ohne Netz pruefen: Fehlerzuordnung 401/403, Auswahl mit Rueckfall, Untergruppen-Baum.
Aufruf (macOS, QGIS 4.2 im Bundle):
APP=/Applications/QGIS-final-4_2_0.app; LIB="$APP/Contents/Resources/python3.12"
QT_QPA_PLATFORM=offscreen PROJ_DATA="$APP/Contents/Resources/qgis/proj" \
PYTHONPATH="$LIB:$LIB/lib-dynload:$LIB/site-packages" \
"$APP/Contents/MacOS/python3.12" tools/headless_linkliste_test.py
Kein Schluessel, keine Anfrage ans Netz: ``_request`` wird ersetzt.
"""
import os, sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from qgis.core import QgsApplication
app = QgsApplication([], False)
QgsApplication.setPrefixPath("/Applications/QGIS-final-4_2_0.app/Contents/MacOS", True)
QgsApplication.setPluginPath("/Applications/QGIS-final-4_2_0.app/Contents/PlugIns/qgis")
app.initQgis()
from qgis.core import QgsLayerTreeGroup
from vln_karten import api_client as ac
from vln_karten.linkliste import zielgruppe
FEHLER = []
def pruefe(bed, text):
print((" OK " if bed else " FEHL ") + text)
if not bed:
FEHLER.append(text)
print("== 401 meldet ab, 403 nicht (0.7.0) ==")
e401, e403, e500 = (ac.fehler_fuer_status(s, "x") for s in (401, 403, 500))
pruefe(isinstance(e401, ac.AuthError) and e401.status == 401, "401 -> AuthError")
pruefe(isinstance(e403, ac.KeinZugriff) and not isinstance(e403, ac.AuthError) and e403.status == 403,
"403 -> KeinZugriff, KEIN AuthError (sonst verwirft das Plugin den API-Key)")
pruefe(type(e500) is ac.ApiError and e500.status == 500, "500 -> ApiError mit Status")
pruefe(isinstance(e403, ac.ApiError), "KeinZugriff bleibt ein ApiError (Meldung statt Abmelden)")
print("== Auswahl: /tgen/auswahl und Rueckfall auf /tgen ==")
client = ac.KartenApiClient(api_key="pruef")
client._request = lambda m, p, **k: {"verfahren": [{"vkz": 27027, "name": "TG A", "zugriff": "voll"},
{"vkz": 31005, "name": "TG B", "zugriff": "lesen"},
{"name": "ohne vkz"}],
"aemter": [{"vkz": 27, "name": "Landkreis Meißen"}]}
a = client.get_auswahl()
pruefe(a["verfahren"] == [{"vkz": "27027", "name": "TG A", "zugriff": "voll"},
{"vkz": "31005", "name": "TG B", "zugriff": "lesen"}], "Verfahren gelesen: %r" % a["verfahren"])
pruefe(a["aemter"] == [{"vkz": "27", "name": "Landkreis Meißen"}], "Landkreise gelesen")
def alter_server(m, p, **k):
if p == "/tgen/auswahl":
raise ac.ApiError("GET /tgen/auswahl lieferte HTTP 400", 400) # /tgen/{vkz} faengt „auswahl“
return [{"VKZ": "22017", "kurzname": "TG Schrebitz"}]
client._request = alter_server
a = client.get_auswahl()
pruefe(a == {"verfahren": [{"vkz": "22017", "name": "TG Schrebitz", "zugriff": "voll"}], "aemter": []},
"aelterer Server: Rueckfall auf /tgen: %r" % a)
def verboten(m, p, **k):
raise ac.fehler_fuer_status(403, "nein")
client._request = verboten
try:
client.get_auswahl()
pruefe(False, "403 muss durchgereicht werden")
except ac.KeinZugriff:
pruefe(True, "403 kommt als KeinZugriff beim Aufrufer an (kein Rueckfall)")
print("== Ebenenbaum: Gruppe › Untergruppe › Layer ==")
oben = QgsLayerTreeGroup("Geodienste 27 Landkreis Meißen")
knoten = {}
natur = zielgruppe(oben, knoten, "Natur und Landschaft", None)
bio = zielgruppe(oben, knoten, "Natur und Landschaft", "Biotope")
bio2 = zielgruppe(oben, knoten, "Natur und Landschaft", " Biotope ")
sonst = zielgruppe(oben, knoten, None, "")
pruefe([g.name() for g in oben.children()] == ["Natur und Landschaft", "Sonstiges"],
"oberste Ebene: Gruppen (ohne Gruppe -> Sonstiges): %r" % [g.name() for g in oben.children()])
pruefe([g.name() for g in natur.children()] == ["Biotope"], "Untergruppe unter der Gruppe")
pruefe(bio2.name() == "Biotope" and len(natur.children()) == 1, "dieselbe Untergruppe nur einmal")
zweiter = zielgruppe(oben, {}, "Natur und Landschaft", "Biotope") # neuer Ladelauf
pruefe(zweiter.name() == "Biotope" and len(oben.children()) == 2 and len(natur.children()) == 1,
"zweiter Ladelauf legt nichts doppelt an")
pruefe(sonst.name() == "Sonstiges" and not sonst.children(), "ohne Untergruppe direkt in die Gruppe")
print("== Ergebnis: %d Fehler ==" % len(FEHLER))
for f in FEHLER:
print(" -", f)
app.exitQgis()
sys.exit(1 if FEHLER else 0)
+22
View File
@@ -69,6 +69,28 @@ pruefe(rolle.horizontalScrollBarPolicy() == Qt.ScrollBarPolicy.ScrollBarAlwaysOf
"kein waagerechter Rollbalken")
pruefe(rolle.minimumWidth() >= 260, "Mindestbreite bleibt lesbar: %d px" % rolle.minimumWidth())
print("== 0.7.0: Landkreise und „nur lesen“ in der Auswahl, Untergruppe in der Dienstliste ==")
from vln_karten.panel import ist_landkreis
panel.set_angemeldet(True, "pruef@example.org")
panel.set_verfahren([{"vkz": "27027", "name": "TG A", "zugriff": "voll"},
{"vkz": "31005", "name": "TG B", "zugriff": "lesen"}],
[{"vkz": "27", "name": "Landkreis Meißen"}])
texte = [panel.combo.itemText(i) for i in range(panel.combo.count())]
pruefe(texte[1:2] == ["Landkreis Meißen — Linkliste des Landkreises"],
"Landkreis steht oben: %r" % texte[1:2])
pruefe("31005 — TG B (nur lesen)" in texte and "27027 — TG A" in texte,
"Verfahren „nur lesen“ gekennzeichnet: %r" % texte)
pruefe(ist_landkreis("27") and ist_landkreis(100) and not ist_landkreis("27027")
and not ist_landkreis(None) and not ist_landkreis("x"), "ist_landkreis")
panel.set_dienste([{"name": "Denkmal Punkte", "gruppe_name": "Kultur", "untergruppe": "Denkmal",
"herkunft_text": "Landkreis"},
{"name": "Wald", "gruppe_name": "Natur", "herkunft_text": "landesweit"}])
pruefe(panel.dienste_liste.item(0).text().startswith("Denkmal Punkte · Kultur › Denkmal · "),
"Untergruppe in der Dienstliste: %r" % panel.dienste_liste.item(0).text())
pruefe(panel.dienste_liste.item(1).text().startswith("Wald · Natur · "), "ohne Untergruppe wie bisher")
panel.set_angemeldet(False)
GERUFEN.clear()
if not os.environ.get("VLN_TOKEN"):
print("== Online-Teil uebersprungen (VLN_TOKEN nicht gesetzt) ==")
print("== Ergebnis: %d Fehler ==" % len(FEHLER))