Add auto-discovery of relations for PostgresQL

Fixed the add relation functionnality: the table is sorted. When the code
was setting the sorted column, the row was sorted and the other columns it was
setting were set on the wrong row.
This commit is contained in:
Patrick Valsecchi 2016-09-26 13:51:37 +02:00 committed by Patrick Valsecchi
parent 9cf99389bb
commit 31a1c23903
19 changed files with 495 additions and 8 deletions

View File

@ -171,6 +171,12 @@ class QgsRelation
*/
QString id() const;
/**
* Generate a (project-wide) unique id for this relation
* @note added in QGIS 3.0
*/
void generateId();
/**
* Access the referencing (child) layer's id
* This is the layer which has the field(s) which point to another layer
@ -241,6 +247,15 @@ class QgsRelation
*/
bool isValid() const;
/**
* Compares the two QgsRelation, ignoring the name and the ID.
*
* @param other The other relation
* @return true if they are similar
* @note added in QGIS 3.0
*/
bool hasEqualDefinition( const QgsRelation& other ) const;
protected:
/**
* Updates the validity status of this relation.

View File

@ -90,6 +90,16 @@ class QgsRelationManager : QObject
*/
QList<QgsRelation> referencedRelations( QgsVectorLayer *layer = 0 ) const;
/**
* Discover all the relations available from the current layers.
*
* @param existingRelations the existing relations to filter them out
* @param layers the current layers
* @return the list of discovered relations
* @note added in QGIS 3.0
*/
static QList<QgsRelation> discoverRelations( const QList<QgsRelation>& existingRelations, const QList<QgsVectorLayer*>& layers );
signals:
/** This signal is emitted when the relations were loaded after reading a project */
void relationsLoaded();

View File

@ -371,6 +371,15 @@ class QgsVectorDataProvider : QgsDataProvider
*/
virtual QSet<QgsMapLayerDependency> dependencies() const;
/**
* Discover the available relations with the given layers.
* @param self the layer using this data provider.
* @param layers the other layers.
* @return the list of N-1 relations from this provider.
* @note added in QGIS 3.0
*/
virtual QList<QgsRelation> discoverRelations( const QgsVectorLayer* self, const QList<QgsVectorLayer*>& layers ) const;
signals:
/** Signals an error in this provider */
void raiseError( const QString& msg );

View File

@ -28,6 +28,7 @@ SET(QGIS_APP_SRCS
qgsdecorationscalebardialog.cpp
qgsdecorationgrid.cpp
qgsdecorationgriddialog.cpp
qgsdiscoverrelationsdlg.cpp
qgsdxfexportdialog.cpp
qgsformannotationdialog.cpp
qgsguivectorlayertools.cpp
@ -207,6 +208,7 @@ SET (QGIS_APP_MOC_HDRS
qgsdecorationgriddialog.h
qgsdelattrdialog.h
qgsdiagramproperties.h
qgsdiscoverrelationsdlg.h
qgsdisplayangle.h
qgsdxfexportdialog.h
qgsfeatureaction.h

View File

@ -0,0 +1,61 @@
/***************************************************************************
qgsdiscoverrelationsdlg.cpp
---------------------
begin : September 2016
copyright : (C) 2016 by Patrick Valsecchi
email : patrick dot valsecchi at camptocamp 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 "qgsdiscoverrelationsdlg.h"
#include "qgsvectorlayer.h"
#include "qgsrelationmanager.h"
#include <QPushButton>
QgsDiscoverRelationsDlg::QgsDiscoverRelationsDlg( const QList<QgsRelation>& existingRelations, const QList<QgsVectorLayer*>& layers, QWidget *parent )
: QDialog( parent )
, mLayers( layers )
{
setupUi( this );
mButtonBox->button( QDialogButtonBox::Ok )->setEnabled( false );
connect( mRelationsTable->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsDiscoverRelationsDlg::onSelectionChanged );
mFoundRelations = QgsRelationManager::discoverRelations( existingRelations, layers );
Q_FOREACH ( const QgsRelation& relation, mFoundRelations ) addRelation( relation );
mRelationsTable->resizeColumnsToContents();
}
void QgsDiscoverRelationsDlg::addRelation( const QgsRelation &rel )
{
const int row = mRelationsTable->rowCount();
mRelationsTable->insertRow( row );
mRelationsTable->setItem( row, 0, new QTableWidgetItem( rel.name() ) );
mRelationsTable->setItem( row, 1, new QTableWidgetItem( rel.referencingLayer()->name() ) );
mRelationsTable->setItem( row, 2, new QTableWidgetItem( rel.fieldPairs().at( 0 ).referencingField() ) );
mRelationsTable->setItem( row, 3, new QTableWidgetItem( rel.referencedLayer()->name() ) );
mRelationsTable->setItem( row, 4, new QTableWidgetItem( rel.fieldPairs().at( 0 ).referencedField() ) );
}
QList<QgsRelation> QgsDiscoverRelationsDlg::relations() const
{
QList<QgsRelation> result;
Q_FOREACH ( const QModelIndex& row, mRelationsTable->selectionModel()->selectedRows() )
{
result.append( mFoundRelations.at( row.row() ) );
}
return result;
}
void QgsDiscoverRelationsDlg::onSelectionChanged()
{
mButtonBox->button( QDialogButtonBox::Ok )->setEnabled( mRelationsTable->selectionModel()->hasSelection() );
}

View File

@ -0,0 +1,53 @@
/***************************************************************************
qgsdiscoverrelationsdlg.h
---------------------
begin : September 2016
copyright : (C) 2016 by Patrick Valsecchi
email : patrick dot valsecchi at camptocamp 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. *
* *
***************************************************************************/
#ifndef QGSDISCOVERRELATIONSDLG_H
#define QGSDISCOVERRELATIONSDLG_H
#include <QDialog>
#include "ui_qgsdiscoverrelationsdlgbase.h"
#include "qgsrelation.h"
class QgsRelationManager;
class QgsVectorLayer;
/**
* Shows the list of relations discovered from the providers.
*
* The user can select some of them to add them to his project.
*/
class APP_EXPORT QgsDiscoverRelationsDlg : public QDialog, private Ui::QgsDiscoverRelationsDlgBase
{
Q_OBJECT
public:
explicit QgsDiscoverRelationsDlg( const QList<QgsRelation>& existingRelations, const QList<QgsVectorLayer*>& layers, QWidget *parent = nullptr );
/**
* Get the selected relations.
*/
QList<QgsRelation> relations() const;
private slots:
void onSelectionChanged();
private:
QList<QgsVectorLayer*> mLayers;
QList<QgsRelation> mFoundRelations;
void addRelation( const QgsRelation &rel );
};
#endif // QGSDISCOVERRELATIONSDLG_H

View File

@ -13,6 +13,7 @@
* *
***************************************************************************/
#include "qgsdiscoverrelationsdlg.h"
#include "qgsrelationadddlg.h"
#include "qgsrelationmanagerdialog.h"
#include "qgsrelationmanager.h"
@ -46,6 +47,7 @@ void QgsRelationManagerDialog::setLayers( const QList< QgsVectorLayer* >& layers
void QgsRelationManagerDialog::addRelation( const QgsRelation &rel )
{
mRelationsTable->setSortingEnabled( false );
int row = mRelationsTable->rowCount();
mRelationsTable->insertRow( row );
@ -54,7 +56,6 @@ void QgsRelationManagerDialog::addRelation( const QgsRelation &rel )
item->setData( Qt::UserRole, QVariant::fromValue<QgsRelation>( rel ) );
mRelationsTable->setItem( row, 0, item );
item = new QTableWidgetItem( rel.referencingLayer()->name() );
item->setFlags( Qt::ItemIsEditable );
mRelationsTable->setItem( row, 1, item );
@ -74,6 +75,7 @@ void QgsRelationManagerDialog::addRelation( const QgsRelation &rel )
item = new QTableWidgetItem( rel.id() );
item->setFlags( Qt::ItemIsEditable );
mRelationsTable->setItem( row, 5, item );
mRelationsTable->setSortingEnabled( true );
}
void QgsRelationManagerDialog::on_mBtnAddRelation_clicked()
@ -118,6 +120,18 @@ void QgsRelationManagerDialog::on_mBtnAddRelation_clicked()
}
}
void QgsRelationManagerDialog::on_mBtnDiscoverRelations_clicked()
{
QgsDiscoverRelationsDlg discoverDlg( relations(), mLayers, this );
if ( discoverDlg.exec() )
{
Q_FOREACH ( const QgsRelation& relation, discoverDlg.relations() )
{
addRelation( relation );
}
}
}
void QgsRelationManagerDialog::on_mBtnRemoveRelation_clicked()
{
if ( mRelationsTable->currentIndex().isValid() )

View File

@ -39,6 +39,7 @@ class APP_EXPORT QgsRelationManagerDialog : public QWidget, private Ui::QgsRelat
public slots:
void on_mBtnAddRelation_clicked();
void on_mBtnDiscoverRelations_clicked();
void on_mBtnRemoveRelation_clicked();
private:

View File

@ -248,6 +248,16 @@ QString QgsRelation::id() const
return mRelationId;
}
void QgsRelation::generateId()
{
mRelationId = QString( "%1_%2_%3_%4" )
.arg( referencingLayerId(),
mFieldPairs.at( 0 ).referencingField(),
referencedLayerId(),
mFieldPairs.at( 0 ).referencedField() );
updateRelationStatus();
}
QString QgsRelation::referencingLayerId() const
{
return mReferencingLayerId;
@ -301,6 +311,11 @@ bool QgsRelation::isValid() const
return mValid;
}
bool QgsRelation::hasEqualDefinition( const QgsRelation& other ) const
{
return mReferencedLayerId == other.mReferencedLayerId && mReferencingLayerId == other.mReferencingLayerId && mFieldPairs == other.mFieldPairs;
}
void QgsRelation::updateRelationStatus()
{
const QMap<QString, QgsMapLayer*>& mapLayers = QgsMapLayerRegistry::instance()->mapLayers();

View File

@ -57,6 +57,8 @@ class CORE_EXPORT QgsRelation
QString referencingField() const { return first; }
//! Get the name of the referenced (parent) field
QString referencedField() const { return second; }
bool operator==( const FieldPair& other ) const { return first == other.first && second == other.second; }
};
/**
@ -210,6 +212,12 @@ class CORE_EXPORT QgsRelation
*/
QString id() const;
/**
* Generate a (project-wide) unique id for this relation
* @note added in QGIS 3.0
*/
void generateId();
/**
* Access the referencing (child) layer's id
* This is the layer which has the field(s) which point to another layer
@ -272,6 +280,15 @@ class CORE_EXPORT QgsRelation
*/
bool isValid() const;
/**
* Compares the two QgsRelation, ignoring the name and the ID.
*
* @param other The other relation
* @return true if they are similar
* @note added in QGIS 3.0
*/
bool hasEqualDefinition( const QgsRelation& other ) const;
protected:
/**
* Updates the validity status of this relation.

View File

@ -19,6 +19,7 @@
#include "qgslogger.h"
#include "qgsmaplayerregistry.h"
#include "qgsproject.h"
#include "qgsvectordataprovider.h"
#include "qgsvectorlayer.h"
QgsRelationManager::QgsRelationManager( QgsProject* project )
@ -217,3 +218,28 @@ void QgsRelationManager::layersRemoved( const QStringList& layers )
emit changed();
}
}
static bool hasRelationWithEqualDefinition( const QList<QgsRelation>& existingRelations, const QgsRelation& relation )
{
Q_FOREACH ( const QgsRelation& cur, existingRelations )
{
if ( cur.hasEqualDefinition( relation ) ) return true;
}
return false;
}
QList<QgsRelation> QgsRelationManager::discoverRelations( const QList<QgsRelation>& existingRelations, const QList<QgsVectorLayer*>& layers )
{
QList<QgsRelation> result;
Q_FOREACH ( const QgsVectorLayer* layer, layers )
{
Q_FOREACH ( const QgsRelation& relation, layer->dataProvider()->discoverRelations( layer, layers ) )
{
if ( !hasRelationWithEqualDefinition( existingRelations, relation ) )
{
result.append( relation );
}
}
}
return result;
}

View File

@ -117,6 +117,16 @@ class CORE_EXPORT QgsRelationManager : public QObject
*/
QList<QgsRelation> referencedRelations( QgsVectorLayer *layer = nullptr ) const;
/**
* Discover all the relations available from the current layers.
*
* @param existingRelations the existing relations to filter them out
* @param layers the current layers
* @return the list of discovered relations
* @note added in QGIS 3.0
*/
static QList<QgsRelation> discoverRelations( const QList<QgsRelation>& existingRelations, const QList<QgsVectorLayer*>& layers );
signals:
/** This signal is emitted when the relations were loaded after reading a project */
void relationsLoaded();

View File

@ -718,3 +718,8 @@ QgsGeometry* QgsVectorDataProvider::convertToProviderType( const QgsGeometry& ge
}
QStringList QgsVectorDataProvider::smEncodings;
QList<QgsRelation> QgsVectorDataProvider::discoverRelations( const QgsVectorLayer*, const QList<QgsVectorLayer*>& ) const
{
return QList<QgsRelation>();
}

View File

@ -28,6 +28,7 @@ class QTextCodec;
#include "qgsfeature.h"
#include "qgsaggregatecalculator.h"
#include "qgsmaplayerdependency.h"
#include "qgsrelation.h"
typedef QList<int> QgsAttributeList;
typedef QSet<int> QgsAttributeIds;
@ -433,6 +434,15 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider
*/
virtual QSet<QgsMapLayerDependency> dependencies() const;
/**
* Discover the available relations with the given layers.
* @param self the layer using this data provider.
* @param layers the other layers.
* @return the list of N-1 relations from this provider.
* @note added in QGIS 3.0
*/
virtual QList<QgsRelation> discoverRelations( const QgsVectorLayer* self, const QList<QgsVectorLayer*>& layers ) const;
signals:
/** Signals an error in this provider */
void raiseError( const QString& msg );

View File

@ -24,6 +24,7 @@
#include <qgsrectangle.h>
#include <qgscoordinatereferencesystem.h>
#include <qgseditformconfig.h>
#include <qgsvectorlayer.h>
#include <QMessageBox>
@ -3907,6 +3908,84 @@ QVariant QgsPostgresProvider::convertValue( QVariant::Type type, QVariant::Type
}
}
QList<QgsVectorLayer*> QgsPostgresProvider::searchLayers( const QList<QgsVectorLayer*>& layers, const QString& connectionInfo, const QString& schema, const QString& tableName )
{
QList<QgsVectorLayer*> result;
Q_FOREACH ( QgsVectorLayer* layer, layers )
{
const QgsPostgresProvider* pgProvider = qobject_cast<QgsPostgresProvider*>( layer->dataProvider() );
if ( pgProvider &&
pgProvider->mUri.connectionInfo( false ) == connectionInfo && pgProvider->mSchemaName == schema && pgProvider->mTableName == tableName )
{
result.append( layer );
}
}
return result;
}
QList<QgsRelation> QgsPostgresProvider::discoverRelations( const QgsVectorLayer* self, const QList<QgsVectorLayer*>& layers ) const
{
QList<QgsRelation> result;
QString sql(
"SELECT RC.CONSTRAINT_NAME, KCU1.COLUMN_NAME, KCU2.CONSTRAINT_SCHEMA, KCU2.TABLE_NAME, KCU2.COLUMN_NAME, KCU1.ORDINAL_POSITION "
"FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS RC "
"INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU1 "
"ON KCU1.CONSTRAINT_CATALOG = RC.CONSTRAINT_CATALOG AND KCU1.CONSTRAINT_SCHEMA = RC.CONSTRAINT_SCHEMA AND KCU1.CONSTRAINT_NAME = RC.CONSTRAINT_NAME "
"INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU2 "
"ON KCU2.CONSTRAINT_CATALOG = RC.UNIQUE_CONSTRAINT_CATALOG AND KCU2.CONSTRAINT_SCHEMA = RC.UNIQUE_CONSTRAINT_SCHEMA AND KCU2.CONSTRAINT_NAME = RC.UNIQUE_CONSTRAINT_NAME "
"AND KCU2.ORDINAL_POSITION = KCU1.ORDINAL_POSITION "
"WHERE KCU1.CONSTRAINT_SCHEMA=" + QgsPostgresConn::quotedValue( mSchemaName ) + " AND KCU1.TABLE_NAME=" + QgsPostgresConn::quotedValue( mTableName ) +
"ORDER BY KCU1.ORDINAL_POSITION"
);
QgsPostgresResult sqlResult( connectionRO()->PQexec( sql ) );
if ( sqlResult.PQresultStatus() != PGRES_TUPLES_OK )
{
QgsLogger::warning( "Error getting the foreign keys of " + mTableName );
return result;
}
int nbFound = 0;
for ( int row = 0; row < sqlResult.PQntuples(); ++row )
{
const QString name = sqlResult.PQgetvalue( row, 0 );
const QString fkColumn = sqlResult.PQgetvalue( row, 1 );
const QString refSchema = sqlResult.PQgetvalue( row, 2 );
const QString refTable = sqlResult.PQgetvalue( row, 3 );
const QString refColumn = sqlResult.PQgetvalue( row, 4 );
const QString position = sqlResult.PQgetvalue( row, 5 );
if ( position == "1" )
{ // first reference field => try to find if we have layers for the referenced table
const QList<QgsVectorLayer*> foundLayers = searchLayers( layers, mUri.connectionInfo( false ), refSchema, refTable );
Q_FOREACH ( const QgsVectorLayer* foundLayer, foundLayers )
{
QgsRelation relation;
relation.setRelationName( name );
relation.setReferencingLayer( self->id() );
relation.setReferencedLayer( foundLayer->id() );
relation.addFieldPair( fkColumn, refColumn );
relation.generateId();
if ( relation.isValid() )
{
result.append( relation );
++nbFound;
}
else
{
QgsLogger::warning( "Invalid relation for " + name );
}
}
}
else
{ // multi reference field => add the field pair to all the referenced layers found
for ( int i = 0; i < nbFound; ++i )
{
result[result.size() - 1 - i].addFieldPair( fkColumn, refColumn );
}
}
}
return result;
}
/**
* Class factory to return a pointer to a newly created
* QgsPostgresProvider object

View File

@ -258,6 +258,8 @@ class QgsPostgresProvider : public QgsVectorDataProvider
*/
static QVariant convertValue( QVariant::Type type, QVariant::Type subType, const QString& value );
virtual QList<QgsRelation> discoverRelations( const QgsVectorLayer* self, const QList<QgsVectorLayer*>& layers ) const override;
signals:
/**
* This is emitted whenever the worker thread has fully calculated the
@ -340,6 +342,11 @@ class QgsPostgresProvider : public QgsVectorDataProvider
*/
QgsPostgresPrimaryKeyType pkType( const QgsField& fld ) const;
/**
* Search all the layers using the given table.
*/
static QList<QgsVectorLayer*> searchLayers( const QList<QgsVectorLayer*>& layers, const QString& connectionInfo, const QString& schema, const QString& tableName );
QgsFields mAttributeFields;
QString mDataComment;

View File

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsDiscoverRelationsDlgBase</class>
<widget class="QDialog" name="QgsDiscoverRelationsDlgBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>700</width>
<height>267</height>
</rect>
</property>
<property name="windowTitle">
<string>Discover relations</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTableWidget" name="mRelationsTable">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::MultiSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderShowSortIndicator" stdset="0">
<bool>true</bool>
</attribute>
<column>
<property name="text">
<string>Name</string>
</property>
</column>
<column>
<property name="text">
<string>Referencing Layer</string>
</property>
</column>
<column>
<property name="text">
<string>Referencing Field</string>
</property>
</column>
<column>
<property name="text">
<string>Referenced Layer</string>
</property>
</column>
<column>
<property name="text">
<string>Referenced Field</string>
</property>
</column>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="mButtonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>mButtonBox</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>mButtonBox</sender>
<signal>accepted()</signal>
<receiver>QgsDiscoverRelationsDlgBase</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>mButtonBox</sender>
<signal>rejected()</signal>
<receiver>QgsDiscoverRelationsDlgBase</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -83,6 +83,17 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="mBtnDiscoverRelations">
<property name="text">
<string>Discover Relations</string>
</property>
<property name="icon">
<iconset resource="../../images/images.qrc">
<normaloff>:/images/themes/default/symbologyAdd.svg</normaloff>:/images/themes/default/symbologyAdd.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="mBtnRemoveRelation">
<property name="text">

View File

@ -53,15 +53,15 @@ class TestQgsRelationEditWidget(unittest.TestCase):
if 'QGIS_PGTEST_DB' in os.environ:
cls.dbconn = os.environ['QGIS_PGTEST_DB']
# Create test layer
cls.vl_b = QgsVectorLayer(cls.dbconn + ' sslmode=disable key=\'pk\' table="qgis_test"."books" sql=', 'test', 'postgres')
cls.vl_a = QgsVectorLayer(cls.dbconn + ' sslmode=disable key=\'pk\' table="qgis_test"."authors" sql=', 'test', 'postgres')
cls.vl_link = QgsVectorLayer(cls.dbconn + ' sslmode=disable key=\'pk\' table="qgis_test"."books_authors" sql=', 'test', 'postgres')
cls.vl_b = QgsVectorLayer(cls.dbconn + ' sslmode=disable key=\'pk\' table="qgis_test"."books" sql=', 'books', 'postgres')
cls.vl_a = QgsVectorLayer(cls.dbconn + ' sslmode=disable key=\'pk\' table="qgis_test"."authors" sql=', 'authors', 'postgres')
cls.vl_link = QgsVectorLayer(cls.dbconn + ' sslmode=disable key=\'pk\' table="qgis_test"."books_authors" sql=', 'books_authors', 'postgres')
QgsMapLayerRegistry.instance().addMapLayer(cls.vl_b)
QgsMapLayerRegistry.instance().addMapLayer(cls.vl_a)
QgsMapLayerRegistry.instance().addMapLayer(cls.vl_link)
relMgr = QgsProject.instance().relationManager()
cls.relMgr = QgsProject.instance().relationManager()
cls.rel_a = QgsRelation()
cls.rel_a.setReferencingLayer(cls.vl_link.id())
@ -69,7 +69,7 @@ class TestQgsRelationEditWidget(unittest.TestCase):
cls.rel_a.addFieldPair('fk_author', 'pk')
cls.rel_a.setRelationId('rel_a')
assert(cls.rel_a.isValid())
relMgr.addRelation(cls.rel_a)
cls.relMgr.addRelation(cls.rel_a)
cls.rel_b = QgsRelation()
cls.rel_b.setReferencingLayer(cls.vl_link.id())
@ -77,7 +77,7 @@ class TestQgsRelationEditWidget(unittest.TestCase):
cls.rel_b.addFieldPair('fk_book', 'pk')
cls.rel_b.setRelationId('rel_b')
assert(cls.rel_b.isValid())
relMgr.addRelation(cls.rel_b)
cls.relMgr.addRelation(cls.rel_b)
# Our mock QgsVectorLayerTools, that allow injecting data where user input is expected
cls.vltools = VlTools()
@ -125,7 +125,7 @@ class TestQgsRelationEditWidget(unittest.TestCase):
self.assertEqual(self.table_view.model().rowCount(), 4)
@unittest.expectedFailure(os.environ['QT_VERSION'] == '4' and os.environ['TRAVIS_OS_NAME'] == 'linux') # It's probably not related to this variables at all, but that's the closest we can get to the real source of this problem at the moment...
@unittest.expectedFailure(os.environ.get('QT_VERSION', '5') == '4' and os.environ.get('TRAVIS_OS_NAME', '') == 'linux') # It's probably not related to this variables at all, but that's the closest we can get to the real source of this problem at the moment...
def test_add_feature(self):
"""
Check if a new related feature is added
@ -201,6 +201,31 @@ class TestQgsRelationEditWidget(unittest.TestCase):
self.assertEqual(2, self.table_view.model().rowCount())
def test_discover_relations(self):
"""
Test the automatic discovery of relations
"""
relations = self.relMgr.discoverRelations([], [self.vl_a, self.vl_b, self.vl_link])
relations = {r.name(): r for r in relations}
self.assertEqual({'books_authors_fk_book_fkey', 'books_authors_fk_author_fkey'}, set(relations.keys()))
ba2b = relations['books_authors_fk_book_fkey']
self.assertTrue(ba2b.isValid())
self.assertEqual('books_authors', ba2b.referencingLayer().name())
self.assertEqual('books', ba2b.referencedLayer().name())
self.assertEqual([0], ba2b.referencingFields())
self.assertEqual([0], ba2b.referencedFields())
ba2a = relations['books_authors_fk_author_fkey']
self.assertTrue(ba2a.isValid())
self.assertEqual('books_authors', ba2a.referencingLayer().name())
self.assertEqual('authors', ba2a.referencedLayer().name())
self.assertEqual([1], ba2a.referencingFields())
self.assertEqual([0], ba2a.referencedFields())
self.assertEqual([], self.relMgr.discoverRelations([self.rel_a, self.rel_b], [self.vl_a, self.vl_b, self.vl_link]))
self.assertEqual(1, len(self.relMgr.discoverRelations([], [self.vl_a, self.vl_link])))
def startTransaction(self):
"""
Start a new transaction and set all layers into transaction mode.