diff --git a/python/core/qgsvectorlayerutils.sip b/python/core/qgsvectorlayerutils.sip index f5a9e5da674..0c61f30a46f 100644 --- a/python/core/qgsvectorlayerutils.sip +++ b/python/core/qgsvectorlayerutils.sip @@ -16,9 +16,17 @@ class QgsVectorLayerUtils * Returns true if the specified value already exists within a field. This method can be used to test for uniqueness * of values inside a layer's attributes. An optional list of ignored feature IDs can be provided, if so, any features * with IDs within this list are ignored when testing for existance of the value. + * @see createUniqueValue() */ static bool valueExists( const QgsVectorLayer* layer, int fieldIndex, const QVariant& value, const QgsFeatureIds& ignoreIds = QgsFeatureIds() ); + /** + * Returns a new attribute value for the specified field index which is guaranteed to be unique. The optional seed + * value can be used as a basis for generated values. + * @see valueExists() + */ + static QVariant createUniqueValue( const QgsVectorLayer* layer, int fieldIndex, const QVariant& seed = QVariant() ); + /** * Tests an attribute value to check whether it passes all constraints which are present on the corresponding field. * Returns true if the attribute value is valid for the field. Any constraint failures will be reported in the errors argument. diff --git a/src/core/qgsvectorlayerutils.cpp b/src/core/qgsvectorlayerutils.cpp index 0ae2ab27e1c..d5845e97eae 100644 --- a/src/core/qgsvectorlayerutils.cpp +++ b/src/core/qgsvectorlayerutils.cpp @@ -15,6 +15,7 @@ #include "qgsvectorlayerutils.h" #include "qgsvectordataprovider.h" +#include bool QgsVectorLayerUtils::valueExists( const QgsVectorLayer* layer, int fieldIndex, const QVariant& value, const QgsFeatureIds& ignoreIds ) { @@ -65,6 +66,86 @@ bool QgsVectorLayerUtils::valueExists( const QgsVectorLayer* layer, int fieldInd return false; } +QVariant QgsVectorLayerUtils::createUniqueValue( const QgsVectorLayer* layer, int fieldIndex, const QVariant& seed ) +{ + if ( !layer ) + return QVariant(); + + QgsFields fields = layer->fields(); + + if ( fieldIndex < 0 || fieldIndex >= fields.count() ) + return QVariant(); + + QgsField field = fields.at( fieldIndex ); + + if ( field.isNumeric() ) + { + QVariant maxVal = layer->maximumValue( fieldIndex ); + QVariant newVar( maxVal.toLongLong() + 1 ); + if ( field.convertCompatible( newVar ) ) + return newVar; + else + return QVariant(); + } + else + { + switch ( field.type() ) + { + case QVariant::String: + { + QString base; + if ( seed.isValid() ) + base = seed.toString(); + + if ( !base.isEmpty() ) + { + // strip any existing _1, _2 from the seed + QRegularExpression rx( "(.*)_\\d+" ); + QRegularExpressionMatch match = rx.match( base ); + if ( match.hasMatch() ) + { + base = match.captured( 1 ); + } + } + else + { + // no base seed - fetch first value from layer + QgsFeatureRequest req; + req.setLimit( 1 ); + req.setSubsetOfAttributes( QgsAttributeList() << fieldIndex ); + req.setFlags( QgsFeatureRequest::NoGeometry ); + QgsFeature f; + layer->getFeatures( req ).nextFeature( f ); + base = f.attribute( fieldIndex ).toString(); + } + + // try variants like base_1, base_2, etc until a new value found + QStringList vals = layer->uniqueStringsMatching( fieldIndex, base ); + + // might already be unique + if ( !base.isEmpty() && !vals.contains( base ) ) + return base; + + for ( int i = 1; i < 10000; ++i ) + { + QString testVal = base + '_' + QString::number( i ); + if ( !vals.contains( testVal ) ) + return testVal; + } + + // failed + return QVariant(); + } + + default: + // todo other types - dates? times? + return QVariant(); + } + } + + return QVariant(); +} + bool QgsVectorLayerUtils::validateAttribute( const QgsVectorLayer* layer, const QgsFeature& feature, int attributeIndex, QStringList& errors, QgsFieldConstraints::ConstraintStrength strength, QgsFieldConstraints::ConstraintOrigin origin ) { diff --git a/src/core/qgsvectorlayerutils.h b/src/core/qgsvectorlayerutils.h index 5d2ae5984f6..9235de34042 100644 --- a/src/core/qgsvectorlayerutils.h +++ b/src/core/qgsvectorlayerutils.h @@ -33,9 +33,17 @@ class CORE_EXPORT QgsVectorLayerUtils * Returns true if the specified value already exists within a field. This method can be used to test for uniqueness * of values inside a layer's attributes. An optional list of ignored feature IDs can be provided, if so, any features * with IDs within this list are ignored when testing for existance of the value. + * @see createUniqueValue() */ static bool valueExists( const QgsVectorLayer* layer, int fieldIndex, const QVariant& value, const QgsFeatureIds& ignoreIds = QgsFeatureIds() ); + /** + * Returns a new attribute value for the specified field index which is guaranteed to be unique. The optional seed + * value can be used as a basis for generated values. + * @see valueExists() + */ + static QVariant createUniqueValue( const QgsVectorLayer* layer, int fieldIndex, const QVariant& seed = QVariant() ); + /** * Tests an attribute value to check whether it passes all constraints which are present on the corresponding field. * Returns true if the attribute value is valid for the field. Any constraint failures will be reported in the errors argument. @@ -45,6 +53,7 @@ class CORE_EXPORT QgsVectorLayerUtils QgsFieldConstraints::ConstraintStrength strength = QgsFieldConstraints::ConstraintStrengthNotSet, QgsFieldConstraints::ConstraintOrigin origin = QgsFieldConstraints::ConstraintOriginNotSet ); + }; #endif // QGSVECTORLAYERUTILS_H diff --git a/tests/src/python/test_qgsvectorlayerutils.py b/tests/src/python/test_qgsvectorlayerutils.py index 787bbc5e646..c94154c0a23 100644 --- a/tests/src/python/test_qgsvectorlayerutils.py +++ b/tests/src/python/test_qgsvectorlayerutils.py @@ -178,6 +178,38 @@ class TestQgsVectorLayerUtils(unittest.TestCase): self.assertEqual(len(errors), 2) print(errors) + def testCreateUniqueValue(self): + """ test creating a unique value """ + layer = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer&field=flddbl:double", + "addfeat", "memory") + # add a bunch of features + f = QgsFeature() + f.setAttributes(["test", 123, 1.0]) + f1 = QgsFeature(2) + f1.setAttributes(["test_1", 124, 1.1]) + f2 = QgsFeature(3) + f2.setAttributes(["test_2", 125, 2.4]) + f3 = QgsFeature(4) + f3.setAttributes(["test_3", 126, 1.7]) + f4 = QgsFeature(5) + f4.setAttributes(["superpig", 127, 0.8]) + self.assertTrue(layer.dataProvider().addFeatures([f, f1, f2, f3, f4])) + + # bad field indices + self.assertFalse(QgsVectorLayerUtils.createUniqueValue(layer, -10)) + self.assertFalse(QgsVectorLayerUtils.createUniqueValue(layer, 10)) + + # integer field + self.assertEqual(QgsVectorLayerUtils.createUniqueValue(layer, 1), 128) + + # double field + self.assertEqual(QgsVectorLayerUtils.createUniqueValue(layer, 2), 3.0) + + # string field + self.assertEqual(QgsVectorLayerUtils.createUniqueValue(layer, 0), 'test_4') + self.assertEqual(QgsVectorLayerUtils.createUniqueValue(layer, 0, 'test_1'), 'test_4') + self.assertEqual(QgsVectorLayerUtils.createUniqueValue(layer, 0, 'seed'), 'seed') + self.assertEqual(QgsVectorLayerUtils.createUniqueValue(layer, 0, 'superpig'), 'superpig_1') if __name__ == '__main__': unittest.main()