Merge pull request 'VLN-API integriert' (#25) from API-Integration_Plan41 into unstable
Reviewed-on: #25
This commit was merged in pull request #25.
This commit is contained in:
+368
-9
@@ -9,7 +9,9 @@ from typing import Optional
|
||||
from sn_basis.functions.qt_wrapper import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QPushButton,
|
||||
QToolButton,
|
||||
QFileDialog,
|
||||
@@ -32,7 +34,8 @@ from sn_basis.modules.DataGrabber import DataGrabber
|
||||
from sn_basis.modules.Dateipruefer import Dateipruefer
|
||||
from sn_plan41.ui.tab_a_logic import TabALogic
|
||||
from sn_basis.modules.linkpruefer import Linkpruefer
|
||||
from sn_basis.modules.stilpruefer import Stilpruefer
|
||||
from sn_basis.modules.stilpruefer import Stilpruefer
|
||||
from sn_plan41.modules.vln_api_logic import VlnApiLogic
|
||||
|
||||
# Konstanten
|
||||
RAUMFILTER_VAR = "Raumfilter"
|
||||
@@ -78,7 +81,11 @@ class TabA(QWidget):
|
||||
|
||||
# UI-Referenzen
|
||||
self._raumfilter_combo: Optional[QComboBox] = None
|
||||
|
||||
|
||||
# VLN-API
|
||||
self.vln_logic: Optional[VlnApiLogic] = None
|
||||
self._vln_restoring: bool = False
|
||||
|
||||
self._build_ui()
|
||||
self._restore_state()
|
||||
|
||||
@@ -92,14 +99,17 @@ class TabA(QWidget):
|
||||
self.pruefmanager = pruefmanager
|
||||
self.data_grabber = data_grabber
|
||||
self.logic = TabALogic(
|
||||
pruefmanager=self.pruefmanager,
|
||||
link_pruefer=Linkpruefer(),
|
||||
stil_pruefer=Stilpruefer(),
|
||||
)
|
||||
|
||||
# DataGrabber in die Logik injizieren
|
||||
pruefmanager=self.pruefmanager,
|
||||
link_pruefer=Linkpruefer(),
|
||||
stil_pruefer=Stilpruefer(),
|
||||
)
|
||||
self.logic.data_grabber = self.data_grabber
|
||||
|
||||
# VLN-API-Logik initialisieren
|
||||
self.vln_logic = VlnApiLogic(self.pruefmanager)
|
||||
self._update_vln_ui_state()
|
||||
self._restore_vln_state()
|
||||
|
||||
def _build_ui(self) -> None:
|
||||
"""Erstellt die komplette UI-Hierarchie mit allen Gruppen."""
|
||||
main_layout = QVBoxLayout()
|
||||
@@ -180,7 +190,84 @@ class TabA(QWidget):
|
||||
self.btn_pipeline.clicked.connect(self._on_load_fachdaten)
|
||||
main_layout.addWidget(self.btn_pipeline)
|
||||
|
||||
|
||||
# === VLN API / PLAN 41 ===
|
||||
self._vln_group_button = QToolButton()
|
||||
self._vln_group_button.setText("VLN API / Plan 41")
|
||||
self._vln_group_button.setCheckable(True)
|
||||
self._vln_group_button.setChecked(False)
|
||||
self._vln_group_button.setToolButtonStyle(ToolButtonTextBesideIcon)
|
||||
self._vln_group_button.setArrowType(ArrowRight)
|
||||
self._vln_group_button.setStyleSheet("font-weight: bold; margin-top: 8px;")
|
||||
self._vln_group_button.toggled.connect(self._toggle_vln_group)
|
||||
main_layout.addWidget(self._vln_group_button)
|
||||
|
||||
self._vln_group_content = QWidget()
|
||||
self._vln_group_content.setSizePolicy(SizePolicyPreferred, SizePolicyMaximum)
|
||||
vln_layout = QVBoxLayout()
|
||||
vln_layout.setSpacing(4)
|
||||
vln_layout.setContentsMargins(10, 4, 4, 8)
|
||||
|
||||
# Status
|
||||
self._vln_status_label = QLabel("Nicht angemeldet")
|
||||
self._vln_status_label.setStyleSheet("color: gray; font-style: italic;")
|
||||
vln_layout.addWidget(self._vln_status_label)
|
||||
|
||||
# E-Mail und Passwort
|
||||
vln_layout.addWidget(QLabel("E-Mail:"))
|
||||
self._vln_mail_edit = QLineEdit()
|
||||
self._vln_mail_edit.setPlaceholderText("mail@beispiel.de")
|
||||
vln_layout.addWidget(self._vln_mail_edit)
|
||||
|
||||
vln_layout.addWidget(QLabel("Passwort:"))
|
||||
self._vln_pass_edit = QLineEdit()
|
||||
try:
|
||||
self._vln_pass_edit.setEchoMode(QLineEdit.EchoMode.Password)
|
||||
except AttributeError:
|
||||
self._vln_pass_edit.setEchoMode(QLineEdit.Password)
|
||||
vln_layout.addWidget(self._vln_pass_edit)
|
||||
|
||||
# Login / Logout Buttons
|
||||
btn_row = QHBoxLayout()
|
||||
self._btn_vln_login = QPushButton("Anmelden")
|
||||
self._btn_vln_login.clicked.connect(self._on_vln_login)
|
||||
self._btn_vln_logout = QPushButton("Abmelden")
|
||||
self._btn_vln_logout.clicked.connect(self._on_vln_logout)
|
||||
btn_row.addWidget(self._btn_vln_login)
|
||||
btn_row.addWidget(self._btn_vln_logout)
|
||||
vln_layout.addLayout(btn_row)
|
||||
|
||||
# Verfahren
|
||||
vln_layout.addWidget(QLabel("Verfahren:"))
|
||||
self._vln_verfahren_combo = QComboBox()
|
||||
self._vln_verfahren_combo.setToolTip("Verfahren (Teilnehmergemeinschaft) wählen")
|
||||
self._vln_verfahren_combo.setEnabled(False)
|
||||
self._vln_verfahren_combo.currentIndexChanged.connect(
|
||||
self._on_vln_verfahren_changed
|
||||
)
|
||||
vln_layout.addWidget(self._vln_verfahren_combo)
|
||||
|
||||
# Plan 41 laden
|
||||
self._btn_p41_laden = QPushButton("Plan 41 (Wege- und Gewässerplan) laden")
|
||||
self._btn_p41_laden.setToolTip(
|
||||
"Lädt Flächen- und Linienlayer für das gewählte Verfahren vom Server"
|
||||
)
|
||||
self._btn_p41_laden.setEnabled(False)
|
||||
self._btn_p41_laden.clicked.connect(self._on_p41_laden)
|
||||
vln_layout.addWidget(self._btn_p41_laden)
|
||||
|
||||
# Upload
|
||||
self._btn_upload = QPushButton("Aktiven Layer hochladen")
|
||||
self._btn_upload.setToolTip(
|
||||
"Lädt alle Plan-41-Layer des aktiven Verfahrens zum Server hoch"
|
||||
)
|
||||
self._btn_upload.setEnabled(False)
|
||||
self._btn_upload.clicked.connect(self._on_upload_active)
|
||||
vln_layout.addWidget(self._btn_upload)
|
||||
|
||||
self._vln_group_content.setLayout(vln_layout)
|
||||
self._vln_group_content.setVisible(False)
|
||||
main_layout.addWidget(self._vln_group_content)
|
||||
|
||||
main_layout.addStretch(1)
|
||||
self.setLayout(main_layout)
|
||||
|
||||
@@ -206,7 +293,279 @@ class TabA(QWidget):
|
||||
pass
|
||||
# Raumfilter (schon im _build_ui behandelt)
|
||||
|
||||
def _restore_vln_state(self) -> None:
|
||||
"""Stellt die zuletzt gewählte VKZ aus der Projektdatei wieder her."""
|
||||
if self.vln_logic is None or not self.vln_logic.is_authenticated:
|
||||
return
|
||||
try:
|
||||
self._populate_verfahren_combo(restore_vkz=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# === UI CALLBACKS ===
|
||||
|
||||
def _toggle_vln_group(self, checked: bool) -> None:
|
||||
self._vln_group_button.setArrowType(ArrowDown if checked else ArrowRight)
|
||||
self._vln_group_content.setVisible(checked)
|
||||
|
||||
def _update_vln_ui_state(self) -> None:
|
||||
"""Passt alle VLN-Controls an den aktuellen Authentifizierungsstatus an."""
|
||||
if self.vln_logic is None:
|
||||
authenticated = False
|
||||
available = False
|
||||
else:
|
||||
authenticated = self.vln_logic.is_authenticated
|
||||
available = self.vln_logic.is_available()
|
||||
|
||||
if not available:
|
||||
self._vln_status_label.setText(
|
||||
"Plugin 'vln_karten' nicht installiert"
|
||||
)
|
||||
self._vln_status_label.setStyleSheet("color: red; font-style: italic;")
|
||||
self._btn_vln_login.setEnabled(False)
|
||||
elif authenticated:
|
||||
mail = (self.vln_logic.mail or "API-Key") if self.vln_logic else ""
|
||||
self._vln_status_label.setText("Angemeldet: %s" % mail)
|
||||
self._vln_status_label.setStyleSheet(
|
||||
"color: green; font-style: italic;"
|
||||
)
|
||||
self._btn_vln_login.setEnabled(False)
|
||||
else:
|
||||
self._vln_status_label.setText("Nicht angemeldet")
|
||||
self._vln_status_label.setStyleSheet("color: gray; font-style: italic;")
|
||||
self._btn_vln_login.setEnabled(True)
|
||||
|
||||
self._btn_vln_logout.setEnabled(authenticated)
|
||||
self._vln_verfahren_combo.setEnabled(authenticated)
|
||||
vkz_chosen = (
|
||||
authenticated
|
||||
and self._vln_verfahren_combo.currentData() is not None
|
||||
)
|
||||
self._btn_p41_laden.setEnabled(vkz_chosen)
|
||||
self._btn_upload.setEnabled(authenticated)
|
||||
|
||||
def _populate_verfahren_combo(self, restore_vkz: bool = False) -> None:
|
||||
"""Füllt die Verfahren-Combo und wählt ggf. die gespeicherte VKZ."""
|
||||
if self.vln_logic is None:
|
||||
return
|
||||
verfahren = self.vln_logic.get_verfahren()
|
||||
self._vln_restoring = True
|
||||
self._vln_verfahren_combo.clear()
|
||||
self._vln_verfahren_combo.addItem("— Verfahren wählen —", None)
|
||||
for v in verfahren:
|
||||
label = (
|
||||
"%s — %s" % (v["vkz"], v["name"])
|
||||
if v["name"]
|
||||
else v["vkz"]
|
||||
)
|
||||
self._vln_verfahren_combo.addItem(label, v["vkz"])
|
||||
self._vln_restoring = False
|
||||
|
||||
if restore_vkz:
|
||||
stored_vkz = self.vln_logic.load_stored_vkz()
|
||||
if stored_vkz:
|
||||
idx = self._vln_verfahren_combo.findData(stored_vkz)
|
||||
if idx >= 0:
|
||||
self._vln_verfahren_combo.setCurrentIndex(idx)
|
||||
|
||||
self._vln_verfahren_combo.setEnabled(True)
|
||||
self._update_vln_ui_state()
|
||||
|
||||
def _on_vln_login(self) -> None:
|
||||
"""Führt den API-Login durch."""
|
||||
if self.vln_logic is None:
|
||||
return
|
||||
mail = self._vln_mail_edit.text().strip()
|
||||
password = self._vln_pass_edit.text()
|
||||
if not mail or not password:
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Anmeldung",
|
||||
"Bitte E-Mail-Adresse und Passwort eingeben.",
|
||||
)
|
||||
return
|
||||
try:
|
||||
self.vln_logic.login(mail, password)
|
||||
except Exception as exc:
|
||||
QMessageBox.warning(self, "Anmeldung fehlgeschlagen", str(exc))
|
||||
return
|
||||
|
||||
self._vln_pass_edit.clear()
|
||||
try:
|
||||
self._populate_verfahren_combo(restore_vkz=True)
|
||||
except Exception as exc:
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Verfahrensliste",
|
||||
"Anmeldung erfolgreich, aber Verfahrensliste konnte nicht "
|
||||
"geladen werden: %s" % exc,
|
||||
)
|
||||
self._update_vln_ui_state()
|
||||
|
||||
def _on_vln_logout(self) -> None:
|
||||
"""Meldet den Benutzer lokal ab."""
|
||||
if self.vln_logic is None:
|
||||
return
|
||||
self.vln_logic.logout()
|
||||
self._vln_restoring = True
|
||||
self._vln_verfahren_combo.clear()
|
||||
self._vln_verfahren_combo.setEnabled(False)
|
||||
self._vln_restoring = False
|
||||
self._update_vln_ui_state()
|
||||
|
||||
def _on_vln_verfahren_changed(self, _index: int) -> None:
|
||||
"""Persistiert die gewählte VKZ."""
|
||||
if self._vln_restoring or self.vln_logic is None:
|
||||
return
|
||||
vkz = self._vln_verfahren_combo.currentData()
|
||||
self.vln_logic.save_vkz(vkz)
|
||||
self._update_vln_ui_state()
|
||||
|
||||
def _on_p41_laden(self) -> None:
|
||||
"""Lädt den Plan-41-Datensatz für die gewählte VKZ."""
|
||||
if self.vln_logic is None:
|
||||
return
|
||||
vkz = self._vln_verfahren_combo.currentData()
|
||||
if not vkz:
|
||||
QMessageBox.warning(
|
||||
self, "Plan 41 laden", "Bitte zuerst ein Verfahren wählen."
|
||||
)
|
||||
return
|
||||
|
||||
# Bereits geladene Layer prüfen
|
||||
existing = self.vln_logic.get_p41_layers(vkz)
|
||||
if existing:
|
||||
try:
|
||||
answer = QMessageBox.question(
|
||||
self,
|
||||
"Layer ersetzen",
|
||||
"Plan 41 für VKZ %s ist bereits geladen.\n"
|
||||
"Beim Neuladen werden die bestehenden Layer ersetzt. Fortfahren?"
|
||||
% vkz,
|
||||
)
|
||||
# Qt6: StandardButton.Yes, Qt5: QMessageBox.Yes
|
||||
yes = getattr(
|
||||
getattr(QMessageBox, "StandardButton", QMessageBox),
|
||||
"Yes",
|
||||
QMessageBox.Yes if hasattr(QMessageBox, "Yes") else None,
|
||||
)
|
||||
if answer != yes:
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
self.vln_logic.remove_p41_layers(vkz)
|
||||
|
||||
try:
|
||||
layers = self.vln_logic.load_p41(vkz)
|
||||
except Exception as exc:
|
||||
# AuthError: Sitzung abgelaufen
|
||||
try:
|
||||
from sn_plan41.modules.vln_api_client import AuthError as _AuthError
|
||||
if isinstance(exc, _AuthError):
|
||||
self.vln_logic.handle_session_expired()
|
||||
self._on_vln_logout()
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Sitzung abgelaufen",
|
||||
"Der API-Key ist nicht mehr gültig — bitte neu anmelden.",
|
||||
)
|
||||
return
|
||||
except ImportError:
|
||||
pass
|
||||
QMessageBox.warning(self, "Laden fehlgeschlagen", str(exc))
|
||||
return
|
||||
|
||||
total = sum(l.featureCount() for l in layers)
|
||||
detail = ", ".join(
|
||||
"%s: %d" % (l.name(), l.featureCount()) for l in layers
|
||||
)
|
||||
if total == 0:
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Plan 41 laden",
|
||||
"Keine Objekte vorhanden — ein leerer Layer wurde angelegt.",
|
||||
)
|
||||
else:
|
||||
try:
|
||||
from sn_basis.functions.message_wrapper import info
|
||||
info(
|
||||
"Plan 41 geladen",
|
||||
"%d Layer geladen (%s)." % (len(layers), detail),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _on_upload_active(self) -> None:
|
||||
"""Lädt den aktiven Layer (und Geschwister-Layer) zum Server hoch."""
|
||||
if self.vln_logic is None:
|
||||
return
|
||||
|
||||
# Zusammenfassung für Bestaetigungs-Dialog ermitteln
|
||||
try:
|
||||
from sn_basis.functions.qgisui_wrapper import iface as _iface
|
||||
active_layer = _iface.activeLayer() if _iface else None
|
||||
except Exception:
|
||||
active_layer = None
|
||||
|
||||
summary = self.vln_logic.upload_summary(active_layer)
|
||||
if summary is None:
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Hochladen nicht möglich",
|
||||
"Der aktive Layer wurde nicht über 'vln_karten' geladen.",
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
answer = QMessageBox.question(
|
||||
self,
|
||||
"Zum Server hochladen",
|
||||
"%s für VKZ %s übertragen?\n\n%s\n\n"
|
||||
"%d Objekte; der bisherige Bestand dieser VKZ wird ersetzt."
|
||||
% (
|
||||
summary["dataset_label"],
|
||||
summary["vkz"],
|
||||
summary["listing"],
|
||||
summary["total"],
|
||||
),
|
||||
)
|
||||
yes = getattr(
|
||||
getattr(QMessageBox, "StandardButton", QMessageBox),
|
||||
"Yes",
|
||||
QMessageBox.Yes if hasattr(QMessageBox, "Yes") else None,
|
||||
)
|
||||
if answer != yes:
|
||||
return
|
||||
except Exception:
|
||||
return
|
||||
|
||||
try:
|
||||
success, message = self.vln_logic.upload_active_layer(active_layer)
|
||||
except Exception as exc:
|
||||
try:
|
||||
from sn_plan41.modules.vln_api_client import AuthError as _AuthError
|
||||
if isinstance(exc, _AuthError):
|
||||
self.vln_logic.handle_session_expired()
|
||||
self._on_vln_logout()
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Sitzung abgelaufen",
|
||||
"Der API-Key ist nicht mehr gültig — bitte neu anmelden.",
|
||||
)
|
||||
return
|
||||
except ImportError:
|
||||
pass
|
||||
QMessageBox.warning(self, "Hochladen fehlgeschlagen", str(exc))
|
||||
return
|
||||
|
||||
if success:
|
||||
try:
|
||||
from sn_basis.functions.message_wrapper import info
|
||||
info("Upload erfolgreich", message)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
QMessageBox.warning(self, "Hochladen fehlgeschlagen", message)
|
||||
def _toggle_group(self, checked: bool) -> None:
|
||||
"""Zeigt/verbirgt Verfahrens-DB-Gruppe."""
|
||||
self.group_button.setArrowType(ArrowDown if checked else ArrowRight)
|
||||
|
||||
Reference in New Issue
Block a user