Compare commits

...

15 Commits

Author SHA1 Message Date
mpeltriaux 74d4ee49cc Merge pull request 'Master' (#558) from master into Docker
Reviewed-on: #558
2026-07-10 14:03:05 +02:00
mpeltriaux f5aaac2acf Merge pull request '# Basemap' (#557) from 546_Remove_WebAtlasRP_service into master
Reviewed-on: #557
2026-07-10 14:01:38 +02:00
mpeltriaux 775e40e9ae # Basemap
* removes discontinued webatlas wms layers from config.json
* enables basemap_grau as default wms layer
2026-07-10 14:00:36 +02:00
mpeltriaux e794f3fff2 Merge pull request '# Improvement PUT API' (#556) from 554_API_PUT_external_identifier into master
Reviewed-on: #556
2026-07-10 13:59:22 +02:00
mpeltriaux b4f2e3232a # Improvement PUT API
* improves error processing and response on PUT endpoint
* consolidates improved error processing into central method for all API endpoints
2026-07-10 13:58:23 +02:00
mpeltriaux e8b583d4c6 Merge pull request 'Master' (#553) from master into Docker
Reviewed-on: #553
2026-06-27 09:32:26 +02:00
mpeltriaux b9c453fdd2 Merge pull request '549 rework geometry conflict fetching' (#552) from 549_Rework_GeometryConflict_fetching into master
Reviewed-on: #552
2026-06-27 09:31:08 +02:00
mpeltriaux 625c591122 # Tests
* updates tests
* enhances workflow of geometry conflict fetching
2026-06-25 08:55:50 +02:00
mpeltriaux f551763798 # Performance boost
* boosts performance of geometry conflict fetching 50-75%
2026-06-25 08:27:09 +02:00
mpeltriaux f57fa4cc20 Merge pull request 'Master' (#551) from master into Docker
Reviewed-on: #551
2026-06-20 12:11:18 +02:00
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 b0f72584e2 Merge pull request 'Master' (#548) from master into Docker
Reviewed-on: #548
2026-06-20 09:28:12 +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
6 changed files with 81 additions and 65 deletions
+15 -3
View File
@@ -6,6 +6,7 @@ Created on: 24.01.22
""" """
import json import json
import uuid
from abc import abstractmethod from abc import abstractmethod
from django.contrib.gis import geos from django.contrib.gis import geos
@@ -171,13 +172,20 @@ class AbstractModelAPISerializer:
Returns: Returns:
""" """
if not id:
raise ValueError("No id provided. The id is expected to live in the URL!")
# First if there is an external identifier linked to an internal one, so we can continue with the internal # First if there is an external identifier linked to an internal one, so we can continue with the internal
try: try:
ext_id = ExternalIdentifier.objects.get(external_id=id) ext_id = ExternalIdentifier.objects.get(external_id=id)
id = ext_id.internal_id id = ext_id.internal_id
except ObjectDoesNotExist: except ObjectDoesNotExist:
# No external id found - let's hope the given id exists internally # Not found as external id - let's check whether this is a valid uuid and therefore
pass # potentially an internal id
try:
uuid.UUID(id)
except ValueError:
raise AssertionError(f"'{id}' is neither a known external identifier nor a valid uuid.")
obj = self.model.objects.get( obj = self.model.objects.get(
id=id, id=id,
@@ -217,7 +225,11 @@ class AbstractModelAPISerializer:
conflict_geometries = geometry.get_conflict_geometries() conflict_geometries = geometry.get_conflict_geometries()
for geom in conflict_geometries: for geom in conflict_geometries:
try: try:
data = geom.get_data_objects(["identifier", "id"])[0] 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: except KeyError:
raise AssertionError(f"Geometry {geom.id} is not attached to any entries. Contact an admin!") raise AssertionError(f"Geometry {geom.id} is not attached to any entries. Contact an admin!")
ids.append( ids.append(
+28 -31
View File
@@ -10,6 +10,7 @@ from json import JSONDecodeError
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.http import JsonResponse, HttpRequest from django.http import JsonResponse, HttpRequest
from django.views.decorators.csrf import csrf_exempt
from api.utils.serializer.v1.compensation import CompensationAPISerializerV1 from api.utils.serializer.v1.compensation import CompensationAPISerializerV1
from api.utils.serializer.v1.deduction import DeductionAPISerializerV1 from api.utils.serializer.v1.deduction import DeductionAPISerializerV1
@@ -28,6 +29,20 @@ class AbstractAPIViewV1(AbstractAPIView):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.serializer = self.serializer() self.serializer = self.serializer()
@csrf_exempt
def dispatch(self, request, *args, **kwargs):
try:
return super().dispatch(request, *args, **kwargs)
except (JSONDecodeError,
AssertionError,
ValueError,
PermissionError) as e:
return self._return_error_response(e, 400)
except ObjectDoesNotExist as e:
return self._return_error_response(e, 404)
except Exception as e:
return self._return_error_response(e, 500)
def get(self, request: HttpRequest, id=None): def get(self, request: HttpRequest, id=None):
""" Handles the GET request """ Handles the GET request
@@ -40,17 +55,12 @@ class AbstractAPIViewV1(AbstractAPIView):
Returns: Returns:
response (JsonResponse) response (JsonResponse)
""" """
try: self.rpp = int(request.GET.get("rpp", self.rpp))
self.rpp = int(request.GET.get("rpp", self.rpp)) self.page_number = int(request.GET.get("p", self.page_number))
self.page_number = int(request.GET.get("p", self.page_number)) self.serializer.rpp = self.rpp
self.serializer.page_number = self.page_number
self.serializer.rpp = self.rpp self.serializer.prepare_lookup(id, self.user)
self.serializer.page_number = self.page_number data = self.serializer.fetch_and_serialize()
self.serializer.prepare_lookup(id, self.user)
data = self.serializer.fetch_and_serialize()
except Exception as e:
return self._return_error_response(e, 500)
return self._return_response(request, data) return self._return_response(request, data)
def post(self, request: HttpRequest): def post(self, request: HttpRequest):
@@ -64,16 +74,9 @@ class AbstractAPIViewV1(AbstractAPIView):
Returns: Returns:
response (JsonResponse) response (JsonResponse)
""" """
try: body = request.body.decode("utf-8")
body = request.body.decode("utf-8") body = json.loads(body)
body = json.loads(body) created_id = self.serializer.create_model_from_json(body, self.user)
created_id = self.serializer.create_model_from_json(body, self.user)
except (JSONDecodeError,
AssertionError,
ValueError,
PermissionError,
ObjectDoesNotExist) as e:
return self._return_error_response(e, 400)
return JsonResponse({"id": created_id}) return JsonResponse({"id": created_id})
def put(self, request: HttpRequest, id=None): def put(self, request: HttpRequest, id=None):
@@ -88,12 +91,9 @@ class AbstractAPIViewV1(AbstractAPIView):
Returns: Returns:
response (JsonResponse) response (JsonResponse)
""" """
try: body = request.body.decode("utf-8")
body = request.body.decode("utf-8") body = json.loads(body)
body = json.loads(body) updated_id = self.serializer.update_model_from_json(id, body, self.user)
updated_id = self.serializer.update_model_from_json(id, body, self.user)
except Exception as e:
return self._return_error_response(e, 500)
return JsonResponse({"id": updated_id}) return JsonResponse({"id": updated_id})
def delete(self, request: HttpRequest, id=None): def delete(self, request: HttpRequest, id=None):
@@ -107,10 +107,7 @@ class AbstractAPIViewV1(AbstractAPIView):
response (JsonResponse) response (JsonResponse)
""" """
try: success = self.serializer.delete_entry(id, self.user)
success = self.serializer.delete_entry(id, self.user)
except Exception as e:
return self._return_error_response(e, 500)
return JsonResponse( return JsonResponse(
{ {
"success": success, "success": success,
+28 -12
View File
@@ -113,7 +113,7 @@ class Geometry(BaseResource):
objs (list): The list of objects objs (list): The list of objects
""" """
objs = [] objs = []
stop_searching = False
# Some related data sets can be processed rather easily # Some related data sets can be processed rather easily
regular_sets = [ regular_sets = [
self.intervention_set, self.intervention_set,
@@ -129,24 +129,40 @@ class Geometry(BaseResource):
else: else:
objs += set_objs objs += set_objs
# ... but we need a special treatment for compensations, since they can be deleted directly OR inherit their stop_searching = len(objs) > 0
# de-facto-deleted status from their deleted parent intervention if stop_searching:
comp_objs = self.compensation_set.filter( break
Q(deleted=None) & Q(intervention__deleted=None)
) if not stop_searching:
if limit_to_attrs: # ... but we need a special treatment for compensations, since they can be deleted directly OR inherit their
objs += comp_objs.values(*limit_to_attrs) # de-facto-deleted status from their deleted parent intervention
else: comp_objs = self.compensation_set.filter(
objs += comp_objs Q(deleted=None) | Q(intervention__deleted=None)
)
if limit_to_attrs:
objs += comp_objs.values(*limit_to_attrs)
else:
objs += comp_objs
return objs return objs
def get_data_object(self, limit_to_attrs: list = None): 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(limit_to_attrs) objs = self.get_data_objects(limit_to_attrs)
assert (len(objs) <= 1) assert (len(objs) <= 1)
result = objs.pop() try:
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):
+3 -1
View File
@@ -679,7 +679,9 @@ class GeoReferencedMixin(models.Model):
conflicting_geometries = self.geometry.get_conflict_geometries() conflicting_geometries = self.geometry.get_conflict_geometries()
data_object_identifiers = [] data_object_identifiers = []
for conflicting_geom in conflicting_geometries: for conflicting_geom in conflicting_geometries:
data_object_identifiers.append(conflicting_geom.get_data_object(["identifier"])) data_obj_id = conflicting_geom.get_data_object(["identifier"])
if data_obj_id:
data_object_identifiers.append(data_obj_id)
add_message = len(data_object_identifiers) > 0 add_message = len(data_object_identifiers) > 0
if add_message: if add_message:
+6 -15
View File
@@ -85,12 +85,6 @@ class GeometryTestCase(BaseTestCase):
) )
def test_get_data_objects(self): def test_get_data_objects(self):
num_objs_with_geom = 0
self.assertEqual(
len(self.geom_1.get_data_objects()),
num_objs_with_geom
)
objs = [ objs = [
self.intervention, self.intervention,
self.compensation, self.compensation,
@@ -98,16 +92,13 @@ class GeometryTestCase(BaseTestCase):
self.ema, self.ema,
] ]
for obj in objs: for obj in objs:
obj.geometry = self.geom_1 if not obj.geometry:
obj.save() obj.geometry = Geometry.objects.create(geom=self.create_dummy_geometry())
obj.save()
num_objs_with_geom += 1 data_objs = obj.geometry.get_data_objects()
geom_objs = self.geom_1.get_data_objects() self.assertEqual(len(data_objs), 1)
self.assertEqual( self.assertIn(obj, data_objs)
len(geom_objs),
num_objs_with_geom
)
self.assertIn(obj, geom_objs)
def test_as_feature_collection(self): def test_as_feature_collection(self):
geometry = self.geom_1.geom geometry = self.geom_1.geom
+1 -3
View File
@@ -56,11 +56,9 @@
], ],
"layers": "layers":
[ [
{ "id": "webatlas_farbe", "folder": "bg", "type": "WMS", "order": -1, "title": "WebatlasRP farbig", "attribution": "LVermGeo", "url": "https://maps.service24.rlp.de/gisserver/services/RP/RP_WebAtlasRP/MapServer/WmsServer?", "name": "RP_WebAtlasRP", "active": true},
{ "id": "webatlas_grau", "folder": "bg", "type": "WMS", "order": -1, "title": "WebatlasRP grau", "attribution": "LVermGeo", "url": "https://maps.service24.rlp.de/gisserver/services/RP/RP_ETRS_Gt/MapServer/WmsServer?", "name": "0", "active": false },
{ "id": "luftbilder", "folder": "bg", "type": "WMS", "order": -1, "title": "Luftbilder", "attribution": "LVermGeo", "url": "https://geo4.service24.rlp.de/wms/rp_dop20.fcgi?", "name": "rp_dop20", "active": false }, { "id": "luftbilder", "folder": "bg", "type": "WMS", "order": -1, "title": "Luftbilder", "attribution": "LVermGeo", "url": "https://geo4.service24.rlp.de/wms/rp_dop20.fcgi?", "name": "rp_dop20", "active": false },
{ "id": "basemap_farbe", "folder": "bg", "type": "WMS", "order": -1, "title": "BasemapDE farbig", "attribution": "BKG", "url": "https://sgx.geodatenzentrum.de/wms_basemapde?", "name": "de_basemapde_web_raster_farbe", "active": false }, { "id": "basemap_farbe", "folder": "bg", "type": "WMS", "order": -1, "title": "BasemapDE farbig", "attribution": "BKG", "url": "https://sgx.geodatenzentrum.de/wms_basemapde?", "name": "de_basemapde_web_raster_farbe", "active": false },
{ "id": "basemap_grau", "folder": "bg", "type": "WMS", "order": -1, "title": "BasemapDE grau", "attribution": "BKG", "url": "https://sgx.geodatenzentrum.de/wms_basemapde?", "name": "de_basemapde_web_raster_grau", "active": false }, { "id": "basemap_grau", "folder": "bg", "type": "WMS", "order": -1, "title": "BasemapDE grau", "attribution": "BKG", "url": "https://sgx.geodatenzentrum.de/wms_basemapde?", "name": "de_basemapde_web_raster_grau", "active": true },
{ "id": "dtk_farbe", "folder": "bg", "type": "WMS", "order": -1, "title": "DTK5 farbig", "attribution": "LVermGeo", "url": "https://geo4.service24.rlp.de/wms/dtk5_rp.fcgi?", "name": "rp_dtk5", "active": false }, { "id": "dtk_farbe", "folder": "bg", "type": "WMS", "order": -1, "title": "DTK5 farbig", "attribution": "LVermGeo", "url": "https://geo4.service24.rlp.de/wms/dtk5_rp.fcgi?", "name": "rp_dtk5", "active": false },
{ "id": "dtk_grau", "folder": "bg", "type": "WMS", "order": -1, "title": "DTK5 grau", "attribution": "LVermGeo", "url": "https://geo4.service24.rlp.de/wms/dtk5_rp.fcgi?", "name": "rp_dtk5_grau", "active": false }, { "id": "dtk_grau", "folder": "bg", "type": "WMS", "order": -1, "title": "DTK5 grau", "attribution": "LVermGeo", "url": "https://geo4.service24.rlp.de/wms/dtk5_rp.fcgi?", "name": "rp_dtk5_grau", "active": false },