From 0cefc0e0b8b6ab0c1ab190b498f9ff39b10d02e8 Mon Sep 17 00:00:00 2001
From: mpeltriaux <Michel.Peltriaux@sgdnord.rlp.de>
Date: Thu, 17 Nov 2022 10:13:22 +0100
Subject: [PATCH 1/2] Fix CompensationAction unit None

* adds correct declaration of unit (qm -> m2) for template rendering
* adds migration to transform existing qm units to m2
---
 .../migrations/0013_auto_20221117_0819.py     | 35 +++++++++++++++++++
 compensation/models/action.py                 |  3 +-
 2 files changed, 37 insertions(+), 1 deletion(-)
 create mode 100644 compensation/migrations/0013_auto_20221117_0819.py

diff --git a/compensation/migrations/0013_auto_20221117_0819.py b/compensation/migrations/0013_auto_20221117_0819.py
new file mode 100644
index 00000000..defcfbc2
--- /dev/null
+++ b/compensation/migrations/0013_auto_20221117_0819.py
@@ -0,0 +1,35 @@
+# Generated by Django 3.1.3 on 2022-11-17 07:19
+
+from django.db import migrations
+
+from compensation.models import UnitChoices
+
+
+def harmonize_action_units(apps, schema_editor):
+    """
+    CompensationAction units (based on UnitChoices) can be mixed up at this point where
+        * qm represents m²  and
+        * m2 represents m²
+
+    We drop qm in support of m2
+
+    """
+    CompensationAction = apps.get_model("compensation", "CompensationAction")
+    actions = CompensationAction.objects.filter(
+        unit="qm"
+    )
+
+    for action in actions:
+        action.unit = UnitChoices.m2
+        action.save()
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('compensation', '0012_auto_20221116_1322'),
+    ]
+
+    operations = [
+        migrations.RunPython(harmonize_action_units),
+    ]
diff --git a/compensation/models/action.py b/compensation/models/action.py
index e54f53d3..58e41c46 100644
--- a/compensation/models/action.py
+++ b/compensation/models/action.py
@@ -19,8 +19,9 @@ class UnitChoices(models.TextChoices):
     """
     cm = "cm", _("cm")
     m = "m", _("m")
+    m2 = "m2", _("m²")
+    m3 = "m3", _("m³")
     km = "km", _("km")
-    qm = "qm", _("m²")
     ha = "ha", _("ha")
     st = "pcs", _("Pieces")  # pieces
 

From a157891f9dd59474d6f798680d9f6ef32b1f5863 Mon Sep 17 00:00:00 2001
From: mpeltriaux <Michel.Peltriaux@sgdnord.rlp.de>
Date: Thu, 17 Nov 2022 13:01:40 +0100
Subject: [PATCH 2/2] Z-axis geometry upload fix

* adds clamping of 3D geometries to 2D geometries if uploaded using the map importer
* extends tests for payment-document linkage
* fixes bug in team-admin selection where autocomplete could not be resolved properly
---
 intervention/tests/test_workflow.py | 10 ++++++++--
 konova/forms/geometry_form.py       | 27 +++++++++++++++++++++++++--
 konova/tests/test_views.py          | 13 +++++++++++++
 user/autocomplete/team.py           |  4 ++--
 4 files changed, 48 insertions(+), 6 deletions(-)

diff --git a/intervention/tests/test_workflow.py b/intervention/tests/test_workflow.py
index 9124c3e8..be885c96 100644
--- a/intervention/tests/test_workflow.py
+++ b/intervention/tests/test_workflow.py
@@ -11,7 +11,7 @@ from django.core.exceptions import ObjectDoesNotExist
 from django.urls import reverse
 
 from compensation.models import Payment, EcoAccountDeduction
-from intervention.models import Intervention
+from intervention.models import Intervention, InterventionDocument
 from konova.settings import ETS_GROUP, ZB_GROUP
 from konova.tests.test_views import BaseWorkflowTestCase
 from user.models import UserActionLogEntry, UserAction
@@ -153,13 +153,16 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
         payment = Payment.objects.create(amount=10.00, due_on=None, comment="No due date because test")
         self.intervention.payments.add(payment)
 
+        # Since there is a payment, we need to add a dummy document (mocking a legal document for payment info)
+        document = self.create_dummy_document(InterventionDocument, self.intervention)
+
         # Run request again
         self.client_user.post(check_url, post_data)
 
         # Update intervention from db
         self.intervention.refresh_from_db()
 
-        # We expect the intervention to be checked now and contains the proper data
+        # We expect the intervention to be checked now and contain the proper data
         # Attention: We check the timestamp only on the date, not the time, since the microseconds delay would result
         # in an unwanted assertion error
         checked = self.intervention.checked
@@ -209,6 +212,9 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
         payment = Payment.objects.create(amount=10.00, due_on=None, comment="No due date because test")
         self.intervention.payments.add(payment)
 
+        # Since there is a payment, we need to add a dummy document (mocking a legal document for payment info)
+        document = self.create_dummy_document(InterventionDocument, self.intervention)
+
         # Run request again
         self.client_user.post(record_url, post_data)
 
diff --git a/konova/forms/geometry_form.py b/konova/forms/geometry_form.py
index 5d25a85d..f76b907f 100644
--- a/konova/forms/geometry_form.py
+++ b/konova/forms/geometry_form.py
@@ -10,6 +10,7 @@ import json
 from django.contrib.gis import gdal
 from django.contrib.gis.forms import MultiPolygonField
 from django.contrib.gis.geos import MultiPolygon, Polygon
+from django.contrib.gis.geos.prototypes.io import WKTWriter
 from django.utils.translation import gettext_lazy as _
 
 from konova.forms.base_form import BaseForm
@@ -74,9 +75,21 @@ class SimpleGeomForm(BaseForm):
         # this case)
         features = []
         features_json = geom.get("features", [])
+        accepted_ogr_types = [
+            "Polygon",
+            "Polygon25D",
+            "MultiPolygon",
+            "MultiPolygon25D",
+        ]
         for feature in features_json:
-            g = gdal.OGRGeometry(json.dumps(feature.get("geometry", feature)), srs=DEFAULT_SRID_RLP)
-            if g.geom_type not in ["Polygon", "MultiPolygon"]:
+            feature_geom = json.dumps(feature.get("geometry", feature))
+            g = gdal.OGRGeometry(feature_geom, srs=DEFAULT_SRID_RLP)
+
+            flatten_geometry = g.coord_dim > 2
+            if flatten_geometry:
+                g = self.__flatten_geom_to_2D(g)
+
+            if g.geom_type not in accepted_ogr_types:
                 self.add_error("geom", _("Only surfaces allowed. Points or lines must be buffered."))
                 is_valid = False
                 return is_valid
@@ -131,3 +144,13 @@ class SimpleGeomForm(BaseForm):
         # Start the parcel update procedure in a background process
         celery_update_parcels.delay(geometry.id)
         return geometry
+
+    def __flatten_geom_to_2D(self, geom):
+        """
+        Enforces a given OGRGeometry from higher dimensions into 2D
+
+        """
+        wkt_w = WKTWriter(dim=2)
+        g_wkt = wkt_w.write(geom.geos).decode("utf-8")
+        geom = gdal.OGRGeometry(g_wkt)
+        return geom
diff --git a/konova/tests/test_views.py b/konova/tests/test_views.py
index 84a4238b..437f114a 100644
--- a/konova/tests/test_views.py
+++ b/konova/tests/test_views.py
@@ -115,6 +115,19 @@ class BaseTestCase(TestCase):
         """
         return f"{prefix}{generate_random_string(3, True)}"
 
+    def create_dummy_document(self, DocumentModel, instance):
+        """ Creates a document db entry which can be used for tests
+
+        """
+        doc = DocumentModel.objects.create(
+            title="TEST_doc",
+            comment="",
+            file=None,
+            date_of_creation="1970-01-01",
+            instance=instance,
+        )
+        return doc
+
     def create_dummy_intervention(self):
         """ Creates an intervention which can be used for tests
 
diff --git a/user/autocomplete/team.py b/user/autocomplete/team.py
index 377c693e..c78053c8 100644
--- a/user/autocomplete/team.py
+++ b/user/autocomplete/team.py
@@ -17,15 +17,15 @@ class TeamAdminAutocomplete(Select2QuerySetView):
     def get_queryset(self):
         if self.request.user.is_anonymous:
             return User.objects.none()
+
         qs = User.objects.filter(
             id__in=self.forwarded.get("members", [])
         ).exclude(
             id__in=self.forwarded.get("admins", [])
         )
         if self.q:
-            # Due to privacy concerns only a full username match will return the proper user entry
             qs = qs.filter(
-                name__icontains=self.q
+                username__icontains=self.q
             )
         qs = qs.order_by(
             "username"