Skip to content

Commit

Permalink
New method QgsVectorLayerUtils::createUniqueValue
Browse files Browse the repository at this point in the history
Returns a new unique attribute value for a layer. For numeric
fields this is the max attribute value + 1, for strings we
attempt to create a unique value by append _1, _2, etc to either
a specified 'seed' value, or failing that to the end of the
value of that field from the first feature in the layer.
  • Loading branch information
nyalldawson authored and m-kuhn committed Nov 16, 2016
1 parent 4682eaf commit 747097d
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 0 deletions.
8 changes: 8 additions & 0 deletions python/core/qgsvectorlayerutils.sip
Expand Up @@ -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.
Expand Down
81 changes: 81 additions & 0 deletions src/core/qgsvectorlayerutils.cpp
Expand Up @@ -15,6 +15,7 @@

#include "qgsvectorlayerutils.h"
#include "qgsvectordataprovider.h"
#include <QRegularExpression>

bool QgsVectorLayerUtils::valueExists( const QgsVectorLayer* layer, int fieldIndex, const QVariant& value, const QgsFeatureIds& ignoreIds )
{
Expand Down Expand Up @@ -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 )
{
Expand Down
9 changes: 9 additions & 0 deletions src/core/qgsvectorlayerutils.h
Expand Up @@ -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.
Expand All @@ -45,6 +53,7 @@ class CORE_EXPORT QgsVectorLayerUtils
QgsFieldConstraints::ConstraintStrength strength = QgsFieldConstraints::ConstraintStrengthNotSet,
QgsFieldConstraints::ConstraintOrigin origin = QgsFieldConstraints::ConstraintOriginNotSet );


};

#endif // QGSVECTORLAYERUTILS_H
32 changes: 32 additions & 0 deletions tests/src/python/test_qgsvectorlayerutils.py
Expand Up @@ -178,6 +178,38 @@ def test_validate_attribute(self):
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()

0 comments on commit 747097d

Please sign in to comment.