Drei getrennte Flurstuecke-Ladelogiken auf eine konsolidiert (#47)

handle_flurst_aus_gemarkungen() hatte eine eigene Kopie des
"Layer bereits vorhanden"-Dialogs und rief lade_flurstuecke_per_gemarkung
direkt auf, statt die bereits vorhandene, getestete Logik in
flurstueck_alkis() wiederzuverwenden. Grund war, dass ohne_dialog=True
zwei unabhaengige Dinge zugleich abschaltete: die Schluessel-Abfrage per
ask_text UND den Existenz-Dialog samt Anhaengen-Dedup.

Neuer Parameter ohne_schluessel_abfrage trennt beides: er ueberspringt
nur die Schluessel-Abfrage (Schluessel kommen bereits aus der
Gemarkungen-Layer-Auswahl), der Existenz-Dialog mit Dedup bleibt aktiv.
ohne_dialog bleibt fuer die automatischen Pipelines unveraendert.

handle_flurst_alkis() und handle_flurst_aus_gemarkungen() teilen sich
jetzt zusaetzlich die Nachbehandlung des Ladeergebnisses
(_uebernimm_flurstuecke_ergebnis). lade_flurstuecke_per_gemarkung() wird
wieder nur noch von einer Stelle aus aufgerufen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 10:26:25 +02:00
co-authored by Claude Sonnet 5
parent e267d51607
commit 0ac0757e42
5 changed files with 150 additions and 194 deletions
+44
View File
@@ -311,6 +311,28 @@ class TestFlurstueckAlkis(unittest.TestCase):
self.assertIsNotNone(layer)
self.assertEqual(status, LoadStatus.FIRST)
# ------------------------------------------------------------------ #
# 10. ohne_schluessel_abfrage: vorgabe_schluessel direkt, kein ask_text (#47)
# ------------------------------------------------------------------ #
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.lade_flurstuecke_per_gemarkung")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
def test_ohne_schluessel_abfrage_ueberspringt_ask_text(
self, mock_ask_text, mock_project, mock_lade
):
"""ohne_schluessel_abfrage=True verwendet vorgabe_schluessel direkt, ohne ask_text aufzurufen."""
mock_project.return_value = _DummyProject()
geladen = _DummyFlstLayer([_DummyFlstFeature("110001_0001__00001")])
mock_lade.return_value = geladen
layer, status = flurstueck_alkis(
None, vorgabe_schluessel=["1234", "5678"], ohne_schluessel_abfrage=True
)
mock_ask_text.assert_not_called()
self.assertIs(layer, geladen)
self.assertEqual(status, LoadStatus.FIRST)
mock_lade.assert_called_once_with(["1234", "5678"])
# ---------------------------------------------------------------------------
@@ -422,6 +444,28 @@ class TestFlurstueckAlkisAnhaengenDedup(unittest.TestCase):
self.assertEqual(status, LoadStatus.APPEND)
self.assertEqual(len(bestehend.added), 2)
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.lade_flurstuecke_per_gemarkung")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis._check_existing_flurstueck_layer")
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
def test_ohne_schluessel_abfrage_append_dedup(self, mock_ask_text, mock_check, mock_lade):
"""ohne_schluessel_abfrage=True: Anhängen dedupliziert trotzdem, ohne nach Schlüsseln zu fragen (#47)."""
bestehend = _DummyFlstLayer([_DummyFlstFeature("110001_0001__00001")])
neu_geladen = _DummyFlstLayer([
_DummyFlstFeature("110001_0001__00001"), # bereits vorhanden
_DummyFlstFeature("110001_0002__00007"), # neu
])
mock_check.return_value = (bestehend, LoadStatus.APPEND)
mock_lade.return_value = neu_geladen
layer, status = flurstueck_alkis(
None, vorgabe_schluessel=["0001", "0002"], ohne_schluessel_abfrage=True
)
mock_ask_text.assert_not_called()
self.assertEqual(status, LoadStatus.APPEND)
self.assertEqual(len(bestehend.added), 1)
self.assertEqual(bestehend.added[0][FELD_FLURSTUECKSKENNZEICHEN], "110001_0002__00007")
if __name__ == "__main__":
unittest.main()
+59 -94
View File
@@ -15,7 +15,6 @@ if "qgis" not in sys.modules:
sys.modules["qgis.utils"] = qgis_utils
from sn_verfahrensgebiet.ui.tabs.working_tab import WorkingTab, LoadStatus
from sn_verfahrensgebiet.functions.flurstueck_alkis import FLST_LAYER_NAME, FELD_FLURSTUECKSKENNZEICHEN
class _DummyLayer:
@@ -106,68 +105,6 @@ class _DummyGemarkungenLayer:
return list(self._selected)
class _DummyFlstFeatureWT:
def __init__(self, kennzeichen):
self._kennzeichen = kennzeichen
def __getitem__(self, key):
if key == FELD_FLURSTUECKSKENNZEICHEN:
return self._kennzeichen
return None
class _DummyFlstFieldsWT:
def indexFromName(self, name):
return 0 if name == FELD_FLURSTUECKSKENNZEICHEN else -1
class _DummyTempLayer:
"""Rückgabe von lade_flurstuecke_per_gemarkung in Tests."""
def __init__(self, features):
self._features = list(features)
def getFeatures(self):
return list(self._features)
def fields(self):
return _DummyFlstFieldsWT()
def isValid(self):
return True
def featureCount(self):
return len(self._features)
class _DummyExistingFlstLayer:
"""Bereits im Projekt vorhandener Flurstück-Layer, zeichnet addFeatures() auf."""
def __init__(self, features):
self._features = list(features)
self.added = None
def getFeatures(self):
return list(self._features)
def fields(self):
return _DummyFlstFieldsWT()
def dataProvider(self):
layer = self
class _Provider:
def addFeatures(self, feats):
layer.added = list(feats)
layer._features.extend(feats)
return True
return _Provider()
def updateExtents(self):
pass
class TestWorkingTab(unittest.TestCase):
def _make_tab(self):
@@ -264,54 +201,82 @@ class TestWorkingTab(unittest.TestCase):
mock_set_layer_id.assert_called_once_with("")
# ------------------------------------------------------------------
# handle_flurst_aus_gemarkungen
# handle_flurst_alkis / handle_flurst_aus_gemarkungen (#47: eine gemeinsame
# Ladelogik in flurstueck_alkis(), hier wird nur noch die Anbindung geprüft)
# ------------------------------------------------------------------
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.success")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.lade_flurstuecke_per_gemarkung")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.QMessageBox")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.apply_style")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.flurstueck_alkis")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface")
def test_handle_flurst_aus_gemarkungen_anhaengen_ueberspringt_vorhandene(
self, mock_iface, mock_project_instance, mock_msgbox_cls, mock_lade, mock_success
def test_handle_flurst_alkis_first_load_uebernimmt_layer(
self, mock_iface, mock_project_instance, mock_flurstueck_alkis, mock_apply_style, mock_success
):
"""Anhängen über den Gemarkungen-Button überspringt bereits vorhandene flurstueckskennzeichen (#28)."""
"""Erstmaliges Laden über 'Flurstücke laden' fügt den Layer dem Projekt hinzu."""
tab = self._make_tab()
mock_iface.mapCanvas.return_value = _DummyCanvas()
project = _DummyProject()
mock_project_instance.return_value = project
layer = _DummyLayer(valid=True, layer_id="flst_first")
mock_flurstueck_alkis.return_value = (layer, LoadStatus.FIRST)
tab.handle_flurst_alkis()
mock_flurstueck_alkis.assert_called_once_with(tab)
self.assertIn(layer, project.added)
mock_success.assert_called_once()
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.success")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.flurstueck_alkis")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface")
def test_handle_flurst_aus_gemarkungen_ruft_flurstueck_alkis_ohne_schluessel_abfrage(
self, mock_iface, mock_project_instance, mock_flurstueck_alkis, mock_success
):
"""Der Gemarkungen-Auswahl-Button ruft dieselbe Ladefunktion mit ohne_schluessel_abfrage=True auf (#47)."""
tab = self._make_tab()
mock_iface.mapCanvas.return_value = _DummyCanvas()
gem_feat = _DummyGemFeature("110001", "Musterstadt")
gem_layer = _DummyGemarkungenLayer([gem_feat])
bestehend = _DummyExistingFlstLayer([_DummyFlstFeatureWT("110001_0001__00001")])
def map_layers_by_name(name):
if name == "Gemarkungen":
return [gem_layer]
if name == FLST_LAYER_NAME:
return [bestehend]
return []
project = types.SimpleNamespace(mapLayersByName=map_layers_by_name)
mock_project_instance.return_value = project
btn_anhaengen = object()
msg_instance = mock_msgbox_cls.return_value
msg_instance.addButton.side_effect = (
lambda text, role: btn_anhaengen if text == "Anhängen" else object()
project = types.SimpleNamespace(
mapLayersByName=lambda name: [gem_layer] if name == "Gemarkungen" else []
)
msg_instance.clickedButton.return_value = btn_anhaengen
neu_geladen = _DummyTempLayer([
_DummyFlstFeatureWT("110001_0001__00001"), # bereits vorhanden
_DummyFlstFeatureWT("110001_0002__00007"), # neu
])
mock_lade.return_value = neu_geladen
mock_project_instance.return_value = project
layer = _DummyLayer(valid=True, layer_id="flst_append")
mock_flurstueck_alkis.return_value = (layer, LoadStatus.APPEND)
tab.handle_flurst_aus_gemarkungen()
self.assertEqual(len(bestehend.added), 1)
self.assertEqual(bestehend.added[0][FELD_FLURSTUECKSKENNZEICHEN], "110001_0002__00007")
mock_flurstueck_alkis.assert_called_once()
_args, kwargs = mock_flurstueck_alkis.call_args
self.assertEqual(kwargs.get("vorgabe_schluessel"), ["0001"])
self.assertEqual(kwargs.get("vorgabe_namen"), ["Musterstadt"])
self.assertTrue(kwargs.get("ohne_schluessel_abfrage"))
mock_success.assert_called_once()
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.error")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.success")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.flurstueck_alkis")
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance")
def test_handle_flurst_aus_gemarkungen_abbruch_zeigt_keine_zusaetzliche_meldung(
self, mock_project_instance, mock_flurstueck_alkis, mock_success, mock_error
):
"""Bricht flurstueck_alkis() ab (None, NONE), zeigt der Button keine eigene Meldung – flurstueck_alkis() hat das schon getan (#47)."""
tab = self._make_tab()
gem_feat = _DummyGemFeature("110001", "Musterstadt")
gem_layer = _DummyGemarkungenLayer([gem_feat])
project = types.SimpleNamespace(
mapLayersByName=lambda name: [gem_layer] if name == "Gemarkungen" else []
)
mock_project_instance.return_value = project
mock_flurstueck_alkis.return_value = (None, LoadStatus.NONE)
tab.handle_flurst_aus_gemarkungen()
mock_success.assert_not_called()
mock_error.assert_not_called()
# ------------------------------------------------------------------
# handle_grenz_alkis
# ------------------------------------------------------------------