Skip to content

Commit

Permalink
Merge pull request #4904 from pblottiere/bugfix_chainfilter
Browse files Browse the repository at this point in the history
Fixes relation reference widget when chain filter option is activated, fixes #16903
  • Loading branch information
Hugo Mercier committed Jul 26, 2017
2 parents d19b4aa + 58ea887 commit 1b9dea9
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 27 deletions.
87 changes: 60 additions & 27 deletions src/gui/editorwidgets/qgsrelationreferencewidget.cpp
Expand Up @@ -198,10 +198,10 @@ void QgsRelationReferenceWidget::setRelation( const QgsRelation &relation, bool
mReferencingFieldIdx = mReferencingLayer->fields().lookupField( relation.fieldPairs().at( 0 ).first );
mAttributeEditorFrame->setObjectName( QStringLiteral( "referencing/" ) + relation.name() );

QgsAttributeEditorContext context( mEditorContext, relation, QgsAttributeEditorContext::Single, QgsAttributeEditorContext::Embed );

if ( mEmbedForm )
{
QgsAttributeEditorContext context( mEditorContext, relation, QgsAttributeEditorContext::Single, QgsAttributeEditorContext::Embed );
mAttributeEditorFrame->setTitle( mReferencedLayer->name() );
mReferencedAttributeForm = new QgsAttributeForm( relation.referencedLayer(), QgsFeature(), context, this );
mAttributeEditorLayout->addWidget( mReferencedAttributeForm );
Expand Down Expand Up @@ -467,6 +467,10 @@ void QgsRelationReferenceWidget::init()
Q_FOREACH ( const QString &fieldName, mFilterFields )
{
int idx = mReferencedLayer->fields().lookupField( fieldName );

if ( idx == -1 )
continue;

QComboBox *cb = new QComboBox();
cb->setProperty( "Field", fieldName );
cb->setProperty( "FieldAlias", mReferencedLayer->attributeDisplayName( idx ) );
Expand Down Expand Up @@ -814,6 +818,29 @@ void QgsRelationReferenceWidget::filterChanged()

Q_ASSERT( scb );

QgsFeature f;
QgsFeatureIds featureIds;
QString filterExpression;

Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
{
if ( cb->currentIndex() != 0 )
{
const QString fieldName = cb->property( "Field" ).toString();

if ( cb->currentText() == nullValue.toString() )
{
filters << QStringLiteral( "\"%1\" IS NULL" ).arg( fieldName );
}
else
{
filters << QgsExpression::createFieldEqualityExpression( fieldName, cb->currentText() );
}
attrs << mReferencedLayer->fields().lookupField( fieldName );
}
}

bool filtered = false;
if ( mChainFilters )
{
QComboBox *ccb = nullptr;
Expand All @@ -834,6 +861,9 @@ void QgsRelationReferenceWidget::filterChanged()
}
else
{
const QString fieldName = cb->property( "Field" ).toString();
filtered = true;

cb->blockSignals( true );
cb->clear();
cb->addItem( cb->property( "FieldAlias" ).toString() );
Expand All @@ -843,8 +873,29 @@ void QgsRelationReferenceWidget::filterChanged()
QStringList texts;
Q_FOREACH ( const QString &txt, mFilterCache[ccb->property( "Field" ).toString()][ccb->currentText()] )
{
texts << txt;
QStringList filtersAttrs = filters;
filtersAttrs << QgsExpression::createFieldEqualityExpression( fieldName, txt );
QString expression = filtersAttrs.join( QStringLiteral( " AND " ) );

QgsAttributeList subset = attrs;
subset << mReferencedLayer->fields().lookupField( fieldName );

QgsFeatureIterator it( mMasterModel->layerCache()->getFeatures( QgsFeatureRequest().setFilterExpression( expression ).setSubsetOfAttributes( subset ) ) );

bool found = false;
while ( it.nextFeature( f ) )
{
if ( !featureIds.contains( f.id() ) )
featureIds << f.id();

found = true;
}

// item is only provided if at least 1 feature exists
if ( found )
texts << txt;
}

texts.sort();
cb->addItems( texts );

Expand All @@ -856,34 +907,16 @@ void QgsRelationReferenceWidget::filterChanged()
}
}

Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
if ( !mChainFilters || ( mChainFilters && !filtered ) )
{
if ( cb->currentIndex() != 0 )
{
const QString fieldName = cb->property( "Field" ).toString();
filterExpression = filters.join( QStringLiteral( " AND " ) );

if ( cb->currentText() == nullValue.toString() )
{
filters << QStringLiteral( "\"%1\" IS NULL" ).arg( fieldName );
}
else
{
filters << QgsExpression::createFieldEqualityExpression( fieldName, cb->currentText() );
}
attrs << mReferencedLayer->fields().lookupField( fieldName );
}
}

QString filterExpression = filters.join( QStringLiteral( " AND " ) );

QgsFeatureIterator it( mMasterModel->layerCache()->getFeatures( QgsFeatureRequest().setFilterExpression( filterExpression ).setSubsetOfAttributes( attrs ) ) );
QgsFeatureIterator it( mMasterModel->layerCache()->getFeatures( QgsFeatureRequest().setFilterExpression( filterExpression ).setSubsetOfAttributes( attrs ) ) );

QgsFeature f;
QgsFeatureIds featureIds;

while ( it.nextFeature( f ) )
{
featureIds << f.id();
while ( it.nextFeature( f ) )
{
featureIds << f.id();
}
}

mFilterModel->setFilteredFeatures( featureIds );
Expand Down
2 changes: 2 additions & 0 deletions src/gui/editorwidgets/qgsrelationreferencewidget.h
Expand Up @@ -241,6 +241,8 @@ class GUI_EXPORT QgsRelationReferenceWidget : public QWidget
QVBoxLayout *mAttributeEditorLayout = nullptr;
QLineEdit *mLineEdit = nullptr;
QLabel *mInvalidLabel = nullptr;

friend class TestQgsRelationReferenceWidget;
};

#endif // QGSRELATIONREFERENCEWIDGET_H
1 change: 1 addition & 0 deletions tests/src/gui/CMakeLists.txt
Expand Up @@ -136,3 +136,4 @@ ADD_QGIS_TEST(filedownloader testqgsfiledownloader.cpp)
ADD_QGIS_TEST(composergui testqgscomposergui.cpp)
ADD_QGIS_TEST(layoutview testqgslayoutview.cpp)
ADD_QGIS_TEST(valuerelationwidgetwrapper testqgsvaluerelationwidgetwrapper.cpp)
ADD_QGIS_TEST(relationreferencewidget testqgsrelationreferencewidget.cpp)
172 changes: 172 additions & 0 deletions tests/src/gui/testqgsrelationreferencewidget.cpp
@@ -0,0 +1,172 @@
/***************************************************************************
testqgsrelationreferencewidget.cpp
--------------------------------------
Date : 21 07 2017
Copyright : (C) 2017 Paul Blottiere
Email : paul dot blottiere at oslandia dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/


#include "qgstest.h"

#include <editorwidgets/core/qgseditorwidgetregistry.h>
#include <qgsapplication.h>
#include "qgseditorwidgetwrapper.h"
#include <editorwidgets/qgsrelationreferencewidget.h>
#include <qgsproject.h>
#include <qgsattributeform.h>
#include <qgsrelationmanager.h>
#include <attributetable/qgsattributetablefiltermodel.h>
#include "qgsgui.h"

class TestQgsRelationReferenceWidget : public QObject
{
Q_OBJECT
public:
TestQgsRelationReferenceWidget() {}

private slots:
void initTestCase(); // will be called before the first testfunction is executed.
void cleanupTestCase(); // will be called after the last testfunction was executed.
void init(); // will be called before each testfunction is executed.
void cleanup(); // will be called after every testfunction.

void testChainFilter();
};

void TestQgsRelationReferenceWidget::initTestCase()
{
QgsApplication::init();
QgsApplication::initQgis();
QgsGui::editorWidgetRegistry()->initEditors();
}

void TestQgsRelationReferenceWidget::cleanupTestCase()
{
QgsApplication::exitQgis();
}

void TestQgsRelationReferenceWidget::init()
{
}

void TestQgsRelationReferenceWidget::cleanup()
{
}

void TestQgsRelationReferenceWidget::testChainFilter()
{
// create layers
QgsVectorLayer vl1( QStringLiteral( "LineString?crs=epsg:3111&field=pk:int&field=fk:int" ), QStringLiteral( "vl1" ), QStringLiteral( "memory" ) );
QgsVectorLayer vl2( QStringLiteral( "LineString?field=pk:int&field=material:string&field=diameter:int&field=raccord:string" ), QStringLiteral( "vl2" ), QStringLiteral( "memory" ) );
QgsProject::instance()->addMapLayer( &vl1, false, false );
QgsProject::instance()->addMapLayer( &vl2, false, false );

// create a relation between them
QgsRelation relation;
relation.setId( QStringLiteral( "vl1.vl2" ) );
relation.setName( QStringLiteral( "vl1.vl2" ) );
relation.setReferencingLayer( vl1.id() );
relation.setReferencedLayer( vl2.id() );
relation.addFieldPair( "fk", "pk" );
QVERIFY( relation.isValid() );
QgsProject::instance()->relationManager()->addRelation( relation );

// add features
QgsFeature ft0( vl1.fields() );
ft0.setAttribute( QStringLiteral( "pk" ), 0 );
ft0.setAttribute( QStringLiteral( "fk" ), 0 );
vl1.startEditing();
vl1.addFeature( ft0 );
vl1.commitChanges();

QgsFeature ft1( vl1.fields() );
ft1.setAttribute( QStringLiteral( "pk" ), 1 );
ft1.setAttribute( QStringLiteral( "fk" ), 1 );
vl1.startEditing();
vl1.addFeature( ft1 );
vl1.commitChanges();

QgsFeature ft2( vl2.fields() );
ft2.setAttribute( QStringLiteral( "pk" ), 10 );
ft2.setAttribute( QStringLiteral( "material" ), "iron" );
ft2.setAttribute( QStringLiteral( "diameter" ), 120 );
ft2.setAttribute( QStringLiteral( "raccord" ), "brides" );
vl2.startEditing();
vl2.addFeature( ft2 );
vl2.commitChanges();

QgsFeature ft3( vl2.fields() );
ft3.setAttribute( QStringLiteral( "pk" ), 11 );
ft3.setAttribute( QStringLiteral( "material" ), "iron" );
ft3.setAttribute( QStringLiteral( "diameter" ), 120 );
ft3.setAttribute( QStringLiteral( "raccord" ), "sleeve" );
vl2.startEditing();
vl2.addFeature( ft3 );
vl2.commitChanges();

QgsFeature ft4( vl2.fields() );
ft4.setAttribute( QStringLiteral( "pk" ), 12 );
ft4.setAttribute( QStringLiteral( "material" ), "steel" );
ft4.setAttribute( QStringLiteral( "diameter" ), 120 );
ft4.setAttribute( QStringLiteral( "raccord" ), "collar" );
vl2.startEditing();
vl2.addFeature( ft4 );
vl2.commitChanges();

// init a relation reference widget
QStringList filterFields = { "material", "diameter", "raccord" };

QgsRelationReferenceWidget w( new QWidget() );
w.setChainFilters( true );
w.setFilterFields( filterFields );
w.setRelation( relation, true );
w.init();

// check default status for comboboxes
QList<QComboBox *> cbs = w.mFilterComboBoxes;
QCOMPARE( cbs.count(), 3 );
Q_FOREACH ( const QComboBox *cb, cbs )
{
if ( cb->currentText() == "raccord" )
QCOMPARE( cb->count(), 5 );
else if ( cb->currentText() == "material" )
QCOMPARE( cb->count(), 4 );
else if ( cb->currentText() == "diameter" )
QCOMPARE( cb->count(), 3 );
}

// set first filter
cbs[0]->setCurrentIndex( cbs[0]->findText( "iron" ) );
cbs[1]->setCurrentIndex( cbs[1]->findText( "120" ) );

Q_FOREACH ( const QComboBox *cb, cbs )
{
if ( cb->itemText( 0 ) == "material" )
QCOMPARE( cb->count(), 4 );
else if ( cb->itemText( 0 ) == "diameter" )
QCOMPARE( cb->count(), 2 );
else if ( cb->itemText( 0 ) == "raccord" )
{
QStringList items;
for ( int i = 0; i < cb->count(); i++ )
items << cb->itemText( i );

QCOMPARE( cb->count(), 3 );
QCOMPARE( items.contains( "collar" ), false );
// collar should not be available in combobox as there's no existing
// feature with the filter expression:
// "material" == 'iron' AND "diameter" == '120' AND "raccord" = 'collar'
}
}
}

QGSTEST_MAIN( TestQgsRelationReferenceWidget )
#include "testqgsrelationreferencewidget.moc"

0 comments on commit 1b9dea9

Please sign in to comment.