diff --git a/docker/integration-tests/test_integration_qgis.py b/docker/integration-tests/test_integration_qgis.py index 409c7c41..a9b1659b 100644 --- a/docker/integration-tests/test_integration_qgis.py +++ b/docker/integration-tests/test_integration_qgis.py @@ -2,6 +2,8 @@ from qgis.core import ( QgsDataSourceUri, QgsFeature, + QgsGeometry, + QgsPoint, QgsProject, QgsVectorDataProvider, QgsVectorLayer, @@ -59,31 +61,40 @@ def test_load_layer(self): self.assertFalse(bool(layer.dataProvider().capabilities() & QgsVectorDataProvider.Capability.AddFeatures)) - def test_load_with_basic_auth(self): - uri = QgsDataSourceUri() - uri.setParam("service", "wfs") - uri.setParam("typename", "tests.point_2056_10fields") - uri.setParam("url", ROOT_URL) - uri.setPassword(self.password) - uri.setUsername(self.user) - - layer = QgsVectorLayer(uri.uri(), "point", "OAPIF") - self.assertTrue(layer.isValid()) - layer = self.project.addMapLayer(layer) - self.assertIsNotNone(layer) - - self.assertTrue(bool(layer.dataProvider().capabilities() & QgsVectorDataProvider.Capability.AddFeatures)) - - f = None - for f in layer.getFeatures(): - pass - self.assertIsInstance(f, QgsFeature) - - f["field_1"] = "xyz" - with edit(layer): - layer.updateFeature(f) - - f = None - for f in layer.getFeatures("field_1='xyz'"): - pass - self.assertIsInstance(f, QgsFeature) + def test_load_and_edit_with_basic_auth(self): + for layer in ("tests.point_2056_10fields_local_json", "tests.point_2056_10fields"): + uri = QgsDataSourceUri() + uri.setParam("service", "wfs") + uri.setParam("typename", layer) + uri.setParam("url", ROOT_URL) + uri.setPassword(self.password) + uri.setUsername(self.user) + + layer = QgsVectorLayer(uri.uri(), layer, "OAPIF") + self.assertTrue(layer.isValid()) + layer = self.project.addMapLayer(layer) + self.assertIsNotNone(layer) + + self.assertTrue(bool(layer.dataProvider().capabilities() & QgsVectorDataProvider.Capability.AddFeatures)) + + f = next(layer.getFeatures()) + self.assertIsInstance(f, QgsFeature) + + f["field_0"] = "xyz" + with edit(layer): + layer.updateFeature(f) + + f = next(layer.getFeatures("field_0='xyz'")) + self.assertIsInstance(f, QgsFeature) + + # create with geometry + f = QgsFeature() + f.setFields(layer.fields()) + f["field_0"] = "Super Green" + geom = QgsGeometry.fromPoint(QgsPoint(2345678.0, 1234567.0)) + f.setGeometry(geom) + with edit(layer): + layer.addFeature(f) + f = next(layer.getFeatures("field_0='Super Green'")) + self.assertIsInstance(f, QgsFeature) + self.assertEquals(geom, f.geometry()) diff --git a/src/django_oapif/decorators.py b/src/django_oapif/decorators.py index 5db59a1a..18225e8f 100644 --- a/src/django_oapif/decorators.py +++ b/src/django_oapif/decorators.py @@ -22,6 +22,7 @@ def register_oapif_viewset( key: Optional[str] = None, geom_db_serializer: Optional[bool] = True, geom_field: [str] = "geom", + crs: Optional[int] = None, custom_serializer_attrs: Dict[str, Any] = None, custom_viewset_attrs: Dict[str, Any] = None, ) -> Callable[[Any], models.Model]: @@ -32,6 +33,7 @@ def register_oapif_viewset( - key: allows to pass a custom name for the collection (defaults to the model's label) - geom_db_serializer: delegate the geometry serialization to the DB - geom_field: the geometry field name. If None, a null geometry is produced + - crs: the EPSG code, if empty CRS84 is assumed - custom_serializer_attrs: allows to pass custom attributes to set to the serializer's Meta (e.g. custom fields) - custom_viewset_attrs: allows to pass custom attributes to set to the viewset (e.g. custom pagination class) """ @@ -45,8 +47,6 @@ def register_oapif_viewset( def inner(Model): """ Create the serializers - 1 for viewsets for models with a geometry and - 1 for viewsets for models without (aka 'non-geometric features'). """ if geom_db_serializer and geom_field: @@ -61,9 +61,15 @@ class Meta: def to_internal_value(self, data): # TODO: this needs improvement!!! - geo = data["geometry"] + geo = None + if "geometry" in data: + geo = data["geometry"] + if crs not in geo: + geo["crs"] = {"type": "name", "properties": {"name": f"urn:ogc:def:crs:EPSG::{Model.crs}"}} data = super().to_internal_value(data) - data[geom_field] = GEOSGeometry(json.dumps(geo)) + print(543534534, data) + if geo: + data[geom_field] = GEOSGeometry(json.dumps(geo)) return data else: @@ -74,6 +80,18 @@ class Meta: fields = "__all__" geo_field = geom_field + def to_internal_value(self, data): + # TODO: this needs improvement!!! + if "geometry" in data and "crs" not in data["geometry"]: + data["geometry"]["crs"] = { + "type": "name", + "properties": {"name": f"urn:ogc:def:crs:EPSG::{Model.crs}"}, + } + print(data) + data = super().to_internal_value(data) + print(12344555, data) + return data + # Create the viewset class Viewset(OAPIFDescribeModelViewSetMixin, viewsets.ModelViewSet): queryset = Model.objects.all() @@ -109,6 +127,8 @@ def get_queryset(self): return qs + setattr(Model, "crs", crs) + # Apply custom serializer attributes for k, v in custom_serializer_attrs.items(): setattr(AutoSerializer.Meta, k, v) diff --git a/src/django_oapif/filters.py b/src/django_oapif/filters.py index 17793f79..8caf82d7 100644 --- a/src/django_oapif/filters.py +++ b/src/django_oapif/filters.py @@ -1,5 +1,3 @@ -from os import getenv - from django.contrib.gis.geos import Polygon from pyproj import CRS, Transformer from rest_framework.filters import BaseFilterBackend @@ -19,7 +17,7 @@ def filter_queryset(self, request, queryset, view): if user_crs: user_crs = get_crs_from_uri(user_crs) - api_crs = CRS.from_epsg(int(getenv("GEOMETRY_SRID", "2056"))) + api_crs = CRS.from_epsg(queryset.model.crs) # TODO support CRS84, not only EPSG codes transformer = Transformer.from_crs(user_crs, api_crs) LL = transformer.transform(coords[0], coords[1]) UR = transformer.transform(coords[2], coords[3]) diff --git a/src/tests/migrations/0001_initial.py b/src/tests/migrations/0001_initial.py index ab6fb88a..fb9e815b 100644 --- a/src/tests/migrations/0001_initial.py +++ b/src/tests/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.5 on 2023-09-29 08:38 +# Generated by Django 4.2.5 on 2023-09-29 13:15 import uuid diff --git a/src/tests/models.py b/src/tests/models.py index 73724109..a2823bf1 100644 --- a/src/tests/models.py +++ b/src/tests/models.py @@ -11,7 +11,7 @@ logger = logging.getLogger(__name__) -@register_oapif_viewset() +@register_oapif_viewset(crs=2056) class Point_2056_10fields(ComputedFieldsModel): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) geom = models.PointField(srid=2056, verbose_name=_("Geometry")) @@ -27,7 +27,7 @@ class Point_2056_10fields(ComputedFieldsModel): field_9 = models.CharField(max_length=255, verbose_name=_("Field 9"), null=True, blank=True) -@register_oapif_viewset(geom_db_serializer=False) +@register_oapif_viewset(crs=2056, geom_db_serializer=False) class Point_2056_10fields_local_json(ComputedFieldsModel): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) geom = models.PointField(srid=2056, verbose_name=_("Geometry")) @@ -59,6 +59,7 @@ class NoGeom_10fields(ComputedFieldsModel): @register_oapif_viewset( + crs=2056, custom_viewset_attrs={"permission_classes": (permissions.DjangoModelPermissions,)}, ) class Line_2056_10fields(ComputedFieldsModel):