From 814f35c4266605cff291ea4384aaf2a7202edc9c Mon Sep 17 00:00:00 2001 From: mpeltriaux 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 b0f9ee4ac00fd6452471174e1cb34e539587e03e Mon Sep 17 00:00:00 2001 From: mpeltriaux 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"