Skip to content
Permalink
Browse files

Merge pull request #36285 from signedav/delete_cascade

Delete cascade on composition relations
  • Loading branch information
m-kuhn committed May 14, 2020
2 parents d090236 + b934830 commit 262568e6fbba6f1bc022d44cbe0ec35a38780a7a
@@ -374,6 +374,21 @@ Constructor for LayerOptions.

};

struct DeleteContext
{

explicit DeleteContext( bool cascade = false, QgsProject *project = 0 );
%Docstring
Constructor for DeleteContext.
%End

QList<QgsVectorLayer *> handledLayers() const;
QgsFeatureIds handledFeatures( QgsVectorLayer *layer ) const;

bool cascade;
QgsProject *project;
};

explicit QgsVectorLayer( const QString &path = QString(), const QString &baseName = QString(),
const QString &providerLib = "ogr", const QgsVectorLayer::LayerOptions &options = QgsVectorLayer::LayerOptions() );
%Docstring
@@ -1265,10 +1280,13 @@ Deletes a vertex from a feature.
.. versionadded:: 2.14
%End

bool deleteSelectedFeatures( int *deletedCount = 0 );
bool deleteSelectedFeatures( int *deletedCount = 0, DeleteContext *context = 0 );
%Docstring
Deletes the selected features

:param deletedCount: The number of successfully deleted features
:param context: The chain of features who will be deleted for feedback and to avoid endless recursions

:return: ``True`` in case of success and ``False`` otherwise
%End

@@ -1947,10 +1965,13 @@ Deletes a list of attribute fields (but does not commit it)
virtual bool addFeatures( QgsFeatureList &features, QgsFeatureSink::Flags flags = 0 ) ${SIP_FINAL};


bool deleteFeature( QgsFeatureId fid );
bool deleteFeature( QgsFeatureId fid, DeleteContext *context = 0 );
%Docstring
Deletes a feature from the layer (but does not commit it).

:param fid: The feature id to delete
:param context: The chain of features who will be deleted for feedback and to avoid endless recursions

.. note::

Calls to deleteFeature() are only valid for layers in which edits have been enabled
@@ -1959,11 +1980,12 @@ Deletes a feature from the layer (but does not commit it).
changes can be discarded by calling rollBack().
%End

bool deleteFeatures( const QgsFeatureIds &fids );
bool deleteFeatures( const QgsFeatureIds &fids, DeleteContext *context = 0 );
%Docstring
Deletes a set of features from the layer (but does not commit it)

:param fids: The feature ids to delete
:param context: The chain of features who will be deleted for feedback and to avoid endless recursions

:return: ``False`` if the layer is not in edit mode or does not support deleting
in case of an active transaction depends on the provider implementation
@@ -207,25 +207,27 @@ created if its fields are not empty.
.. versionadded:: 3.0
%End

bool deleteFeature( QgsFeatureId fid ) const;
bool deleteFeature( QgsFeatureId fid, QgsVectorLayer::DeleteContext *context = 0 ) const;
%Docstring
Deletes a feature from joined layers. The feature id given in
parameter is the one coming from the target layer.

:param fid: The feature id from the target layer to delete
:param context: The chain of features which will be deleted for feedback and to avoid infinite recursions. Can be ``None``.

:return: ``False`` if an error happened, ``True`` otherwise


.. versionadded:: 3.0
%End

bool deleteFeatures( const QgsFeatureIds &fids ) const;
bool deleteFeatures( const QgsFeatureIds &fids, QgsVectorLayer::DeleteContext *context = 0 ) const;
%Docstring
Deletes a list of features from joined layers. Feature ids given
in a parameter are those coming from the target layer.

:param fids: Feature ids from the target layer to delete
:param context: The chain of features who will be deleted for feedback and to avoid infinite recursions. Can be ``None``.

:return: ``False`` if an error happened, ``True`` otherwise

@@ -296,6 +296,17 @@ The following operations will be performed to convert the input features:

.. versionadded:: 3.12
%End

static bool impactsCascadeFeatures( const QgsVectorLayer *layer, const QgsFeatureIds &fids, const QgsProject *project, QgsDuplicateFeatureContext &context /Out/ );
%Docstring

:return: ``True`` if at least one feature of the ``fids`` on ``layer`` is connected as parent in at
least one composition relation of the ``project`` or contains joins, where cascade delete is set.
Details about cascading effects will be written to ``context``.

.. versionadded:: 3.14
%End

};


@@ -9014,17 +9014,52 @@ void QgisApp::deleteSelected( QgsMapLayer *layer, QWidget *parent, bool checkFea
}
}

QgsVectorLayerUtils::QgsDuplicateFeatureContext infoContext;
if ( QgsVectorLayerUtils::impactsCascadeFeatures( vlayer, vlayer->selectedFeatureIds(), QgsProject::instance(), infoContext ) )
{
QString childrenInfo;
int childrenCount = 0;
const auto infoContextLayers = infoContext.layers();
for ( QgsVectorLayer *chl : infoContextLayers )
{
childrenCount += infoContext.duplicatedFeatures( chl ).size();
childrenInfo += ( tr( "%1 feature(s) on layer \"%2\", " ).arg( infoContext.duplicatedFeatures( chl ).size() ).arg( chl->name() ) );
}

// for extra safety to make sure we know that the delete can have impact on children and joins
int res = QMessageBox::question( mMapCanvas, tr( "Delete at least %3 feature(s) on other layer(s)" ).arg( childrenCount ),
tr( "Delete %1 feature(s) on layer \"%2\" and %3as well.\nAnd all the further descendants of them.\nDelete these features?" ).arg( numberOfSelectedFeatures ).arg( vlayer->name() ).arg( childrenInfo ),
QMessageBox::Yes | QMessageBox::No );
if ( res != QMessageBox::Yes )
return;
}

vlayer->beginEditCommand( tr( "Features deleted" ) );
int deletedCount = 0;
if ( !vlayer->deleteSelectedFeatures( &deletedCount ) )
QgsVectorLayer::DeleteContext context( true, QgsProject::instance() );
if ( !vlayer->deleteSelectedFeatures( &deletedCount, &context ) )
{
visibleMessageBar()->pushMessage( tr( "Problem deleting features" ),
tr( "A problem occurred during deletion from layer \"%1\". %n feature(s) not deleted.", nullptr, numberOfSelectedFeatures - deletedCount ).arg( vlayer->name() ),
Qgis::Warning );
}
else
{
showStatusMessage( tr( "%n feature(s) deleted.", "number of features deleted", numberOfSelectedFeatures ) );
const auto contextLayers = context.handledLayers();
// if it affects more than one layer, print feedback for all descendants
if ( contextLayers.size() > 1 )
{
deletedCount = 0;
QString feedbackMessage;
for ( QgsVectorLayer *contextLayer : contextLayers )
{
feedbackMessage += tr( "%1 on layer %2. " ).arg( context.handledFeatures( contextLayer ).size() ).arg( contextLayer->name() );
deletedCount += context.handledFeatures( contextLayer ).size();
}
visibleMessageBar()->pushMessage( tr( "%1 features deleted: %2" ).arg( deletedCount ).arg( feedbackMessage ), Qgis::Success );
}

showStatusMessage( tr( "%n feature(s) deleted.", "number of features deleted", deletedCount ) );
}

vlayer->endEditCommand();
@@ -864,7 +864,42 @@ void QgsAttributeTableDialog::setFilterExpression( const QString &filterString,
void QgsAttributeTableDialog::deleteFeature( const QgsFeatureId fid )
{
QgsDebugMsg( QStringLiteral( "Delete %1" ).arg( fid ) );
mLayer->deleteFeature( fid );

QgsVectorLayerUtils::QgsDuplicateFeatureContext infoContext;
if ( QgsVectorLayerUtils::impactsCascadeFeatures( mLayer, QgsFeatureIds() << fid, QgsProject::instance(), infoContext ) )
{
QString childrenInfo;
int childrenCount = 0;
const auto infoContextLayers = infoContext.layers();
for ( QgsVectorLayer *chl : infoContextLayers )
{
childrenCount += infoContext.duplicatedFeatures( chl ).size();
childrenInfo += ( tr( "%1 feature(s) on layer \"%2\", " ).arg( infoContext.duplicatedFeatures( chl ).size() ).arg( chl->name() ) );
}

// for extra safety to make sure we know that the delete can have impact on children and joins
int res = QMessageBox::question( this, tr( "Delete at least %3 feature(s) on other layer(s)" ).arg( childrenCount ),
tr( "Delete of feature on layer \"%2\" and %3as well.\nAnd all the further descendants of them.\nDelete these features?" ).arg( mLayer->name() ).arg( childrenInfo ),
QMessageBox::Yes | QMessageBox::No );
if ( res != QMessageBox::Yes )
return;
}

QgsVectorLayer::DeleteContext context( true, QgsProject::instance() );
mLayer->deleteFeature( fid, &context );
const auto contextLayers = context.handledLayers();
//if it effected more than one layer, print feedback for all descendants
if ( contextLayers.size() > 1 )
{
int deletedCount = 0;
QString feedbackMessage;
for ( QgsVectorLayer *contextLayer : contextLayers )
{
feedbackMessage += tr( "%1 on layer %2. " ).arg( context.handledFeatures( contextLayer ).size() ).arg( contextLayer->name() );
deletedCount += context.handledFeatures( contextLayer ).size();
}
QgisApp::instance()->messageBar()->pushMessage( tr( "%1 features deleted: %2" ).arg( deletedCount ).arg( feedbackMessage ), Qgis::Success );
}
}

void QgsAttributeTableDialog::showContextMenu( QgsActionMenu *menu, const QgsFeatureId fid )
@@ -1158,7 +1158,7 @@ QgsVectorLayer::EditResult QgsVectorLayer::deleteVertex( QgsFeatureId featureId,
}


bool QgsVectorLayer::deleteSelectedFeatures( int *deletedCount )
bool QgsVectorLayer::deleteSelectedFeatures( int *deletedCount, QgsVectorLayer::DeleteContext *context )
{
if ( !mValid || !mDataProvider || !( mDataProvider->capabilities() & QgsVectorDataProvider::DeleteFeatures ) )
{
@@ -1177,7 +1177,7 @@ bool QgsVectorLayer::deleteSelectedFeatures( int *deletedCount )
const auto constSelectedFeatures = selectedFeatures;
for ( QgsFeatureId fid : constSelectedFeatures )
{
deleted += deleteFeature( fid ); // removes from selection
deleted += deleteFeature( fid, context ); // removes from selection
}

triggerRepaint();
@@ -3187,36 +3187,88 @@ bool QgsVectorLayer::deleteAttributes( const QList<int> &attrs )
return deleted;
}

bool QgsVectorLayer::deleteFeature( QgsFeatureId fid )
bool QgsVectorLayer::deleteFeatureCascade( QgsFeatureId fid, QgsVectorLayer::DeleteContext *context )
{
if ( !mEditBuffer )
return false;

if ( context && context->cascade )
{
if ( context->mHandledFeatures.contains( this ) )
{
QgsFeatureIds handledFeatureIds = context->mHandledFeatures.value( this );
if ( handledFeatureIds.contains( fid ) )
{
// avoid endless recursion
return false;
}
else
{
// add feature id
handledFeatureIds << fid;
context->mHandledFeatures.insert( this, handledFeatureIds );
}
}
else
{
// add layer and feature id
context->mHandledFeatures.insert( this, QgsFeatureIds() << fid );
}

const QList<QgsRelation> relations = context->project->relationManager()->referencedRelations( this );

for ( const QgsRelation &relation : relations )
{
//check if composition (and not association)
if ( relation.strength() == QgsRelation::Composition )
{
//get features connected over this relation
QgsFeatureIterator relatedFeaturesIt = relation.getRelatedFeatures( getFeature( fid ) );
QgsFeatureIds childFeatureIds;
QgsFeature childFeature;
while ( relatedFeaturesIt.nextFeature( childFeature ) )
{
childFeatureIds.insert( childFeature.id() );
}
if ( childFeatureIds.count() > 0 )
{
relation.referencingLayer()->startEditing();
relation.referencingLayer()->deleteFeatures( childFeatureIds, context );
}
}
}
}

if ( mJoinBuffer->containsJoins() )
mJoinBuffer->deleteFeature( fid );
mJoinBuffer->deleteFeature( fid, context );

bool res = mEditBuffer->deleteFeature( fid );
if ( res )
{
mSelectedFeatureIds.remove( fid ); // remove it from selection
updateExtents();
}

return res;
}

bool QgsVectorLayer::deleteFeatures( const QgsFeatureIds &fids )
bool QgsVectorLayer::deleteFeature( QgsFeatureId fid, QgsVectorLayer::DeleteContext *context )
{
if ( !mEditBuffer )
{
QgsDebugMsgLevel( QStringLiteral( "Cannot delete features (mEditBuffer==NULL)" ), 1 );
return false;

bool res = deleteFeatureCascade( fid, context );

if ( res )
{
mSelectedFeatureIds.remove( fid ); // remove it from selection
updateExtents();
}

if ( mJoinBuffer->containsJoins() )
mJoinBuffer->deleteFeatures( fids );
return res;
}

bool res = mEditBuffer->deleteFeatures( fids );
bool QgsVectorLayer::deleteFeatures( const QgsFeatureIds &fids, QgsVectorLayer::DeleteContext *context )
{
bool res = true;
const auto constFids = fids;
for ( QgsFeatureId fid : constFids )
res = deleteFeatureCascade( fid, context ) && res;

if ( res )
{
@@ -5511,3 +5563,17 @@ void QgsVectorLayer::onDirtyTransaction( const QString &sql, const QString &name
qobject_cast<QgsVectorLayerEditPassthrough *>( mEditBuffer )->update( tr, sql, name );
}
}

QList<QgsVectorLayer *> QgsVectorLayer::DeleteContext::handledLayers() const
{
QList<QgsVectorLayer *> layers;
QMap<QgsVectorLayer *, QgsFeatureIds>::const_iterator i;
for ( i = mHandledFeatures.begin(); i != mHandledFeatures.end(); ++i )
layers.append( i.key() );
return layers;
}

QgsFeatureIds QgsVectorLayer::DeleteContext::handledFeatures( QgsVectorLayer *layer ) const
{
return mHandledFeatures[layer];
}

0 comments on commit 262568e

Please sign in to comment.
You can’t perform that action at this time.