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:
+75
-24
@@ -1,8 +1,4 @@
|
||||
"""HTTP-Client für die VLN-Manager-API (Karten-Plugin map.php + Stammdaten).
|
||||
|
||||
API-Vertrag laut Doku im Wissensdatenbank-Vault
|
||||
("API.md", "API – Karten (map.php).md", "API – Schnittstellen (Referenz).md",
|
||||
Stand 2026-06-08):
|
||||
"""HTTP-Client für die VLN-Manager-API.
|
||||
|
||||
Basis-URL: https://api.flurneuordnung-sachsen.de/v2
|
||||
|
||||
@@ -11,15 +7,15 @@ Stand 2026-06-08):
|
||||
Antwort: {"data": {"userauth": "<url_token>", "id": ...}}
|
||||
Das userauth-Token ist der API-Key (= PERSON.url_token).
|
||||
|
||||
GET /tgen -> Teilnehmergemeinschaften (Verfahren)
|
||||
GET /tgen/auswahl -> {"verfahren": [{vkz, name, zugriff}], "aemter": [{vkz, name}]}
|
||||
Eigene Verfahren ("voll") und — als oFB — die Verfahren der eigenen Landkreise
|
||||
("lesen"), dazu die Landkreise selbst (Manager seit 06.10.2026; Rückfall: GET /tgen).
|
||||
GET /ogc/collections?vkz=… -> Sammlungen (s. ogc_layers.py), ``vln:schreibbar`` je Sammlung
|
||||
GET /linkliste?vkz=… -> Geodienste eines Verfahrens oder Landkreises (s. linkliste.py)
|
||||
|
||||
GET /maps/<layer>/{vkz} -> GeoJSON FeatureCollection (EPSG:25833)
|
||||
PUT /maps/<layer>/{vkz} <- GeoJSON (ersetzt den Layer dieser VKZ)
|
||||
Antwort: {"data": {"vkz", "layer", "art", "speicher_id"}, "status": "ok"}
|
||||
|
||||
Layer: umringe, p41, st, kas, we
|
||||
Auth nach Login über den Header "X-API-Key".
|
||||
Fehlerformat: RFC 7807 (application/problem+json).
|
||||
Auth über den Header "X-API-Key". Fehlerformat: RFC 7807 (application/problem+json).
|
||||
401 = nicht (mehr) angemeldet (AuthError), 403 = angemeldet, aber nicht berechtigt
|
||||
(KeinZugriff) — nur 401 führt zum Abmelden.
|
||||
|
||||
Es wird QgsBlockingNetworkRequest verwendet, damit Proxy- und
|
||||
Zertifikatseinstellungen aus QGIS automatisch greifen.
|
||||
@@ -36,11 +32,38 @@ DEFAULT_BASE_URL = "https://api.flurneuordnung-sachsen.de/v2"
|
||||
|
||||
|
||||
class ApiError(Exception):
|
||||
"""Fehler bei der Kommunikation mit der Karten-API."""
|
||||
"""Fehler bei der Kommunikation mit der Karten-API.
|
||||
|
||||
:ivar status: HTTP-Status, wenn der Server geantwortet hat (sonst ``None``).
|
||||
"""
|
||||
|
||||
def __init__(self, nachricht="", status=None):
|
||||
super().__init__(nachricht)
|
||||
self.status = status
|
||||
|
||||
|
||||
class AuthError(ApiError):
|
||||
"""API-Key fehlt, ist abgelaufen oder hat keine Berechtigung (401/403)."""
|
||||
"""Nicht (mehr) angemeldet: API-Key fehlt oder ist abgelaufen (401) — dann neu anmelden."""
|
||||
|
||||
|
||||
class KeinZugriff(ApiError):
|
||||
"""Angemeldet, aber für diese Aktion nicht berechtigt (403).
|
||||
|
||||
Kein Grund abzumelden: eine oFB liest z. B. die Verfahren ihres Landkreises, darf dort aber
|
||||
nicht schreiben. Bis 0.6.0 meldete jedes 403 ab und verwarf den gespeicherten API-Key.
|
||||
"""
|
||||
|
||||
|
||||
def fehler_fuer_status(status, text):
|
||||
"""Die passende Ausnahme für einen HTTP-Fehlerstatus.
|
||||
|
||||
:returns: ``AuthError`` (401), ``KeinZugriff`` (403), sonst ``ApiError`` — mit ``status``.
|
||||
"""
|
||||
if status == 401:
|
||||
return AuthError(text, status)
|
||||
if status == 403:
|
||||
return KeinZugriff(text, status)
|
||||
return ApiError(text, status)
|
||||
|
||||
|
||||
class KartenApiClient:
|
||||
@@ -83,9 +106,34 @@ class KartenApiClient:
|
||||
# Verfahren (Teilnehmergemeinschaften)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def get_auswahl(self):
|
||||
"""Verfahren und Landkreise zur Auswahl — ``GET /tgen/auswahl``.
|
||||
|
||||
:returns: ``{"verfahren": [{vkz, name, zugriff}], "aemter": [{vkz, name}]}`` mit ``vkz``
|
||||
als Text und ``zugriff`` ``"voll"`` oder ``"lesen"`` (eine oFB liest die Verfahren
|
||||
ihres Landkreises). Ein älterer Server kennt die Route nicht (400/404) — dann
|
||||
Rückfall auf :meth:`get_verfahren`, alles ``"voll"``, keine Landkreise.
|
||||
"""
|
||||
try:
|
||||
data = self._request("GET", "/tgen/auswahl")
|
||||
except ApiError as exc:
|
||||
if getattr(exc, "status", None) in (400, 404):
|
||||
return {"verfahren": [dict(v, zugriff="voll") for v in self.get_verfahren()],
|
||||
"aemter": []}
|
||||
raise
|
||||
if not isinstance(data, dict) or not isinstance(data.get("verfahren"), list):
|
||||
raise ApiError("Antwort von /tgen/auswahl hat ein unerwartetes Format: %s"
|
||||
% json.dumps(data)[:200])
|
||||
verfahren = [{"vkz": str(v.get("vkz")), "name": str(v.get("name") or ""),
|
||||
"zugriff": "lesen" if v.get("zugriff") == "lesen" else "voll"}
|
||||
for v in data["verfahren"] if isinstance(v, dict) and v.get("vkz")]
|
||||
aemter = [{"vkz": str(a.get("vkz")), "name": str(a.get("name") or "")}
|
||||
for a in (data.get("aemter") or []) if isinstance(a, dict) and a.get("vkz")]
|
||||
return {"verfahren": verfahren, "aemter": aemter}
|
||||
|
||||
def get_verfahren(self):
|
||||
"""Liste der Verfahren (TGs) als [{"vkz": ..., "name": ...}, ...],
|
||||
sortiert nach VKZ. Quelle: GET /tgen."""
|
||||
sortiert nach VKZ. Quelle: GET /tgen (Rückfall für ältere Server)."""
|
||||
data = self._request("GET", "/tgen")
|
||||
|
||||
rows = None
|
||||
@@ -210,19 +258,22 @@ class KartenApiClient:
|
||||
status = int(status) if status is not None else None
|
||||
|
||||
if status in (401, 403):
|
||||
raise AuthError(
|
||||
raise fehler_fuer_status(
|
||||
status,
|
||||
"Keine Berechtigung für %s %s (HTTP %s)%s"
|
||||
% (method, path, status, self._error_detail(content))
|
||||
)
|
||||
if error != QgsBlockingNetworkRequest.NoError:
|
||||
raise ApiError(
|
||||
"%s %s fehlgeschlagen: %s%s"
|
||||
% (method, path, blocking.errorMessage(), self._error_detail(content))
|
||||
% (method, path, status, self._error_detail(content)),
|
||||
)
|
||||
if status is not None and status >= 400:
|
||||
raise ApiError(
|
||||
"%s %s lieferte HTTP %s%s"
|
||||
% (method, path, status, self._error_detail(content))
|
||||
% (method, path, status, self._error_detail(content)),
|
||||
status,
|
||||
)
|
||||
if error != QgsBlockingNetworkRequest.NoError:
|
||||
raise ApiError(
|
||||
"%s %s fehlgeschlagen: %s%s"
|
||||
% (method, path, blocking.errorMessage(), self._error_detail(content)),
|
||||
status,
|
||||
)
|
||||
|
||||
if not content:
|
||||
|
||||
+32
-11
@@ -1,11 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Linkliste der Geodienste — die Dienste eines Verfahrens als QGIS-Layer.
|
||||
|
||||
Die Liste kommt vom VLN-Manager (``GET /v2/linkliste?vkz=…``) und ist dort vierstufig gepflegt:
|
||||
landesweit, Landkreis, Team und Verfahren erben aufeinander. Das Plugin bekommt bereits die
|
||||
**aufgelöste** Liste — es muss nichts zusammenrechnen, sondern nur laden. Je Eintrag steht dabei,
|
||||
woher er stammt (``herkunft``), damit man in QGIS sieht, ob ein Dienst landesweit gilt oder nur
|
||||
für dieses eine Verfahren eingetragen wurde.
|
||||
Die Liste kommt vom VLN-Manager (``GET /v2/linkliste?vkz=…``, ``vkz`` eines Verfahrens oder
|
||||
— seit 0.7.0 — eines Landkreises). Gepflegt wird sie landesweit und vom Landkreis (oFB); Team
|
||||
und Teilnehmergemeinschaft sehen sie nur (Erik, 06.10.2026) — an- und abgehakt wird hier im
|
||||
Plugin. Das Plugin bekommt bereits die **aufgelöste** Liste — es muss nichts zusammenrechnen,
|
||||
sondern nur laden. Je Eintrag steht dabei, woher er stammt (``herkunft``), und — wenn gesetzt —
|
||||
seine ``untergruppe``: der Ebenenbaum wird dann Gruppe › Untergruppe › Layer.
|
||||
|
||||
Vier Anbieter kommen vor, jeder braucht in QGIS eine andere Datenquelle:
|
||||
|
||||
@@ -155,6 +156,29 @@ def layer_bauen(eintrag, zeit_ms=20000):
|
||||
return None
|
||||
|
||||
|
||||
def zielgruppe(oben, knoten, gruppe_name, untergruppe=None):
|
||||
"""Die Ebenengruppe für einen Dienst: Gruppe, darunter — falls gesetzt — die Untergruppe.
|
||||
|
||||
Bestehende Gruppen werden wiederverwendet (``findGroup``), damit ein zweiter Ladelauf keine
|
||||
doppelten Knoten anlegt. Ohne Gruppe gilt „Sonstiges“, wie bisher.
|
||||
|
||||
:param oben: ``QgsLayerTreeGroup`` „Geodienste <vkz> <name>“.
|
||||
:param knoten: Zwischenspeicher ``{(gruppe, untergruppe): QgsLayerTreeGroup}`` des Ladelaufs.
|
||||
:returns: die Gruppe, in die der Layer gehört.
|
||||
"""
|
||||
gruppe_name = str(gruppe_name or "Sonstiges")
|
||||
untergruppe = str(untergruppe or "").strip()
|
||||
if (gruppe_name, "") not in knoten:
|
||||
knoten[(gruppe_name, "")] = oben.findGroup(gruppe_name) or oben.addGroup(gruppe_name)
|
||||
gruppe = knoten[(gruppe_name, "")]
|
||||
if not untergruppe:
|
||||
return gruppe
|
||||
if (gruppe_name, untergruppe) not in knoten:
|
||||
knoten[(gruppe_name, untergruppe)] = (gruppe.findGroup(untergruppe)
|
||||
or gruppe.addGroup(untergruppe))
|
||||
return knoten[(gruppe_name, untergruppe)]
|
||||
|
||||
|
||||
def dienste_hinzufuegen(iface, base_url, vkz, vkz_name, dienste, api_key=None, nur_ids=None):
|
||||
"""Die Dienste als Layer in die Gruppe „Geodienste“ hängen — **schrittweise**.
|
||||
|
||||
@@ -171,7 +195,7 @@ def dienste_hinzufuegen(iface, base_url, vkz, vkz_name, dienste, api_key=None, n
|
||||
wurzel = projekt.layerTreeRoot()
|
||||
oben_name = ("%s %s %s" % (GRUPPE, vkz, vkz_name or "")).strip()
|
||||
oben = wurzel.findGroup(oben_name) or wurzel.insertGroup(0, oben_name)
|
||||
untergruppen = {}
|
||||
knoten = {}
|
||||
|
||||
balken = QProgressBar()
|
||||
balken.setMaximum(max(1, len(auswahl)))
|
||||
@@ -196,16 +220,13 @@ def dienste_hinzufuegen(iface, base_url, vkz, vkz_name, dienste, api_key=None, n
|
||||
return
|
||||
eintrag = auswahl[zustand["i"]]
|
||||
zustand["i"] += 1
|
||||
gruppe_name = str(eintrag.get("gruppe_name") or "Sonstiges")
|
||||
if gruppe_name not in untergruppen:
|
||||
untergruppen[gruppe_name] = (oben.findGroup(gruppe_name)
|
||||
or oben.addGroup(gruppe_name))
|
||||
ziel = zielgruppe(oben, knoten, eintrag.get("gruppe_name"), eintrag.get("untergruppe"))
|
||||
layer = layer_bauen(eintrag)
|
||||
if layer is not None and layer.isValid():
|
||||
if eintrag.get("stil"):
|
||||
stil_anwenden(layer, base_url, eintrag.get("id"), api_key)
|
||||
projekt.addMapLayer(layer, False)
|
||||
untergruppen[gruppe_name].addLayer(layer)
|
||||
ziel.addLayer(layer)
|
||||
zustand["geladen"] += 1
|
||||
else:
|
||||
zustand["fehler"].append(str(eintrag.get("name") or "?"))
|
||||
|
||||
@@ -5,7 +5,7 @@ qgisMaximumVersion=4.99
|
||||
supportsQt6=True
|
||||
description=Karten des VLN Managers in QGIS: OGC API Features, alle Layer editierbar — von der Vorerhebung über das Maßnahmenblatt bis zu Widmungen und Verkehrszeichen.
|
||||
about=Anmeldung mit der Manager-Kennung (der API-Key wird gemerkt), Verfahrenslayer über die OGC API laden und bearbeiten (jede Änderung geht einzeln zurück), Geodienste aus der Linkliste, Stile vom Server. Verkehrszeichen und Widmungen lassen sich wie im Web setzen: Schild aus der Palette wählen, erster Klick Standort, zweiter Klick Blickrichtung; die Schildgrafiken kommen von der API und liegen danach lokal.
|
||||
version=0.6.0
|
||||
version=0.7.0
|
||||
author=Erik
|
||||
email=phpwelt@gmail.com
|
||||
tracker=https://entwicklung.flurneuordnung-sachsen.de/VLN_SN/API_Karte_QGISDemo/issues
|
||||
@@ -15,5 +15,6 @@ category=Web
|
||||
icon=
|
||||
experimental=True
|
||||
deprecated=False
|
||||
changelog=0.6.0 Widmungen und Verkehrszeichen: eigene Layergruppe, Schildgrafiken vom Server (lokal abgelegt), Palette mit Suche, Werkzeuge „Schild setzen" und „Zusatzzeichen"; Seitenleiste rollt statt das QGIS-Fenster zu strecken (Issue #1).
|
||||
changelog=0.7.0 Linkliste mit Untergruppen (Gruppe › Untergruppe › Layer) und für den ganzen Landkreis; Auswahl über /tgen/auswahl: als oFB die Verfahren des Landkreises „nur lesen“, Layer dort schreibgeschützt; ein 403 meldet nicht mehr ab.
|
||||
0.6.0 Widmungen und Verkehrszeichen: eigene Layergruppe, Schildgrafiken vom Server (lokal abgelegt), Palette mit Suche, Werkzeuge „Schild setzen" und „Zusatzzeichen"; Seitenleiste rollt statt das QGIS-Fenster zu strecken (Issue #1).
|
||||
0.5.0 Geodienste aus der Linkliste, Stile speichern, Seitenleiste statt Werkzeugleiste.
|
||||
|
||||
@@ -14,7 +14,9 @@ Dieses Modul zeigt den Weg aus PyQGIS:
|
||||
``X-API-Key`` mitschickt. Das Passwort landet nie in QGIS. Alternative ohne Plugin:
|
||||
Methode ``Basic`` mit dem Manager-Login (E-Mail + Passwort) — die API nimmt beides.
|
||||
2. **Sammlungen** des Verfahrens holen (``GET /ogc/collections?vkz=…``) — die API listet nur, was
|
||||
der Nutzer sehen darf (Vorsitz/Kassenbetreuer/Stellvertreter der TG; Verband alles).
|
||||
der Nutzer sehen darf (Vorsitz/Kassenbetreuer/Stellvertreter der TG; Verband alles; seit
|
||||
06.10.2026 auch die oFB des Landkreises, aber nur lesend). ``vln:schreibbar: false`` an einer
|
||||
Sammlung heißt: der Layer wird schreibgeschützt geladen.
|
||||
3. Je Sammlung einen ``QgsVectorLayer(uri, titel, "OAPIF")`` in einer Layer-Gruppe anlegen.
|
||||
|
||||
Sammlungs-IDs: ``<layer>-<vkz>`` (umringe, p41, st, kas, we, ngg, biotop = jüngster Stand wie
|
||||
@@ -158,7 +160,8 @@ def stil_speichern(layer, base_url, cid, api_key=None, authcfg=None, vorlage=Fal
|
||||
"""Den aktuellen Layer-Stil als QML an den Server schicken (PUT …/style.qml).
|
||||
|
||||
:param vorlage: als Vorlage der Art speichern — gilt dann fuer alle Verfahren.
|
||||
:returns: ``(True, Text)`` oder ``(False, Meldung)``; 403 heisst: Gruppe MODUL_KARTENSTIL fehlt.
|
||||
:returns: ``(True, Text)`` oder ``(False, Meldung)``; 403 heisst: im Verfahren nur lesend
|
||||
(oFB) oder — bei einer Vorlage — die Gruppe MODUL_KARTENSTIL fehlt.
|
||||
"""
|
||||
pfad = os.path.join(tempfile.gettempdir(), "vln_upload_%s.qml" % cid.replace("*", "_"))
|
||||
meldung = layer.saveNamedStyle(pfad)
|
||||
@@ -180,8 +183,9 @@ def stil_speichern(layer, base_url, cid, api_key=None, authcfg=None, vorlage=Fal
|
||||
if code == 200:
|
||||
return True, "%d Zeichen gespeichert%s" % (len(rumpf), " (Vorlage der Art)" if vorlage else "")
|
||||
if code == 403:
|
||||
return False, ("Keine Berechtigung: Stile darf nur speichern, wer in der Gruppe "
|
||||
"MODUL_KARTENSTIL ist (oder Verbandsmitarbeiter).")
|
||||
return False, ("Keine Berechtigung: den Stil eines Verfahrens speichert nur, wer dort "
|
||||
"schreiben darf (nicht bei „nur lesen“); eine Vorlage für alle Verfahren "
|
||||
"nur die Gruppe MODUL_KARTENSTIL oder der Verband.")
|
||||
if code in (401, None):
|
||||
raise AuthError("Anmeldung abgelaufen")
|
||||
return False, "Server antwortete mit HTTP %s: %s" % (code, bytes(antwort.content())[:200].decode("utf-8", "replace"))
|
||||
@@ -345,6 +349,10 @@ def layer_hinzufuegen(iface, base_url, vkz, vkz_name, collections, authcfg=None,
|
||||
layer = QgsVectorLayer(layer_uri(base_url, cid, authcfg, coll.get("storageCrs"), api_key),
|
||||
titel, "OAPIF")
|
||||
if layer.isValid():
|
||||
if coll.get("vln:schreibbar") is False:
|
||||
# Nur lesen (oFB des Landkreises, Manager seit 06.10.2026): QGIS soll kein
|
||||
# Bearbeiten anbieten, das erst beim Speichern am Server scheitert.
|
||||
layer.setReadOnly(True)
|
||||
# Verkehrszeichen brauchen ihre Grafiken, BEVOR der Stil greift — sonst zeigt der
|
||||
# Ausdruck auf eine Datei, die es lokal nicht gibt, und QGIS malt ein Fragezeichen.
|
||||
if cid.startswith(("widmung-", "blattzeichen-")) and client is not None:
|
||||
|
||||
+42
-15
@@ -34,6 +34,18 @@ PANEL_TITEL = "VLN Karten"
|
||||
PANEL_OBJEKT = "VlnKartenPanel"
|
||||
|
||||
|
||||
def ist_landkreis(vkz):
|
||||
"""Steht ``vkz`` für einen Landkreis (oFB, 10–100) statt für ein Verfahren?
|
||||
|
||||
Landkreise stehen seit 0.7.0 oben in der Auswahl — gewählt lädt man dort nur die
|
||||
Linkliste des Landkreises, keine Verfahrenslayer.
|
||||
"""
|
||||
try:
|
||||
return 10 <= int(vkz) <= 100
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
|
||||
|
||||
def _klein(widget):
|
||||
"""Hinweistexte eine Stufe kleiner und gedämpft — wie in QGIS-Panels üblich.
|
||||
|
||||
@@ -237,15 +249,17 @@ class VlnPanel(QDockWidget):
|
||||
knopf.blockSignals(False)
|
||||
|
||||
def _bau_dienste(self):
|
||||
"""Die Linkliste des gewählten Verfahrens — vierstufig gepflegt, hier nur geladen.
|
||||
"""Die Linkliste des gewählten Verfahrens oder Landkreises — gepflegt landesweit und vom
|
||||
Landkreis, hier nur geladen.
|
||||
|
||||
Die Liste kommt fertig aufgelöst vom Server (landesweit, Landkreis, Team, Verfahren);
|
||||
je Zeile steht die Herkunft dabei, damit man sieht, wo ein Dienst herkommt.
|
||||
Die Liste kommt fertig aufgelöst vom Server; je Zeile stehen Gruppe, Untergruppe und
|
||||
Herkunft dabei, damit man sieht, wo ein Dienst herkommt.
|
||||
"""
|
||||
kasten = QGroupBox("Geodienste (Linkliste)")
|
||||
lage = QVBoxLayout(kasten)
|
||||
lage.setSpacing(6)
|
||||
lage.addWidget(_klein(QLabel("WMS-, WFS- und ArcGIS-Dienste für dieses Verfahren.")))
|
||||
lage.addWidget(_klein(QLabel("WMS-, WFS- und ArcGIS-Dienste für dieses Verfahren "
|
||||
"oder den gewählten Landkreis.")))
|
||||
self.dienste_liste = QListWidget()
|
||||
# Bewusst niedrig: die Mindesthoehe aller Kaesten zusammen ist die Mindesthoehe des
|
||||
# Docks (s. Rollflaeche im Konstruktor). 140 px hier machten die Seitenleiste auf
|
||||
@@ -276,11 +290,14 @@ class VlnPanel(QDockWidget):
|
||||
self.dienste_liste.item(i).setCheckState(ziel)
|
||||
|
||||
def set_dienste(self, dienste):
|
||||
"""Die geholte Liste anzeigen: Name, Gruppe und Herkunft, alle zunächst angehakt."""
|
||||
"""Die geholte Liste anzeigen: Name, Gruppe › Untergruppe und Herkunft, alle zunächst
|
||||
angehakt."""
|
||||
self.dienste_liste.clear()
|
||||
for d in dienste or []:
|
||||
text = "%s · %s · %s" % (d.get("name"), d.get("gruppe_name") or "",
|
||||
d.get("herkunft_text") or "")
|
||||
gruppe = d.get("gruppe_name") or ""
|
||||
if d.get("untergruppe"):
|
||||
gruppe = "%s › %s" % (gruppe, d.get("untergruppe"))
|
||||
text = "%s · %s · %s" % (d.get("name"), gruppe, d.get("herkunft_text") or "")
|
||||
if d.get("stil"):
|
||||
text += " · mit Stil"
|
||||
eintrag = QListWidgetItem(text)
|
||||
@@ -304,8 +321,8 @@ class VlnPanel(QDockWidget):
|
||||
lage = QVBoxLayout(kasten)
|
||||
lage.setSpacing(6)
|
||||
self.stil_knopf = QPushButton("Stil des aktiven Layers speichern")
|
||||
self.stil_knopf.setToolTip("Den Stil dieses Verfahrens darf jeder speichern, der das "
|
||||
"Verfahren sieht")
|
||||
self.stil_knopf.setToolTip("Den Stil dieses Verfahrens darf speichern, wer im Verfahren "
|
||||
"schreiben darf (nicht bei „nur lesen“)")
|
||||
self.stil_knopf.clicked.connect(lambda: self._ruf("save_style"))
|
||||
self.vorlage_haken = QCheckBox("als Vorlage für alle Verfahren")
|
||||
self.vorlage_haken.setToolTip("Gilt dann für diese Art in jedem Verfahren, z. B. für "
|
||||
@@ -344,12 +361,22 @@ class VlnPanel(QDockWidget):
|
||||
self.dienste_liste.clear()
|
||||
self._stumm = False
|
||||
|
||||
def set_verfahren(self, verfahren):
|
||||
"""``verfahren``: Liste von ``{"vkz":…, "name":…}`` aus der API."""
|
||||
self._verfahren = [
|
||||
("%s — %s" % (v["vkz"], v["name"]) if v.get("name") else str(v["vkz"]), v["vkz"])
|
||||
for v in verfahren
|
||||
]
|
||||
def set_verfahren(self, verfahren, aemter=()):
|
||||
"""Die Auswahl füllen.
|
||||
|
||||
:param verfahren: ``[{"vkz", "name", "zugriff"}]`` aus der API; ``zugriff == "lesen"``
|
||||
bekommt den Zusatz „nur lesen“ (oFB: Verfahren des eigenen Landkreises).
|
||||
:param aemter: ``[{"vkz", "name"}]`` — die eigenen Landkreise stehen oben; gewählt lädt
|
||||
man dort nur die Linkliste des Landkreises.
|
||||
"""
|
||||
eintraege = [("%s — Linkliste des Landkreises" % (a.get("name") or a["vkz"]), a["vkz"])
|
||||
for a in aemter]
|
||||
for v in verfahren:
|
||||
text = "%s — %s" % (v["vkz"], v["name"]) if v.get("name") else str(v["vkz"])
|
||||
if v.get("zugriff") == "lesen":
|
||||
text += " (nur lesen)"
|
||||
eintraege.append((text, v["vkz"]))
|
||||
self._verfahren = eintraege
|
||||
self._liste_fuellen()
|
||||
|
||||
def set_status(self, text):
|
||||
|
||||
+23
-9
@@ -17,7 +17,7 @@ from qgis.PyQt.QtWidgets import QAction, QDialog, QMessageBox
|
||||
from qgis.core import Qgis, QgsDataSourceUri, QgsProject
|
||||
|
||||
from .api_client import ApiError, AuthError, KartenApiClient
|
||||
from .panel import VlnPanel
|
||||
from .panel import VlnPanel, ist_landkreis
|
||||
from .login_dialog import LoginDialog, SETTINGS_GROUP
|
||||
from . import linkliste
|
||||
from . import ogc_layers
|
||||
@@ -143,9 +143,10 @@ class VlnKartenPlugin:
|
||||
self.populate_verfahren()
|
||||
|
||||
def populate_verfahren(self):
|
||||
"""Füllt die Auswahlliste mit allen Verfahren aus GET /tgen."""
|
||||
"""Füllt die Auswahlliste aus ``GET /tgen/auswahl``: eigene Verfahren, als oFB die
|
||||
Verfahren des Landkreises (nur lesen) und die Landkreise selbst (für die Linkliste)."""
|
||||
try:
|
||||
verfahren = self.client.get_verfahren()
|
||||
auswahl = self.client.get_auswahl()
|
||||
except AuthError:
|
||||
self._handle_session_expired()
|
||||
return
|
||||
@@ -159,8 +160,15 @@ class VlnKartenPlugin:
|
||||
return
|
||||
|
||||
self.panel.set_angemeldet(True, self.client.mail or "API-Key")
|
||||
self.panel.set_verfahren(verfahren)
|
||||
self.panel.set_status("%d Verfahren geladen." % len(verfahren))
|
||||
verfahren, aemter = auswahl["verfahren"], auswahl["aemter"]
|
||||
self.panel.set_verfahren(verfahren, aemter)
|
||||
text = "%d Verfahren geladen" % len(verfahren)
|
||||
lesend = sum(1 for v in verfahren if v.get("zugriff") == "lesen")
|
||||
if lesend:
|
||||
text += " (davon %d nur lesen)" % lesend
|
||||
if aemter:
|
||||
text += ", %d Landkreis(e) für die Linkliste" % len(aemter)
|
||||
self.panel.set_status(text + ".")
|
||||
self.restore_project_selection()
|
||||
|
||||
def _handle_session_expired(self):
|
||||
@@ -229,6 +237,12 @@ class VlnKartenPlugin:
|
||||
if not vkz:
|
||||
self._show_error("Kein Verfahren", "Bitte zuerst ein Verfahren auswählen.")
|
||||
return
|
||||
if ist_landkreis(vkz):
|
||||
self._show_error("Landkreis gewählt",
|
||||
"Für einen Landkreis gibt es keine Verfahrenslayer — bitte ein "
|
||||
"Verfahren wählen. Die Linkliste des Landkreises holen Sie unter "
|
||||
"„Geodienste“.")
|
||||
return
|
||||
authcfg = ogc_layers.authcfg_sicherstellen(self.client.api_key, SETTINGS_GROUP)
|
||||
if not authcfg:
|
||||
self.iface.messageBar().pushMessage(
|
||||
@@ -363,11 +377,11 @@ class VlnKartenPlugin:
|
||||
self.panel.set_status("%d von %d Layern gespeichert." % (len(gespeichert), len(offen)))
|
||||
|
||||
def run_dienste_holen(self):
|
||||
"""Die Linkliste des gewählten Verfahrens holen und in der Seitenleiste anzeigen.
|
||||
"""Die Linkliste des gewählten Verfahrens oder Landkreises holen und anzeigen.
|
||||
|
||||
Der Server liefert sie bereits aufgelöst: landesweit, Landkreis, Team und Verfahren
|
||||
sind zusammengeführt, geerbte Einträge tragen ihre Herkunft. Das Plugin rechnet nichts
|
||||
nach, es zeigt und lädt.
|
||||
Der Server liefert sie bereits aufgelöst (landesweit und Landkreis), geerbte Einträge
|
||||
tragen ihre Herkunft. Das Plugin rechnet nichts nach, es zeigt und lädt. Ein 403 meldet
|
||||
nicht ab (seit 0.7.0), sondern zeigt nur, dass hier keine Berechtigung besteht.
|
||||
"""
|
||||
if not self._ensure_logged_in():
|
||||
return
|
||||
|
||||
@@ -226,7 +226,7 @@ class SchildSetzen(QgsMapTool):
|
||||
if layer is None:
|
||||
return
|
||||
if not layer.isEditable() and not layer.startEditing():
|
||||
self._melden("Der Layer lässt sich nicht bearbeiten.", True)
|
||||
self._melden("Der Layer lässt sich nicht bearbeiten — bei „nur lesen“ fehlt das Schreibrecht im Verfahren.", True)
|
||||
return
|
||||
namen = [f.name() for f in layer.fields()]
|
||||
f = QgsFeature(layer.fields())
|
||||
@@ -293,7 +293,7 @@ class ZusatzAnhaengen(QgsMapTool):
|
||||
self._melden("Schon vorhanden oder schon zwei Zusatzzeichen — nichts geändert.", True)
|
||||
return
|
||||
if not layer.isEditable() and not layer.startEditing():
|
||||
self._melden("Der Layer lässt sich nicht bearbeiten.", True)
|
||||
self._melden("Der Layer lässt sich nicht bearbeiten — bei „nur lesen“ fehlt das Schreibrecht im Verfahren.", True)
|
||||
return
|
||||
layer.changeAttributeValue(treffer.id(), layer.fields().indexOf("zusatz"),
|
||||
", ".join(neu))
|
||||
|
||||
Reference in New Issue
Block a user