Skip to content

Commit 1b9dea9

Browse files
author
Hugo Mercier
authoredJul 26, 2017
Merge pull request #4904 from pblottiere/bugfix_chainfilter
Fixes relation reference widget when chain filter option is activated, fixes #16903
2 parents d19b4aa + 58ea887 commit 1b9dea9

File tree

4 files changed

+235
-27
lines changed

4 files changed

+235
-27
lines changed
 

‎src/gui/editorwidgets/qgsrelationreferencewidget.cpp

Lines changed: 60 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,10 @@ void QgsRelationReferenceWidget::setRelation( const QgsRelation &relation, bool
198198
mReferencingFieldIdx = mReferencingLayer->fields().lookupField( relation.fieldPairs().at( 0 ).first );
199199
mAttributeEditorFrame->setObjectName( QStringLiteral( "referencing/" ) + relation.name() );
200200

201-
QgsAttributeEditorContext context( mEditorContext, relation, QgsAttributeEditorContext::Single, QgsAttributeEditorContext::Embed );
202201

203202
if ( mEmbedForm )
204203
{
204+
QgsAttributeEditorContext context( mEditorContext, relation, QgsAttributeEditorContext::Single, QgsAttributeEditorContext::Embed );
205205
mAttributeEditorFrame->setTitle( mReferencedLayer->name() );
206206
mReferencedAttributeForm = new QgsAttributeForm( relation.referencedLayer(), QgsFeature(), context, this );
207207
mAttributeEditorLayout->addWidget( mReferencedAttributeForm );
@@ -467,6 +467,10 @@ void QgsRelationReferenceWidget::init()
467467
Q_FOREACH ( const QString &fieldName, mFilterFields )
468468
{
469469
int idx = mReferencedLayer->fields().lookupField( fieldName );
470+
471+
if ( idx == -1 )
472+
continue;
473+
470474
QComboBox *cb = new QComboBox();
471475
cb->setProperty( "Field", fieldName );
472476
cb->setProperty( "FieldAlias", mReferencedLayer->attributeDisplayName( idx ) );
@@ -814,6 +818,29 @@ void QgsRelationReferenceWidget::filterChanged()
814818

815819
Q_ASSERT( scb );
816820

821+
QgsFeature f;
822+
QgsFeatureIds featureIds;
823+
QString filterExpression;
824+
825+
Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
826+
{
827+
if ( cb->currentIndex() != 0 )
828+
{
829+
const QString fieldName = cb->property( "Field" ).toString();
830+
831+
if ( cb->currentText() == nullValue.toString() )
832+
{
833+
filters << QStringLiteral( "\"%1\" IS NULL" ).arg( fieldName );
834+
}
835+
else
836+
{
837+
filters << QgsExpression::createFieldEqualityExpression( fieldName, cb->currentText() );
838+
}
839+
attrs << mReferencedLayer->fields().lookupField( fieldName );
840+
}
841+
}
842+
843+
bool filtered = false;
817844
if ( mChainFilters )
818845
{
819846
QComboBox *ccb = nullptr;
@@ -834,6 +861,9 @@ void QgsRelationReferenceWidget::filterChanged()
834861
}
835862
else
836863
{
864+
const QString fieldName = cb->property( "Field" ).toString();
865+
filtered = true;
866+
837867
cb->blockSignals( true );
838868
cb->clear();
839869
cb->addItem( cb->property( "FieldAlias" ).toString() );
@@ -843,8 +873,29 @@ void QgsRelationReferenceWidget::filterChanged()
843873
QStringList texts;
844874
Q_FOREACH ( const QString &txt, mFilterCache[ccb->property( "Field" ).toString()][ccb->currentText()] )
845875
{
846-
texts << txt;
876+
QStringList filtersAttrs = filters;
877+
filtersAttrs << QgsExpression::createFieldEqualityExpression( fieldName, txt );
878+
QString expression = filtersAttrs.join( QStringLiteral( " AND " ) );
879+
880+
QgsAttributeList subset = attrs;
881+
subset << mReferencedLayer->fields().lookupField( fieldName );
882+
883+
QgsFeatureIterator it( mMasterModel->layerCache()->getFeatures( QgsFeatureRequest().setFilterExpression( expression ).setSubsetOfAttributes( subset ) ) );
884+
885+
bool found = false;
886+
while ( it.nextFeature( f ) )
887+
{
888+
if ( !featureIds.contains( f.id() ) )
889+
featureIds << f.id();
890+
891+
found = true;
892+
}
893+
894+
// item is only provided if at least 1 feature exists
895+
if ( found )
896+
texts << txt;
847897
}
898+
848899
texts.sort();
849900
cb->addItems( texts );
850901

@@ -856,34 +907,16 @@ void QgsRelationReferenceWidget::filterChanged()
856907
}
857908
}
858909

859-
Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
910+
if ( !mChainFilters || ( mChainFilters && !filtered ) )
860911
{
861-
if ( cb->currentIndex() != 0 )
862-
{
863-
const QString fieldName = cb->property( "Field" ).toString();
912+
filterExpression = filters.join( QStringLiteral( " AND " ) );
864913

865-
if ( cb->currentText() == nullValue.toString() )
866-
{
867-
filters << QStringLiteral( "\"%1\" IS NULL" ).arg( fieldName );
868-
}
869-
else
870-
{
871-
filters << QgsExpression::createFieldEqualityExpression( fieldName, cb->currentText() );
872-
}
873-
attrs << mReferencedLayer->fields().lookupField( fieldName );
874-
}
875-
}
876-
877-
QString filterExpression = filters.join( QStringLiteral( " AND " ) );
878-
879-
QgsFeatureIterator it( mMasterModel->layerCache()->getFeatures( QgsFeatureRequest().setFilterExpression( filterExpression ).setSubsetOfAttributes( attrs ) ) );
914+
QgsFeatureIterator it( mMasterModel->layerCache()->getFeatures( QgsFeatureRequest().setFilterExpression( filterExpression ).setSubsetOfAttributes( attrs ) ) );
880915

881-
QgsFeature f;
882-
QgsFeatureIds featureIds;
883-
884-
while ( it.nextFeature( f ) )
885-
{
886-
featureIds << f.id();
916+
while ( it.nextFeature( f ) )
917+
{
918+
featureIds << f.id();
919+
}
887920
}
888921

889922
mFilterModel->setFilteredFeatures( featureIds );

‎src/gui/editorwidgets/qgsrelationreferencewidget.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,8 @@ class GUI_EXPORT QgsRelationReferenceWidget : public QWidget
241241
QVBoxLayout *mAttributeEditorLayout = nullptr;
242242
QLineEdit *mLineEdit = nullptr;
243243
QLabel *mInvalidLabel = nullptr;
244+
245+
friend class TestQgsRelationReferenceWidget;
244246
};
245247

246248
#endif // QGSRELATIONREFERENCEWIDGET_H

‎tests/src/gui/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,4 @@ ADD_QGIS_TEST(filedownloader testqgsfiledownloader.cpp)
136136
ADD_QGIS_TEST(composergui testqgscomposergui.cpp)
137137
ADD_QGIS_TEST(layoutview testqgslayoutview.cpp)
138138
ADD_QGIS_TEST(valuerelationwidgetwrapper testqgsvaluerelationwidgetwrapper.cpp)
139+
ADD_QGIS_TEST(relationreferencewidget testqgsrelationreferencewidget.cpp)
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
/***************************************************************************
2+
testqgsrelationreferencewidget.cpp
3+
--------------------------------------
4+
Date : 21 07 2017
5+
Copyright : (C) 2017 Paul Blottiere
6+
Email : paul dot blottiere at oslandia dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
17+
#include "qgstest.h"
18+
19+
#include <editorwidgets/core/qgseditorwidgetregistry.h>
20+
#include <qgsapplication.h>
21+
#include "qgseditorwidgetwrapper.h"
22+
#include <editorwidgets/qgsrelationreferencewidget.h>
23+
#include <qgsproject.h>
24+
#include <qgsattributeform.h>
25+
#include <qgsrelationmanager.h>
26+
#include <attributetable/qgsattributetablefiltermodel.h>
27+
#include "qgsgui.h"
28+
29+
class TestQgsRelationReferenceWidget : public QObject
30+
{
31+
Q_OBJECT
32+
public:
33+
TestQgsRelationReferenceWidget() {}
34+
35+
private slots:
36+
void initTestCase(); // will be called before the first testfunction is executed.
37+
void cleanupTestCase(); // will be called after the last testfunction was executed.
38+
void init(); // will be called before each testfunction is executed.
39+
void cleanup(); // will be called after every testfunction.
40+
41+
void testChainFilter();
42+
};
43+
44+
void TestQgsRelationReferenceWidget::initTestCase()
45+
{
46+
QgsApplication::init();
47+
QgsApplication::initQgis();
48+
QgsGui::editorWidgetRegistry()->initEditors();
49+
}
50+
51+
void TestQgsRelationReferenceWidget::cleanupTestCase()
52+
{
53+
QgsApplication::exitQgis();
54+
}
55+
56+
void TestQgsRelationReferenceWidget::init()
57+
{
58+
}
59+
60+
void TestQgsRelationReferenceWidget::cleanup()
61+
{
62+
}
63+
64+
void TestQgsRelationReferenceWidget::testChainFilter()
65+
{
66+
// create layers
67+
QgsVectorLayer vl1( QStringLiteral( "LineString?crs=epsg:3111&field=pk:int&field=fk:int" ), QStringLiteral( "vl1" ), QStringLiteral( "memory" ) );
68+
QgsVectorLayer vl2( QStringLiteral( "LineString?field=pk:int&field=material:string&field=diameter:int&field=raccord:string" ), QStringLiteral( "vl2" ), QStringLiteral( "memory" ) );
69+
QgsProject::instance()->addMapLayer( &vl1, false, false );
70+
QgsProject::instance()->addMapLayer( &vl2, false, false );
71+
72+
// create a relation between them
73+
QgsRelation relation;
74+
relation.setId( QStringLiteral( "vl1.vl2" ) );
75+
relation.setName( QStringLiteral( "vl1.vl2" ) );
76+
relation.setReferencingLayer( vl1.id() );
77+
relation.setReferencedLayer( vl2.id() );
78+
relation.addFieldPair( "fk", "pk" );
79+
QVERIFY( relation.isValid() );
80+
QgsProject::instance()->relationManager()->addRelation( relation );
81+
82+
// add features
83+
QgsFeature ft0( vl1.fields() );
84+
ft0.setAttribute( QStringLiteral( "pk" ), 0 );
85+
ft0.setAttribute( QStringLiteral( "fk" ), 0 );
86+
vl1.startEditing();
87+
vl1.addFeature( ft0 );
88+
vl1.commitChanges();
89+
90+
QgsFeature ft1( vl1.fields() );
91+
ft1.setAttribute( QStringLiteral( "pk" ), 1 );
92+
ft1.setAttribute( QStringLiteral( "fk" ), 1 );
93+
vl1.startEditing();
94+
vl1.addFeature( ft1 );
95+
vl1.commitChanges();
96+
97+
QgsFeature ft2( vl2.fields() );
98+
ft2.setAttribute( QStringLiteral( "pk" ), 10 );
99+
ft2.setAttribute( QStringLiteral( "material" ), "iron" );
100+
ft2.setAttribute( QStringLiteral( "diameter" ), 120 );
101+
ft2.setAttribute( QStringLiteral( "raccord" ), "brides" );
102+
vl2.startEditing();
103+
vl2.addFeature( ft2 );
104+
vl2.commitChanges();
105+
106+
QgsFeature ft3( vl2.fields() );
107+
ft3.setAttribute( QStringLiteral( "pk" ), 11 );
108+
ft3.setAttribute( QStringLiteral( "material" ), "iron" );
109+
ft3.setAttribute( QStringLiteral( "diameter" ), 120 );
110+
ft3.setAttribute( QStringLiteral( "raccord" ), "sleeve" );
111+
vl2.startEditing();
112+
vl2.addFeature( ft3 );
113+
vl2.commitChanges();
114+
115+
QgsFeature ft4( vl2.fields() );
116+
ft4.setAttribute( QStringLiteral( "pk" ), 12 );
117+
ft4.setAttribute( QStringLiteral( "material" ), "steel" );
118+
ft4.setAttribute( QStringLiteral( "diameter" ), 120 );
119+
ft4.setAttribute( QStringLiteral( "raccord" ), "collar" );
120+
vl2.startEditing();
121+
vl2.addFeature( ft4 );
122+
vl2.commitChanges();
123+
124+
// init a relation reference widget
125+
QStringList filterFields = { "material", "diameter", "raccord" };
126+
127+
QgsRelationReferenceWidget w( new QWidget() );
128+
w.setChainFilters( true );
129+
w.setFilterFields( filterFields );
130+
w.setRelation( relation, true );
131+
w.init();
132+
133+
// check default status for comboboxes
134+
QList<QComboBox *> cbs = w.mFilterComboBoxes;
135+
QCOMPARE( cbs.count(), 3 );
136+
Q_FOREACH ( const QComboBox *cb, cbs )
137+
{
138+
if ( cb->currentText() == "raccord" )
139+
QCOMPARE( cb->count(), 5 );
140+
else if ( cb->currentText() == "material" )
141+
QCOMPARE( cb->count(), 4 );
142+
else if ( cb->currentText() == "diameter" )
143+
QCOMPARE( cb->count(), 3 );
144+
}
145+
146+
// set first filter
147+
cbs[0]->setCurrentIndex( cbs[0]->findText( "iron" ) );
148+
cbs[1]->setCurrentIndex( cbs[1]->findText( "120" ) );
149+
150+
Q_FOREACH ( const QComboBox *cb, cbs )
151+
{
152+
if ( cb->itemText( 0 ) == "material" )
153+
QCOMPARE( cb->count(), 4 );
154+
else if ( cb->itemText( 0 ) == "diameter" )
155+
QCOMPARE( cb->count(), 2 );
156+
else if ( cb->itemText( 0 ) == "raccord" )
157+
{
158+
QStringList items;
159+
for ( int i = 0; i < cb->count(); i++ )
160+
items << cb->itemText( i );
161+
162+
QCOMPARE( cb->count(), 3 );
163+
QCOMPARE( items.contains( "collar" ), false );
164+
// collar should not be available in combobox as there's no existing
165+
// feature with the filter expression:
166+
// "material" == 'iron' AND "diameter" == '120' AND "raccord" = 'collar'
167+
}
168+
}
169+
}
170+
171+
QGSTEST_MAIN( TestQgsRelationReferenceWidget )
172+
#include "testqgsrelationreferencewidget.moc"

0 commit comments

Comments
 (0)
Please sign in to comment.