From cf53e69d741ae11474aea644837bfaba984319bc Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Wed, 17 Jun 2026 11:57:19 +0200 Subject: [PATCH] # Geometry conflicts on API * refactors internal fetching of GeometryConflict data * adds serializing of GeometryConflict entry data (identifier, id) to GET API calls --- api/utils/serializer/serializer.py | 26 ++++++++++++++++++ api/utils/serializer/v1/serializer.py | 1 + konova/models/geometry.py | 38 ++++++++++++++++++++++++--- konova/models/object.py | 23 +++++----------- 4 files changed, 68 insertions(+), 20 deletions(-) diff --git a/api/utils/serializer/serializer.py b/api/utils/serializer/serializer.py index 96386509..f952df73 100644 --- a/api/utils/serializer/serializer.py +++ b/api/utils/serializer/serializer.py @@ -202,3 +202,29 @@ class AbstractModelAPISerializer: obj (Intervention) """ raise NotImplementedError("Must be implemented in subclasses") + + + def _geometry_conflicts_to_list(self, geometry) -> list: + """ Serializes geometry conflict ids into dict + + Args: + geometry (Geometry): The geometry to fetch geometry conflicts from + + Returns: + ids (list): Serialized geometry conflicts as dict objects inside a list + """ + ids = [] + conflict_geometries = geometry.get_conflict_geometries() + for geom in conflict_geometries: + try: + data = geom.get_data_objects(["identifier", "id"])[0] + except KeyError: + raise AssertionError(f"Geometry {geom.id} is not attached to any entries. Contact an admin!") + ids.append( + { + "identifier": data["identifier"], + "id": data["id"], + } + ) + + return ids diff --git a/api/utils/serializer/v1/serializer.py b/api/utils/serializer/v1/serializer.py index b22aa504..e239613a 100644 --- a/api/utils/serializer/v1/serializer.py +++ b/api/utils/serializer/v1/serializer.py @@ -54,6 +54,7 @@ class AbstractModelAPISerializerV1(AbstractModelAPISerializer): "created_on": self._created_on_to_json(entry), "modified_on": self._modified_on_to_json(entry), "external_identifiers": ext_ids, + "geometry_conflicts": self._geometry_conflicts_to_list(entry.geometry) } self._extend_properties_data(entry) geo_json["properties"] = self.properties_data diff --git a/konova/models/geometry.py b/konova/models/geometry.py index 9e211b3c..cc3f581a 100644 --- a/konova/models/geometry.py +++ b/konova/models/geometry.py @@ -125,7 +125,7 @@ class Geometry(BaseResource): deleted=None ) if limit_to_attrs: - objs += set_objs.values_list(*limit_to_attrs, flat=True) + objs += set_objs.values(*limit_to_attrs) else: objs += set_objs @@ -135,16 +135,16 @@ class Geometry(BaseResource): Q(deleted=None) & Q(intervention__deleted=None) ) if limit_to_attrs: - objs += comp_objs.values_list(*limit_to_attrs, flat=True) + objs += comp_objs.values(*limit_to_attrs) else: objs += comp_objs return objs - def get_data_object(self): + def get_data_object(self, limit_to_attrs: list = None): """ Getter for the specific data object which is related to this geometry """ - objs = self.get_data_objects() + objs = self.get_data_objects(limit_to_attrs) assert (len(objs) <= 1) result = objs.pop() return result @@ -436,6 +436,16 @@ class Geometry(BaseResource): output_geom.transform(DEFAULT_SRID_RLP) return output_geom + def get_conflict_geometries(self): + """ Getter for geometry ids which conflict with this geometry or are conflicted by this one + + Returns: + geom_ids (list): List of geometry ids + """ + conflict_geoms_id = GeometryConflict.get_conflict_geometries(self) + conflict_geoms = Geometry.objects.filter(id__in=conflict_geoms_id) + return conflict_geoms + class GeometryConflict(UuidModel): """ @@ -459,3 +469,23 @@ class GeometryConflict(UuidModel): def __str__(self): return f"{self.conflicting_geometry.id} conflicts with {self.affected_geometry.id}" + + @staticmethod + def get_conflict_geometries(geometry: Geometry): + """ Getter for geometries which conflict in one or another way with the given one + + Args: + geometry (Geometry): The geometry which shall be checked + + Returns: + conflict_geometries (QuerySet): QuerySet of geometries which have conflicts with the given geometry + """ + conflict_geometries = GeometryConflict.objects.filter( + affected_geometry=geometry.id, + ).values_list("conflicting_geometry__id", flat=True) + conflict_geometries = conflict_geometries.union( + GeometryConflict.objects.filter( + conflicting_geometry=geometry.id, + ).values_list("affected_geometry__id", flat=True) + ) + return conflict_geometries \ No newline at end of file diff --git a/konova/models/object.py b/konova/models/object.py index 7faa0c4d..15894629 100644 --- a/konova/models/object.py +++ b/konova/models/object.py @@ -676,24 +676,15 @@ class GeoReferencedMixin(models.Model): if self.geometry is None: return request - instance_objs = [] - needed_data_object_attrs = [ - "identifier" - ] - conflicts = self.geometry.conflicts_geometries.iterator() + conflicting_geometries = self.geometry.get_conflict_geometries() + data_object_identifiers = [] + for conflicting_geom in conflicting_geometries: + data_object_identifiers.append(conflicting_geom.get_data_object(["identifier"])) - for conflict in conflicts: - # Only check the affected geometry of this conflict, since we know the conflicting geometry is self.geometry - instance_objs += conflict.affected_geometry.get_data_objects(needed_data_object_attrs) - - conflicts = self.geometry.conflicted_by_geometries.iterator() - for conflict in conflicts: - # Only check the conflicting geometry of this conflict, since we know the affected geometry is self.geometry - instance_objs += conflict.conflicting_geometry.get_data_objects(needed_data_object_attrs) - - add_message = len(instance_objs) > 0 + add_message = len(data_object_identifiers) > 0 if add_message: - instance_identifiers = ", ".join(instance_objs) + data_object_identifiers = [x["identifier"] for x in data_object_identifiers] + instance_identifiers = ", ".join(data_object_identifiers) message_str = GEOMETRY_CONFLICT_WITH_TEMPLATE.format(instance_identifiers) messages.info(request, message_str) return request -- 2.52.0