Merge pull request 'Bugfix/16 flurstueck alkis testabgleich' (#51) from bugfix/16-flurstueck-alkis-testabgleich into unstable
Reviewed-on: #51
This commit was merged in pull request #51.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
- Intern: Tests von TestFlurstueckAlkis an die seit längerem geänderte Ladelogik angeglichen (Mocks zeigten noch auf die alte Ein-Query-/Zwei-Knopf-Variante), bbox-Dokumentation in flurstueck_alkis.py korrigiert (bbox wird nicht mehr ausgewertet) – keine Verhaltensänderung (Test-Audit #16)
|
||||
- Intern: Der Button "aus gewählten Gemarkungen laden" nutzt jetzt dieselbe Ladelogik wie "Flurstücke laden" (flurstueck_alkis) statt einer eigenen, parallelen Implementierung – konsolidiert drei bisher getrennte Code-Pfade zum Flurstücke-Laden auf einen (#47)
|
||||
- Bugfix: Flurstücke aus ALKIS an bestehenden Layer anhängen überspringt jetzt bereits vorhandene Flurstücke (Abgleich über flurstueckskennzeichen), statt sie doppelt zu laden – betrifft sowohl den Dialog in "Flurstücke laden" als auch den Button "aus gewählten Gemarkungen laden" (#28)
|
||||
- Fortschrittsdialog beim Laden aus ALKIS schließt jetzt zuverlässig bei jedem Abbruch (Bugfix)
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
Lädt Flurstücke aus dem ALKIS-WFS-Dienst (``adv:AX_Flurstueck``) anhand von
|
||||
Gemarkungsschlüsseln, die der Nutzer interaktiv eingibt.
|
||||
|
||||
Der WFS-Layer wird direkt (nicht als Memory-Layer) ins Projekt geladen,
|
||||
da die einzelnen Features für die spätere Auswahl benötigt werden.
|
||||
Für jede Gemarkungsnummer wird eine eigene WFS-Anfrage gestellt (umgeht das
|
||||
WFS-Feature-Limit einer Gesamtabfrage); alle Ergebnisse werden anschließend in
|
||||
einem gemeinsamen Memory-Layer zusammengeführt.
|
||||
|
||||
**Erweiterung gegenüber dem Original:**
|
||||
|
||||
@@ -16,10 +17,11 @@ da die einzelnen Features für die spätere Auswahl benötigt werden.
|
||||
- ``vorgabe_namen``: Zugehörige Bezeichnungen, die im Dialog als Hinweis
|
||||
angezeigt werden (kein eigenständiges Feld, nur Anzeigetext).
|
||||
|
||||
- ``bbox``: Optionaler BBox-Parameter, der – sofern übergeben – als
|
||||
zusätzlicher serverseitiger Filter in die WFS-URI eingebettet wird. So
|
||||
werden ausschließlich Flurstücke geladen, die den Verfahrensgebiets-Rahmen
|
||||
überlappen.
|
||||
- ``bbox``: Nicht mehr verwendet. Die Flurstück-Geometrie kommt ausschließlich
|
||||
aus dem ALKIS-WFS je Gemarkung; eine BBox filtert höchstens vorgelagert die
|
||||
Gemarkungsauswahl (siehe ``gemarkung_alkis.py``), nie die Flurstücke selbst.
|
||||
Der Parameter bleibt nur aus Gründen der Signatur-Rückwärtskompatibilität
|
||||
erhalten.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -6,8 +6,11 @@ Getestet werden:
|
||||
- Nutzerabbruch bei ask_text → (None, LoadStatus.NONE)
|
||||
- Ungültiger WFS-Layer → (None, LoadStatus.NONE)
|
||||
- Erfolgreicher Ladevorgang → gültiger Layer + LoadStatus.FIRST
|
||||
- Bestehender Layer: Nutzer wählt "Neu laden" → LoadStatus.RELOAD
|
||||
- Bestehender Layer: Nutzer wählt "Behalten" → LoadStatus.KEEP
|
||||
- Bestehender Layer, Drei-Knopf-Dialog: "Überschreiben" → LoadStatus.RELOAD,
|
||||
"Abbrechen" → (None, LoadStatus.NONE), "Anhängen" siehe
|
||||
TestFlurstueckAlkisAnhaengenDedup
|
||||
- vorgabe_schluessel/vorgabe_namen füllen den Eingabedialog vor
|
||||
- ohne_schluessel_abfrage=True überspringt die ask_text-Abfrage
|
||||
"""
|
||||
|
||||
import sys
|
||||
@@ -32,6 +35,7 @@ if "qgis" not in sys.modules:
|
||||
|
||||
from sn_verfahrensgebiet.functions.flurstueck_alkis import (
|
||||
flurstueck_alkis,
|
||||
lade_flurstuecke_per_gemarkung,
|
||||
FLST_LAYER_NAME,
|
||||
FELD_FLURSTUECKSKENNZEICHEN,
|
||||
)
|
||||
@@ -53,8 +57,8 @@ class _DummyLayer:
|
||||
def id(self):
|
||||
return self._id
|
||||
|
||||
def extent(self):
|
||||
return object()
|
||||
def featureCount(self):
|
||||
return 1
|
||||
|
||||
|
||||
class _DummyProject:
|
||||
@@ -82,7 +86,7 @@ class TestFlurstueckAlkis(unittest.TestCase):
|
||||
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance")
|
||||
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
|
||||
def test_user_cancel_returns_none(self, mock_ask_text, mock_project, mock_msgbox):
|
||||
"""ask_text → ok=False, text="" → (None, NONE)."""
|
||||
"""Prüft, dass ein Abbruch im Eingabedialog (ok=False) (None, LoadStatus.NONE) liefert."""
|
||||
mock_ask_text.return_value = ("", False)
|
||||
mock_project.return_value = _DummyProject()
|
||||
|
||||
@@ -95,7 +99,7 @@ class TestFlurstueckAlkis(unittest.TestCase):
|
||||
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance")
|
||||
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
|
||||
def test_empty_text_after_confirmation_returns_none(self, mock_ask_text, mock_project, mock_msgbox):
|
||||
"""ask_text → ok=True, aber Text leer → (None, NONE)."""
|
||||
"""Prüft, dass ein bestätigter, aber leerer Eingabetext ebenfalls (None, LoadStatus.NONE) liefert."""
|
||||
mock_ask_text.return_value = (" ", True)
|
||||
mock_project.return_value = _DummyProject()
|
||||
|
||||
@@ -114,7 +118,7 @@ class TestFlurstueckAlkis(unittest.TestCase):
|
||||
def test_invalid_wfs_layer_returns_none(
|
||||
self, mock_ask_text, mock_project, mock_wfs_layer_cls, mock_msgbox
|
||||
):
|
||||
"""WFS-Layer nicht valide → (None, NONE)."""
|
||||
"""Prüft, dass ein ungültiger WFS-Layer (None, LoadStatus.NONE) liefert."""
|
||||
mock_ask_text.return_value = ("1234", True)
|
||||
mock_project.return_value = _DummyProject()
|
||||
mock_wfs_layer_cls.return_value = _DummyLayer(valid=False)
|
||||
@@ -127,67 +131,64 @@ class TestFlurstueckAlkis(unittest.TestCase):
|
||||
# ------------------------------------------------------------------ #
|
||||
# 3. Erfolgreicher Erstladevorgang #
|
||||
# ------------------------------------------------------------------ #
|
||||
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsVectorLayer")
|
||||
@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_success_returns_layer_and_first_status(
|
||||
self, mock_ask_text, mock_project, mock_wfs_layer_cls
|
||||
self, mock_ask_text, mock_project, mock_lade
|
||||
):
|
||||
"""Kein bestehender Layer, WFS valide → (layer, FIRST)."""
|
||||
"""Prüft, dass ohne bestehenden Layer der von lade_flurstuecke_per_gemarkung gelieferte Layer mit LoadStatus.FIRST zurückgegeben wird."""
|
||||
mock_ask_text.return_value = ("1234, 5678", True)
|
||||
mock_project.return_value = _DummyProject()
|
||||
valid_layer = _DummyLayer(valid=True, layer_id="wfs_ok")
|
||||
mock_wfs_layer_cls.return_value = valid_layer
|
||||
mock_lade.return_value = valid_layer
|
||||
|
||||
layer, status = flurstueck_alkis(None)
|
||||
|
||||
self.assertIs(layer, valid_layer)
|
||||
self.assertEqual(status, LoadStatus.FIRST)
|
||||
mock_lade.assert_called_once_with(["1234", "5678"])
|
||||
|
||||
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsVectorLayer")
|
||||
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance")
|
||||
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
|
||||
def test_sql_filter_contains_all_schluessel(
|
||||
self, mock_ask_text, mock_project, mock_wfs_layer_cls
|
||||
):
|
||||
"""Der WFS-URI enthält alle eingegebenen Gemarkungsschlüssel."""
|
||||
mock_ask_text.return_value = ("1111, 2222, 3333", True)
|
||||
mock_project.return_value = _DummyProject()
|
||||
mock_wfs_layer_cls.return_value = _DummyLayer(valid=True)
|
||||
def test_sql_filter_contains_all_schluessel(self, mock_wfs_layer_cls):
|
||||
"""Prüft, dass lade_flurstuecke_per_gemarkung für jeden Gemarkungsschlüssel eine eigene WFS-Anfrage mit dessen Schlüssel stellt."""
|
||||
# Ungültig genügt: nur die aufgebaute URI je Aufruf wird geprüft, das
|
||||
# tatsächliche Zusammenführen der Features ist nicht Teil dieses Tests.
|
||||
mock_wfs_layer_cls.return_value = _DummyLayer(valid=False)
|
||||
|
||||
flurstueck_alkis(None)
|
||||
lade_flurstuecke_per_gemarkung(["1111", "2222", "3333"])
|
||||
|
||||
# Erster Positionsarg von QgsVectorLayer ist der URI-String
|
||||
call_args = mock_wfs_layer_cls.call_args
|
||||
uri_str = call_args[0][0] if call_args[0] else call_args[1].get("path", "")
|
||||
self.assertIn("1111", uri_str)
|
||||
self.assertIn("2222", uri_str)
|
||||
self.assertIn("3333", uri_str)
|
||||
uris = [call.args[0] for call in mock_wfs_layer_cls.call_args_list]
|
||||
self.assertEqual(len(uris), 3)
|
||||
self.assertIn("1111", uris[0])
|
||||
self.assertIn("2222", uris[1])
|
||||
self.assertIn("3333", uris[2])
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 4. Bestehender Layer – Nutzer wählt "Neu laden" #
|
||||
# 4. Bestehender Layer – Nutzer klickt "Überschreiben" #
|
||||
# ------------------------------------------------------------------ #
|
||||
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsVectorLayer")
|
||||
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.lade_flurstuecke_per_gemarkung")
|
||||
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QMessageBox")
|
||||
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance")
|
||||
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
|
||||
def test_existing_layer_reload_yes(
|
||||
self, mock_ask_text, mock_project, mock_msgbox_cls, mock_wfs_layer_cls
|
||||
self, mock_ask_text, mock_project, mock_msgbox_cls, mock_lade
|
||||
):
|
||||
"""Bestehender Layer + Nutzer: 'Ja, neu laden' → LoadStatus.RELOAD."""
|
||||
"""Prüft, dass bei bestehendem Layer und Klick auf 'Überschreiben' der alte Layer entfernt und LoadStatus.RELOAD geliefert wird."""
|
||||
existing = _DummyLayer(valid=True, layer_id="old_flst")
|
||||
project = _DummyProject(named_layers=[existing])
|
||||
mock_ask_text.return_value = ("1234", True)
|
||||
mock_project.return_value = project
|
||||
|
||||
# StandardButton als int, damit | Operator funktioniert
|
||||
mock_msgbox_cls.StandardButton.Yes = 0x4000
|
||||
mock_msgbox_cls.StandardButton.No = 0x10000
|
||||
# QMessageBox.question → Yes
|
||||
mock_msgbox_cls.question.return_value = 0x4000
|
||||
# Drei-Knopf-Dialog: addButton() liefert je Aufruf einen eigenen
|
||||
# Sentinel, clickedButton() simuliert den Klick auf "Überschreiben".
|
||||
btn_ueberschreiben, btn_anhaengen, btn_abbrechen = object(), object(), object()
|
||||
msg_instance = mock_msgbox_cls.return_value
|
||||
msg_instance.addButton.side_effect = [btn_ueberschreiben, btn_anhaengen, btn_abbrechen]
|
||||
msg_instance.clickedButton.return_value = btn_ueberschreiben
|
||||
|
||||
new_layer = _DummyLayer(valid=True, layer_id="new_flst")
|
||||
mock_wfs_layer_cls.return_value = new_layer
|
||||
mock_lade.return_value = new_layer
|
||||
|
||||
layer, status = flurstueck_alkis(None)
|
||||
|
||||
@@ -196,46 +197,46 @@ class TestFlurstueckAlkis(unittest.TestCase):
|
||||
self.assertEqual(status, LoadStatus.RELOAD)
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 5. Bestehender Layer – Nutzer wählt "Behalten" #
|
||||
# 5. Bestehender Layer – Nutzer klickt "Abbrechen" #
|
||||
# ------------------------------------------------------------------ #
|
||||
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QMessageBox")
|
||||
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance")
|
||||
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
|
||||
def test_existing_layer_keep_no(
|
||||
def test_existing_layer_abbrechen_returns_none(
|
||||
self, mock_ask_text, mock_project, mock_msgbox_cls
|
||||
):
|
||||
"""Bestehender Layer + Nutzer: 'Nein, behalten' → LoadStatus.KEEP."""
|
||||
"""Prüft, dass bei bestehendem Layer und Klick auf 'Abbrechen' nichts verändert wird und (None, LoadStatus.NONE) geliefert wird."""
|
||||
existing = _DummyLayer(valid=True, layer_id="keep_flst")
|
||||
project = _DummyProject(named_layers=[existing])
|
||||
mock_ask_text.return_value = ("1234", True)
|
||||
mock_project.return_value = project
|
||||
|
||||
# StandardButton als int, damit | Operator funktioniert
|
||||
mock_msgbox_cls.StandardButton.Yes = 0x4000
|
||||
mock_msgbox_cls.StandardButton.No = 0x10000
|
||||
# QMessageBox.question → No
|
||||
mock_msgbox_cls.question.return_value = 0x10000
|
||||
btn_ueberschreiben, btn_anhaengen, btn_abbrechen = object(), object(), object()
|
||||
msg_instance = mock_msgbox_cls.return_value
|
||||
msg_instance.addButton.side_effect = [btn_ueberschreiben, btn_anhaengen, btn_abbrechen]
|
||||
msg_instance.clickedButton.return_value = btn_abbrechen
|
||||
|
||||
layer, status = flurstueck_alkis(None)
|
||||
|
||||
self.assertIs(layer, existing)
|
||||
self.assertEqual(status, LoadStatus.KEEP)
|
||||
self.assertIsNone(layer)
|
||||
self.assertEqual(status, LoadStatus.NONE)
|
||||
self.assertEqual(project.removed, []) # nichts gelöscht
|
||||
mock_ask_text.assert_not_called() # Abbruch schon vor der Schlüsselabfrage
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 6. vorgabe_schluessel füllt ask_text-Default vor #
|
||||
# ------------------------------------------------------------------ #
|
||||
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsVectorLayer")
|
||||
@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_vorgabe_schluessel_prefills_default_text(
|
||||
self, mock_ask_text, mock_project, mock_wfs_layer_cls
|
||||
self, mock_ask_text, mock_project, mock_lade
|
||||
):
|
||||
"""vorgabe_schluessel=['1234','5678'] → ask_text erhält '1234, 5678' als default_text."""
|
||||
"""Prüft, dass vorgabe_schluessel als vorbelegter default_text im Eingabedialog erscheint."""
|
||||
mock_ask_text.return_value = ("1234, 5678", True)
|
||||
mock_project.return_value = _DummyProject()
|
||||
mock_wfs_layer_cls.return_value = _DummyLayer(valid=True)
|
||||
mock_lade.return_value = _DummyLayer(valid=True)
|
||||
|
||||
flurstueck_alkis(None, vorgabe_schluessel=["1234", "5678"])
|
||||
|
||||
@@ -249,16 +250,16 @@ class TestFlurstueckAlkis(unittest.TestCase):
|
||||
# ------------------------------------------------------------------ #
|
||||
# 7. vorgabe_namen erscheint im Label-Text #
|
||||
# ------------------------------------------------------------------ #
|
||||
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsVectorLayer")
|
||||
@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_vorgabe_namen_appears_in_label(
|
||||
self, mock_ask_text, mock_project, mock_wfs_layer_cls
|
||||
self, mock_ask_text, mock_project, mock_lade
|
||||
):
|
||||
"""vorgabe_namen=['Musterstadt'] → ask_text-label enthält den Namen."""
|
||||
"""Prüft, dass vorgabe_namen im Beschriftungstext des Eingabedialogs erscheint."""
|
||||
mock_ask_text.return_value = ("9999", True)
|
||||
mock_project.return_value = _DummyProject()
|
||||
mock_wfs_layer_cls.return_value = _DummyLayer(valid=True)
|
||||
mock_lade.return_value = _DummyLayer(valid=True)
|
||||
|
||||
flurstueck_alkis(None, vorgabe_namen=["Musterstadt"])
|
||||
|
||||
@@ -268,43 +269,18 @@ class TestFlurstueckAlkis(unittest.TestCase):
|
||||
self.assertIn("Musterstadt", label_arg)
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 8. bbox wird in WFS-URI eingebettet #
|
||||
# 8. Rückwärtskompatibilität: alle neuen Params optional #
|
||||
# ------------------------------------------------------------------ #
|
||||
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsVectorLayer")
|
||||
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsProject.instance")
|
||||
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text")
|
||||
def test_bbox_embedded_in_wfs_uri(
|
||||
self, mock_ask_text, mock_project, mock_wfs_layer_cls
|
||||
):
|
||||
"""bbox-Parameter → WFS-URI enthält 'bbox=' Substring."""
|
||||
mock_ask_text.return_value = ("1234", True)
|
||||
mock_project.return_value = _DummyProject()
|
||||
mock_wfs_layer_cls.return_value = _DummyLayer(valid=True)
|
||||
|
||||
class _Rect:
|
||||
def xMinimum(self): return 414000.0
|
||||
def yMinimum(self): return 5657000.0
|
||||
def xMaximum(self): return 415000.0
|
||||
def yMaximum(self): return 5658000.0
|
||||
|
||||
flurstueck_alkis(None, bbox=_Rect())
|
||||
|
||||
uri_str = mock_wfs_layer_cls.call_args[0][0]
|
||||
self.assertIn("bbox=", uri_str.lower())
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 9. Rückwärtskompatibilität: alle neuen Params optional #
|
||||
# ------------------------------------------------------------------ #
|
||||
@patch("sn_verfahrensgebiet.functions.flurstueck_alkis.QgsVectorLayer")
|
||||
@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_backwards_compatible_no_new_params(
|
||||
self, mock_ask_text, mock_project, mock_wfs_layer_cls
|
||||
self, mock_ask_text, mock_project, mock_lade
|
||||
):
|
||||
"""flurstueck_alkis(tab) ohne neue Parameter → kein Fehler."""
|
||||
"""Prüft, dass flurstueck_alkis() ohne die neuen optionalen Parameter weiterhin fehlerfrei einen Layer mit LoadStatus.FIRST liefert."""
|
||||
mock_ask_text.return_value = ("0000", True)
|
||||
mock_project.return_value = _DummyProject()
|
||||
mock_wfs_layer_cls.return_value = _DummyLayer(valid=True)
|
||||
mock_lade.return_value = _DummyLayer(valid=True)
|
||||
|
||||
layer, status = flurstueck_alkis(None)
|
||||
|
||||
@@ -312,7 +288,7 @@ class TestFlurstueckAlkis(unittest.TestCase):
|
||||
self.assertEqual(status, LoadStatus.FIRST)
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 10. ohne_schluessel_abfrage: vorgabe_schluessel direkt, kein ask_text (#47)
|
||||
# 9. 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")
|
||||
@@ -320,7 +296,7 @@ class TestFlurstueckAlkis(unittest.TestCase):
|
||||
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."""
|
||||
"""Prüft, dass ohne_schluessel_abfrage=True vorgabe_schluessel direkt verwendet und ask_text nicht aufgerufen wird."""
|
||||
mock_project.return_value = _DummyProject()
|
||||
geladen = _DummyFlstLayer([_DummyFlstFeature("110001_0001__00001")])
|
||||
mock_lade.return_value = geladen
|
||||
|
||||
Reference in New Issue
Block a user