Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add tests and fixes for Atlas
The change to the the feature filter group filterFeatures
(do not clear pre-existing filters) should not be a behavioral
change: additional filters are ANDed.
  • Loading branch information
elpaso committed Feb 27, 2021
1 parent 59202c4 commit 618966a
Show file tree
Hide file tree
Showing 8 changed files with 673 additions and 461 deletions.
Expand Up @@ -30,7 +30,7 @@ Constructor
virtual void filterFeatures( const QgsVectorLayer *layer, QgsFeatureRequest &filterFeatures ) const;

%Docstring
Filter the features of the layer
Filter the features of the layer.

:param layer: the layer to control
:param filterFeatures: the request to fill
Expand Down
7 changes: 7 additions & 0 deletions src/core/layout/qgslayoutatlas.cpp
Expand Up @@ -288,6 +288,13 @@ int QgsLayoutAtlas::updateFeatures()
req.setFilterExpression( mFilterExpression );
}

#ifdef HAVE_SERVER_PYTHON_PLUGINS
if ( mLayout->renderContext().featureFilterProvider() )
{
mLayout->renderContext().featureFilterProvider()->filterFeatures( mCoverageLayer.get(), req );
}
#endif

QgsFeatureIterator fit = mCoverageLayer->getFeatures( req );

std::unique_ptr<QgsExpression> nameExpression;
Expand Down
27 changes: 23 additions & 4 deletions src/core/layout/qgslayoutitemattributetable.cpp
Expand Up @@ -29,6 +29,7 @@
#include "qgsexception.h"
#include "qgsmapsettings.h"
#include "qgsexpressioncontextutils.h"
#include "qgsexpressionnodeimpl.h"
#include "qgsgeometryengine.h"
#include "qgsconditionalstyle.h"

Expand Down Expand Up @@ -717,11 +718,22 @@ QgsLayoutTableColumns QgsLayoutItemAttributeTable::filteredColumns()
return allowedColumns;
}

QHash<const QString, QSet<QString>> columnAttributesMap;
QSet<QString> allowedAttributes;

for ( const auto &c : qgis::as_const( allowedColumns ) )
{
if ( ! c.attribute().isEmpty() )
allowedAttributes.insert( c.attribute() );
if ( ! c.attribute().isEmpty() && ! columnAttributesMap.contains( c.attribute() ) )
{
columnAttributesMap[ c.attribute() ] = QSet<QString>();
const QgsExpression columnExp { c.attribute() };
const auto constRefs { columnExp.findNodes<QgsExpressionNodeColumnRef>() };
for ( const auto &cref : constRefs )
{
columnAttributesMap[ c.attribute() ].insert( cref->name() );
allowedAttributes.insert( cref->name() );
}
}
}

if ( mLayout->renderContext().featureFilterProvider() )
Expand All @@ -735,9 +747,16 @@ QgsLayoutTableColumns QgsLayoutItemAttributeTable::filteredColumns()
if ( filteredAttributesSet != allowedAttributes )
{
const auto forbidden { allowedAttributes.subtract( filteredAttributesSet ) };
allowedColumns.erase( std::remove_if( allowedColumns.begin(), allowedColumns.end(), [ &forbidden ]( QgsLayoutTableColumn & c ) -> bool
allowedColumns.erase( std::remove_if( allowedColumns.begin(), allowedColumns.end(), [ &columnAttributesMap, &forbidden ]( QgsLayoutTableColumn & c ) -> bool
{
return forbidden.contains( c.attribute() );
for ( const auto &f : qgis::as_const( forbidden ) )
{
if ( columnAttributesMap[ c.attribute() ].contains( f ) )
{
return true;
}
}
return false;
} ), allowedColumns.end() );

}
Expand Down
1 change: 0 additions & 1 deletion src/server/qgsfeaturefilterprovidergroup.cpp
Expand Up @@ -20,7 +20,6 @@

void QgsFeatureFilterProviderGroup::filterFeatures( const QgsVectorLayer *layer, QgsFeatureRequest &filterFeatures ) const
{
filterFeatures.disableFilter();
for ( const QgsFeatureFilterProvider *provider : mProviders )
{
QgsFeatureRequest temp;
Expand Down
2 changes: 1 addition & 1 deletion src/server/qgsfeaturefilterprovidergroup.h
Expand Up @@ -36,7 +36,7 @@ class SERVER_EXPORT QgsFeatureFilterProviderGroup : public QgsFeatureFilterProvi
QgsFeatureFilterProviderGroup() = default;

/**
* Filter the features of the layer
* Filter the features of the layer.
* \param layer the layer to control
* \param filterFeatures the request to fill
*/
Expand Down
8 changes: 6 additions & 2 deletions src/server/services/wms/qgswmsrenderer.cpp
Expand Up @@ -394,7 +394,7 @@ namespace QgsWms
{
filterString.append( " AND " );
}
filterString.append( QString( "\"%1\" = %2" ).arg( pkAttributeNames.at( j ) ).arg( atlasPk.at( currentAtlasPk ) ) );
filterString.append( QString( "\"%1\" = %2" ).arg( pkAttributeNames.at( j ), atlasPk.at( currentAtlasPk ) ) );
++currentAtlasPk;
}

Expand Down Expand Up @@ -509,6 +509,10 @@ namespace QgsWms
QgsLayoutExporter atlasPngExport( atlas->layout() );
atlasPngExport.exportToImage( tempOutputFile.fileName(), exportSettings );
}
else
{
throw QgsServiceException( QStringLiteral( "Bad request" ), QStringLiteral( "Atlas error: empty atlas." ), QString(), 400 );
}
}
else
{
Expand Down Expand Up @@ -3255,7 +3259,7 @@ namespace QgsWms
if ( !( *mapIt )->renderingErrors().isEmpty() )
{
const QgsMapRendererJob::Error e = ( *mapIt )->renderingErrors().at( 0 );
throw QgsException( QStringLiteral( "Rendering error : '%1' in layer %2" ).arg( e.message ).arg( e.layerID ) );
throw QgsException( QStringLiteral( "Rendering error : '%1' in layer %2" ).arg( e.message, e.layerID ) );
}
}
}
Expand Down
103 changes: 80 additions & 23 deletions tests/src/python/test_qgsserver_accesscontrol_wms_getprint_postgres.py
Expand Up @@ -34,7 +34,7 @@


class RestrictedAccessControl(QgsAccessControlFilter):
"""Restrict access to pk1 = 1 AND pk2 = 1"""
"""Restricts access to pk1 = 1 AND pk2 = 1"""

# Be able to deactivate the access control to have a reference point
active = {
Expand Down Expand Up @@ -75,7 +75,7 @@ def authorizedLayerAttributes(self, layer, attributes):
allowed = []

for attr in attributes:
if "name" not in attr and "virtual" not in attr: # spellok
if "name" != attr and "virtual" != attr: # spellok
allowed.append(attr) # spellok

return allowed
Expand Down Expand Up @@ -143,6 +143,42 @@ def setUpClass(cls):
cls._accesscontrol = RestrictedAccessControl(cls._server_iface)
cls._server_iface.registerAccessControl(cls._accesscontrol, 100)

def setUp(self):
super().setUp()
self._accesscontrol.active['authorizedLayerAttributes'] = False
self._accesscontrol.active['layerFilterExpression'] = False
self._accesscontrol.active['layerFilterSubsetString'] = False
self._accesscontrol.active['layerPermissions'] = False

def _check_exception(self, qs, exception_text):
"""Check that server throws"""

req = QgsBufferServerRequest('http://my_server/' + qs)
res = QgsBufferServerResponse()
self._server.handleRequest(req, res, self.test_project)
self.assertEqual(res.statusCode(), 400)
self.assertTrue(exception_text in bytes(res.body()).decode('utf8'))

def _check_white(self, qs):
"""Check that output is a white image"""

req = QgsBufferServerRequest('http://my_server/' + qs)
res = QgsBufferServerResponse()
self._server.handleRequest(req, res, self.test_project)
self.assertEqual(res.statusCode(), 200)

result_path = os.path.join(self.temp_dir.path(), 'white.png')
with open(result_path, 'wb+') as f:
f.write(res.body())

# A full white image is expected
image = QImage(result_path)
self.assertTrue(image.isGrayscale())
color = image.pixelColor(100, 100)
self.assertEqual(color.red(), 255)
self.assertEqual(color.green(), 255)
self.assertEqual(color.blue(), 255)

def test_wms_getprint_postgres(self):
"""Test issue GH #41800 """

Expand Down Expand Up @@ -176,36 +212,17 @@ def test_wms_getprint_postgres(self):
self.assertEqual(color.green(), 0)
self.assertEqual(color.blue(), 0)

def _check_white():

req = QgsBufferServerRequest('http://my_server/' + qs)
res = QgsBufferServerResponse()
self._server.handleRequest(req, res, self.test_project)
self.assertEqual(res.statusCode(), 200)

result_path = os.path.join(self.temp_dir.path(), 'white.png')
with open(result_path, 'wb+') as f:
f.write(res.body())

# A full white image is expected
image = QImage(result_path)
self.assertTrue(image.isGrayscale())
color = image.pixelColor(100, 100)
self.assertEqual(color.red(), 255)
self.assertEqual(color.green(), 255)
self.assertEqual(color.blue(), 255)

# Now activate the rule to exclude the feature where pk1 = 1, pk2 = 2
# A white image is expected

self._accesscontrol.active['layerFilterExpression'] = True
_check_white()
self._check_white(qs)

# Activate the other rule for subset string

self._accesscontrol.active['layerFilterExpression'] = False
self._accesscontrol.active['layerFilterSubsetString'] = True
_check_white()
self._check_white(qs)

# Activate the other rule for layer permission

Expand Down Expand Up @@ -285,6 +302,46 @@ def _check_white():

self._img_diff_error(res.body(), res.headers(), "WMS_GetPrint_postgres_print2_filtered")

def test_atlas(self):
"""Test atlas"""

qs = "?" + "&".join(["%s=%s" % i for i in list({
'SERVICE': "WMS",
'VERSION': "1.3.0",
'REQUEST': "GetPrint",
'CRS': 'EPSG:4326',
'FORMAT': 'png',
'LAYERS': 'multiple_pks',
'DPI': 72,
'TEMPLATE': "print1",
}.items())])

req = QgsBufferServerRequest('http://my_server/' + qs + '&ATLAS_PK=1,1')
res = QgsBufferServerResponse()

self._server.handleRequest(req, res, self.test_project)
self.assertEqual(res.statusCode(), 200)

result_path = os.path.join(self.temp_dir.path(), 'atlas_1_2.png')
with open(result_path, 'wb+') as f:
f.write(res.body())

# A full red image is expected
image = QImage(result_path)
self.assertFalse(image.isGrayscale())
color = image.pixelColor(100, 100)
self.assertEqual(color.red(), 255)
self.assertEqual(color.green(), 0)
self.assertEqual(color.blue(), 0)

# Forbid 1-1
self._accesscontrol.active['layerFilterSubsetString'] = True
self._check_exception(qs + '&ATLAS_PK=1,2', "Atlas error: empty atlas.")

self._accesscontrol.active['layerFilterSubsetString'] = False
self._accesscontrol.active['layerFilterExpression'] = True
self._check_exception(qs + '&ATLAS_PK=1,2', "Atlas error: empty atlas.")


if __name__ == '__main__':
unittest.main()

0 comments on commit 618966a

Please sign in to comment.