Tab „Eigentümer“ mit Flurstückssuche (#56)

Gemarkung (Name oder Nummer) und Flurstücksnummer eingeben, die Treffer
werden im Layer „Flurstücke“ ausgewählt, angezoomt und kurz hervorgehoben.
Verglichen wird das flurstueckskennzeichen, Gemarkungsnamen werden über
den Layer „Gemarkungen“ aufgelöst. Ohne Layer „Flurstücke“ weist ein
Dialog darauf hin, zuerst Verfahrensgebiet und Flurstücke zu laden.

Erster Schritt von #56; Eigentümersuche und Anzeigefenster folgen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 12:44:45 +02:00
co-authored by Claude Opus 5.5
parent 5004a8d5bf
commit c8b380b5d3
6 changed files with 531 additions and 3 deletions
+101
View File
@@ -0,0 +1,101 @@
"""Tests für den Tab „Eigentümer“ – Flurstückssuche (#56)."""
import sys
import types
import unittest
from unittest.mock import MagicMock, patch
if "qgis" not in sys.modules:
qgis_mod = types.ModuleType("qgis")
qgis_utils = types.ModuleType("qgis.utils")
qgis_utils.iface = types.SimpleNamespace()
qgis_utils.plugins = {}
qgis_mod.utils = qgis_utils
sys.modules["qgis"] = qgis_mod
sys.modules["qgis.utils"] = qgis_utils
from sn_verfahrensgebiet.ui.tabs import eigentuemer_tab as et
from sn_verfahrensgebiet.ui.tabs.eigentuemer_tab import EigentuemerTab
from sn_verfahrensgebiet.functions.flurstueck_suche import (
FlurstueckSucheFehler,
KeinFlurstueckslayer,
SuchErgebnis,
)
def _tab(gemarkung="0268", flurstueck="562"):
tab = EigentuemerTab.__new__(EigentuemerTab)
tab.gemarkung_edit = types.SimpleNamespace(text=lambda: gemarkung)
tab.flurstueck_edit = types.SimpleNamespace(text=lambda: flurstueck)
return tab
@patch.object(et, "success")
@patch.object(et, "info")
@patch.object(et, "warning")
@patch.object(et, "show_info_dialog")
class TestFlurstueckSuchen(unittest.TestCase):
def test_ohne_flurstueckslayer_erscheint_hinweisdialog(self, dialog, warnung, hinweis, erfolg):
"""Prüft, dass ohne Layer „Flurstücke“ ein Dialog zum Laden von Verfahrensgebiet und Flurstücken erscheint."""
with patch.object(et, "flurstueck_suchen", side_effect=KeinFlurstueckslayer()):
_tab()._on_flurstueck_suchen()
dialog.assert_called_once()
self.assertIn("Flurstücke aus ALKIS laden", dialog.call_args[0][1])
warnung.assert_not_called()
erfolg.assert_not_called()
def test_eingabefehler_als_warnung(self, dialog, warnung, hinweis, erfolg):
"""Prüft, dass eine ungültige Eingabe als Warnung in der Meldungsleiste erscheint, nicht als Dialog."""
with patch.object(et, "flurstueck_suchen", side_effect=FlurstueckSucheFehler("falsch")):
_tab()._on_flurstueck_suchen()
warnung.assert_called_once_with("Flurstückssuche", "falsch")
dialog.assert_not_called()
def test_kein_treffer(self, dialog, warnung, hinweis, erfolg):
"""Prüft, dass ohne Treffer ein Hinweis erscheint und keine Auswahl gesetzt wird."""
layer = MagicMock()
with patch.object(et, "flurstueck_suchen", return_value=SuchErgebnis(layer, [])):
_tab(flurstueck="999")._on_flurstueck_suchen()
hinweis.assert_called_once()
self.assertIn("999", hinweis.call_args[0][1])
layer.selectByIds.assert_not_called()
def test_treffer_werden_ausgewaehlt_und_angezoomt(self, dialog, warnung, hinweis, erfolg):
"""Prüft, dass Treffer im Flurstückslayer ausgewählt, angezoomt und gemeldet werden."""
layer = MagicMock()
layer.name.return_value = "Flurstücke"
canvas = MagicMock()
with patch.object(et, "flurstueck_suchen", return_value=SuchErgebnis(layer, [1, 2])) as suche, \
patch.object(et, "iface", types.SimpleNamespace(mapCanvas=lambda: canvas)):
_tab(gemarkung=" Beispielhausen ", flurstueck=" 562 ")._on_flurstueck_suchen()
suche.assert_called_once_with("Beispielhausen", "562")
layer.selectByIds.assert_called_once_with([1, 2])
canvas.zoomToSelected.assert_called_once_with(layer)
canvas.flashFeatureIds.assert_called_once_with(layer, [1, 2])
erfolg.assert_called_once()
self.assertIn("2 Flurstücke", erfolg.call_args[0][1])
def test_ohne_kartenansicht_bleibt_auswahl(self, dialog, warnung, hinweis, erfolg):
"""Prüft, dass die Auswahl auch ohne Kartenansicht gesetzt und gemeldet wird."""
layer = MagicMock()
with patch.object(et, "flurstueck_suchen", return_value=SuchErgebnis(layer, [7])), \
patch.object(et, "iface", types.SimpleNamespace()):
_tab()._on_flurstueck_suchen()
layer.selectByIds.assert_called_once_with([7])
self.assertIn("1 Flurstück", erfolg.call_args[0][1])
class TestDockWidgetTabs(unittest.TestCase):
def test_tab_ist_im_dockwidget_eingetragen(self):
"""Prüft, dass der Tab „Eigentümer“ im DockWidget direkt nach dem Bearbeitungs-Tab steht."""
from sn_verfahrensgebiet.ui.dockwidget import DockWidget
from sn_verfahrensgebiet.ui.tabs.working_tab import WorkingTab
self.assertEqual(DockWidget.tabs[:2], [WorkingTab, EigentuemerTab])
self.assertEqual(EigentuemerTab.tab_title, "Eigentümer")
if __name__ == "__main__":
unittest.main()
+146
View File
@@ -0,0 +1,146 @@
"""Tests für die Flurstückssuche im Layer „Flurstücke“ (#56)."""
import types
import unittest
from unittest.mock import patch
from sn_verfahrensgebiet.functions import flurstueck_suche as fs
class _Feature(dict):
def __init__(self, fid, **attribute):
super().__init__(attribute)
self._fid = fid
def id(self):
return self._fid
class _Layer:
def __init__(self, name, features, felder=("flurstueckskennzeichen",)):
self._name = name
self._features = features
self._felder = list(felder)
def name(self):
return self._name
def fields(self):
return types.SimpleNamespace(
indexFromName=lambda n: self._felder.index(n) if n in self._felder else -1
)
def getFeatures(self):
return iter(self._features)
def _flurstuecke():
return _Layer("Flurstücke", [
_Feature(1, flurstueckskennzeichen="140268___00562____00"),
_Feature(2, flurstueckskennzeichen="140268___00562000100"),
_Feature(3, flurstueckskennzeichen="140268___00012000300"),
_Feature(4, flurstueckskennzeichen="140269___00562____00"),
_Feature(5, flurstueckskennzeichen="140268___00012000a00"),
_Feature(6, flurstueckskennzeichen=None),
])
def _gemarkungen():
return _Layer("Gemarkungen", [
_Feature(1, schluessel="140640268", ave_name="Beispielhausen"),
_Feature(2, schluessel="140640269", ave_name="Musterdorf"),
_Feature(3, schluessel="140650270", ave_name="Musterdorf"),
], felder=("schluessel", "ave_name"))
def _projekt(*layer):
nach_name = {lyr.name(): lyr for lyr in layer}
projekt = types.SimpleNamespace(
mapLayersByName=lambda name: [nach_name[name]] if name in nach_name else []
)
return types.SimpleNamespace(instance=lambda: projekt)
class TestEingabe(unittest.TestCase):
def test_flurstuecksnummer_mit_und_ohne_nenner(self):
"""Prüft, dass Zähler und Nenner getrennt und führende Nullen entfernt werden."""
self.assertEqual(fs.zerlege_flurstuecksnummer("562"), ("562", ""))
self.assertEqual(fs.zerlege_flurstuecksnummer(" 0012 / 03 "), ("12", "3"))
self.assertEqual(fs.zerlege_flurstuecksnummer("12/A"), ("12", "a"))
def test_ungueltige_flurstuecksnummer(self):
"""Prüft, dass leere oder unzulässige Flurstücksnummern abgelehnt werden."""
for text in ("", "abc", "12/", "12/3/4", "12-3"):
with self.subTest(text=text):
with self.assertRaises(fs.FlurstueckSucheFehler):
fs.zerlege_flurstuecksnummer(text)
def test_kennzeichen_zerlegen(self):
"""Prüft, dass Gemarkung, Zähler und Nenner aus dem ALKIS-Kennzeichen gelesen werden."""
self.assertEqual(fs.zerlege_kennzeichen("140268___00562____00"), ("0268", "562", ""))
self.assertEqual(fs.zerlege_kennzeichen("140268___00012000300"), ("0268", "12", "3"))
self.assertEqual(fs.zerlege_kennzeichen("140268___00012000a00"), ("0268", "12", "a"))
self.assertIsNone(fs.zerlege_kennzeichen("1402"))
self.assertIsNone(fs.zerlege_kennzeichen(None))
def test_gemarkungsnummer_aus_ziffern(self):
"""Prüft, dass Ziffern als Gemarkungsnummer gelten, auch ohne führende Nullen oder als langer Schlüssel."""
self.assertEqual(fs.gemarkungsnummern("268"), ["0268"])
self.assertEqual(fs.gemarkungsnummern("140268"), ["0268"])
def test_gemarkungsname_ueber_gemarkungslayer(self):
"""Prüft, dass ein Gemarkungsname ohne Rücksicht auf Großschreibung in alle passenden Nummern übersetzt wird."""
self.assertEqual(fs.gemarkungsnummern("beispielhausen", _gemarkungen()), ["0268"])
self.assertEqual(fs.gemarkungsnummern("Musterdorf", _gemarkungen()), ["0269", "0270"])
def test_gemarkungsname_ohne_layer_oder_unbekannt(self):
"""Prüft, dass ein Name ohne Gemarkungslayer oder ohne Treffer eine verständliche Meldung ergibt."""
with self.assertRaisesRegex(fs.FlurstueckSucheFehler, "Gemarkungsnummer"):
fs.gemarkungsnummern("Beispielhausen", None)
with self.assertRaisesRegex(fs.FlurstueckSucheFehler, "keine Gemarkung"):
fs.gemarkungsnummern("Nirgendwo", _gemarkungen())
with self.assertRaises(fs.FlurstueckSucheFehler):
fs.gemarkungsnummern(" ")
class TestSuche(unittest.TestCase):
def test_mit_nenner_genau_ein_treffer(self):
"""Prüft, dass mit Nenner nur das passende Flurstück der Gemarkung gefunden wird."""
self.assertEqual(fs.finde_flurstuecke(_flurstuecke(), ["0268"], "12", "3"), [3])
self.assertEqual(fs.finde_flurstuecke(_flurstuecke(), ["0268"], "12", "a"), [5])
def test_ohne_nenner_alle_mit_diesem_zaehler(self):
"""Prüft, dass ohne Nenner alle Flurstücke mit dem Zähler gefunden werden, aber nur in der gesuchten Gemarkung."""
self.assertEqual(fs.finde_flurstuecke(_flurstuecke(), ["0268"], "562", ""), [1, 2])
self.assertEqual(fs.finde_flurstuecke(_flurstuecke(), ["0268", "0269"], "562", ""), [1, 2, 4])
def test_kein_treffer(self):
"""Prüft, dass ein nicht vorhandenes Flurstück eine leere Trefferliste ergibt."""
self.assertEqual(fs.finde_flurstuecke(_flurstuecke(), ["0268"], "999", ""), [])
def test_layer_ohne_kennzeichenfeld(self):
"""Prüft, dass ein Flurstückslayer ohne Feld „flurstueckskennzeichen“ gemeldet wird."""
layer = _Layer("Flurstücke", [], felder=("gml_id",))
with self.assertRaisesRegex(fs.FlurstueckSucheFehler, "flurstueckskennzeichen"):
fs.finde_flurstuecke(layer, ["0268"], "562", "")
def test_fehlender_flurstueckslayer_bricht_ab(self):
"""Prüft, dass die Suche ohne Layer „Flurstücke“ mit dem Hinweis abbricht, zuerst Verfahrensgebiet und Flurstücke zu laden."""
with patch.object(fs, "QgsProject", _projekt(_gemarkungen())):
with self.assertRaises(fs.KeinFlurstueckslayer) as kontext:
fs.flurstueck_suchen("0268", "562")
self.assertIn("Flurstücke aus ALKIS laden", str(kontext.exception))
def test_suche_im_projekt_mit_gemarkungsname(self):
"""Prüft, dass die Suche im Projekt den Gemarkungsnamen auflöst und Layer samt Treffern liefert."""
flurstuecke = _flurstuecke()
with patch.object(fs, "QgsProject", _projekt(flurstuecke, _gemarkungen())):
ergebnis = fs.flurstueck_suchen("Beispielhausen", "12/3")
self.assertIs(ergebnis.layer, flurstuecke)
self.assertEqual(ergebnis.feature_ids, [3])
if __name__ == "__main__":
unittest.main()