Compare commits
16 Commits
1.13.2
...
ee2c859a9e
| Author | SHA1 | Date | |
|---|---|---|---|
| ee2c859a9e | |||
| 328f672ec0 | |||
| 047c9489fe | |||
| 38b81996ed | |||
| 4c4d64cc3d | |||
| fbde03caec | |||
| 43eb598d3f | |||
| b7fac0ae03 | |||
| 447ba942b5 | |||
| 6df47f1615 | |||
| e25d549a97 | |||
| 5e65b8f4dc | |||
| 22cddb9902 | |||
| c986bd0b92 | |||
| 2c60d86177 | |||
| b7792ececc |
@@ -71,7 +71,7 @@ class APIV1CreateTestCase(BaseAPIV1TestCase):
|
|||||||
# Expect this first request to fail, since user has no shared access on the intervention, we want to create
|
# Expect this first request to fail, since user has no shared access on the intervention, we want to create
|
||||||
# a compensation for
|
# a compensation for
|
||||||
response = self._run_create_request(url, post_body)
|
response = self._run_create_request(url, post_body)
|
||||||
self.assertEqual(response.status_code, 500, msg=response.content)
|
self.assertEqual(response.status_code, 400, msg=response.content)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertGreater(len(content.get("errors", [])), 0, msg=response.content)
|
self.assertGreater(len(content.get("errors", [])), 0, msg=response.content)
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ Created on: 21.01.22
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
|
from json import JSONDecodeError
|
||||||
|
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.http import JsonResponse, HttpRequest
|
from django.http import JsonResponse, HttpRequest
|
||||||
|
|
||||||
from api.utils.serializer.v1.compensation import CompensationAPISerializerV1
|
from api.utils.serializer.v1.compensation import CompensationAPISerializerV1
|
||||||
@@ -66,8 +68,12 @@ class AbstractAPIViewV1(AbstractAPIView):
|
|||||||
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 Exception as e:
|
except (JSONDecodeError,
|
||||||
return self._return_error_response(e, 500)
|
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):
|
||||||
|
|||||||
@@ -81,9 +81,7 @@ class AbstractAPIView(View):
|
|||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
content = [error.__str__()]
|
content = [f"{error.__class__.__name__}: {str(error)}"]
|
||||||
if hasattr(error, "messages"):
|
|
||||||
content = error.messages
|
|
||||||
return JsonResponse(
|
return JsonResponse(
|
||||||
{
|
{
|
||||||
"errors": content
|
"errors": content
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="align-middle">
|
<td class="align-middle">
|
||||||
{% if deduction.intervention.recorded %}
|
{% if deduction.intervention.recorded %}
|
||||||
<em title="{% trans 'Recorded on' %} {{obj.recorded.timestamp}} {% trans 'by' %} {{obj.recorded.user}}" class='fas fa-bookmark registered-bookmark'></em>
|
<em title="{% trans 'Recorded on' %} {{deduction.intervention.recorded.timestamp}} {% trans 'by' %} {{deduction.intervention.recorded.user}}" class='fas fa-bookmark registered-bookmark'></em>
|
||||||
{% else %}
|
{% else %}
|
||||||
<em title="{% trans 'Not recorded yet' %}" class='far fa-bookmark'></em>
|
<em title="{% trans 'Not recorded yet' %}" class='far fa-bookmark'></em>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ class SimpleGeomForm(BaseForm):
|
|||||||
disabled=False,
|
disabled=False,
|
||||||
)
|
)
|
||||||
_num_geometries_ignored: int = 0
|
_num_geometries_ignored: int = 0
|
||||||
|
empty = False
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.read_only = kwargs.pop("read_only", True)
|
self.read_only = kwargs.pop("read_only", True)
|
||||||
@@ -49,11 +50,11 @@ class SimpleGeomForm(BaseForm):
|
|||||||
raise AttributeError
|
raise AttributeError
|
||||||
|
|
||||||
geojson = self.instance.geometry.as_feature_collection(srid=DEFAULT_SRID_RLP)
|
geojson = self.instance.geometry.as_feature_collection(srid=DEFAULT_SRID_RLP)
|
||||||
self._set_geojson_properties(geojson, title=self.instance.identifier or None)
|
geojson = self._set_geojson_properties(geojson, title=self.instance.identifier or None)
|
||||||
geom = json.dumps(geojson)
|
geom = json.dumps(geojson)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# If no geometry exists for this form, we simply set the value to None and zoom to the maximum level
|
# If no geometry exists for this form, we simply set the value to None and zoom to the maximum level
|
||||||
geom = ""
|
geom = json.dumps({})
|
||||||
self.empty = True
|
self.empty = True
|
||||||
|
|
||||||
self.initialize_form_field("output", geom)
|
self.initialize_form_field("output", geom)
|
||||||
@@ -62,17 +63,17 @@ class SimpleGeomForm(BaseForm):
|
|||||||
super().is_valid()
|
super().is_valid()
|
||||||
is_valid = True
|
is_valid = True
|
||||||
|
|
||||||
# Get geojson from form
|
# Make sure invalid geometry is properly rendered again to the user
|
||||||
geom = self.data.get("output", None)
|
# Therefore: write submitted data back into form field
|
||||||
if geom is None or len(geom) == 0:
|
# (does not matter whether we know if it is valid or invalid)
|
||||||
# empty geometry is a valid geometry
|
submitted_data = self.data["output"]
|
||||||
self.cleaned_data["output"] = MultiPolygon(srid=DEFAULT_SRID_RLP).ewkt
|
submitted_data = json.loads(submitted_data)
|
||||||
return is_valid
|
submitted_data = self._set_geojson_properties(submitted_data)
|
||||||
geom = json.loads(geom)
|
self.initialize_form_field("output", json.dumps(submitted_data))
|
||||||
|
|
||||||
# Write submitted data back into form field to make sure invalid geometry
|
# Get geojson from form for validity checking
|
||||||
# will be rendered again on failed submit
|
geom = self.data.get("output", json.dumps({}))
|
||||||
self.initialize_form_field("output", self.data["output"])
|
geom = json.loads(geom)
|
||||||
|
|
||||||
# Initialize features list with empty MultiPolygon, so that an empty input will result in a
|
# Initialize features list with empty MultiPolygon, so that an empty input will result in a
|
||||||
# proper empty MultiPolygon object
|
# proper empty MultiPolygon object
|
||||||
@@ -84,20 +85,23 @@ class SimpleGeomForm(BaseForm):
|
|||||||
"MultiPolygon",
|
"MultiPolygon",
|
||||||
"MultiPolygon25D",
|
"MultiPolygon25D",
|
||||||
]
|
]
|
||||||
|
# Check validity for each feature of the geometry
|
||||||
for feature in features_json:
|
for feature in features_json:
|
||||||
feature_geom = feature.get("geometry", feature)
|
feature_geom = feature.get("geometry", feature)
|
||||||
if feature_geom is None:
|
if feature_geom is None:
|
||||||
# Fallback for rare cases where a feature does not contain any geometry
|
# Fallback for rare cases where a feature does not contain any geometry
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Try to create a geometry object from the single feature
|
||||||
feature_geom = json.dumps(feature_geom)
|
feature_geom = json.dumps(feature_geom)
|
||||||
g = gdal.OGRGeometry(feature_geom, srs=DEFAULT_SRID_RLP)
|
g = gdal.OGRGeometry(feature_geom, srs=DEFAULT_SRID_RLP)
|
||||||
|
|
||||||
flatten_geometry = g.coord_dim > 2
|
geometry_has_unwanted_dimensions = g.coord_dim > 2
|
||||||
if flatten_geometry:
|
if geometry_has_unwanted_dimensions:
|
||||||
g = self.__flatten_geom_to_2D(g)
|
g = self.__flatten_geom_to_2D(g)
|
||||||
|
|
||||||
if g.geom_type not in accepted_ogr_types:
|
geometry_type_is_accepted = g.geom_type not in accepted_ogr_types
|
||||||
|
if geometry_type_is_accepted:
|
||||||
self.add_error("output", _("Only surfaces allowed. Points or lines must be buffered."))
|
self.add_error("output", _("Only surfaces allowed. Points or lines must be buffered."))
|
||||||
is_valid &= False
|
is_valid &= False
|
||||||
return is_valid
|
return is_valid
|
||||||
@@ -109,27 +113,33 @@ class SimpleGeomForm(BaseForm):
|
|||||||
self._num_geometries_ignored += 1
|
self._num_geometries_ignored += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Whatever this geometry object is -> try to create a Polygon from it
|
||||||
|
# The resulting polygon object automatically detects whether a valid polygon has been created or not
|
||||||
g = Polygon.from_ewkt(g.ewkt)
|
g = Polygon.from_ewkt(g.ewkt)
|
||||||
is_valid &= g.valid
|
is_valid &= g.valid
|
||||||
if not g.valid:
|
if not g.valid:
|
||||||
self.add_error("output", g.valid_reason)
|
self.add_error("output", g.valid_reason)
|
||||||
return is_valid
|
return is_valid
|
||||||
|
|
||||||
|
# If the resulting polygon is just a single polygon, we add it to the list of properly casted features
|
||||||
if isinstance(g, Polygon):
|
if isinstance(g, Polygon):
|
||||||
features.append(g)
|
features.append(g)
|
||||||
elif isinstance(g, MultiPolygon):
|
elif isinstance(g, MultiPolygon):
|
||||||
|
# The resulting polygon could be of type MultiPolygon (due to multiple surfaces)
|
||||||
|
# If so, we extract all polygons from the MultiPolygon and extend the casted features list
|
||||||
features.extend(list(g))
|
features.extend(list(g))
|
||||||
|
|
||||||
# Unionize all geometry features into one new MultiPolygon
|
# Unionize all polygon features into one new MultiPolygon
|
||||||
if features:
|
if features:
|
||||||
form_geom = MultiPolygon(*features, srid=DEFAULT_SRID_RLP).unary_union
|
form_geom = MultiPolygon(*features, srid=DEFAULT_SRID_RLP).unary_union
|
||||||
else:
|
else:
|
||||||
|
# If no features have been processed, this indicates an empty geometry - so we store an empty geometry
|
||||||
form_geom = MultiPolygon(srid=DEFAULT_SRID_RLP)
|
form_geom = MultiPolygon(srid=DEFAULT_SRID_RLP)
|
||||||
|
|
||||||
# Make sure to convert into a MultiPolygon. Relevant if a single Polygon is provided.
|
# Make sure to convert into a MultiPolygon. Relevant if a single Polygon is provided.
|
||||||
form_geom = Geometry.cast_to_multipolygon(form_geom)
|
form_geom = Geometry.cast_to_multipolygon(form_geom)
|
||||||
|
|
||||||
# Write unioned Multipolygon into cleaned data
|
# Write unionized Multipolygon back into cleaned data
|
||||||
if self.cleaned_data is None:
|
if self.cleaned_data is None:
|
||||||
self.cleaned_data = {}
|
self.cleaned_data = {}
|
||||||
self.cleaned_data["output"] = form_geom.ewkt
|
self.cleaned_data["output"] = form_geom.ewkt
|
||||||
@@ -252,6 +262,8 @@ class SimpleGeomForm(BaseForm):
|
|||||||
"""
|
"""
|
||||||
features = geojson.get("features", [])
|
features = geojson.get("features", [])
|
||||||
for feature in features:
|
for feature in features:
|
||||||
|
if not feature.get("properties", None):
|
||||||
|
feature["properties"] = {}
|
||||||
feature["properties"]["editable"] = not self.read_only
|
feature["properties"]["editable"] = not self.read_only
|
||||||
if title:
|
if title:
|
||||||
feature["properties"]["title"] = title
|
feature["properties"]["title"] = title
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import json
|
|||||||
from django.contrib.gis.db.models import MultiPolygonField
|
from django.contrib.gis.db.models import MultiPolygonField
|
||||||
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
|
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
|
from django.db.models import Q
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.contrib.gis.geos import MultiPolygon
|
from django.contrib.gis.geos import MultiPolygon
|
||||||
|
|
||||||
@@ -109,17 +110,26 @@ class Geometry(BaseResource):
|
|||||||
objs (list): The list of objects
|
objs (list): The list of objects
|
||||||
"""
|
"""
|
||||||
objs = []
|
objs = []
|
||||||
sets = [
|
|
||||||
|
# Some related data sets can be processed rather easily
|
||||||
|
regular_sets = [
|
||||||
self.intervention_set,
|
self.intervention_set,
|
||||||
self.compensation_set,
|
|
||||||
self.ema_set,
|
self.ema_set,
|
||||||
self.ecoaccount_set,
|
self.ecoaccount_set,
|
||||||
]
|
]
|
||||||
for _set in sets:
|
for _set in regular_sets:
|
||||||
set_objs = _set.filter(
|
set_objs = _set.filter(
|
||||||
deleted=None
|
deleted=None
|
||||||
)
|
)
|
||||||
objs += set_objs
|
objs += set_objs
|
||||||
|
|
||||||
|
# ... but we need a special treatment for compensations, since they can be deleted directly OR inherit their
|
||||||
|
# de-facto-deleted status from their deleted parent intervention
|
||||||
|
comp_objs = self.compensation_set.filter(
|
||||||
|
Q(deleted=None) & Q(intervention__deleted=None)
|
||||||
|
)
|
||||||
|
objs += comp_objs
|
||||||
|
|
||||||
return objs
|
return objs
|
||||||
|
|
||||||
def get_data_object(self):
|
def get_data_object(self):
|
||||||
@@ -397,7 +407,10 @@ class Geometry(BaseResource):
|
|||||||
"""
|
"""
|
||||||
output_geom = input_geom
|
output_geom = input_geom
|
||||||
if not isinstance(input_geom, MultiPolygon):
|
if not isinstance(input_geom, MultiPolygon):
|
||||||
output_geom = MultiPolygon(input_geom, srid=DEFAULT_SRID_RLP)
|
try:
|
||||||
|
output_geom = MultiPolygon(input_geom, srid=DEFAULT_SRID_RLP)
|
||||||
|
except TypeError as e:
|
||||||
|
raise AssertionError(f"Only (Multi)Polygon allowed! Could not convert {input_geom.geom_type} to MultiPolygon")
|
||||||
return output_geom
|
return output_geom
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -677,12 +677,12 @@ class GeoReferencedMixin(models.Model):
|
|||||||
return request
|
return request
|
||||||
|
|
||||||
instance_objs = []
|
instance_objs = []
|
||||||
conflicts = self.geometry.conflicts_geometries.all()
|
conflicts = self.geometry.conflicts_geometries.iterator()
|
||||||
|
|
||||||
for conflict in conflicts:
|
for conflict in conflicts:
|
||||||
instance_objs += conflict.affected_geometry.get_data_objects()
|
instance_objs += conflict.affected_geometry.get_data_objects()
|
||||||
|
|
||||||
conflicts = self.geometry.conflicted_by_geometries.all()
|
conflicts = self.geometry.conflicted_by_geometries.iterator()
|
||||||
for conflict in conflicts:
|
for conflict in conflicts:
|
||||||
instance_objs += conflict.conflicting_geometry.get_data_objects()
|
instance_objs += conflict.conflicting_geometry.get_data_objects()
|
||||||
|
|
||||||
|
|||||||
@@ -11,4 +11,4 @@ BASE_TITLE = "KSP - Kompensationsverzeichnis Service Portal"
|
|||||||
BASE_FRONTEND_TITLE = "Kompensationsverzeichnis Service Portal"
|
BASE_FRONTEND_TITLE = "Kompensationsverzeichnis Service Portal"
|
||||||
TAB_TITLE_IDENTIFIER = "tab_title"
|
TAB_TITLE_IDENTIFIER = "tab_title"
|
||||||
HELP_LINK = "https://dienste.naturschutz.rlp.de/doku/doku.php?id=ksp2:start"
|
HELP_LINK = "https://dienste.naturschutz.rlp.de/doku/doku.php?id=ksp2:start"
|
||||||
IMPRESSUM_LINK = "https://naturschutz.rlp.de/index.php?q=impressum"
|
IMPRESSUM_LINK = "https://naturschutz.rlp.de/ueber-uns/impressum"
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
|
|||||||
Created on: 11.12.23
|
Created on: 11.12.23
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import json
|
||||||
|
from json import JSONDecodeError
|
||||||
|
|
||||||
from django.views.debug import ExceptionReporter
|
from django.views.debug import ExceptionReporter
|
||||||
|
|
||||||
|
|
||||||
@@ -30,7 +33,7 @@ class KonovaExceptionReporter(ExceptionReporter):
|
|||||||
"""
|
"""
|
||||||
whitelist = [
|
whitelist = [
|
||||||
"is_email",
|
"is_email",
|
||||||
"unicdoe_hint",
|
"unicode_hint",
|
||||||
"frames",
|
"frames",
|
||||||
"request",
|
"request",
|
||||||
"user_str",
|
"user_str",
|
||||||
@@ -39,6 +42,8 @@ class KonovaExceptionReporter(ExceptionReporter):
|
|||||||
"raising_view_name",
|
"raising_view_name",
|
||||||
"exception_type",
|
"exception_type",
|
||||||
"exception_value",
|
"exception_value",
|
||||||
|
"filtered_GET_items",
|
||||||
|
"filtered_POST_items",
|
||||||
]
|
]
|
||||||
clean_data = dict()
|
clean_data = dict()
|
||||||
for entry in whitelist:
|
for entry in whitelist:
|
||||||
@@ -56,7 +61,28 @@ class KonovaExceptionReporter(ExceptionReporter):
|
|||||||
"""
|
"""
|
||||||
tb_data = super().get_traceback_data()
|
tb_data = super().get_traceback_data()
|
||||||
|
|
||||||
|
return_data = tb_data
|
||||||
if self.is_email:
|
if self.is_email:
|
||||||
tb_data = self._filter_traceback_data(tb_data)
|
filtered_data = dict()
|
||||||
|
filtered_data.update(self._filter_traceback_data(tb_data))
|
||||||
|
filtered_data.update(self._filter_POST_body(tb_data))
|
||||||
|
return_data = filtered_data
|
||||||
|
return return_data
|
||||||
|
|
||||||
return tb_data
|
def _filter_POST_body(self, tb_data: dict):
|
||||||
|
""" Filters POST body from traceback data
|
||||||
|
|
||||||
|
"""
|
||||||
|
post_data = tb_data.get("request", None)
|
||||||
|
if post_data:
|
||||||
|
post_data = post_data.body
|
||||||
|
try:
|
||||||
|
post_data = json.loads(post_data)
|
||||||
|
except JSONDecodeError:
|
||||||
|
pass
|
||||||
|
post_data = {
|
||||||
|
"filtered_POST_items": [
|
||||||
|
("body", post_data),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return post_data
|
||||||
@@ -188,6 +188,7 @@
|
|||||||
{
|
{
|
||||||
"title": "Ebene hinzufügen",
|
"title": "Ebene hinzufügen",
|
||||||
"preview": true,
|
"preview": true,
|
||||||
|
"editable": true,
|
||||||
"wms_options": [ "https://sgx.geodatenzentrum.de/wms_topplus_open" ],
|
"wms_options": [ "https://sgx.geodatenzentrum.de/wms_topplus_open" ],
|
||||||
"wfs_options": [ "http://213.139.159.34:80/geoserver/uesg/wfs" ],
|
"wfs_options": [ "http://213.139.159.34:80/geoserver/uesg/wfs" ],
|
||||||
"wfs_proxy": "/client/proxy?",
|
"wfs_proxy": "/client/proxy?",
|
||||||
|
|||||||
2
templates/map/client/dist/netgis.min.css
vendored
2
templates/map/client/dist/netgis.min.css
vendored
File diff suppressed because one or more lines are too long
8777
templates/map/client/dist/netgis.min.js
vendored
8777
templates/map/client/dist/netgis.min.js
vendored
File diff suppressed because it is too large
Load Diff
@@ -13,9 +13,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if geom_form.geom.errors %}
|
{% if geom_form.output.errors %}
|
||||||
<div class="alert-danger p-2">
|
<div class="alert-danger p-2">
|
||||||
{% for error in geom_form.geom.errors %}
|
{% for error in geom_form.output.errors %}
|
||||||
<strong class="invalid">{{ error }}</strong>
|
<strong class="invalid">{{ error }}</strong>
|
||||||
<br>
|
<br>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
Reference in New Issue
Block a user