Update all attributes in a single transaction

Fixes #17869
This commit is contained in:
Blottiere Paul 2018-01-23 13:07:35 +00:00
parent 77163ba0ec
commit c1fac42518
16 changed files with 350 additions and 1 deletions

View File

@ -1502,6 +1502,50 @@ Returns true if the feature's attribute was successfully changed.
.. seealso:: :py:func:`changeGeometry`
.. seealso:: :py:func:`updateFeature`
%End
bool changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues = QgsAttributeMap(), bool skeipDefaultValues = false );
%Docstring
Changes attributes' values for a feature (but does not immediately
commit the changes).
The ``fid`` argument specifies the ID of the feature to be changed.
The new values to be assigned to the fields are given by ``newValues``.
If a valid QVariant is specified for a field in ``oldValues``, it will be
used as the field value in the case of an undo operation corresponding
to this attribute value change. If an invalid QVariant is used (the
default behavior), then the feature's current value will be
automatically retrieved and used. Note that this involves a feature
request to the underlying data provider, so it is more efficient to
explicitly pass an oldValue if it is already available.
If ``skipDefaultValue`` is set to true, default field values will not
be updated. This can be used to override default field value
expressions.
Returns true if feature's attributes was successfully changed.
.. note::
Calls to changeAttributeValues() are only valid for layers in
which edits have been enabled by a call to startEditing(). Changes made
to features using this method are not committed to the underlying data
provider until a commitChanges() call is made. Any uncommitted changes
can be discarded by calling rollBack().
.. seealso:: :py:func:`startEditing`
.. seealso:: :py:func:`commitChanges`
.. seealso:: :py:func:`changeGeometry`
.. seealso:: :py:func:`updateFeature`
.. seealso:: :py:func:`changeAttributeValue`
.. versionadded:: 3.0
%End
bool addAttribute( const QgsField &field );

View File

@ -58,6 +58,15 @@ Change feature's geometry
virtual bool changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue = QVariant() );
%Docstring
Changed an attribute value (but does not commit it)
%End
virtual bool changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues );
%Docstring
Changes values of attributes (but does not commit it).
:return: true if attributes are well updated, false otherwise
.. versionadded:: 3.0
%End
virtual bool addAttribute( const QgsField &field );

View File

@ -30,6 +30,17 @@ class QgsVectorLayerEditPassthrough : QgsVectorLayerEditBuffer
virtual bool changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue = QVariant() );
virtual bool changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues );
%Docstring
Changes values of attributes (but does not commit it).
:return: true if attributes are well updated, false otherwise
.. versionadded:: 3.0
%End
virtual bool addAttribute( const QgsField &field );
virtual bool deleteAttribute( int attr );

View File

@ -185,6 +185,23 @@ created if its fields are not empty.
:return: false if an error happened, true otherwise
.. versionadded:: 3.0
%End
bool changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues = QgsAttributeMap() );
%Docstring
Changes attributes' values in joined layers. The feature id given in
parameter is the one added in target layer. If the corresponding joined
feature does not exist in a joined layer, then it's automatically
created if its fields are not empty.
:param fid: The feature id
:param newValues: The new values for attributes
:param oldValues: The old values for attributes
:return: false if an error happened, true otherwise
.. versionadded:: 3.0
%End

View File

@ -202,6 +202,37 @@ Constructor for QgsVectorLayerUndoPassthroughCommandChangeAttribute
};
class QgsVectorLayerUndoPassthroughCommandChangeAttributes: QgsVectorLayerUndoPassthroughCommand
{
%Docstring
Undo command for changing attributes' values from a vector layer in transaction group.
.. versionadded:: 3.0
%End
%TypeHeaderCode
#include "qgsvectorlayerundopassthroughcommand.h"
%End
public:
QgsVectorLayerUndoPassthroughCommandChangeAttributes( QgsVectorLayerEditBuffer *buffer /Transfer/, QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues = QgsAttributeMap() );
%Docstring
Constructor for QgsVectorLayerUndoPassthroughCommandChangeAttributes
:param buffer: associated edit buffer
:param fid: feature ID of feature
:param newValues: New values for attributes
:param oldValues: Old values for attributes
%End
virtual void undo();
virtual void redo();
};
class QgsVectorLayerUndoPassthroughCommandAddAttribute : QgsVectorLayerUndoPassthroughCommand
{
%Docstring

View File

@ -2387,6 +2387,64 @@ bool QgsVectorLayer::changeAttributeValue( QgsFeatureId fid, int field, const QV
return result;
}
bool QgsVectorLayer::changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues, bool skipDefaultValues )
{
bool result = true;
QgsAttributeMap newValuesJoin;
QgsAttributeMap oldValuesJoin;
QgsAttributeMap newValuesNotJoin;
QgsAttributeMap oldValuesNotJoin;
for ( auto it = newValues.constBegin(); it != newValues.constEnd(); ++it )
{
const int field = it.key();
const QVariant newValue = it.value();
QVariant oldValue;
if ( oldValues.contains( field ) )
oldValue = oldValues[field];
switch ( fields().fieldOrigin( field ) )
{
case QgsFields::OriginJoin:
newValuesJoin[field] = newValue;
oldValuesJoin[field] = oldValue;
break;
case QgsFields::OriginProvider:
case QgsFields::OriginEdit:
case QgsFields::OriginExpression:
{
newValuesNotJoin[field] = newValue;
oldValuesNotJoin[field] = oldValue;
break;
}
case QgsFields::OriginUnknown:
break;
}
}
if ( ! newValuesJoin.isEmpty() && mJoinBuffer )
{
result = mJoinBuffer->changeAttributeValues( fid, newValuesJoin, oldValuesJoin );
}
if ( ! newValuesNotJoin.isEmpty() && mEditBuffer && mDataProvider )
{
result &= mEditBuffer->changeAttributeValues( fid, newValues, oldValues );
}
if ( result && !skipDefaultValues && !mDefaultValueOnUpdateFields.isEmpty() )
{
updateDefaultValues( fid );
}
return result;
}
bool QgsVectorLayer::addAttribute( const QgsField &field )
{
if ( !mEditBuffer || !mDataProvider )

View File

@ -1395,6 +1395,43 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
*/
bool changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue = QVariant(), bool skipDefaultValues = false );
/**
* Changes attributes' values for a feature (but does not immediately
* commit the changes).
* The \a fid argument specifies the ID of the feature to be changed.
*
* The new values to be assigned to the fields are given by \a newValues.
*
* If a valid QVariant is specified for a field in \a oldValues, it will be
* used as the field value in the case of an undo operation corresponding
* to this attribute value change. If an invalid QVariant is used (the
* default behavior), then the feature's current value will be
* automatically retrieved and used. Note that this involves a feature
* request to the underlying data provider, so it is more efficient to
* explicitly pass an oldValue if it is already available.
*
* If \a skipDefaultValue is set to true, default field values will not
* be updated. This can be used to override default field value
* expressions.
*
* Returns true if feature's attributes was successfully changed.
*
* \note Calls to changeAttributeValues() are only valid for layers in
* which edits have been enabled by a call to startEditing(). Changes made
* to features using this method are not committed to the underlying data
* provider until a commitChanges() call is made. Any uncommitted changes
* can be discarded by calling rollBack().
*
* \see startEditing()
* \see commitChanges()
* \see changeGeometry()
* \see updateFeature()
* \see changeAttributeValue()
*
* \since QGIS 3.0
*/
bool changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues = QgsAttributeMap(), bool skeipDefaultValues = false );
/**
* Add an attribute field (but does not commit it)
* returns true if the field was added
@ -2376,6 +2413,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
//! stores information about uncommitted changes to layer
QgsVectorLayerEditBuffer *mEditBuffer = nullptr;
friend class QgsVectorLayerEditBuffer;
friend class QgsVectorLayerEditPassthrough;
//stores information about joined layers
QgsVectorLayerJoinBuffer *mJoinBuffer = nullptr;

View File

@ -208,6 +208,23 @@ bool QgsVectorLayerEditBuffer::changeGeometry( QgsFeatureId fid, const QgsGeomet
return true;
}
bool QgsVectorLayerEditBuffer::changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues )
{
bool success = true;
for ( auto it = newValues.constBegin() ; it != newValues.constEnd(); ++it )
{
const int field = it.key();
const QVariant newValue = it.value();
QVariant oldValue;
if ( oldValues.contains( field ) )
oldValue = oldValues[field];
success &= changeAttributeValue( fid, field, newValue, oldValue );
}
return success;
}
bool QgsVectorLayerEditBuffer::changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue )
{

View File

@ -65,6 +65,13 @@ class CORE_EXPORT QgsVectorLayerEditBuffer : public QObject
//! Changed an attribute value (but does not commit it)
virtual bool changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue = QVariant() );
/**
* Changes values of attributes (but does not commit it).
* \returns true if attributes are well updated, false otherwise
* \since QGIS 3.0
*/
virtual bool changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues );
/**
* Add an attribute field (but does not commit it)
returns true if the field was added */
@ -265,6 +272,7 @@ class CORE_EXPORT QgsVectorLayerEditBuffer : public QObject
friend class QgsVectorLayerUndoPassthroughCommandDeleteFeatures;
friend class QgsVectorLayerUndoPassthroughCommandChangeGeometry;
friend class QgsVectorLayerUndoPassthroughCommandChangeAttribute;
friend class QgsVectorLayerUndoPassthroughCommandChangeAttributes;
friend class QgsVectorLayerUndoPassthroughCommandAddAttribute;
friend class QgsVectorLayerUndoPassthroughCommandDeleteAttribute;
friend class QgsVectorLayerUndoPassthroughCommandRenameAttribute;

View File

@ -86,6 +86,11 @@ bool QgsVectorLayerEditPassthrough::changeAttributeValue( QgsFeatureId fid, int
return modify( new QgsVectorLayerUndoPassthroughCommandChangeAttribute( this, fid, field, newValue ) );
}
bool QgsVectorLayerEditPassthrough::changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues )
{
return modify( new QgsVectorLayerUndoPassthroughCommandChangeAttributes( this, fid, newValues, oldValues ) );
}
bool QgsVectorLayerEditPassthrough::addAttribute( const QgsField &field )
{
return modify( new QgsVectorLayerUndoPassthroughCommandAddAttribute( this, field ) );

View File

@ -38,6 +38,14 @@ class CORE_EXPORT QgsVectorLayerEditPassthrough : public QgsVectorLayerEditBuffe
bool deleteFeatures( const QgsFeatureIds &fids ) override;
bool changeGeometry( QgsFeatureId fid, const QgsGeometry &geom ) override;
bool changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue = QVariant() ) override;
/**
* Changes values of attributes (but does not commit it).
* \returns true if attributes are well updated, false otherwise
* \since QGIS 3.0
*/
bool changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues ) override;
bool addAttribute( const QgsField &field ) override;
bool deleteAttribute( int attr ) override;
bool renameAttribute( int attr, const QString &newName ) override;

View File

@ -637,6 +637,25 @@ bool QgsVectorLayerJoinBuffer::changeAttributeValue( QgsFeatureId fid, int field
return false;
}
bool QgsVectorLayerJoinBuffer::changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues )
{
bool success = true;
for ( auto it = newValues.constBegin(); it != newValues.constEnd(); ++it )
{
const int field = it.key();
const QVariant newValue = it.value();
QVariant oldValue;
if ( oldValues.contains( field ) )
oldValue = oldValues[field];
success &= changeAttributeValue( fid, field, newValue, oldValue );
}
return success;
}
bool QgsVectorLayerJoinBuffer::deleteFeature( QgsFeatureId fid ) const
{
return deleteFeatures( QgsFeatureIds() << fid );

View File

@ -171,6 +171,22 @@ class CORE_EXPORT QgsVectorLayerJoinBuffer : public QObject, public QgsFeatureSi
*/
bool changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue = QVariant() );
/**
* Changes attributes' values in joined layers. The feature id given in
* parameter is the one added in target layer. If the corresponding joined
* feature does not exist in a joined layer, then it's automatically
* created if its fields are not empty.
*
* \param fid The feature id
* \param newValues The new values for attributes
* \param oldValues The old values for attributes
*
* \returns false if an error happened, true otherwise
*
* \since QGIS 3.0
*/
bool changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues = QgsAttributeMap() );
/**
* Deletes a feature from joined layers. The feature id given in
* parameter is the one coming from the target layer.

View File

@ -403,3 +403,35 @@ void QgsVectorLayerUndoPassthroughCommandUpdate::redo()
}
}
}
QgsVectorLayerUndoPassthroughCommandChangeAttributes::QgsVectorLayerUndoPassthroughCommandChangeAttributes( QgsVectorLayerEditBuffer *buffer, QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues )
: QgsVectorLayerUndoPassthroughCommand( buffer, QObject::tr( "change attribute value" ) )
, mFid( fid )
, mNewValues( newValues )
, mOldValues( oldValues )
{
}
void QgsVectorLayerUndoPassthroughCommandChangeAttributes::undo()
{
if ( rollBackToSavePoint() )
{
for ( auto it = mNewValues.constBegin(); it != mNewValues.constEnd(); ++it )
{
emit mBuffer->attributeValueChanged( mFid, it.key(), it.value() );
}
}
}
void QgsVectorLayerUndoPassthroughCommandChangeAttributes::redo()
{
QgsChangedAttributesMap attribMap;
attribMap.insert( mFid, mNewValues );
if ( setSavePoint() && mBuffer->L->dataProvider()->changeAttributeValues( attribMap ) )
{
for ( auto it = mNewValues.constBegin(); it != mNewValues.constEnd(); ++it )
{
emit mBuffer->attributeValueChanged( mFid, it.key(), it.value() );
}
}
}

View File

@ -203,6 +203,35 @@ class CORE_EXPORT QgsVectorLayerUndoPassthroughCommandChangeAttribute: public Qg
const QVariant mOldValue;
};
/**
* \ingroup core
* \class QgsVectorLayerUndoPassthroughCommandChangeAttributes
* \brief Undo command for changing attributes' values from a vector layer in transaction group.
* \since QGIS 3.0
*/
class CORE_EXPORT QgsVectorLayerUndoPassthroughCommandChangeAttributes: public QgsVectorLayerUndoPassthroughCommand
{
public:
/**
* Constructor for QgsVectorLayerUndoPassthroughCommandChangeAttributes
* \param buffer associated edit buffer
* \param fid feature ID of feature
* \param newValues New values for attributes
* \param oldValues Old values for attributes
*/
QgsVectorLayerUndoPassthroughCommandChangeAttributes( QgsVectorLayerEditBuffer *buffer SIP_TRANSFER, QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues = QgsAttributeMap() );
void undo() override;
void redo() override;
private:
QgsFeatureId mFid;
const QgsAttributeMap mNewValues;
const QgsAttributeMap mOldValues;
};
/**
* \ingroup core
* \class QgsVectorLayerUndoPassthroughCommandAddAttribute

View File

@ -330,6 +330,9 @@ bool QgsAttributeForm::saveEdits()
{
mLayer->beginEditCommand( mEditCommandMessage );
QgsAttributeMap newValues;
QgsAttributeMap oldValues;
int n = 0;
for ( int i = 0; i < dst.count(); ++i )
{
@ -346,10 +349,14 @@ bool QgsAttributeForm::saveEdits()
QgsDebugMsg( QString( "src:'%1' (type:%2, isNull:%3, isValid:%4)" )
.arg( src.at( i ).toString(), src.at( i ).typeName() ).arg( src.at( i ).isNull() ).arg( src.at( i ).isValid() ) );
success &= mLayer->changeAttributeValue( mFeature.id(), i, dst.at( i ), src.at( i ) );
newValues[i] = dst.at( i );
oldValues[i] = src.at( i );
n++;
}
success = mLayer->changeAttributeValues( mFeature.id(), newValues, oldValues );
if ( success && n > 0 )
{
mLayer->endEditCommand();