WFS3 add ACL and other visibility options

This commit is contained in:
Alessandro Pasotti 2019-08-28 08:14:12 +02:00
parent db6d34a846
commit 95c0ad54ff
24 changed files with 2823 additions and 272 deletions

View File

@ -12,6 +12,7 @@
class QgsServerApiUtils
{
%Docstring
@ -57,7 +58,6 @@ This method takes into account the ACL restrictions provided by QGIS Server Acce
.. note::
project must not be NULL
TODO: implement ACL
%End

View File

@ -207,9 +207,11 @@ Fallback to the default content type of the handler if none of the above matches
Returns a link to the parent page up to ``levels`` in the HTML hierarchy from the given ``url``, MAP query argument is preserved
%End
static QgsVectorLayer *layerFromCollection( const QgsServerApiContext &context, const QString &collectionId );
static QgsVectorLayer *layerFromCollectionId( const QgsServerApiContext &context, const QString &collectionId );
%Docstring
Returns a vector layer from the ``collectionId`` in the given ``context``
Returns a vector layer from the ``collectionId`` in the given ``context``.
:raises QgsServerApiNotFoundError: if the layer could not be found.
%End

View File

@ -25,6 +25,12 @@
#include <QString>
#include "qgsproject.h"
#include "qgsserverprojectutils.h"
#include "qgsserverapicontext.h"
#ifdef HAVE_SERVER_PYTHON_PLUGINS
#include "qgsaccesscontrol.h"
#include "qgsserverinterface.h"
#endif
class QgsRectangle;
class QgsCoordinateReferenceSystem;
@ -79,7 +85,6 @@ class SERVER_EXPORT QgsServerApiUtils
* This method takes into account the ACL restrictions provided by QGIS Server Access Control plugins.
*
* \note project must not be NULL
* TODO: implement ACL
*/
static const QVector<QgsMapLayer *> publishedWfsLayers( const QgsProject *project );
@ -92,28 +97,34 @@ class SERVER_EXPORT QgsServerApiUtils
*
* QVector<QgsVectorLayer*> vectorLayers = publishedLayers<QgsVectorLayer>();
*
* TODO: implement ACL
* \note not available in Python bindings
* \see publishedWfsLayers()
*/
template <typename T>
static const QVector<T *> publishedWfsLayers( const QgsProject *project )
static const QVector<const T *> publishedWfsLayers( const QgsServerApiContext &context )
{
const QStringList wfsLayerIds = QgsServerProjectUtils::wfsLayerIds( *project );
const QStringList wfstUpdateLayersId = QgsServerProjectUtils::wfstUpdateLayerIds( *project );
const QStringList wfstInsertLayersId = QgsServerProjectUtils::wfstInsertLayerIds( *project );
const QStringList wfstDeleteLayersId = QgsServerProjectUtils::wfstDeleteLayerIds( *project );
QVector<T *> result;
const auto constLayers { project->layers<T *>() };
for ( const auto &layer : constLayers )
#ifdef HAVE_SERVER_PYTHON_PLUGINS
QgsAccessControl *accessControl = context.serverInterface()->accessControls();
#endif
const QgsProject *project = context.project();
QVector<const T *> result;
if ( project )
{
if ( wfstUpdateLayersId.contains( layer->id() ) ||
wfstInsertLayersId.contains( layer->id() ) ||
wfstDeleteLayersId.contains( layer->id() ) )
const QStringList wfsLayerIds = QgsServerProjectUtils::wfsLayerIds( *project );
const auto constLayers { project->layers<T *>() };
for ( const auto &layer : constLayers )
{
if ( ! wfsLayerIds.contains( layer->id() ) )
{
continue;
}
#ifdef HAVE_SERVER_PYTHON_PLUGINS
if ( accessControl && !accessControl->layerReadPermission( layer ) )
{
continue;
}
#endif
result.push_back( layer );
}
}
return result;
}

View File

@ -230,7 +230,7 @@ QgsVectorLayer *QgsServerOgcApiHandler::layerFromContext( const QgsServerApiCont
}
const QString collectionId { match.captured( QStringLiteral( "collectionId" ) ) };
// May throw if not found
return layerFromCollection( context, collectionId );
return layerFromCollectionId( context, collectionId );
}
@ -463,12 +463,12 @@ QString QgsServerOgcApiHandler::parentLink( const QUrl &url, int levels )
return result.toString();
}
QgsVectorLayer *QgsServerOgcApiHandler::layerFromCollection( const QgsServerApiContext &context, const QString &collectionId )
QgsVectorLayer *QgsServerOgcApiHandler::layerFromCollectionId( const QgsServerApiContext &context, const QString &collectionId )
{
const auto mapLayers { context.project()->mapLayersByShortName<QgsVectorLayer *>( collectionId ) };
if ( mapLayers.count() != 1 )
{
throw QgsServerApiImproperlyConfiguredException( QStringLiteral( "Collection with given id (%1) was not found or multiple matches were found" ).arg( collectionId ) );
throw QgsServerApiNotFoundError( QStringLiteral( "Collection with given id (%1) was not found or multiple matches were found" ).arg( collectionId ) );
}
return mapLayers.first();
}

View File

@ -314,9 +314,10 @@ class SERVER_EXPORT QgsServerOgcApiHandler
static QString parentLink( const QUrl &url, int levels = 1 );
/**
* Returns a vector layer from the \a collectionId in the given \a context
* Returns a vector layer from the \a collectionId in the given \a context.
* \throws QgsServerApiNotFoundError if the layer could not be found.
*/
static QgsVectorLayer *layerFromCollection( const QgsServerApiContext &context, const QString &collectionId );
static QgsVectorLayer *layerFromCollectionId( const QgsServerApiContext &context, const QString &collectionId );
/**
* Returns the defaultResponse as JSON

View File

@ -28,6 +28,12 @@
#include "qgsbufferserverrequest.h"
#include "qgsserverprojectutils.h"
#include "qgsserverinterface.h"
#include "qgsexpressioncontext.h"
#include "qgsexpressioncontextutils.h"
#ifdef HAVE_SERVER_PYTHON_PLUGINS
#include "qgsfilterrestorer.h"
#endif
#include <QMimeDatabase>
@ -187,6 +193,77 @@ json QgsWfs3APIHandler::schema( const QgsServerApiContext &context ) const
return data;
}
void QgsWfs3AbstractItemsHandler::checkLayerIsAccessible( const QgsVectorLayer *mapLayer, const QgsServerApiContext &context ) const
{
const QVector<const QgsVectorLayer *> publishedLayers = QgsServerApiUtils::publishedWfsLayers<QgsVectorLayer>( context );
if ( ! publishedLayers.contains( mapLayer ) )
{
throw QgsServerApiNotFoundError( QStringLiteral( "Collection was not found" ) );
}
}
QgsFeatureRequest QgsWfs3AbstractItemsHandler::filteredRequest( const QgsVectorLayer *layer, const QgsServerApiContext &context ) const
{
QgsFeatureRequest featureRequest;
QgsExpressionContext expressionContext;
expressionContext << QgsExpressionContextUtils::globalScope()
<< QgsExpressionContextUtils::projectScope( context.project() )
<< QgsExpressionContextUtils::layerScope( layer );
featureRequest.setExpressionContext( expressionContext );
//is there alias info for this vector layer?
QMap< int, QString > layerAliasInfo;
const QgsStringMap aliasMap = layer->attributeAliases();
for ( const auto &aliasKey : aliasMap.keys() )
{
int attrIndex = layer->fields().lookupField( aliasKey );
if ( attrIndex != -1 )
{
layerAliasInfo.insert( attrIndex, aliasIt.value() );
}
}
QgsAttributeList attrIndexes = layer->attributeList();
// Removed attributes
//excluded attributes for this layer
const QSet<QString> &layerExcludedAttributes = layer->excludeAttributesWfs();
if ( !attrIndexes.isEmpty() && !layerExcludedAttributes.isEmpty() )
{
const QgsFields &fields = layer->fields();
for ( const QString &excludedAttribute : layerExcludedAttributes )
{
int fieldNameIdx = fields.indexOf( excludedAttribute );
if ( fieldNameIdx > -1 && attrIndexes.contains( fieldNameIdx ) )
{
attrIndexes.removeOne( fieldNameIdx );
}
}
}
featureRequest.setSubsetOfAttributes( attrIndexes );
#ifdef HAVE_SERVER_PYTHON_PLUGINS
// Python plugins can make further modifications to the allowed attributes
QgsAccessControl *accessControl = context.serverInterface()->accessControls();
if ( accessControl )
{
accessControl->filterFeatures( layer, featureRequest );
QStringList attributes = QStringList();
for ( int idx : attrIndexes )
{
attributes.append( layer->fields().field( idx ).name() );
}
featureRequest.setSubsetOfAttributes(
accessControl->layerAttributes( layer, attributes ),
layer->fields() );
}
#endif
return featureRequest;
}
QgsWfs3LandingPageHandler::QgsWfs3LandingPageHandler()
{
}
@ -369,12 +446,26 @@ void QgsWfs3CollectionsHandler::handleRequest( const QgsServerApiContext &contex
"crs", crss
}
};
if ( context.project() )
{
// TODO: include meshes?
for ( const auto &layer : context.project()->layers<QgsVectorLayer *>( ) )
const QgsProject *project = context.project();
const QStringList wfsLayerIds = QgsServerProjectUtils::wfsLayerIds( *project );
for ( const QString &wfsLayerId : wfsLayerIds )
{
const QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( project->mapLayer( wfsLayerId ) );
if ( !layer )
{
continue;
}
if ( layer->type() != QgsMapLayerType::VectorLayer )
{
continue;
}
// Check if the layer is published, raise not found if it is not
checkLayerIsAccessible( layer, context );
const std::string title { layer->title().isEmpty() ? layer->name().toStdString() : layer->title().toStdString() };
const QString shortName { layer->shortName().isEmpty() ? layer->name() : layer->shortName() };
data["collections"].push_back(
@ -495,9 +586,20 @@ void QgsWfs3DescribeCollectionHandler::handleRequest( const QgsServerApiContext
}
const QString collectionId { match.captured( QStringLiteral( "collectionId" ) ) };
// May throw if not found
const QgsVectorLayer *mapLayer { layerFromCollection( context, collectionId ) };
const QgsVectorLayer *mapLayer { layerFromCollectionId( context, collectionId ) };
Q_ASSERT( mapLayer );
const QgsProject *project = context.project();
const QStringList wfsLayerIds = QgsServerProjectUtils::wfsLayerIds( *project );
if ( ! wfsLayerIds.contains( mapLayer->id() ) )
{
throw QgsServerApiNotFoundError( QStringLiteral( "Collection was not found" ) );
}
// Check if the layer is published, raise not found if it is not
checkLayerIsAccessible( mapLayer, context );
const std::string title { mapLayer->title().isEmpty() ? mapLayer->name().toStdString() : mapLayer->title().toStdString() };
const QString shortName { mapLayer->shortName().isEmpty() ? mapLayer->name() : mapLayer->shortName() };
json linksList = links( context );
@ -561,7 +663,7 @@ json QgsWfs3DescribeCollectionHandler::schema( const QgsServerApiContext &contex
json data;
Q_ASSERT( context.project() );
const auto layers { QgsServerApiUtils::publishedWfsLayers<QgsVectorLayer>( context.project() ) };
const auto layers { QgsServerApiUtils::publishedWfsLayers<QgsVectorLayer>( context ) };
// Construct the context with collection id
for ( const auto &mapLayer : layers )
{
@ -722,7 +824,7 @@ json QgsWfs3CollectionsItemsHandler::schema( const QgsServerApiContext &context
json data;
Q_ASSERT( context.project() );
const auto layers { QgsServerApiUtils::publishedWfsLayers<QgsVectorLayer>( context.project() ) };
const QVector<const QgsVectorLayer *> layers { QgsServerApiUtils::publishedWfsLayers<QgsVectorLayer>( context ) };
// Construct the context with collection id
for ( const auto &mapLayer : layers )
{
@ -836,6 +938,10 @@ void QgsWfs3CollectionsItemsHandler::handleRequest( const QgsServerApiContext &c
}
QgsVectorLayer *mapLayer { layerFromContext( context ) };
Q_ASSERT( mapLayer );
// Check if the layer is published, raise not found if it is not
checkLayerIsAccessible( mapLayer, context );
const std::string title { mapLayer->title().isEmpty() ? mapLayer->name().toStdString() : mapLayer->title().toStdString() };
const QString shortName { mapLayer->shortName().isEmpty() ? mapLayer->name() : mapLayer->shortName() };
@ -1063,27 +1169,49 @@ void QgsWfs3CollectionsFeatureHandler::handleRequest( const QgsServerApiContext
{
throw QgsServerApiImproperlyConfiguredException( QStringLiteral( "Project is invalid or undefined" ) );
}
// Check collectionId
const QRegularExpressionMatch match { path().match( context.request()->url().path( ) ) };
if ( ! match.hasMatch() )
{
throw QgsServerApiNotFoundError( QStringLiteral( "Collection was not found" ) );
}
const QString collectionId { match.captured( QStringLiteral( "collectionId" ) ) };
// May throw if not found
QgsVectorLayer *mapLayer { layerFromCollection( context, collectionId ) };
QgsVectorLayer *mapLayer { layerFromCollectionId( context, collectionId ) };
Q_ASSERT( mapLayer );
// Check if the layer is published, raise not found if it is not
checkLayerIsAccessible( mapLayer, context );
const std::string title { mapLayer->title().isEmpty() ? mapLayer->name().toStdString() : mapLayer->title().toStdString() };
if ( context.request()->method() == QgsServerRequest::Method::GetMethod )
{
const QString featureId { match.captured( QStringLiteral( "featureId" ) ) };
QgsJsonExporter exporter { mapLayer };
const QgsFeature feature { mapLayer->getFeature( featureId.toLongLong() ) };
if ( ! feature.isValid() )
#ifdef HAVE_SERVER_PYTHON_PLUGINS
QgsAccessControl *accessControl = context.serverInterface()->accessControls();
//scoped pointer to restore all original layer filters (subsetStrings) when pointer goes out of scope
//there's LOTS of potential exit paths here, so we avoid having to restore the filters manually
std::unique_ptr< QgsOWSServerFilterRestorer > filterRestorer( new QgsOWSServerFilterRestorer() );
if ( accessControl )
{
QgsOWSServerFilterRestorer::applyAccessControlLayerFilters( accessControl, mapLayer, filterRestorer->originalFilters() );
}
#endif
QgsFeatureRequest featureRequest = filteredRequest( mapLayer, context );
featureRequest.setFilterFid( featureId.toLongLong() );
QgsFeature feature;
QgsFeatureIterator it { mapLayer->getFeatures( featureRequest ) };
if ( ! it.nextFeature( feature ) && feature.isValid() )
{
QgsServerApiInternalServerError( QStringLiteral( "Invalid feature [%1]" ).arg( featureId ) );
}
json data = exporter.exportFeatureToJsonObject( feature );
data["links"] = links( context );
json navigation = json::array();
@ -1114,7 +1242,7 @@ json QgsWfs3CollectionsFeatureHandler::schema( const QgsServerApiContext &contex
json data;
Q_ASSERT( context.project() );
const auto layers { QgsServerApiUtils::publishedWfsLayers<QgsVectorLayer>( context.project() ) };
const auto layers { QgsServerApiUtils::publishedWfsLayers<QgsVectorLayer>( context ) };
// Construct the context with collection id
for ( const auto &mapLayer : layers )
{

View File

@ -20,12 +20,35 @@
#include "qgsserverogcapihandler.h"
class QgsFeatureRequest;
class QgsServerOgcApi;
/**
* The QgsWfs3AbstractItemsHandler class provides some
* functionality which is common to the handlers that
* return items.
*/
class QgsWfs3AbstractItemsHandler: public QgsServerOgcApiHandler
{
public:
/**
* Checks if the layer is published in WFS (and perform additional checks for access
* control if plugins are enabled)
* and throws an exception if it is not.
* \param layer the map layer
* \param context the server api context
* \throws QgsServerApiNotFoundException if the layer is NOT published
*/
void checkLayerIsAccessible( const QgsVectorLayer *layer, const QgsServerApiContext &context ) const;
QgsFeatureRequest filteredRequest( const QgsVectorLayer *layer, const QgsServerApiContext &context ) const;
};
/**
* The APIHandler class Wfs3handles the API definition
*/
class QgsWfs3APIHandler: public QgsServerOgcApiHandler
class QgsWfs3APIHandler: public QgsWfs3AbstractItemsHandler
{
public:
@ -44,6 +67,7 @@ class QgsWfs3APIHandler: public QgsServerOgcApiHandler
private:
const QgsServerOgcApi *mApi = nullptr;
};
@ -125,7 +149,7 @@ class QgsWfs3ConformanceHandler: public QgsServerOgcApiHandler
* The CollectionsHandler lists all available collections for the current project
* Path: /collections
*/
class QgsWfs3CollectionsHandler: public QgsServerOgcApiHandler
class QgsWfs3CollectionsHandler: public QgsWfs3AbstractItemsHandler
{
public:
@ -155,7 +179,7 @@ class QgsWfs3CollectionsHandler: public QgsServerOgcApiHandler
* The DescribeCollectionHandler describes a single collection
* Path: /collections/{collectionId}
*/
class QgsWfs3DescribeCollectionHandler: public QgsServerOgcApiHandler
class QgsWfs3DescribeCollectionHandler: public QgsWfs3AbstractItemsHandler
{
public:
QgsWfs3DescribeCollectionHandler( );
@ -175,7 +199,7 @@ class QgsWfs3DescribeCollectionHandler: public QgsServerOgcApiHandler
* The CollectionsItemsHandler list all items in the collection
* Path: /collections/{collectionId}
*/
class QgsWfs3CollectionsItemsHandler: public QgsServerOgcApiHandler
class QgsWfs3CollectionsItemsHandler: public QgsWfs3AbstractItemsHandler
{
public:
QgsWfs3CollectionsItemsHandler( );
@ -204,7 +228,7 @@ class QgsWfs3CollectionsItemsHandler: public QgsServerOgcApiHandler
};
class QgsWfs3CollectionsFeatureHandler: public QgsServerOgcApiHandler
class QgsWfs3CollectionsFeatureHandler: public QgsWfs3AbstractItemsHandler
{
public:
QgsWfs3CollectionsFeatureHandler( );

View File

@ -70,7 +70,7 @@ class QgsServerAPIUtilsTest(QgsServerTestBase):
"""Test published WMS CRSs"""
project = QgsProject()
project.read(unitTestDataPath('qgis_server') + '/test_project.qgs')
project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs')
crss = QgsServerApiUtils.publishedCrsList(project)
self.assertTrue('http://www.opengis.net/def/crs/OGC/1.3/CRS84' in crss)
self.assertTrue('http://www.opengis.net/def/crs/EPSG/9.6.2/3857' in crss)
@ -118,7 +118,7 @@ class QgsServerAPITestBase(QgsServerTestBase):
""" QGIS API server tests"""
# Set to True in child classes to re-generate reference files for this class
regeregenerate_api_reference = False
regeregenerate_api_reference = True
def dump(self, response):
"""Returns the response body as str"""
@ -282,7 +282,7 @@ class QgsServerAPITest(QgsServerAPITestBase):
request = QgsBufferServerRequest('http://server.qgis.org/wfs3/api.openapi3')
project = QgsProject()
project.read(unitTestDataPath('qgis_server') + '/test_project.qgs')
project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs')
self.compareApi(request, project, 'test_wfs3_api_project.json')
def test_wfs3_conformance(self):
@ -304,14 +304,14 @@ class QgsServerAPITest(QgsServerAPITestBase):
"""Test WFS3 API collections in json format"""
request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections.json')
project = QgsProject()
project.read(unitTestDataPath('qgis_server') + '/test_project.qgs')
project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs')
self.compareApi(request, project, 'test_wfs3_collections_project.json')
def test_wfs3_collections_html(self):
"""Test WFS3 API collections in html format"""
request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections.html')
project = QgsProject()
project.read(unitTestDataPath('qgis_server') + '/test_project.qgs')
project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs')
self.compareApi(request, project, 'test_wfs3_collections_project.html')
def test_wfs3_collections_content_type(self):
@ -320,7 +320,7 @@ class QgsServerAPITest(QgsServerAPITestBase):
request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections')
request.setHeader('Accept', 'text/html')
project = QgsProject()
project.read(unitTestDataPath('qgis_server') + '/test_project.qgs')
project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs')
response = QgsBufferServerResponse()
self.server.handleRequest(request, response, project)
self.assertEqual(response.headers()['Content-Type'], 'text/html')
@ -328,14 +328,14 @@ class QgsServerAPITest(QgsServerAPITestBase):
def test_wfs3_collection_items(self):
"""Test WFS3 API items"""
project = QgsProject()
project.read(unitTestDataPath('qgis_server') + '/test_project.qgs')
project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs')
request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/testlayer%20èé/items')
self.compareApi(request, project, 'test_wfs3_collections_items_testlayer_èé.json')
def test_wfs3_collection_items_crs(self):
"""Test WFS3 API items with CRS"""
project = QgsProject()
project.read(unitTestDataPath('qgis_server') + '/test_project.qgs')
project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs')
encoded_crs = parse.quote('http://www.opengis.net/def/crs/EPSG/9.6.2/3857', safe='')
request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/testlayer%20èé/items?crs={}'.format(encoded_crs))
self.compareApi(request, project, 'test_wfs3_collections_items_testlayer_èé_crs_3857.json')
@ -343,7 +343,7 @@ class QgsServerAPITest(QgsServerAPITestBase):
def test_invalid_args(self):
"""Test wrong args"""
project = QgsProject()
project.read(unitTestDataPath('qgis_server') + '/test_project.qgs')
project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs')
request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/testlayer%20èé/items?limit=-1')
response = QgsBufferServerResponse()
self.server.handleRequest(request, response, project)
@ -359,14 +359,14 @@ class QgsServerAPITest(QgsServerAPITestBase):
def test_wfs3_collection_items_limit(self):
"""Test WFS3 API item limits"""
project = QgsProject()
project.read(unitTestDataPath('qgis_server') + '/test_project.qgs')
project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs')
request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/testlayer%20èé/items?limit=1')
self.compareApi(request, project, 'test_wfs3_collections_items_testlayer_èé_limit_1.json')
def test_wfs3_collection_items_limit_offset(self):
"""Test WFS3 API offset"""
project = QgsProject()
project.read(unitTestDataPath('qgis_server') + '/test_project.qgs')
project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs')
request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/testlayer%20èé/items?limit=1&offset=1')
self.compareApi(request, project, 'test_wfs3_collections_items_testlayer_èé_limit_1_offset_1.json')
request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/testlayer%20èé/items?limit=1&offset=-1')
@ -383,7 +383,7 @@ class QgsServerAPITest(QgsServerAPITestBase):
def test_wfs3_collection_items_bbox(self):
"""Test WFS3 API bbox"""
project = QgsProject()
project.read(unitTestDataPath('qgis_server') + '/test_project.qgs')
project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs')
request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/testlayer%20èé/items?bbox=8.203495,44.901482,8.203497,44.901484')
self.compareApi(request, project, 'test_wfs3_collections_items_testlayer_èé_bbox.json')
@ -411,16 +411,23 @@ class QgsServerAPITest(QgsServerAPITestBase):
def test_wfs3_field_filters(self):
"""Test field filters"""
project = QgsProject()
project.read(unitTestDataPath('qgis_server') + '/test_project.qgs')
project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs')
# Check not published
response = QgsBufferServerResponse()
request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/testlayer3/items?name=two')
self.compareApi(request, project, 'test_wfs3_collections_items_testlayer3_name_eq_two.json')
self.server.handleRequest(request, response, project)
self.assertEqual(response.statusCode(), 404) # Not found
request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/layer_with_short_name/items?name=two')
self.server.handleRequest(request, response, project)
self.assertEqual(response.statusCode(), 200) # Bad request
self.compareApi(request, project, 'test_wfs3_collections_items_testlayer_with_short_name_eq_two.json')
def test_wfs3_field_filters_star(self):
"""Test field filters"""
project = QgsProject()
project.read(unitTestDataPath('qgis_server') + '/test_project.qgs')
request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/testlayer3/items?name=tw*')
self.compareApi(request, project, 'test_wfs3_collections_items_testlayer3_name_eq_tw_star.json')
project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs')
request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/testlayer_with_short_name/items?name=tw*')
self.compareApi(request, project, 'test_wfs3_collections_items_testlayer_with_short_name_eq_tw_star.json')
class Handler1(QgsServerOgcApiHandler):
@ -552,7 +559,7 @@ class QgsServerOgcAPITest(QgsServerAPITestBase):
"""Test OGC API Handler"""
project = QgsProject()
project.read(unitTestDataPath('qgis_server') + '/test_project.qgs')
project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs')
request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/testlayer%20èé/items?limit=-1')
response = QgsBufferServerResponse()
@ -632,7 +639,7 @@ class QgsServerOgcAPITest(QgsServerAPITestBase):
"""Test OGC API Handler content types"""
project = QgsProject()
project.read(unitTestDataPath('qgis_server') + '/test_project.qgs')
project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs')
request = QgsBufferServerRequest('http://server.qgis.org/api3/handlerthree?value1=9.5')
response = QgsBufferServerResponse()

View File

@ -584,6 +584,200 @@ Content-Type: application/openapi+json;version=3.0
"tags": "Capabilities"
}
},
"/wfs3/collections/exclude_attribute/items": {
"get": {
"description": "Every feature in a dataset belongs to a collection. A dataset may consist of multiple feature collections. A feature collection is often a collection of features of a similar type, based on a common schema. Use content negotiation or specify a file extension to request HTML (.html) or GeoJSON (.json).",
"operationId": "getFeatures_testlayer_èé_2_a5f61891_b949_43e3_ad30_84013fc922de",
"parameters": [
[
{
"$ref": "#/components/parameters/limit"
},
{
"$ref": "#/components/parameters/offset"
},
{
"$ref": "#/components/parameters/resultType"
},
{
"$ref": "#/components/parameters/bbox"
},
{
"$ref": "#/components/parameters/bbox-crs"
}
],
{
"description": "Filter the collection by 'id'",
"explode": false,
"in": "query",
"name": "id",
"required": false,
"schema": {
"type": "integer"
},
"style": "form"
},
{
"description": "Filter the collection by 'name'",
"explode": false,
"in": "query",
"name": "name",
"required": false,
"schema": {
"type": "string"
},
"style": "form"
},
{
"description": "Filter the collection by 'utf8nameè'",
"explode": false,
"in": "query",
"name": "utf8nameè",
"required": false,
"schema": {
"type": "string"
},
"style": "form"
}
],
"responses": [
[
"200",
{
"content": {
"application/geo+json": {
"schema": {
"$ref": "#/components/schemas/featureCollectionGeoJSON"
}
},
"text/html": {
"schema": {
"type": "string"
}
}
},
"description": "Metadata about the collection 'A test vector layer' shared by this API."
}
],
{
"default": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/exception"
},
"text/html": {
"schema": {
"type": "string"
}
}
}
},
"description": "An error occurred."
}
}
],
"summary": "Retrieve features of 'A test vector layer' feature collection",
"tags": "Features"
}
},
"/wfs3/collections/short-name/items": {
"get": {
"description": "Every feature in a dataset belongs to a collection. A dataset may consist of multiple feature collections. A feature collection is often a collection of features of a similar type, based on a common schema. Use content negotiation or specify a file extension to request HTML (.html) or GeoJSON (.json).",
"operationId": "getFeatures_testlayer_èé_cf86cf11_222f_4b62_929c_12cfc82b9774",
"parameters": [
[
{
"$ref": "#/components/parameters/limit"
},
{
"$ref": "#/components/parameters/offset"
},
{
"$ref": "#/components/parameters/resultType"
},
{
"$ref": "#/components/parameters/bbox"
},
{
"$ref": "#/components/parameters/bbox-crs"
}
],
{
"description": "Filter the collection by 'id'",
"explode": false,
"in": "query",
"name": "id",
"required": false,
"schema": {
"type": "integer"
},
"style": "form"
},
{
"description": "Filter the collection by 'name'",
"explode": false,
"in": "query",
"name": "name",
"required": false,
"schema": {
"type": "string"
},
"style": "form"
},
{
"description": "Filter the collection by 'utf8nameè'",
"explode": false,
"in": "query",
"name": "utf8nameè",
"required": false,
"schema": {
"type": "string"
},
"style": "form"
}
],
"responses": [
[
"200",
{
"content": {
"application/geo+json": {
"schema": {
"$ref": "#/components/schemas/featureCollectionGeoJSON"
}
},
"text/html": {
"schema": {
"type": "string"
}
}
},
"description": "Metadata about the collection 'A test vector layer' shared by this API."
}
],
{
"default": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/exception"
},
"text/html": {
"schema": {
"type": "string"
}
}
}
},
"description": "An error occurred."
}
}
],
"summary": "Retrieve features of 'A test vector layer' feature collection",
"tags": "Features"
}
},
"/wfs3/collections/testlayer èé/items": {
"get": {
"description": "Every feature in a dataset belongs to a collection. A dataset may consist of multiple feature collections. A feature collection is often a collection of features of a similar type, based on a common schema. Use content negotiation or specify a file extension to request HTML (.html) or GeoJSON (.json).",
@ -771,6 +965,186 @@ Content-Type: application/openapi+json;version=3.0
"tags": "Capabilities"
}
},
"/wfs3collections/exclude_attribute": {
"get": {
"description": "Metadata about a feature collection.",
"operationId": "describeCollection_testlayer_èé_2_a5f61891_b949_43e3_ad30_84013fc922de",
"responses": [
[
"200",
{
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/collectionInfo"
}
},
"text/html": {
"schema": {
"type": "string"
}
}
},
"description": "Metadata about the collection 'A test vector layer' shared by this API."
}
],
{
"default": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/exception"
},
"text/html": {
"schema": {
"type": "string"
}
}
}
},
"description": "An error occurred."
}
}
],
"summary": "Describe the 'A test vector layer' feature collection",
"tags": "Capabilities"
}
},
"/wfs3collections/exclude_attribute/items/{featureId}": {
"get": {
"description": "Retrieve a feature; use content negotiation or specify a file extension to request HTML (.html or GeoJSON (.json)",
"operationId": "getFeature_testlayer_èé_2_a5f61891_b949_43e3_ad30_84013fc922de",
"responses": [
[
"200",
{
"content": {
"application/geo+json": {
"schema": {
"$ref": "#/components/schemas/featureGeoJSON"
}
},
"text/html": {
"schema": {
"type": "string"
}
}
},
"description": "Retrieve a 'A test vector layer' feature by 'featureId'."
}
],
{
"default": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/exception"
},
"text/html": {
"schema": {
"type": "string"
}
}
}
},
"description": "An error occurred."
}
}
],
"summary": "Retrieve a single feature from the 'A test vector layer' feature collection",
"tags": "Features"
}
},
"/wfs3collections/short-name": {
"get": {
"description": "Metadata about a feature collection.",
"operationId": "describeCollection_testlayer_èé_cf86cf11_222f_4b62_929c_12cfc82b9774",
"responses": [
[
"200",
{
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/collectionInfo"
}
},
"text/html": {
"schema": {
"type": "string"
}
}
},
"description": "Metadata about the collection 'A test vector layer' shared by this API."
}
],
{
"default": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/exception"
},
"text/html": {
"schema": {
"type": "string"
}
}
}
},
"description": "An error occurred."
}
}
],
"summary": "Describe the 'A test vector layer' feature collection",
"tags": "Capabilities"
}
},
"/wfs3collections/short-name/items/{featureId}": {
"get": {
"description": "Retrieve a feature; use content negotiation or specify a file extension to request HTML (.html or GeoJSON (.json)",
"operationId": "getFeature_testlayer_èé_cf86cf11_222f_4b62_929c_12cfc82b9774",
"responses": [
[
"200",
{
"content": {
"application/geo+json": {
"schema": {
"$ref": "#/components/schemas/featureGeoJSON"
}
},
"text/html": {
"schema": {
"type": "string"
}
}
},
"description": "Retrieve a 'A test vector layer' feature by 'featureId'."
}
],
{
"default": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/exception"
},
"text/html": {
"schema": {
"type": "string"
}
}
}
},
"description": "An error occurred."
}
}
],
"summary": "Retrieve a single feature from the 'A test vector layer' feature collection",
"tags": "Features"
}
},
"/wfs3collections/testlayer èé": {
"get": {
"description": "Metadata about a feature collection.",
@ -877,5 +1251,5 @@ Content-Type: application/openapi+json;version=3.0
"name": "Features"
}
],
"timeStamp": "2019-08-19T14:43:48Z"
"timeStamp": "2019-08-27T20:35:54Z"
}

View File

@ -19,5 +19,5 @@ Content-Type: application/json
"type": "text/html"
}
],
"timeStamp": "2019-07-30T09:17:49Z"
"timeStamp": "2019-08-27T20:35:54Z"
}

View File

@ -1,40 +0,0 @@
Content-Type: application/geo+json
{
"features": [
{
"geometry": {
"coordinates": [
8.203547,
44.901436
],
"type": "Point"
},
"id": 1,
"properties": {
"id": 2,
"name": "two",
"utf8nameè": "two àò"
},
"type": "Feature"
}
],
"links": [
{
"href": "http://server.qgis.org/wfs3/collections/testlayer3/items.geojson?name=tw*",
"rel": "self",
"title": "Retrieve the features of the collection as GEOJSON",
"type": "application/geo+json"
},
{
"href": "http://server.qgis.org/wfs3/collections/testlayer3/items.html?name=tw*",
"rel": "alternate",
"title": "Retrieve the features of the collection as HTML",
"type": "text/html"
}
],
"numberMatched": 1,
"numberReturned": 1,
"timeStamp": "2019-07-30T09:17:49Z",
"type": "FeatureCollection"
}

View File

@ -1,40 +0,0 @@
Content-Type: application/geo+json
{
"features": [
{
"geometry": {
"coordinates": [
8.203547,
44.901436
],
"type": "Point"
},
"id": 1,
"properties": {
"id": 2,
"name": "two",
"utf8nameè": "two àò"
},
"type": "Feature"
}
],
"links": [
{
"href": "http://server.qgis.org/wfs3/collections/testlayer3/items.geojson?name=two",
"rel": "self",
"title": "Retrieve the features of the collection as GEOJSON",
"type": "application/geo+json"
},
{
"href": "http://server.qgis.org/wfs3/collections/testlayer3/items.html?name=two",
"rel": "alternate",
"title": "Retrieve the features of the collection as HTML",
"type": "text/html"
}
],
"numberMatched": 1,
"numberReturned": 1,
"timeStamp": "2019-07-30T09:17:49Z",
"type": "FeatureCollection"
}

View File

@ -0,0 +1,3 @@
Content-Type: application/json
[{"code":"API not found error","description":"Collection with given id (testlayer_with_short_name) was not found or multiple matches were found"}]

View File

@ -67,6 +67,6 @@ Content-Type: application/geo+json
],
"numberMatched": 3,
"numberReturned": 3,
"timeStamp": "2019-07-30T09:17:49Z",
"timeStamp": "2019-08-27T20:35:54Z",
"type": "FeatureCollection"
}

View File

@ -35,6 +35,6 @@ Content-Type: application/geo+json
],
"numberMatched": 1,
"numberReturned": 1,
"timeStamp": "2019-07-30T09:17:49Z",
"timeStamp": "2019-08-27T20:35:54Z",
"type": "FeatureCollection"
}

View File

@ -51,6 +51,6 @@ Content-Type: application/geo+json
],
"numberMatched": 2,
"numberReturned": 2,
"timeStamp": "2019-07-30T09:17:49Z",
"timeStamp": "2019-08-27T20:35:54Z",
"type": "FeatureCollection"
}

View File

@ -67,6 +67,6 @@ Content-Type: application/geo+json
],
"numberMatched": 3,
"numberReturned": 3,
"timeStamp": "2019-07-30T09:17:49Z",
"timeStamp": "2019-08-27T20:35:54Z",
"type": "FeatureCollection"
}

View File

@ -42,6 +42,6 @@ Content-Type: application/geo+json
],
"numberMatched": 3,
"numberReturned": 1,
"timeStamp": "2019-07-30T09:17:49Z",
"timeStamp": "2019-08-27T20:35:54Z",
"type": "FeatureCollection"
}

View File

@ -49,6 +49,6 @@ Content-Type: application/geo+json
],
"numberMatched": 3,
"numberReturned": 1,
"timeStamp": "2019-07-30T09:17:49Z",
"timeStamp": "2019-08-27T20:35:54Z",
"type": "FeatureCollection"
}

View File

@ -54,28 +54,13 @@
">A test vector layer</a></h3>
<h3><a href="
http://server.qgis.org/wfs3/collections/testlayer3.html
">testlayer3</a></h3>
<h3><a href="
http://server.qgis.org/wfs3/collections/testlayer2.html
">testlayer2</a></h3>
<h3><a href="
http://server.qgis.org/wfs3/collections/layer_with_short_name.html
">A Layer with a short name</a></h3>
<h3><a href="
http://server.qgis.org/wfs3/collections/exclude_attribute.html
">A test vector layer</a></h3>
<h3><a href="
http://server.qgis.org/wfs3/collections/fields_alias.html
http://server.qgis.org/wfs3/collections/short-name.html
">A test vector layer</a></h3>

View File

@ -37,111 +37,6 @@ Content-Type: application/json
"name": "testlayer èé",
"title": "A test vector layer"
},
{
"crs": [
"http://www.opengis.net/def/crs/OGC/1.3/CRS84",
"http://www.opengis.net/def/crs/EPSG/9.6.2/4326",
"http://www.opengis.net/def/crs/EPSG/9.6.2/3857"
],
"description": "",
"extent": {
"crs": "http://www.opengis.net/def/crs/OGC/1.3/CRS84",
"spatial": [
[
8.203459307036344,
44.90139483904469,
8.203546993993488,
44.901482526001836
]
]
},
"links": [
{
"href": "http://server.qgis.org/wfs3/collections/testlayer3/items.json",
"rel": "item",
"title": "testlayer3 as GeoJSON",
"type": "application/geo+json"
},
{
"href": "http://server.qgis.org/wfs3/collections/testlayer3/items.html",
"rel": "item",
"title": "testlayer3 as HTML",
"type": "text/html"
}
],
"name": "testlayer3",
"title": "testlayer3"
},
{
"crs": [
"http://www.opengis.net/def/crs/OGC/1.3/CRS84",
"http://www.opengis.net/def/crs/EPSG/9.6.2/4326",
"http://www.opengis.net/def/crs/EPSG/9.6.2/3857"
],
"description": "",
"extent": {
"crs": "http://www.opengis.net/def/crs/OGC/1.3/CRS84",
"spatial": [
[
8.203459307036344,
44.90139483904469,
8.203546993993488,
44.901482526001836
]
]
},
"links": [
{
"href": "http://server.qgis.org/wfs3/collections/testlayer2/items.json",
"rel": "item",
"title": "testlayer2 as GeoJSON",
"type": "application/geo+json"
},
{
"href": "http://server.qgis.org/wfs3/collections/testlayer2/items.html",
"rel": "item",
"title": "testlayer2 as HTML",
"type": "text/html"
}
],
"name": "testlayer2",
"title": "testlayer2"
},
{
"crs": [
"http://www.opengis.net/def/crs/OGC/1.3/CRS84",
"http://www.opengis.net/def/crs/EPSG/9.6.2/4326",
"http://www.opengis.net/def/crs/EPSG/9.6.2/3857"
],
"description": "A Layer with an abstract",
"extent": {
"crs": "http://www.opengis.net/def/crs/OGC/1.3/CRS84",
"spatial": [
[
8.203459307036344,
44.90139483904469,
8.203546993993488,
44.901482526001836
]
]
},
"links": [
{
"href": "http://server.qgis.org/wfs3/collections/layer_with_short_name/items.json",
"rel": "item",
"title": "A Layer with a short name as GeoJSON",
"type": "application/geo+json"
},
{
"href": "http://server.qgis.org/wfs3/collections/layer_with_short_name/items.html",
"rel": "item",
"title": "A Layer with a short name as HTML",
"type": "text/html"
}
],
"name": "layer_with_short_name",
"title": "A Layer with a short name"
},
{
"crs": [
"http://www.opengis.net/def/crs/OGC/1.3/CRS84",
@ -197,19 +92,19 @@ Content-Type: application/json
},
"links": [
{
"href": "http://server.qgis.org/wfs3/collections/fields_alias/items.json",
"href": "http://server.qgis.org/wfs3/collections/short-name/items.json",
"rel": "item",
"title": "A test vector layer as GeoJSON",
"type": "application/geo+json"
},
{
"href": "http://server.qgis.org/wfs3/collections/fields_alias/items.html",
"href": "http://server.qgis.org/wfs3/collections/short-name/items.html",
"rel": "item",
"title": "A test vector layer as HTML",
"type": "text/html"
}
],
"name": "fields_alias",
"name": "short-name",
"title": "A test vector layer"
}
],
@ -232,5 +127,5 @@ Content-Type: application/json
"type": "text/html"
}
],
"timeStamp": "2019-07-30T09:17:49Z"
"timeStamp": "2019-08-27T20:35:54Z"
}

View File

@ -22,5 +22,5 @@ Content-Type: application/json
"type": "text/html"
}
],
"timeStamp": "2019-07-30T09:17:49Z"
"timeStamp": "2019-08-27T20:35:54Z"
}

View File

@ -33,5 +33,5 @@ Content-Type: application/json
"type": "application/openapi+json;version=3.0"
}
],
"timeStamp": "2019-07-30T09:17:49Z"
"timeStamp": "2019-08-27T20:35:54Z"
}

File diff suppressed because it is too large Load Diff