Skip to content

Commit

Permalink
Add a base class implemention for QgsVectorDataProvider::changeFeatures
Browse files Browse the repository at this point in the history
Previously this method would only succeed for providers which
explicitly implement it. Now, providers which do not implement
changeFeatures but do support both ChangeAttributeValues
and ChangeGeometries capabilities will use a non-optimised
version of changeFeatures which calls changeAttributeValues
and changeGeometries in turn.

This makes QgsVectorDataProvider::changeFeatures easier to use
in scripts - instead of writing manual fallbacks for providers
which do not implement it you can instead safely call this
method regardless of the provider and it will succeed wherever
both the attributes/geometries can be changed.

Also add a provider unit test covering this.
  • Loading branch information
nyalldawson committed Dec 27, 2016
1 parent 901cd29 commit b256075
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 5 deletions.
10 changes: 7 additions & 3 deletions src/core/qgsvectordataprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,13 @@ bool QgsVectorDataProvider::changeGeometryValues( const QgsGeometryMap &geometry
bool QgsVectorDataProvider::changeFeatures( const QgsChangedAttributesMap &attr_map,
const QgsGeometryMap &geometry_map )
{
Q_UNUSED( attr_map );
Q_UNUSED( geometry_map );
return false;
if ( !( capabilities() & ChangeAttributeValues ) || !( capabilities() & ChangeGeometries ) )
return false;

bool result = true;
result = result && changeAttributeValues( attr_map );
result = result && changeGeometryValues( geometry_map );
return result;
}

bool QgsVectorDataProvider::createSpatialIndex()
Expand Down
8 changes: 6 additions & 2 deletions src/core/qgsvectordataprovider.h
Original file line number Diff line number Diff line change
Expand Up @@ -274,14 +274,18 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider
virtual bool renameAttributes( const QgsFieldNameMap& renamedAttributes );

/**
* Changes attribute values of existing features.
* Changes attribute values of existing features. This should
* succeed if the provider reports the ChangeAttributeValues capability.
* @param attr_map a map containing changed attributes
* @return true in case of success and false in case of failure
*/
virtual bool changeAttributeValues( const QgsChangedAttributesMap &attr_map );

/**
* Changes attribute values and geometries of existing features.
* Changes attribute values and geometries of existing features. This should
* succeed if the provider reports both the ChangeAttributeValues and
* ChangeGeometries capabilities. Providers which report the ChangeFeatures
* capability implement an optimised version of this method.
* @param attr_map a map containing changed attributes
* @param geometry_map A QgsGeometryMap whose index contains the feature IDs
* that will have their geometries changed.
Expand Down
48 changes: 48 additions & 0 deletions tests/src/python/providertestbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -802,3 +802,51 @@ def testChangeGeometries(self):
# expect fail
self.assertFalse(l.dataProvider().changeGeometryValues(changes),
'Provider reported no ChangeGeometries capability, but returned true to changeGeometryValues')

def testChangeFeatures(self):
if not getattr(self, 'getEditableLayer', None):
return

l = self.getEditableLayer()
self.assertTrue(l.isValid())

features = [f for f in l.dataProvider().getFeatures()]

# find 2 features to change attributes for
features = [f for f in l.dataProvider().getFeatures()]
# need to keep order here
to_change = [f for f in features if f.attributes()[0] == 1]
to_change.extend([f for f in features if f.attributes()[0] == 2])
# changes by feature id, for changeAttributeValues call
attribute_changes = {to_change[0].id(): {1: 501, 3: 'new string'}, to_change[1].id(): {1: 502, 4: 'NEW'}}
# changes by pk, for testing after retrieving changed features
new_attr_map = {1: {1: 501, 3: 'new string'}, 2: {1: 502, 4: 'NEW'}}

# find 2 features to change geometries for
to_change = [f for f in features if f.attributes()[0] == 1]
to_change.extend([f for f in features if f.attributes()[0] == 3])
# changes by feature id, for changeGeometryValues call
geometry_changes = {to_change[0].id(): QgsGeometry.fromWkt('Point (10 20)'), to_change[1].id(): QgsGeometry()}
# changes by pk, for testing after retrieving changed features
new_geom_map = {1: QgsGeometry.fromWkt('Point ( 10 20 )'), 3: QgsGeometry()}

if l.dataProvider().capabilities() & QgsVectorDataProvider.ChangeGeometries and l.dataProvider().capabilities() & QgsVectorDataProvider.ChangeAttributeValues:
# expect success
result = l.dataProvider().changeFeatures(attribute_changes, geometry_changes)
self.assertTrue(result,
'Provider reported ChangeGeometries and ChangeAttributeValues capability, but returned False to changeFeatures')

# check result
self.testGetFeatures(l.dataProvider(), changed_attributes=new_attr_map, changed_geometries=new_geom_map)

# change empty list, should return true for consistency
self.assertTrue(l.dataProvider().changeFeatures({}, {}))

elif not l.dataProvider().capabilities() & QgsVectorDataProvider.ChangeGeometries:
# expect fail
self.assertFalse(l.dataProvider().changeFeatures(attribute_changes, geometry_changes),
'Provider reported no ChangeGeometries capability, but returned true to changeFeatures')
elif not l.dataProvider().capabilities() & QgsVectorDataProvider.ChangeAttributeValues:
# expect fail
self.assertFalse(l.dataProvider().changeFeatures(attribute_changes, geometry_changes),
'Provider reported no ChangeAttributeValues capability, but returned true to changeFeatures')

0 comments on commit b256075

Please sign in to comment.