From 60d92d4b5d0825a40a7a71fdff659046664b1f50 Mon Sep 17 00:00:00 2001 From: qgis-bot <58983587+qgis-bot@users.noreply.github.com> Date: Mon, 27 Jan 2025 10:51:11 +0100 Subject: [PATCH] [Backport release-3_40] Bugfix gh60185 server wfs aspatial forbidden update (#60271) * [memory] Make sure spatial capabilities are not set for aspatial layers * [server][wfs] Fix getcap not allowing updates on aspatial layers Fix #60185 * memory layer expand test case --- .../providers/memory/qgsmemoryprovider.cpp | 11 +++-- .../services/wfs/qgswfsgetcapabilities.cpp | 2 +- tests/src/python/test_provider_memory.py | 39 ++++++++++++++++++ tests/src/python/test_qgsserver_wfs.py | 41 ++++++++++++++++++- 4 files changed, 88 insertions(+), 5 deletions(-) diff --git a/src/core/providers/memory/qgsmemoryprovider.cpp b/src/core/providers/memory/qgsmemoryprovider.cpp index f8aa6043e5a2..e86bd4b600e7 100644 --- a/src/core/providers/memory/qgsmemoryprovider.cpp +++ b/src/core/providers/memory/qgsmemoryprovider.cpp @@ -804,9 +804,14 @@ Qgis::SpatialIndexPresence QgsMemoryProvider::hasSpatialIndex() const Qgis::VectorProviderCapabilities QgsMemoryProvider::capabilities() const { - return Qgis::VectorProviderCapability::AddFeatures | Qgis::VectorProviderCapability::DeleteFeatures | Qgis::VectorProviderCapability::ChangeGeometries | - Qgis::VectorProviderCapability::ChangeAttributeValues | Qgis::VectorProviderCapability::AddAttributes | Qgis::VectorProviderCapability::DeleteAttributes | Qgis::VectorProviderCapability::RenameAttributes | Qgis::VectorProviderCapability::CreateSpatialIndex | - Qgis::VectorProviderCapability::SelectAtId | Qgis::VectorProviderCapability::CircularGeometries | Qgis::VectorProviderCapability::FastTruncate; + Qgis::VectorProviderCapabilities caps { Qgis::VectorProviderCapability::AddFeatures | Qgis::VectorProviderCapability::DeleteFeatures | + Qgis::VectorProviderCapability::ChangeAttributeValues | Qgis::VectorProviderCapability::AddAttributes | Qgis::VectorProviderCapability::DeleteAttributes | Qgis::VectorProviderCapability::RenameAttributes | + Qgis::VectorProviderCapability::SelectAtId | Qgis::VectorProviderCapability::FastTruncate }; + if ( mWkbType != Qgis::WkbType::NoGeometry ) + { + caps |= Qgis::VectorProviderCapability::CreateSpatialIndex | Qgis::VectorProviderCapability::CircularGeometries | Qgis::VectorProviderCapability::ChangeGeometries ; + } + return caps; } bool QgsMemoryProvider::truncate() diff --git a/src/server/services/wfs/qgswfsgetcapabilities.cpp b/src/server/services/wfs/qgswfsgetcapabilities.cpp index acfbf19dbf76..fb54a6986052 100644 --- a/src/server/services/wfs/qgswfsgetcapabilities.cpp +++ b/src/server/services/wfs/qgswfsgetcapabilities.cpp @@ -530,7 +530,7 @@ namespace QgsWfs operationsElement.appendChild( operationElement ); } - if ( ( provider->capabilities() & Qgis::VectorProviderCapability::ChangeAttributeValues ) && ( provider->capabilities() & Qgis::VectorProviderCapability::ChangeGeometries ) && wfstUpdateLayersId.contains( layer->id() ) ) + if ( ( provider->capabilities() & Qgis::VectorProviderCapability::ChangeAttributeValues ) && ( !layer->isSpatial() || provider->capabilities() & Qgis::VectorProviderCapability::ChangeGeometries ) && wfstUpdateLayersId.contains( layer->id() ) ) { //wfs:Update element QDomElement operationElement = doc.createElement( QStringLiteral( "Operation" ) ); diff --git a/tests/src/python/test_provider_memory.py b/tests/src/python/test_provider_memory.py index d4fbc466b279..b568f68828f4 100644 --- a/tests/src/python/test_provider_memory.py +++ b/tests/src/python/test_provider_memory.py @@ -15,6 +15,7 @@ from qgis.PyQt.QtCore import QByteArray, QDate, QDateTime, QTime, QVariant from qgis.core import ( NULL, + Qgis, QgsCoordinateReferenceSystem, QgsEditorWidgetSetup, QgsFeature, @@ -734,6 +735,44 @@ def testUniqueSource(self): layer2 = QgsVectorLayer("Point", "test2", "memory") self.assertNotEqual(layer.source(), layer2.source()) + def testAspatialLayerHasNoGeometryRelatedCapabilities(self): + + layer = QgsMemoryProviderUtils.createMemoryLayer("my name", QgsFields()) + self.assertTrue(layer.isValid()) + self.assertFalse(layer.isSpatial()) + self.assertFalse( + layer.dataProvider().capabilities() + & Qgis.VectorProviderCapability.ChangeGeometries + ) + self.assertFalse( + layer.dataProvider().capabilities() + & Qgis.VectorProviderCapability.CircularGeometries + ) + self.assertFalse( + layer.dataProvider().capabilities() + & Qgis.VectorProviderCapability.CreateSpatialIndex + ) + + def testSpatialLayerHasGeometryRelatedCapabilities(self): + + layer = QgsMemoryProviderUtils.createMemoryLayer( + "my name", QgsFields(), QgsWkbTypes.Type.Point + ) + self.assertTrue(layer.isValid()) + self.assertTrue(layer.isSpatial()) + self.assertTrue( + layer.dataProvider().capabilities() + & Qgis.VectorProviderCapability.ChangeGeometries + ) + self.assertTrue( + layer.dataProvider().capabilities() + & Qgis.VectorProviderCapability.CircularGeometries + ) + self.assertTrue( + layer.dataProvider().capabilities() + & Qgis.VectorProviderCapability.CreateSpatialIndex + ) + def testCreateMemoryLayer(self): """ Test QgsMemoryProviderUtils.createMemoryLayer() diff --git a/tests/src/python/test_qgsserver_wfs.py b/tests/src/python/test_qgsserver_wfs.py index 77538fa68f7b..dce205027217 100644 --- a/tests/src/python/test_qgsserver_wfs.py +++ b/tests/src/python/test_qgsserver_wfs.py @@ -38,10 +38,16 @@ QgsGeometry, QgsProject, QgsVectorLayer, + QgsMemoryProviderUtils, + QgsWkbTypes, + QgsVectorDataProvider, + QgsFields, + QgsField, ) -from qgis.server import QgsServerRequest +from qgis.server import QgsServerRequest, QgsServer, QgsBufferServerResponse from qgis.testing import unittest from test_qgsserver import QgsServerTestBase +from qgis.PyQt.QtCore import QVariant, QUrl # Strip path and content length because path may vary RE_STRIP_UNCHECKABLE = rb'MAP=[^"]+|Content-Length: \d+|timeStamp="[^"]+"' @@ -1530,6 +1536,39 @@ def test_GetFeature_with_datetime(self): project_file=project_file, ) + def test_wfs_aspatial_getcapabilities(self): + ### Test issue GH #60185 - WFS GetCapabilities for aspatial layers""" + + # create a memory layer with no geometry + fields = QgsFields() + fields.append(QgsField("id", QVariant.Int)) + fields.append(QgsField("name", QVariant.String)) + layer = QgsMemoryProviderUtils.createMemoryLayer( + "no_geom", fields, QgsWkbTypes.NoGeometry + ) + + provider = layer.dataProvider() + self.assertTrue(layer.isValid()) + self.assertFalse(layer.isSpatial()) + self.assertFalse( + provider.capabilities() & QgsVectorDataProvider.Capability.ChangeGeometries + ) + + project = QgsProject() + project.addMapLayer(layer) + project.writeEntry("WFSLayers", "/", [layer.id()]) + project.writeEntry("WFSTLayers", "Update", [layer.id()]) + project.writeEntry("WFSTLayers", "Insert", [layer.id()]) + project.writeEntry("WFSTLayers", "Delete", [layer.id()]) + + server = QgsServer() + request = QgsServerRequest() + request.setUrl(QUrl("?SERVICE=WFS&REQUEST=GetCapabilities")) + response = QgsBufferServerResponse() + server.handleRequest(request, response, project) + body = response.body().data().decode("utf8").replace("\n", "") + self.assertIn("Update", body) + if __name__ == "__main__": unittest.main()