Skip to content

Commit

Permalink
Add support for attribute renaming to OGR provider
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Jun 2, 2016
1 parent 58cc890 commit 251474a
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 12 deletions.
47 changes: 47 additions & 0 deletions src/providers/ogr/qgsogrprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1315,6 +1315,48 @@ bool QgsOgrProvider::deleteAttributes( const QgsAttributeIds &attributes )
#endif
}

bool QgsOgrProvider::renameAttributes( const QgsFieldNameMap& renamedAttributes )
{
if ( !doInitialActionsForEdition() )
return false;

#if defined(GDAL_VERSION_NUM) && GDAL_VERSION_NUM >= 1900
QgsFieldNameMap::const_iterator renameIt = renamedAttributes.constBegin();
bool result = true;
for ( ; renameIt != renamedAttributes.constEnd(); ++renameIt )
{
int fieldIndex = renameIt.key();
if ( fieldIndex < 0 || fieldIndex >= mAttributeFields.count() )
{
pushError( tr( "Invalid attribute index" ) );
result = false;
continue;
}
if ( mAttributeFields.indexFromName( renameIt.value() ) >= 0 )
{
//field name already in use
pushError( tr( "Error renaming field %1: name '%2' already exists" ).arg( fieldIndex ).arg( renameIt.value() ) );
result = false;
continue;
}

//type does not matter, it will not be used
OGRFieldDefnH fld = OGR_Fld_Create( mEncoding->fromUnicode( renameIt.value() ), OFTReal );
if ( OGR_L_AlterFieldDefn( ogrLayer, fieldIndex, fld, ALTER_NAME_FLAG ) != OGRERR_NONE )
{
pushError( tr( "OGR error renaming field %1: %2" ).arg( fieldIndex ).arg( CPLGetLastErrorMsg() ) );
result = false;
}
}
loadFields();
return result;
#else
Q_UNUSED( attributes );
pushError( tr( "Renaming fields is not supported prior to GDAL 1.9.0" ) );
return false;
#endif
}


bool QgsOgrProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_map )
{
Expand Down Expand Up @@ -1724,6 +1766,11 @@ void QgsOgrProvider::computeCapabilities()
ability |= DeleteAttributes;
}

if ( mWriteAccessPossible && OGR_L_TestCapability( ogrLayer, "AlterFieldDefn" ) )
{
ability |= RenameAttributes;
}

#if defined(OLCStringsAsUTF8)
if ( !OGR_L_TestCapability( ogrLayer, OLCStringsAsUTF8 ) )
{
Expand Down
12 changes: 1 addition & 11 deletions src/providers/ogr/qgsogrprovider.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,19 +137,9 @@ class QgsOgrProvider : public QgsVectorDataProvider
/** Deletes a feature*/
virtual bool deleteFeatures( const QgsFeatureIds & id ) override;

/**
* Adds new attributes
* @param attributes list of new attributes
* @return true in case of success and false in case of failure
*/
virtual bool addAttributes( const QList<QgsField> &attributes ) override;

/**
* Deletes existing attributes
* @param attributes a set containing names of attributes
* @return true in case of success and false in case of failure
*/
virtual bool deleteAttributes( const QgsAttributeIds &attributes ) override;
virtual bool renameAttributes( const QgsFieldNameMap& renamedAttributes ) override;

/** Changes attribute values of existing features */
virtual bool changeAttributeValues( const QgsChangedAttributesMap &attr_map ) override;
Expand Down
46 changes: 46 additions & 0 deletions tests/src/python/test_provider_shapefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,5 +294,51 @@ def testreloadData(self):
# And now check that fields are up-to-date
self.assertEquals(len(vl1.fields()), len(vl2.fields()))

def testRenameAttributes(self):
''' Test renameAttributes() '''

tmpdir = tempfile.mkdtemp()
self.dirs_to_cleanup.append(tmpdir)
srcpath = os.path.join(TEST_DATA_DIR, 'provider')
for file in glob.glob(os.path.join(srcpath, 'shapefile.*')):
shutil.copy(os.path.join(srcpath, file), tmpdir)
datasource = os.path.join(tmpdir, 'shapefile.shp')

vl = QgsVectorLayer(u'{}|layerid=0'.format(datasource), u'test', u'ogr')
provider = vl.dataProvider()

# bad rename
self.assertFalse(provider.renameAttributes({-1: 'not_a_field'}))
self.assertFalse(provider.renameAttributes({100: 'not_a_field'}))
# already exists
self.assertFalse(provider.renameAttributes({2: 'cnt'}))

# rename one field
self.assertTrue(provider.renameAttributes({2: 'newname'}))
self.assertEqual(provider.fields().at(2).name(), 'newname')
vl.updateFields()
fet = next(vl.getFeatures())
self.assertEqual(fet.fields()[2].name(), 'newname')

# rename two fields
self.assertTrue(provider.renameAttributes({2: 'newname2', 3: 'another'}))
self.assertEqual(provider.fields().at(2).name(), 'newname2')
self.assertEqual(provider.fields().at(3).name(), 'another')
vl.updateFields()
fet = next(vl.getFeatures())
self.assertEqual(fet.fields()[2].name(), 'newname2')
self.assertEqual(fet.fields()[3].name(), 'another')

# close file and reopen, then recheck to confirm that changes were saved to file
del vl
vl = None
vl = QgsVectorLayer(u'{}|layerid=0'.format(datasource), u'test', u'ogr')
provider = vl.dataProvider()
self.assertEqual(provider.fields().at(2).name(), 'newname2')
self.assertEqual(provider.fields().at(3).name(), 'another')
fet = next(vl.getFeatures())
self.assertEqual(fet.fields()[2].name(), 'newname2')
self.assertEqual(fet.fields()[3].name(), 'another')

if __name__ == '__main__':
unittest.main()
18 changes: 17 additions & 1 deletion tests/src/python/test_provider_tabfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
__revision__ = '$Format:%H$'

import os

import tempfile
from qgis.core import QgsVectorLayer, QgsFeatureRequest, QgsVectorDataProvider
from qgis.PyQt.QtCore import QDate, QTime, QDateTime, QVariant
from qgis.testing import (
Expand All @@ -22,6 +22,9 @@
)
import osgeo.gdal
from utilities import unitTestDataPath
import tempfile
import shutil
import glob

start_app()
TEST_DATA_DIR = unitTestDataPath()
Expand All @@ -31,6 +34,18 @@

class TestPyQgsTabfileProvider(unittest.TestCase):

@classmethod
def setUpClass(cls):
"""Run before all tests"""
cls.basetestpath = tempfile.mkdtemp()
cls.dirs_to_cleanup = [cls.basetestpath]

@classmethod
def tearDownClass(cls):
"""Run after all tests"""
for dirname in cls.dirs_to_cleanup:
shutil.rmtree(dirname, True)

def testDateTimeFormats(self):
# check that date and time formats are correctly interpreted
basetestfile = os.path.join(TEST_DATA_DIR, 'tab_file.tab')
Expand Down Expand Up @@ -75,5 +90,6 @@ def testUpdateMode(self):
self.assertEquals(vl.dataProvider().property("_debug_open_mode"), "read-only")
self.assertTrue(vl.dataProvider().isValid())


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

0 comments on commit 251474a

Please sign in to comment.