Skip to content

Commit 38a3b2a

Browse files
volayanyalldawson
authored andcommittedFeb 26, 2019
[processing] do not allow using unsupported file formats
Show warning message if user selects incompatible output file format fixes #21089 (cherry picked from commit 13bff96)
1 parent 28cdd97 commit 38a3b2a

File tree

9 files changed

+152
-12
lines changed

9 files changed

+152
-12
lines changed
 

‎python/core/auto_generated/processing/qgsprocessingprovider.sip.in

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,15 @@ formats for geometry-less layers can override this method to return a different
138138
.. seealso:: :py:func:`supportsNonFileBasedOutput`
139139

140140
.. versionadded:: 3.4.3
141+
%End
142+
143+
virtual bool isSupportedOutputValue( const QVariant &outputValue, const QgsProcessingDestinationParameter *parameter, QgsProcessingContext &context, QString &error /Out/ ) const;
144+
%Docstring
145+
Returns true if the specified ``outputValue`` is of a supported file format for the given destination ``parameter``.
146+
147+
If the output value is not supported, ``error`` will be set to a descriptive message explaining why.
148+
149+
.. versionadded:: 3.6
141150
%End
142151

143152
virtual QString defaultVectorFileExtension( bool hasGeometry = true ) const;

‎python/plugins/processing/algs/gdal/GdalAlgorithmDialog.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,5 @@ def parametersHaveChanged(self):
142142
self.text.setPlainText(" ".join(commands))
143143
except AlgorithmDialogBase.InvalidParameterValue as e:
144144
self.text.setPlainText(self.tr("Invalid value for parameter '{0}'").format(e.parameter.description()))
145+
except AlgorithmDialogBase.InvalidOutputExtension as e:
146+
self.text.setPlainText(e.message)

‎python/plugins/processing/gui/AlgorithmDialog.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
__revision__ = '$Format:%H$'
2727

28+
import os
2829
from pprint import pformat
2930
import time
3031

@@ -44,6 +45,7 @@
4445
QgsProcessingParameterFeatureSink,
4546
QgsProcessingParameterRasterDestination,
4647
QgsProcessingAlgorithm,
48+
QgsProcessingParameters,
4749
QgsProxyProgressTask,
4850
QgsTaskManager)
4951
from qgis.gui import (QgsGui,
@@ -154,11 +156,18 @@ def getParameterValues(self):
154156
if self.mainWidget().checkBoxes[param.name()].isChecked():
155157
dest_project = QgsProject.instance()
156158

157-
value = self.mainWidget().outputWidgets[param.name()].getValue()
159+
widget = self.mainWidget().outputWidgets[param.name()]
160+
value = widget.getValue()
161+
158162
if value and isinstance(value, QgsProcessingOutputLayerDefinition):
159163
value.destinationProject = dest_project
160164
if value:
161165
parameters[param.name()] = value
166+
if param.isDestination():
167+
context = dataobjects.createContext()
168+
ok, error = self.algorithm().provider().isSupportedOutputValue(value, param, context)
169+
if not ok:
170+
raise AlgorithmDialogBase.InvalidOutputExtension(widget, error)
162171

163172
return self.algorithm().preprocessParameters(parameters)
164173

@@ -294,6 +303,18 @@ def on_complete(ok, results):
294303
self.messageBar().clearWidgets()
295304
self.messageBar().pushMessage("", self.tr("Wrong or missing parameter value: {0}").format(e.parameter.description()),
296305
level=Qgis.Warning, duration=5)
306+
except AlgorithmDialogBase.InvalidOutputExtension as e:
307+
try:
308+
self.buttonBox().accepted.connect(lambda e=e:
309+
e.widget.setPalette(QPalette()))
310+
palette = e.widget.palette()
311+
palette.setColor(QPalette.Base, QColor(255, 255, 0))
312+
e.widget.setPalette(palette)
313+
except:
314+
pass
315+
self.messageBar().clearWidgets()
316+
self.messageBar().pushMessage("", e.message,
317+
level=Qgis.Warning, duration=5)
297318

298319
def finish(self, successful, result, context, feedback):
299320
keepOpen = not successful or ProcessingConfig.getSetting(ProcessingConfig.KEEP_DIALOG_OPEN)

‎python/plugins/processing/gui/AlgorithmDialogBase.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,9 @@ class InvalidParameterValue(Exception):
3232

3333
def __init__(self, param, widget):
3434
(self.parameter, self.widget) = (param, widget)
35+
36+
class InvalidOutputExtension(Exception):
37+
38+
def __init__(self, widget, message):
39+
self.widget = widget
40+
self.message = message

‎src/core/processing/qgsprocessingprovider.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,66 @@ QStringList QgsProcessingProvider::supportedOutputTableExtensions() const
107107
return supportedOutputVectorLayerExtensions();
108108
}
109109

110+
bool QgsProcessingProvider::isSupportedOutputValue( const QVariant &outputValue, const QgsProcessingDestinationParameter *parameter, QgsProcessingContext &context, QString &error ) const
111+
{
112+
QString outputPath = QgsProcessingParameters::parameterAsOutputLayer( parameter, outputValue, context );
113+
error.clear();
114+
if ( parameter->type() == QgsProcessingParameterVectorDestination::typeName()
115+
|| parameter->type() == QgsProcessingParameterFeatureSink::typeName() )
116+
{
117+
if ( outputPath.isEmpty() || outputPath.startsWith( QLatin1String( "memory:" ) ) )
118+
{
119+
if ( !supportsNonFileBasedOutput() )
120+
{
121+
error = tr( "This algorithm only supports disk-based outputs" );
122+
return false;
123+
}
124+
return true;
125+
}
126+
127+
QString providerKey;
128+
QString uri;
129+
QString layerName;
130+
QMap<QString, QVariant> options;
131+
bool useWriter = false;
132+
QString format;
133+
QString extension;
134+
QgsProcessingUtils::parseDestinationString( outputPath, providerKey, uri, layerName, format, options, useWriter, extension );
135+
136+
if ( providerKey != QLatin1String( "ogr" ) )
137+
{
138+
if ( !supportsNonFileBasedOutput() )
139+
{
140+
error = tr( "This algorithm only supports disk-based outputs" );
141+
return false;
142+
}
143+
return true;
144+
}
145+
146+
if ( !supportedOutputVectorLayerExtensions().contains( extension, Qt::CaseInsensitive ) )
147+
{
148+
error = tr( "%1 files are not supported as outputs for this algorithm" ).arg( extension );
149+
return false;
150+
}
151+
return true;
152+
}
153+
else if ( parameter->type() == QgsProcessingParameterRasterDestination::typeName() )
154+
{
155+
QFileInfo fi( outputPath );
156+
const QString extension = fi.completeSuffix();
157+
if ( !supportedOutputRasterLayerExtensions().contains( extension, Qt::CaseInsensitive ) )
158+
{
159+
error = tr( "%1 files are not supported as outputs for this algorithm" ).arg( extension );
160+
return false;
161+
}
162+
return true;
163+
}
164+
else
165+
{
166+
return true;
167+
}
168+
}
169+
110170
QString QgsProcessingProvider::defaultVectorFileExtension( bool hasGeometry ) const
111171
{
112172
QgsSettings settings;

‎src/core/processing/qgsprocessingprovider.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,15 @@ class CORE_EXPORT QgsProcessingProvider : public QObject
140140
*/
141141
virtual QStringList supportedOutputTableExtensions() const;
142142

143+
/**
144+
* Returns true if the specified \a outputValue is of a supported file format for the given destination \a parameter.
145+
*
146+
* If the output value is not supported, \a error will be set to a descriptive message explaining why.
147+
*
148+
* \since QGIS 3.6
149+
*/
150+
virtual bool isSupportedOutputValue( const QVariant &outputValue, const QgsProcessingDestinationParameter *parameter, QgsProcessingContext &context, QString &error SIP_OUT ) const;
151+
143152
/**
144153
* Returns the default file extension to use for vector outputs created by the
145154
* provider.

‎src/core/processing/qgsprocessingutils.cpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -350,8 +350,9 @@ QString QgsProcessingUtils::stringToPythonLiteral( const QString &string )
350350
return s;
351351
}
352352

353-
void QgsProcessingUtils::parseDestinationString( QString &destination, QString &providerKey, QString &uri, QString &layerName, QString &format, QMap<QString, QVariant> &options, bool &useWriter )
353+
void QgsProcessingUtils::parseDestinationString( QString &destination, QString &providerKey, QString &uri, QString &layerName, QString &format, QMap<QString, QVariant> &options, bool &useWriter, QString &extension )
354354
{
355+
extension.clear();
355356
QRegularExpression splitRx( QStringLiteral( "^(.{3,}?):(.*)$" ) );
356357
QRegularExpressionMatch match = splitRx.match( destination );
357358
if ( match.hasMatch() )
@@ -373,7 +374,12 @@ void QgsProcessingUtils::parseDestinationString( QString &destination, QString &
373374
options.insert( QStringLiteral( "layerName" ), layerName );
374375
}
375376
uri = dsUri.database();
376-
format = QgsVectorFileWriter::driverForExtension( QFileInfo( uri ).completeSuffix() );
377+
extension = QFileInfo( uri ).completeSuffix();
378+
format = QgsVectorFileWriter::driverForExtension( extension );
379+
}
380+
else
381+
{
382+
extension = QFileInfo( uri ).completeSuffix();
377383
}
378384
options.insert( QStringLiteral( "update" ), true );
379385
}
@@ -385,7 +391,6 @@ void QgsProcessingUtils::parseDestinationString( QString &destination, QString &
385391
providerKey = QStringLiteral( "ogr" );
386392
QRegularExpression splitRx( QStringLiteral( "^(.*)\\.(.*?)$" ) );
387393
QRegularExpressionMatch match = splitRx.match( destination );
388-
QString extension;
389394
if ( match.hasMatch() )
390395
{
391396
extension = match.captured( 2 );
@@ -443,8 +448,9 @@ QgsFeatureSink *QgsProcessingUtils::createFeatureSink( QString &destination, Qgs
443448
QString uri;
444449
QString layerName;
445450
QString format;
451+
QString extension;
446452
bool useWriter = false;
447-
parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter );
453+
parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter, extension );
448454

449455
QgsFields newFields = fields;
450456
if ( useWriter && providerKey == QLatin1String( "ogr" ) )

‎src/core/processing/qgsprocessingutils.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,9 +288,10 @@ class CORE_EXPORT QgsProcessingUtils
288288
*/
289289
static QgsMapLayer *loadMapLayerFromString( const QString &string, LayerHint typeHint = UnknownType );
290290

291-
static void parseDestinationString( QString &destination, QString &providerKey, QString &uri, QString &layerName, QString &format, QMap<QString, QVariant> &options, bool &useWriter );
291+
static void parseDestinationString( QString &destination, QString &providerKey, QString &uri, QString &layerName, QString &format, QMap<QString, QVariant> &options, bool &useWriter, QString &extension );
292292

293293
friend class TestQgsProcessing;
294+
friend class QgsProcessingProvider;
294295

295296
};
296297

‎tests/src/analysis/testqgsprocessing.cpp

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1486,59 +1486,66 @@ void TestQgsProcessing::parseDestinationString()
14861486
QString layerName;
14871487
QString format;
14881488
QVariantMap options;
1489+
QString extension;
14891490
bool useWriter = false;
14901491

14911492
// simple shapefile output
14921493
QString destination = QStringLiteral( "d:/test.shp" );
1493-
QgsProcessingUtils::parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter );
1494+
QgsProcessingUtils::parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter, extension );
14941495
QCOMPARE( destination, QStringLiteral( "d:/test.shp" ) );
14951496
QCOMPARE( providerKey, QStringLiteral( "ogr" ) );
14961497
QCOMPARE( uri, QStringLiteral( "d:/test.shp" ) );
14971498
QCOMPARE( format, QStringLiteral( "ESRI Shapefile" ) );
1499+
QCOMPARE( extension, QStringLiteral( "shp" ) );
14981500
QVERIFY( useWriter );
14991501

15001502
// postgis output
15011503
destination = QStringLiteral( "postgis:dbname='db' host=DBHOST port=5432 table=\"calcs\".\"output\" (geom) sql=" );
1502-
QgsProcessingUtils::parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter );
1504+
QgsProcessingUtils::parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter, extension );
15031505
QCOMPARE( providerKey, QStringLiteral( "postgres" ) );
15041506
QCOMPARE( uri, QStringLiteral( "dbname='db' host=DBHOST port=5432 table=\"calcs\".\"output\" (geom) sql=" ) );
15051507
QVERIFY( !useWriter );
1508+
QVERIFY( extension.isEmpty() );
15061509
// postgres key should also work
15071510
destination = QStringLiteral( "postgres:dbname='db' host=DBHOST port=5432 table=\"calcs\".\"output\" (geom) sql=" );
1508-
QgsProcessingUtils::parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter );
1511+
QgsProcessingUtils::parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter, extension );
15091512
QCOMPARE( providerKey, QStringLiteral( "postgres" ) );
15101513
QCOMPARE( uri, QStringLiteral( "dbname='db' host=DBHOST port=5432 table=\"calcs\".\"output\" (geom) sql=" ) );
15111514
QVERIFY( !useWriter );
1515+
QVERIFY( extension.isEmpty() );
15121516

15131517
// full uri shp output
15141518
options.clear();
15151519
destination = QStringLiteral( "ogr:d:/test.shp" );
1516-
QgsProcessingUtils::parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter );
1520+
QgsProcessingUtils::parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter, extension );
15171521
QCOMPARE( providerKey, QStringLiteral( "ogr" ) );
15181522
QCOMPARE( uri, QStringLiteral( "d:/test.shp" ) );
15191523
QCOMPARE( options.value( QStringLiteral( "update" ) ).toBool(), true );
15201524
QVERIFY( !options.contains( QStringLiteral( "layerName" ) ) );
15211525
QVERIFY( !useWriter );
1526+
QCOMPARE( extension, QStringLiteral( "shp" ) );
15221527

15231528
// full uri geopackage output
15241529
options.clear();
15251530
destination = QStringLiteral( "ogr:d:/test.gpkg" );
1526-
QgsProcessingUtils::parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter );
1531+
QgsProcessingUtils::parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter, extension );
15271532
QCOMPARE( providerKey, QStringLiteral( "ogr" ) );
15281533
QCOMPARE( uri, QStringLiteral( "d:/test.gpkg" ) );
15291534
QCOMPARE( options.value( QStringLiteral( "update" ) ).toBool(), true );
15301535
QVERIFY( !options.contains( QStringLiteral( "layerName" ) ) );
15311536
QVERIFY( !useWriter );
1537+
QCOMPARE( extension, QStringLiteral( "gpkg" ) );
15321538

15331539
// full uri geopackage table output with layer name
15341540
options.clear();
15351541
destination = QStringLiteral( "ogr:dbname='d:/package.gpkg' table=\"mylayer\" (geom) sql=" );
1536-
QgsProcessingUtils::parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter );
1542+
QgsProcessingUtils::parseDestinationString( destination, providerKey, uri, layerName, format, options, useWriter, extension );
15371543
QCOMPARE( providerKey, QStringLiteral( "ogr" ) );
15381544
QCOMPARE( uri, QStringLiteral( "d:/package.gpkg" ) );
15391545
QCOMPARE( options.value( QStringLiteral( "update" ) ).toBool(), true );
15401546
QCOMPARE( options.value( QStringLiteral( "layerName" ) ).toString(), QStringLiteral( "mylayer" ) );
15411547
QVERIFY( !useWriter );
1548+
QCOMPARE( extension, QStringLiteral( "gpkg" ) );
15421549
}
15431550

15441551
void TestQgsProcessing::createFeatureSink()
@@ -4859,6 +4866,16 @@ void TestQgsProcessing::parameterVectorOut()
48594866

48604867
def.reset( new QgsProcessingParameterVectorDestination( "with_geom", QString(), QgsProcessing::TypeVectorAnyGeometry, QString(), true ) );
48614868
DummyProvider3 provider;
4869+
QString error;
4870+
QVERIFY( !provider.isSupportedOutputValue( "d:/test.shp", def.get(), context, error ) );
4871+
QVERIFY( !provider.isSupportedOutputValue( "d:/test.SHP", def.get(), context, error ) );
4872+
QVERIFY( !provider.isSupportedOutputValue( "ogr:d:/test.shp", def.get(), context, error ) );
4873+
QVERIFY( !provider.isSupportedOutputValue( QgsProcessingOutputLayerDefinition( "d:/test.SHP" ), def.get(), context, error ) );
4874+
QVERIFY( provider.isSupportedOutputValue( "d:/test.mif", def.get(), context, error ) );
4875+
QVERIFY( provider.isSupportedOutputValue( "d:/test.MIF", def.get(), context, error ) );
4876+
QVERIFY( provider.isSupportedOutputValue( "ogr:d:/test.MIF", def.get(), context, error ) );
4877+
QVERIFY( provider.isSupportedOutputValue( QgsProcessingOutputLayerDefinition( "d:/test.MIF" ), def.get(), context, error ) );
4878+
48624879
provider.loadAlgorithms();
48634880
def->mOriginalProvider = &provider;
48644881
QCOMPARE( def->supportedOutputVectorLayerExtensions().count(), 2 );
@@ -4967,6 +4984,15 @@ void TestQgsProcessing::parameterRasterOut()
49674984
QCOMPARE( fromCode->flags(), def->flags() );
49684985
QCOMPARE( fromCode->defaultValue(), def->defaultValue() );
49694986

4987+
DummyProvider3 provider;
4988+
QString error;
4989+
QVERIFY( !provider.isSupportedOutputValue( "d:/test.tif", def.get(), context, error ) );
4990+
QVERIFY( !provider.isSupportedOutputValue( "d:/test.TIF", def.get(), context, error ) );
4991+
QVERIFY( !provider.isSupportedOutputValue( QgsProcessingOutputLayerDefinition( "d:/test.tif" ), def.get(), context, error ) );
4992+
QVERIFY( provider.isSupportedOutputValue( "d:/test.mig", def.get(), context, error ) );
4993+
QVERIFY( provider.isSupportedOutputValue( "d:/test.MIG", def.get(), context, error ) );
4994+
QVERIFY( provider.isSupportedOutputValue( QgsProcessingOutputLayerDefinition( "d:/test.MIG" ), def.get(), context, error ) );
4995+
49704996
// test layers to load on completion
49714997
def.reset( new QgsProcessingParameterRasterDestination( "x", QStringLiteral( "desc" ), QStringLiteral( "default.tif" ), true ) );
49724998
QgsProcessingOutputLayerDefinition fs = QgsProcessingOutputLayerDefinition( QStringLiteral( "test.tif" ) );

0 commit comments

Comments
 (0)
Please sign in to comment.