Skip to content

Commit

Permalink
Merge pull request #7315 from m-kuhn/confirmDeleteLinkedNm
Browse files Browse the repository at this point in the history
Confirm delete when feature is still linked
  • Loading branch information
m-kuhn committed Jul 31, 2018
2 parents c1eef4e + 4ce0182 commit 01bf140
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 4 deletions.
82 changes: 79 additions & 3 deletions src/gui/qgsrelationeditorwidget.cpp
Expand Up @@ -33,6 +33,7 @@

#include <QHBoxLayout>
#include <QLabel>
#include <QMessageBox>

QgsRelationEditorWidget::QgsRelationEditorWidget( QWidget *parent )
: QgsCollapsibleGroupBox( parent )
Expand Down Expand Up @@ -492,9 +493,84 @@ void QgsRelationEditorWidget::deleteSelectedFeatures()

void QgsRelationEditorWidget::deleteFeatures( const QgsFeatureIds &featureids )
{
QgsVectorLayer *layer = mNmRelation.isValid() ? mNmRelation.referencedLayer() : mRelation.referencingLayer();
layer->deleteFeatures( featureids );
updateUi();
bool deleteFeatures = true;

QgsVectorLayer *layer;
if ( mNmRelation.isValid() )
{
layer = mNmRelation.referencedLayer();

// When deleting a linked feature within an N:M relation,
// check if the feature is linked to more than just one feature.
// In case it is linked more than just once, ask the user for confirmation
// as it is likely he was not aware of the implications and might either
// leave the dataset in a corrupted state (referential integrity) or if
// the fk constraint is ON CASCADE DELETE, there may be several linking
// entries deleted along.

QgsFeatureRequest deletedFeaturesRequest;
deletedFeaturesRequest.setFilterFids( featureids );
deletedFeaturesRequest.setFlags( QgsFeatureRequest::NoGeometry );
deletedFeaturesRequest.setSubsetOfAttributes( QgsAttributeList() << mNmRelation.referencedFields().first() );

QgsFeatureIterator deletedFeatures = layer->getFeatures( deletedFeaturesRequest );
QStringList deletedFeaturesPks;
QgsFeature feature;
while ( deletedFeatures.nextFeature( feature ) )
{
deletedFeaturesPks.append( QgsExpression::quotedValue( feature.attribute( mNmRelation.referencedFields().first() ) ) );
}

QgsFeatureRequest linkingFeaturesRequest;
linkingFeaturesRequest.setFlags( QgsFeatureRequest::NoGeometry );
linkingFeaturesRequest.setSubsetOfAttributes( QgsAttributeList() );

QString linkingFeaturesRequestExpression;
if ( !deletedFeaturesPks.empty() )
{
linkingFeaturesRequestExpression = QStringLiteral( "%1 IN (%2)" ).arg( QgsExpression::quotedColumnRef( mNmRelation.fieldPairs().first().first ), deletedFeaturesPks.join( ',' ) );
linkingFeaturesRequest.setFilterExpression( linkingFeaturesRequestExpression );

QgsFeatureIterator relatedLinkingFeatures = mNmRelation.referencingLayer()->getFeatures( linkingFeaturesRequest );

int relatedLinkingFeaturesCount = 0;
while ( relatedLinkingFeatures.nextFeature( feature ) )
{
relatedLinkingFeaturesCount++;
}

if ( deletedFeaturesPks.size() == 1 && relatedLinkingFeaturesCount > 1 )
{
QMessageBox messageBox( QMessageBox::Question, tr( "Really delete entry?" ), tr( "The entry on %1 is still linked to %2 features on %3. Do you want to delete it?" ).arg( mNmRelation.referencedLayer()->name(), QString::number( relatedLinkingFeaturesCount ), mRelation.referencedLayer()->name() ), QMessageBox::NoButton, this );
messageBox.addButton( QMessageBox::Cancel );
QAbstractButton *deleteButton = messageBox.addButton( tr( "Delete" ), QMessageBox::AcceptRole );

messageBox.exec();
if ( messageBox.clickedButton() != deleteButton )
deleteFeatures = false;
}
else if ( deletedFeaturesPks.size() > 1 && relatedLinkingFeaturesCount > deletedFeaturesPks.size() )
{
QMessageBox messageBox( QMessageBox::Question, tr( "Really delete entries?" ), tr( "The %1 entries on %2 are still linked to %3 features on %4. Do you want to delete them?" ).arg( QString::number( deletedFeaturesPks.size() ), mNmRelation.referencedLayer()->name(), QString::number( relatedLinkingFeaturesCount ), mRelation.referencedLayer()->name() ), QMessageBox::NoButton, this );
messageBox.addButton( QMessageBox::Cancel );
QAbstractButton *deleteButton = messageBox.addButton( tr( "Delete" ), QMessageBox::AcceptRole );

messageBox.exec();
if ( messageBox.clickedButton() != deleteButton )
deleteFeatures = false;
}
}
}
else
{
layer = mRelation.referencingLayer();
}

if ( deleteFeatures )
{
layer->deleteFeatures( featureids );
updateUi();
}
}

void QgsRelationEditorWidget::unlinkFeature( const QgsFeatureId featureid )
Expand Down
19 changes: 18 additions & 1 deletion tests/src/python/test_qgsrelationeditwidget.py
Expand Up @@ -34,7 +34,13 @@
)

from qgis.PyQt.QtCore import QTimer
from qgis.PyQt.QtWidgets import QToolButton, QTableView, QApplication
from qgis.PyQt.QtWidgets import (
QToolButton,
QMessageBox,
QDialogButtonBox,
QTableView,
QApplication
)
from qgis.testing import start_app, unittest

start_app()
Expand Down Expand Up @@ -92,6 +98,7 @@ def setUp(self):

def tearDown(self):
self.rollbackTransaction()
del self.transaction

def test_delete_feature(self):
"""
Expand All @@ -108,6 +115,16 @@ def test_delete_feature(self):
self.widget.featureSelectionManager().select([fid])

btn = self.widget.findChild(QToolButton, 'mDeleteFeatureButton')

def clickOk():
# Click the "Delete features" button on the confirmation message
# box
widget = self.widget.findChild(QMessageBox)
buttonBox = widget.findChild(QDialogButtonBox)
deleteButton = next((b for b in buttonBox.buttons() if buttonBox.buttonRole(b) == QDialogButtonBox.AcceptRole))
deleteButton.click()

QTimer.singleShot(1, clickOk)
btn.click()

# This is the important check that the feature is deleted
Expand Down

0 comments on commit 01bf140

Please sign in to comment.