Compare commits

..

6 Commits

Author SHA1 Message Date
mpeltriaux 08884cb370 Merge pull request '# HOTFIX' (#550) from hotfix_indexErrorOnAPIGet into master
Reviewed-on: #550
2026-06-20 12:10:51 +02:00
mpeltriaux 9b5defec6d # HOTFIX
* fixes bug where unfetchable data entries of a GeometryConflict would result in an error via API
2026-06-20 12:10:13 +02:00
mpeltriaux 0425430e65 Merge pull request '# HOTFIX' (#547) from hotfix_indexErrorOnAPIGet into master
Reviewed-on: #547
2026-06-20 09:27:55 +02:00
mpeltriaux fec313445d # HOTFIX
* fixes a bug where detected GeometryConflicts with deleted entries would case an IndexError
2026-06-20 09:27:02 +02:00
mpeltriaux 28db96b081 Merge pull request '# Geometry conflicts on API' (#544) from 534_Return_geometry_conflicts_on_API into master
Reviewed-on: #544
2026-06-17 11:58:55 +02:00
mpeltriaux cf53e69d74 # Geometry conflicts on API
* refactors internal fetching of GeometryConflict data
* adds serializing of GeometryConflict entry data (identifier, id) to GET API calls
2026-06-17 11:57:19 +02:00
4 changed files with 87 additions and 22 deletions
+30
View File
@@ -202,3 +202,33 @@ class AbstractModelAPISerializer:
obj (Intervention) obj (Intervention)
""" """
raise NotImplementedError("Must be implemented in subclasses") 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"])
if len(data) == 0:
# expected behaviour in case of deleted data object
continue
data = data[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
+1
View File
@@ -54,6 +54,7 @@ class AbstractModelAPISerializerV1(AbstractModelAPISerializer):
"created_on": self._created_on_to_json(entry), "created_on": self._created_on_to_json(entry),
"modified_on": self._modified_on_to_json(entry), "modified_on": self._modified_on_to_json(entry),
"external_identifiers": ext_ids, "external_identifiers": ext_ids,
"geometry_conflicts": self._geometry_conflicts_to_list(entry.geometry)
} }
self._extend_properties_data(entry) self._extend_properties_data(entry)
geo_json["properties"] = self.properties_data geo_json["properties"] = self.properties_data
+46 -5
View File
@@ -125,7 +125,7 @@ class Geometry(BaseResource):
deleted=None deleted=None
) )
if limit_to_attrs: if limit_to_attrs:
objs += set_objs.values_list(*limit_to_attrs, flat=True) objs += set_objs.values(*limit_to_attrs)
else: else:
objs += set_objs objs += set_objs
@@ -135,18 +135,29 @@ class Geometry(BaseResource):
Q(deleted=None) & Q(intervention__deleted=None) Q(deleted=None) & Q(intervention__deleted=None)
) )
if limit_to_attrs: if limit_to_attrs:
objs += comp_objs.values_list(*limit_to_attrs, flat=True) objs += comp_objs.values(*limit_to_attrs)
else: else:
objs += comp_objs objs += comp_objs
return 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 Getter for the specific data object which is related to this geometry.
!!! Only returns undeleted entries !!!
Returns:
result (str|None): Returns the desired attributes or None if the data object is marked as deleted
""" """
objs = self.get_data_objects() objs = self.get_data_objects(limit_to_attrs)
assert (len(objs) <= 1) assert (len(objs) <= 1)
try:
result = objs.pop() result = objs.pop()
except IndexError:
# If this happens, we just processed a GeometryConflict with an entry which is marked as deleted.
# Therefore we return None
result = None
return result return result
def update_parcels(self): def update_parcels(self):
@@ -436,6 +447,16 @@ class Geometry(BaseResource):
output_geom.transform(DEFAULT_SRID_RLP) output_geom.transform(DEFAULT_SRID_RLP)
return output_geom 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): class GeometryConflict(UuidModel):
""" """
@@ -459,3 +480,23 @@ class GeometryConflict(UuidModel):
def __str__(self): def __str__(self):
return f"{self.conflicting_geometry.id} conflicts with {self.affected_geometry.id}" 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
+9 -16
View File
@@ -676,24 +676,17 @@ class GeoReferencedMixin(models.Model):
if self.geometry is None: if self.geometry is None:
return request return request
instance_objs = [] conflicting_geometries = self.geometry.get_conflict_geometries()
needed_data_object_attrs = [ data_object_identifiers = []
"identifier" for conflicting_geom in conflicting_geometries:
] data_obj_id = conflicting_geom.get_data_object(["identifier"])
conflicts = self.geometry.conflicts_geometries.iterator() if data_obj_id:
data_object_identifiers.append(data_obj_id)
for conflict in conflicts: add_message = len(data_object_identifiers) > 0
# 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
if add_message: 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) message_str = GEOMETRY_CONFLICT_WITH_TEMPLATE.format(instance_identifiers)
messages.info(request, message_str) messages.info(request, message_str)
return request return request