From 2ef643f4e004bfe43ddffe3dd03ce9fce856f063 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Wed, 23 Nov 2022 13:51:05 +0100 Subject: [PATCH 1/2] Geometry race condition fix * fixes race condition for geometry conflict and parcel calculation * harmonizes empty geometries from None/MultiPolygonEmpty to MultiPolygonEmpty --- compensation/forms/compensation.py | 36 ++++++++++--------- compensation/forms/eco_account.py | 15 ++++---- ema/forms.py | 16 +++++---- intervention/forms/intervention.py | 18 ++++++---- konova/forms/geometry_form.py | 3 ++ konova/models/geometry.py | 5 +++ .../konova/includes/parcels/parcels.html | 6 ++++ konova/views/geometry.py | 6 ++-- 8 files changed, 65 insertions(+), 40 deletions(-) diff --git a/compensation/forms/compensation.py b/compensation/forms/compensation.py index c518679..647051d 100644 --- a/compensation/forms/compensation.py +++ b/compensation/forms/compensation.py @@ -129,12 +129,11 @@ class NewCompensationForm(AbstractCompensationForm, self.initialize_form_field("identifier", identifier) self.fields["identifier"].widget.attrs["url"] = reverse_lazy("compensation:new-id") - def __create_comp(self, user, geom_form) -> Compensation: + def __create_comp(self, user): """ Creates the compensation from form data Args: user (User): The performing user - geom_form (SimpleGeomForm): The geometry form Returns: comp (Compensation): The compensation object @@ -150,8 +149,6 @@ class NewCompensationForm(AbstractCompensationForm, # Create log entry action = UserActionLogEntry.get_created_action(user) - # Process the geometry form - geometry = geom_form.save(action) # Finally create main object comp = Compensation.objects.create( @@ -162,18 +159,23 @@ class NewCompensationForm(AbstractCompensationForm, is_cef=is_cef, is_coherence_keeping=is_coherence_keeping, is_pik=is_pik, - geometry=geometry, comment=comment, ) # Add the log entry to the main objects log list comp.log.add(action) - return comp + return comp, action def save(self, user: User, geom_form: SimpleGeomForm): with transaction.atomic(): - comp = self.__create_comp(user, geom_form) + comp, action = self.__create_comp(user) comp.intervention.mark_as_edited(user, edit_comment=COMPENSATION_ADDED_TEMPLATE.format(comp.identifier)) + + # Process the geometry form + geometry = geom_form.save(action) + comp.geometry = geometry + comp.save() + return comp @@ -205,6 +207,9 @@ class EditCompensationForm(NewCompensationForm): def save(self, user: User, geom_form: SimpleGeomForm): with transaction.atomic(): + # Create log entry + action = UserActionLogEntry.get_edited_action(user) + # Fetch data from cleaned POST values identifier = self.cleaned_data.get("identifier", None) title = self.cleaned_data.get("title", None) @@ -214,17 +219,9 @@ class EditCompensationForm(NewCompensationForm): is_pik = self.cleaned_data.get("is_pik", None) comment = self.cleaned_data.get("comment", None) - # Create log entry - action = UserActionLogEntry.get_edited_action(user) - - # Process the geometry form - geometry = geom_form.save(action) - - # Finally create main object self.instance.identifier = identifier self.instance.title = title self.instance.intervention = intervention - self.instance.geometry = geometry self.instance.is_cef = is_cef self.instance.is_coherence_keeping = is_coherence_keeping self.instance.comment = comment @@ -233,6 +230,11 @@ class EditCompensationForm(NewCompensationForm): self.instance.save() self.instance.log.add(action) - intervention.mark_as_edited(user, self.request, EDITED_GENERAL_DATA) - return self.instance \ No newline at end of file + + # Process the geometry form (NOT ATOMIC TRANSACTION DUE TO CELERY!) + geometry = geom_form.save(action) + self.instance.geometry = geometry + self.instance.save() + + return self.instance diff --git a/compensation/forms/eco_account.py b/compensation/forms/eco_account.py index 360dd34..dd167c6 100644 --- a/compensation/forms/eco_account.py +++ b/compensation/forms/eco_account.py @@ -94,8 +94,6 @@ class NewEcoAccountForm(AbstractCompensationForm, CompensationResponsibleFormMix # Create log entry action = UserActionLogEntry.get_created_action(user) - # Process the geometry form - geometry = geom_form.save(action) handler = Handler.objects.create( type=handler_type, @@ -119,7 +117,6 @@ class NewEcoAccountForm(AbstractCompensationForm, CompensationResponsibleFormMix responsible=responsible, deductable_surface=surface, created=action, - geometry=geometry, comment=comment, is_pik=is_pik, legal=legal @@ -129,6 +126,10 @@ class NewEcoAccountForm(AbstractCompensationForm, CompensationResponsibleFormMix # Add the log entry to the main objects log list acc.log.add(action) + # Process the geometry form + geometry = geom_form.save(action) + acc.geometry = geometry + acc.save() acc.update_deductable_rest() return acc @@ -185,9 +186,6 @@ class EditEcoAccountForm(NewEcoAccountForm): # Create log entry action = UserActionLogEntry.get_edited_action(user) - # Process the geometry form - geometry = geom_form.save(action) - # Update responsible data self.instance.responsible.handler.type = handler_type self.instance.responsible.handler.detail = handler_detail @@ -204,7 +202,6 @@ class EditEcoAccountForm(NewEcoAccountForm): self.instance.identifier = identifier self.instance.title = title self.instance.deductable_surface = surface - self.instance.geometry = geometry self.instance.comment = comment self.instance.is_pik = is_pik self.instance.modified = action @@ -213,6 +210,10 @@ class EditEcoAccountForm(NewEcoAccountForm): # Add the log entry to the main objects log list self.instance.log.add(action) + # Process the geometry form (NOT ATOMIC TRANSACTION DUE TO CELERY!) + geometry = geom_form.save(action) + self.instance.geometry = geometry + self.instance.save() self.instance.update_deductable_rest() return self.instance diff --git a/ema/forms.py b/ema/forms.py index bbe09a8..bca7224 100644 --- a/ema/forms.py +++ b/ema/forms.py @@ -64,8 +64,6 @@ class NewEmaForm(AbstractCompensationForm, CompensationResponsibleFormMixin, Pik # Create log entry action = UserActionLogEntry.get_created_action(user) - # Process the geometry form - geometry = geom_form.save(action) handler = Handler.objects.create( type=handler_type, @@ -83,7 +81,6 @@ class NewEmaForm(AbstractCompensationForm, CompensationResponsibleFormMixin, Pik title=title, responsible=responsible, created=action, - geometry=geometry, comment=comment, is_pik=is_pik, ) @@ -93,6 +90,11 @@ class NewEmaForm(AbstractCompensationForm, CompensationResponsibleFormMixin, Pik # Add the log entry to the main objects log list acc.log.add(action) + + # Process the geometry form (NOT ATOMIC TRANSACTION DUE TO CELERY!) + geometry = geom_form.save(action) + acc.geometry = geometry + acc.save() return acc @@ -141,8 +143,6 @@ class EditEmaForm(NewEmaForm): # Create log entry action = UserActionLogEntry.get_edited_action(user) - # Process the geometry form - geometry = geom_form.save(action) # Update responsible data self.instance.responsible.handler.type = handler_type @@ -155,7 +155,6 @@ class EditEmaForm(NewEmaForm): # Update main oject data self.instance.identifier = identifier self.instance.title = title - self.instance.geometry = geometry self.instance.comment = comment self.instance.is_pik = is_pik self.instance.modified = action @@ -163,6 +162,11 @@ class EditEmaForm(NewEmaForm): # Add the log entry to the main objects log list self.instance.log.add(action) + + # Process the geometry form (NOT ATOMIC TRANSACTION DUE TO CELERY!) + geometry = geom_form.save(action) + self.instance.geometry = geometry + self.instance.save() return self.instance diff --git a/intervention/forms/intervention.py b/intervention/forms/intervention.py index 00b772b..8086fd7 100644 --- a/intervention/forms/intervention.py +++ b/intervention/forms/intervention.py @@ -263,9 +263,6 @@ class NewInterventionForm(BaseForm): handler=handler, ) - # Process the geometry form - geometry = geom_form.save(action) - # Finally create main object, holding the other objects intervention = Intervention.objects.create( identifier=identifier, @@ -273,7 +270,6 @@ class NewInterventionForm(BaseForm): responsible=responsibility_data, legal=legal_data, created=action, - geometry=geometry, comment=comment, ) @@ -282,6 +278,12 @@ class NewInterventionForm(BaseForm): # Add the performing user as the first user having access to the data intervention.share_with_user(user) + + # Process the geometry form (NOT ATOMIC TRANSACTION DUE TO CELERY!) + geometry = geom_form.save(action) + intervention.geometry = geometry + intervention.save() + return intervention @@ -370,9 +372,6 @@ class EditInterventionForm(NewInterventionForm): user_action = self.instance.mark_as_edited(user, edit_comment=EDITED_GENERAL_DATA) - geometry = geom_form.save(user_action) - self.instance.geometry = geometry - self.instance.log.add(user_action) self.instance.identifier = identifier @@ -381,5 +380,10 @@ class EditInterventionForm(NewInterventionForm): self.instance.modified = user_action self.instance.save() + # Process the geometry form (NOT ATOMIC TRANSACTION DUE TO CELERY!) + geometry = geom_form.save(user_action) + self.instance.geometry = geometry + self.instance.save() + return self.instance diff --git a/konova/forms/geometry_form.py b/konova/forms/geometry_form.py index 95e0a7d..09449af 100644 --- a/konova/forms/geometry_form.py +++ b/konova/forms/geometry_form.py @@ -63,6 +63,7 @@ class SimpleGeomForm(BaseForm): geom = self.data["geom"] if geom is None or len(geom) == 0: # empty geometry is a valid geometry + self.cleaned_data["geom"] = MultiPolygon(srid=DEFAULT_SRID_RLP).ewkt return is_valid geom = json.loads(geom) @@ -106,6 +107,8 @@ class SimpleGeomForm(BaseForm): return is_valid features.append(polygon) + + # Unionize all geometry features into one new MultiPolygon form_geom = MultiPolygon(srid=DEFAULT_SRID_RLP) for feature in features: form_geom = form_geom.union(feature) diff --git a/konova/models/geometry.py b/konova/models/geometry.py index 492ba43..f216903 100644 --- a/konova/models/geometry.py +++ b/konova/models/geometry.py @@ -116,6 +116,11 @@ class Geometry(BaseResource): """ from konova.models import Parcel, District, ParcelIntersection, Municipal, ParcelGroup + + if self.geom.empty: + # Nothing to do + return + parcel_fetcher = ParcelWFSFetcher( geometry_id=self.id, ) diff --git a/konova/templates/konova/includes/parcels/parcels.html b/konova/templates/konova/includes/parcels/parcels.html index 9512c09..ae61c56 100644 --- a/konova/templates/konova/includes/parcels/parcels.html +++ b/konova/templates/konova/includes/parcels/parcels.html @@ -17,11 +17,17 @@
+ {% if geom_form.instance.geometry %}
+ {% else %} +
+ {% translate 'No geometry entry found on database. Please contact an admin!' %} +
+ {% endif %}
\ No newline at end of file diff --git a/konova/views/geometry.py b/konova/views/geometry.py index e2e8737..868d629 100644 --- a/konova/views/geometry.py +++ b/konova/views/geometry.py @@ -32,16 +32,16 @@ def get_geom_parcels(request: HttpRequest, id: str): parcels = geom.get_underlying_parcels() geos_geom = geom.geom - parcels_are_currently_calculated = geos_geom is not None and geos_geom.area > 0 and len(parcels) == 0 + geometry_exists = not geos_geom.empty + parcels_are_currently_calculated = geometry_exists and geos_geom.area > 0 and len(parcels) == 0 parcels_available = len(parcels) > 0 - no_geometry_given = geos_geom is None if parcels_are_currently_calculated: # Parcels are being calculated right now. Change the status code, so polling stays active for fetching # resutls after the calculation status_code = 200 - if parcels_available or no_geometry_given: + if parcels_available or not geometry_exists: parcels = parcels.order_by("-municipal", "flr", "flrstck_zhlr", "flrstck_nnr") municipals = parcels.order_by("municipal").distinct("municipal").values("municipal__id") municipals = Municipal.objects.filter(id__in=municipals) From 79fd3ad29d90e750794cdff8ff119f7760f25f5b Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Wed, 23 Nov 2022 16:05:27 +0100 Subject: [PATCH 2/2] API - Geometry empty * removes mapping of empty geometry to None due to general switch to empty geometry usage --- api/utils/serializer/serializer.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/utils/serializer/serializer.py b/api/utils/serializer/serializer.py index e0511b8..04a0312 100644 --- a/api/utils/serializer/serializer.py +++ b/api/utils/serializer/serializer.py @@ -136,8 +136,6 @@ class AbstractModelAPISerializer: geometry = geos.fromstr(geojson) if geometry.srid != DEFAULT_SRID_RLP: geometry.transform(DEFAULT_SRID_RLP) - if geometry.empty: - geometry = None return geometry def _get_obj_from_db(self, id, user):