diff --git a/images/images.qrc b/images/images.qrc
index d301528e2dc..9dc459e01c8 100644
--- a/images/images.qrc
+++ b/images/images.qrc
@@ -591,6 +591,7 @@
themes/default/propertyicons/system.svg
themes/default/propertyicons/transparency.svg
themes/default/propertyicons/spacer.svg
+ themes/default/propertyicons/relations.svg
themes/default/rendererCategorizedSymbol.svg
themes/default/rendererGraduatedSymbol.svg
themes/default/rendererNullSymbol.svg
diff --git a/images/themes/default/propertyicons/relations.svg b/images/themes/default/propertyicons/relations.svg
new file mode 100644
index 00000000000..1651d1c041c
--- /dev/null
+++ b/images/themes/default/propertyicons/relations.svg
@@ -0,0 +1,31 @@
+
diff --git a/python/core/auto_generated/qgsmaplayer.sip.in b/python/core/auto_generated/qgsmaplayer.sip.in
index 79f8601c779..0e2d1bd727d 100644
--- a/python/core/auto_generated/qgsmaplayer.sip.in
+++ b/python/core/auto_generated/qgsmaplayer.sip.in
@@ -91,6 +91,7 @@ This is the base class for all map layer types (vector, raster).
Rendering,
CustomProperties,
GeometryOptions,
+ Relations,
AllStyleCategories
};
typedef QFlags StyleCategories;
diff --git a/python/core/auto_generated/qgsrelationmanager.sip.in b/python/core/auto_generated/qgsrelationmanager.sip.in
index fb084ca6378..581b87d7c90 100644
--- a/python/core/auto_generated/qgsrelationmanager.sip.in
+++ b/python/core/auto_generated/qgsrelationmanager.sip.in
@@ -104,7 +104,7 @@ Gets all relations where the specified layer (and field) is the referencing part
:return: A list of relations matching the given layer and fieldIdx.
%End
- QList referencedRelations( QgsVectorLayer *layer = 0 ) const;
+ QList referencedRelations( const QgsVectorLayer *layer = 0 ) const;
%Docstring
Gets all relations where this layer is the referenced part (i.e. the parent table with the primary key being referenced from another layer).
diff --git a/python/core/auto_generated/qgsvectorlayer.sip.in b/python/core/auto_generated/qgsvectorlayer.sip.in
index a9b6d1d4aaa..294daa263ef 100644
--- a/python/core/auto_generated/qgsvectorlayer.sip.in
+++ b/python/core/auto_generated/qgsvectorlayer.sip.in
@@ -2014,6 +2014,8 @@ Returns the layer's relations, where the foreign key is on this layer.
:return: A list of relations
%End
+
+
QgsVectorLayerEditBuffer *editBuffer();
%Docstring
Buffer with uncommitted editing operations. Only valid after editing has been turned on.
diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp
index 5301c30e7f9..5107e351ee0 100644
--- a/src/app/qgisapp.cpp
+++ b/src/app/qgisapp.cpp
@@ -75,9 +75,11 @@
#include "qgssettings.h"
#include "qgsnetworkaccessmanager.h"
+#include "qgsrelationmanager.h"
#include "qgsapplication.h"
#include "qgslayerstylingwidget.h"
#include "qgstaskmanager.h"
+#include "qgsweakrelation.h"
#include "qgsziputils.h"
#include "qgsbrowserguimodel.h"
#include "qgsvectorlayerjoinbuffer.h"
@@ -667,13 +669,24 @@ void QgisApp::onActiveLayerChanged( QgsMapLayer *layer )
void QgisApp::vectorLayerStyleLoaded( QgsMapLayer::StyleCategories categories )
{
- if ( categories.testFlag( QgsMapLayer::StyleCategory::Forms ) )
+
+ QgsVectorLayer *vl = qobject_cast( sender() );
+
+ if ( vl && vl->isValid( ) )
{
- QgsVectorLayer *vl = qobject_cast( sender() );
- if ( vl && vl->isValid( ) )
+
+ // Check broken dependencies in forms
+ if ( categories.testFlag( QgsMapLayer::StyleCategory::Forms ) )
{
- checkVectorLayerDependencies( vl );
+ resolveVectorLayerDependencies( vl );
}
+
+ // Check broken relations and try to restore them
+ if ( categories.testFlag( QgsMapLayer::StyleCategory::Relations ) )
+ {
+ resolveVectorLayerWeakRelations( vl );
+ }
+
}
}
@@ -1995,25 +2008,85 @@ QgsMessageBar *QgisApp::visibleMessageBar()
}
}
-QList QgisApp::findBrokenWidgetDependencies( QgsVectorLayer *vl )
+const QList QgisApp::findBrokenLayerDependencies( QgsVectorLayer *vl, QgsMapLayer::StyleCategories categories ) const
{
QList brokenDependencies;
- // Check for missing layer widget dependencies
- for ( int i = 0; i < vl->fields().count(); i++ )
+
+ if ( categories.testFlag( QgsMapLayer::StyleCategory::Forms ) )
{
- const QgsEditorWidgetSetup setup = QgsGui::editorWidgetRegistry()->findBest( vl, vl->fields().field( i ).name() );
- QgsFieldFormatter *fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
- if ( fieldFormatter )
+ for ( int i = 0; i < vl->fields().count(); i++ )
{
- const auto constDependencies { fieldFormatter->layerDependencies( setup.config() ) };
- for ( const QgsVectorLayerRef &dependency : constDependencies )
+ const QgsEditorWidgetSetup setup = QgsGui::editorWidgetRegistry()->findBest( vl, vl->fields().field( i ).name() );
+ QgsFieldFormatter *fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
+ if ( fieldFormatter )
{
- const QgsVectorLayer *depVl { QgsVectorLayerRef( dependency ).resolveWeakly(
- QgsProject::instance(),
- QgsVectorLayerRef::MatchType::Name ) };
- if ( ! depVl || ! depVl->isValid() )
+ const QList constDependencies { fieldFormatter->layerDependencies( setup.config() ) };
+ for ( const QgsVectorLayerRef &dependency : constDependencies )
{
- brokenDependencies.append( dependency );
+ const QgsVectorLayer *depVl { QgsVectorLayerRef( dependency ).resolveWeakly(
+ QgsProject::instance(),
+ QgsVectorLayerRef::MatchType::Name ) };
+ if ( ! depVl || ! depVl->isValid() )
+ {
+ brokenDependencies.append( dependency );
+ }
+ }
+ }
+ }
+ }
+
+ if ( categories.testFlag( QgsMapLayer::StyleCategory::Relations ) )
+ {
+ // Check for layer weak relations
+ const QList constWeakRelations { vl->weakRelations() };
+ for ( const QgsWeakRelation &rel : constWeakRelations )
+ {
+ QgsRelation relation { rel.resolvedRelation( QgsProject::instance(), QgsVectorLayerRef::MatchType::Name ) };
+ QgsVectorLayerRef dependency;
+ bool found = false;
+ if ( ! relation.isValid() )
+ {
+ // This is the big question: do we really
+ // want to automatically load the referencing layer(s) too?
+ // This could potentially lead to a cascaded load of a
+ // long list of layers.
+ // The code is in place but let's leave it disabled for now.
+ if ( relation.referencedLayer() == vl )
+ {
+ // Do nothing because vl is the referenced layer
+#if 0
+ dependency = rel.referencingLayer();
+ found = true;
+#endif
+ }
+ else if ( relation.referencingLayer() == vl )
+ {
+ dependency = rel.referencedLayer();
+ found = true;
+ }
+ else
+ {
+ // Something wrong is going on here, maybe this relation
+ // does not really apply to this layer?
+ QgsMessageLog::logMessage( tr( "None of the layers in the relation stored in the style match the current layer, skipping relation id: %1." ).arg( relation.id() ) );
+ }
+
+ if ( found )
+ {
+ // Make sure we don't add it twice
+ bool refFound = false;
+ for ( const QgsVectorLayerRef &otherRef : qgis::as_const( brokenDependencies ) )
+ {
+ if ( dependency.layerId == otherRef.layerId || ( dependency.source == otherRef.source && dependency.provider == otherRef.provider ) )
+ {
+ refFound = true;
+ break;
+ }
+ }
+ if ( ! refFound )
+ {
+ brokenDependencies.append( dependency );
+ }
}
}
}
@@ -2021,11 +2094,11 @@ QList QgisApp::findBrokenWidgetDependencies( QgsVectorLayer *
return brokenDependencies;
}
-void QgisApp::checkVectorLayerDependencies( QgsVectorLayer *vl )
+void QgisApp::resolveVectorLayerDependencies( QgsVectorLayer *vl, QgsMapLayer::StyleCategories categories )
{
if ( vl && vl->isValid() )
{
- const auto constDependencies { findBrokenWidgetDependencies( vl ) };
+ const auto constDependencies { findBrokenLayerDependencies( vl, categories ) };
for ( const QgsVectorLayerRef &dependency : constDependencies )
{
// try to aggressively resolve the broken dependencies
@@ -2115,6 +2188,31 @@ void QgisApp::checkVectorLayerDependencies( QgsVectorLayer *vl )
}
}
+void QgisApp::resolveVectorLayerWeakRelations( QgsVectorLayer *vectorLayer )
+{
+ if ( vectorLayer && vectorLayer->isValid() )
+ {
+ const QList constWeakRelations { vectorLayer->weakRelations( ) };
+ for ( const QgsWeakRelation &rel : constWeakRelations )
+ {
+ QgsRelation relation { rel.resolvedRelation( QgsProject::instance(), QgsVectorLayerRef::MatchType::Name ) };
+ if ( relation.isValid() )
+ {
+ // Avoid duplicates
+ const QList constRelations { QgsProject::instance()->relationManager()->relations().values() };
+ for ( const QgsRelation &other : constRelations )
+ {
+ if ( relation.hasEqualDefinition( other ) )
+ {
+ continue;
+ }
+ }
+ QgsProject::instance()->relationManager()->addRelation( relation );
+ }
+ }
+ }
+}
+
void QgisApp::dataSourceManager( const QString &pageName )
{
if ( ! mDataSourceManagerDialog )
@@ -6506,7 +6604,7 @@ bool QgisApp::addProject( const QString &projectFile )
{
if ( vl->isValid() )
{
- checkVectorLayerDependencies( vl );
+ resolveVectorLayerDependencies( vl );
}
}
diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h
index bd13868a141..b6afb1bbd13 100644
--- a/src/app/qgisapp.h
+++ b/src/app/qgisapp.h
@@ -2025,15 +2025,31 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
QgsMessageBar *visibleMessageBar();
/**
- * Searches for layer widget dependencies
- * \return a list of weak references to broken widget layer dependencies
+ * Searches for layer dependencies by querying the form widgets and the
+ * \a vectorLayer itself for broken relations. Style \a categories can be
+ * used to limit the search to one or more of the currently implemented search
+ * categories ("Forms" for the form widgets and "Relations" for layer weak relations).
+ * \return a list of weak references to broken layer dependencies
*/
- QList< QgsVectorLayerRef > findBrokenWidgetDependencies( QgsVectorLayer *vectorLayer );
+ const QList< QgsVectorLayerRef > findBrokenLayerDependencies( QgsVectorLayer *vectorLayer,
+ QgsMapLayer::StyleCategories categories = QgsMapLayer::StyleCategory::AllStyleCategories ) const;
/**
- * Scans the \a vectorLayer for broken dependencies and warns the user
+ * Scans the \a vectorLayer for broken dependencies and automatically
+ * try to load the missing layers, users are notified about the operation
+ * result. Style \a categories can be
+ * used to exclude one of the currently implemented search categories
+ * ("Forms" for the form widgets and "Relations" for layer weak relations).
*/
- void checkVectorLayerDependencies( QgsVectorLayer *vectorLayer );
+ void resolveVectorLayerDependencies( QgsVectorLayer *vectorLayer,
+ QgsMapLayer::StyleCategories categories = QgsMapLayer::AllStyleCategories );
+
+ /**
+ * Scans the \a vectorLayer for weak relations and automatically
+ * try to resolve and create the broken relations.
+ */
+ void resolveVectorLayerWeakRelations( QgsVectorLayer *vectorLayer );
+
QgisAppStyleSheet *mStyleSheetBuilder = nullptr;
diff --git a/src/app/qgsmaplayerstylecategoriesmodel.cpp b/src/app/qgsmaplayerstylecategoriesmodel.cpp
index 2f69319f15f..ad879398ae5 100644
--- a/src/app/qgsmaplayerstylecategoriesmodel.cpp
+++ b/src/app/qgsmaplayerstylecategoriesmodel.cpp
@@ -77,7 +77,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
switch ( category )
{
- case QgsMapLayer::LayerConfiguration:
+ case QgsMapLayer::StyleCategory::LayerConfiguration:
switch ( role )
{
case Qt::DisplayRole:
@@ -88,7 +88,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/system.svg" ) );
}
break;
- case QgsMapLayer::Symbology:
+ case QgsMapLayer::StyleCategory::Symbology:
switch ( role )
{
case Qt::DisplayRole:
@@ -99,7 +99,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/symbology.svg" ) );
}
break;
- case QgsMapLayer::Symbology3D:
+ case QgsMapLayer::StyleCategory::Symbology3D:
switch ( role )
{
case Qt::DisplayRole:
@@ -110,7 +110,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/3d.svg" ) );
}
break;
- case QgsMapLayer::Labeling:
+ case QgsMapLayer::StyleCategory::Labeling:
switch ( role )
{
case Qt::DisplayRole:
@@ -121,7 +121,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/labels.svg" ) );
}
break;
- case QgsMapLayer::Fields:
+ case QgsMapLayer::StyleCategory::Fields:
switch ( role )
{
case Qt::DisplayRole:
@@ -132,7 +132,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/mSourceFields.svg" ) );
}
break;
- case QgsMapLayer::Forms:
+ case QgsMapLayer::StyleCategory::Forms:
switch ( role )
{
case Qt::DisplayRole:
@@ -143,7 +143,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/mActionFormView.svg" ) );
}
break;
- case QgsMapLayer::Actions:
+ case QgsMapLayer::StyleCategory::Actions:
switch ( role )
{
case Qt::DisplayRole:
@@ -154,7 +154,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/action.svg" ) );
}
break;
- case QgsMapLayer::MapTips:
+ case QgsMapLayer::StyleCategory::MapTips:
switch ( role )
{
case Qt::DisplayRole:
@@ -165,7 +165,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/display.svg" ) );
}
break;
- case QgsMapLayer::Diagrams:
+ case QgsMapLayer::StyleCategory::Diagrams:
switch ( role )
{
case Qt::DisplayRole:
@@ -176,7 +176,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/diagram.svg" ) );
}
break;
- case QgsMapLayer::AttributeTable:
+ case QgsMapLayer::StyleCategory::AttributeTable:
switch ( role )
{
case Qt::DisplayRole:
@@ -187,7 +187,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/mActionOpenTable.svg" ) );
}
break;
- case QgsMapLayer::Rendering:
+ case QgsMapLayer::StyleCategory::Rendering:
switch ( role )
{
case Qt::DisplayRole:
@@ -198,7 +198,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/rendering.svg" ) );
}
break;
- case QgsMapLayer::CustomProperties:
+ case QgsMapLayer::StyleCategory::CustomProperties:
switch ( role )
{
case Qt::DisplayRole:
@@ -209,7 +209,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/mActionOptions.svg" ) );
}
break;
- case QgsMapLayer::GeometryOptions:
+ case QgsMapLayer::StyleCategory::GeometryOptions:
switch ( role )
{
case Qt::DisplayRole:
@@ -220,7 +220,18 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/digitizing.svg" ) );
}
break;
- case QgsMapLayer::AllStyleCategories:
+ case QgsMapLayer::StyleCategory::Relations:
+ switch ( role )
+ {
+ case Qt::DisplayRole:
+ return tr( "Relations" );
+ case Qt::ToolTipRole:
+ return tr( "Relations with other layers" );
+ case Qt::DecorationRole:
+ return QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/relations.svg" ) );
+ }
+ break;
+ case QgsMapLayer::StyleCategory::AllStyleCategories:
switch ( role )
{
case Qt::DisplayRole:
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 66ec28b7708..059098f2738 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -339,6 +339,7 @@ SET(QGIS_CORE_SRCS
qgsreadwritecontext.cpp
qgsreadwritelocker.cpp
qgsrelation.cpp
+ qgsweakrelation.cpp
qgsrelationmanager.cpp
qgsrenderchecker.cpp
qgsrendercontext.cpp
@@ -840,6 +841,7 @@ SET(QGIS_CORE_HDRS
qgsreadwritecontext.h
qgsreadwritelocker.h
qgsrelation.h
+ qgsweakrelation.h
qgsrelationmanager.h
qgsrenderchecker.h
qgsrendercontext.h
diff --git a/src/core/qgsmaplayer.cpp b/src/core/qgsmaplayer.cpp
index 0fcc29a064f..8d34cdf9933 100644
--- a/src/core/qgsmaplayer.cpp
+++ b/src/core/qgsmaplayer.cpp
@@ -267,15 +267,6 @@ bool QgsMapLayer::readLayerXml( const QDomElement &layerElement, QgsReadWriteCon
QgsReadWriteContextCategoryPopper p = context.enterCategory( tr( "Layer" ), mne.text() );
- // now let the children grab what they need from the Dom node.
- layerError = !readXml( layerElement, context );
-
- // overwrite CRS with what we read from project file before the raster/vector
- // file reading functions changed it. They will if projections is specified in the file.
- // FIXME: is this necessary?
- QgsCoordinateReferenceSystem::setCustomCrsValidation( savedValidation );
- mCRS = savedCRS;
-
// the internal name is just the data source basename
//QFileInfo dataSourceFileInfo( mDataSource );
//internalName = dataSourceFileInfo.baseName();
@@ -296,7 +287,6 @@ bool QgsMapLayer::readLayerXml( const QDomElement &layerElement, QgsReadWriteCon
setRefreshOnNofifyMessage( layerElement.attribute( QStringLiteral( "refreshOnNotifyMessage" ), QString() ) );
setRefreshOnNotifyEnabled( layerElement.attribute( QStringLiteral( "refreshOnNotifyEnabled" ), QStringLiteral( "0" ) ).toInt() );
-
// set name
mnl = layerElement.namedItem( QStringLiteral( "layername" ) );
mne = mnl.toElement();
@@ -304,6 +294,15 @@ bool QgsMapLayer::readLayerXml( const QDomElement &layerElement, QgsReadWriteCon
//name can be translated
setName( context.projectTranslator()->translate( QStringLiteral( "project:layers:%1" ).arg( layerElement.namedItem( QStringLiteral( "id" ) ).toElement().text() ), mne.text() ) );
+ // now let the children grab what they need from the Dom node.
+ layerError = !readXml( layerElement, context );
+
+ // overwrite CRS with what we read from project file before the raster/vector
+ // file reading functions changed it. They will if projections is specified in the file.
+ // FIXME: is this necessary? Yes, it is (autumn 2019)
+ QgsCoordinateReferenceSystem::setCustomCrsValidation( savedValidation );
+ mCRS = savedCRS;
+
//short name
QDomElement shortNameElem = layerElement.firstChildElement( QStringLiteral( "shortname" ) );
if ( !shortNameElem.isNull() )
diff --git a/src/core/qgsmaplayer.h b/src/core/qgsmaplayer.h
index ec431c10600..d3e454c5a89 100644
--- a/src/core/qgsmaplayer.h
+++ b/src/core/qgsmaplayer.h
@@ -161,8 +161,9 @@ class CORE_EXPORT QgsMapLayer : public QObject
Rendering = 1 << 10, //!< Rendering: scale visibility, simplify method, opacity
CustomProperties = 1 << 11, //!< Custom properties (by plugins for instance)
GeometryOptions = 1 << 12, //!< Geometry validation configuration
+ Relations = 1 << 13, //!< Relations
AllStyleCategories = LayerConfiguration | Symbology | Symbology3D | Labeling | Fields | Forms | Actions |
- MapTips | Diagrams | AttributeTable | Rendering | CustomProperties | GeometryOptions,
+ MapTips | Diagrams | AttributeTable | Rendering | CustomProperties | GeometryOptions | Relations,
};
Q_ENUM( StyleCategory )
Q_DECLARE_FLAGS( StyleCategories, StyleCategory )
diff --git a/src/core/qgsrelationmanager.cpp b/src/core/qgsrelationmanager.cpp
index 5469f103050..404a9560713 100644
--- a/src/core/qgsrelationmanager.cpp
+++ b/src/core/qgsrelationmanager.cpp
@@ -27,8 +27,11 @@ QgsRelationManager::QgsRelationManager( QgsProject *project )
{
if ( mProject )
{
+ // TODO: QGIS 4 remove: relations are now stored with the layer style
connect( project, &QgsProject::readProjectWithContext, this, &QgsRelationManager::readProject );
+ // TODO: QGIS 4 remove: relations are now stored with the layer style
connect( project, &QgsProject::writeProject, this, &QgsRelationManager::writeProject );
+
connect( project, &QgsProject::layersRemoved, this, &QgsRelationManager::layersRemoved );
}
}
@@ -148,7 +151,7 @@ QList QgsRelationManager::referencingRelations( const QgsVectorLaye
return relations;
}
-QList QgsRelationManager::referencedRelations( QgsVectorLayer *layer ) const
+QList QgsRelationManager::referencedRelations( const QgsVectorLayer *layer ) const
{
if ( !layer )
{
diff --git a/src/core/qgsrelationmanager.h b/src/core/qgsrelationmanager.h
index 0f5f48565ff..53b76f5ff13 100644
--- a/src/core/qgsrelationmanager.h
+++ b/src/core/qgsrelationmanager.h
@@ -121,7 +121,7 @@ class CORE_EXPORT QgsRelationManager : public QObject
*
* \returns A list of relations where the specified layer is the referenced part.
*/
- QList referencedRelations( QgsVectorLayer *layer = nullptr ) const;
+ QList referencedRelations( const QgsVectorLayer *layer = nullptr ) const;
/**
* Discover all the relations available from the current layers.
diff --git a/src/core/qgsvectorlayer.cpp b/src/core/qgsvectorlayer.cpp
index baa946d7a8a..854d45fcd7b 100644
--- a/src/core/qgsvectorlayer.cpp
+++ b/src/core/qgsvectorlayer.cpp
@@ -67,6 +67,7 @@
#include "qgsproviderregistry.h"
#include "qgsrectangle.h"
#include "qgsrelationmanager.h"
+#include "qgsweakrelation.h"
#include "qgsrendercontext.h"
#include "qgsvectordataprovider.h"
#include "qgsvectorlayereditbuffer.h"
@@ -2026,6 +2027,80 @@ bool QgsVectorLayer::readSymbology( const QDomNode &layerNode, QString &errorMes
updateFields();
}
+ if ( categories.testFlag( Relations ) )
+ {
+
+ const QgsPathResolver resolver { QgsProject::instance()->pathResolver() };
+
+ // Restore referenced layers: relations where "this" is the child layer (the referencing part, that holds the FK)
+ QDomNodeList referencedLayersNodeList = layerNode.toElement().elementsByTagName( QStringLiteral( "referencedLayers" ) );
+ if ( referencedLayersNodeList.size() > 0 )
+ {
+ const QDomNodeList relationNodes { referencedLayersNodeList.at( 0 ).childNodes() };
+ for ( int i = 0; i < relationNodes.length(); ++i )
+ {
+ const QDomElement relationElement = relationNodes.at( i ).toElement();
+ QList fieldPairs;
+ const QDomNodeList fieldPairNodes { relationElement.elementsByTagName( QStringLiteral( "fieldPair" ) ) };
+ for ( int j = 0; j < fieldPairNodes.length(); ++j )
+ {
+ const QDomElement fieldPairElement = fieldPairNodes.at( i ).toElement();
+ fieldPairs.push_back( { fieldPairElement.attribute( QStringLiteral( "referencing" ) ),
+ fieldPairElement.attribute( QStringLiteral( "referenced" ) ) } );
+ }
+ mWeakRelations.push_back( QgsWeakRelation { relationElement.attribute( QStringLiteral( "id" ) ),
+ relationElement.attribute( QStringLiteral( "name" ) ),
+ static_cast( relationElement.attribute( QStringLiteral( "strength" ) ).toInt() ),
+ // Referencing
+ id(),
+ name(),
+ resolver.writePath( publicSource() ),
+ providerType(),
+ // Referenced
+ relationElement.attribute( QStringLiteral( "layerId" ) ),
+ relationElement.attribute( QStringLiteral( "layerName" ) ),
+ relationElement.attribute( QStringLiteral( "dataSource" ) ),
+ relationElement.attribute( QStringLiteral( "providerKey" ) ),
+ fieldPairs
+ } );
+ }
+ }
+
+ // Restore referencing layers: relations where "this" is the parent layer (the referenced part where the FK points to)
+ QDomNodeList referencingLayersNodeList = layerNode.toElement().elementsByTagName( QStringLiteral( "referencingLayers" ) );
+ if ( referencingLayersNodeList.size() > 0 )
+ {
+ const QDomNodeList relationNodes { referencingLayersNodeList.at( 0 ).childNodes() };
+ for ( int i = 0; i < relationNodes.length(); ++i )
+ {
+ const QDomElement relationElement = relationNodes.at( i ).toElement();
+ QList fieldPairs;
+ const QDomNodeList fieldPairNodes { relationElement.elementsByTagName( QStringLiteral( "fieldPair" ) ) };
+ for ( int j = 0; j < fieldPairNodes.length(); ++j )
+ {
+ const QDomElement fieldPairElement = fieldPairNodes.at( i ).toElement();
+ fieldPairs.push_back( { fieldPairElement.attribute( QStringLiteral( "referencing" ) ),
+ fieldPairElement.attribute( QStringLiteral( "referenced" ) ) } );
+ }
+ mWeakRelations.push_back( QgsWeakRelation { relationElement.attribute( QStringLiteral( "id" ) ),
+ relationElement.attribute( QStringLiteral( "name" ) ),
+ static_cast( relationElement.attribute( QStringLiteral( "strength" ) ).toInt() ),
+ // Referencing
+ relationElement.attribute( QStringLiteral( "layerId" ) ),
+ relationElement.attribute( QStringLiteral( "layerName" ) ),
+ relationElement.attribute( QStringLiteral( "dataSource" ) ),
+ relationElement.attribute( QStringLiteral( "providerKey" ) ),
+ // Referenced
+ id(),
+ name(),
+ resolver.writePath( publicSource() ),
+ providerType(),
+ fieldPairs
+ } );
+ }
+ }
+ }
+
QDomElement layerElement = layerNode.toElement();
readCommonStyle( layerElement, context, categories );
@@ -2445,6 +2520,74 @@ bool QgsVectorLayer::writeSymbology( QDomNode &node, QDomDocument &doc, QString
if ( categories.testFlag( GeometryOptions ) )
mGeometryOptions->writeXml( node );
+
+ // Relation information for both referenced and referencing sides
+ if ( categories.testFlag( Relations ) )
+ {
+
+ const QgsPathResolver resolver { QgsProject::instance()->pathResolver() };
+
+ // Store referenced layers: relations where "this" is the child layer (the referencing part, that holds the FK)
+ QDomElement referencedLayersElement = doc.createElement( QStringLiteral( "referencedLayers" ) );
+ node.appendChild( referencedLayersElement );
+
+ const auto constReferencingRelations { QgsProject::instance()->relationManager()->referencingRelations( this ) };
+ for ( const auto &rel : constReferencingRelations )
+ {
+
+ QDomElement relationElement = doc.createElement( QStringLiteral( "relation" ) );
+ referencedLayersElement.appendChild( relationElement );
+
+ relationElement.setAttribute( QStringLiteral( "id" ), rel.id() );
+ relationElement.setAttribute( QStringLiteral( "name" ), rel.name() );
+ relationElement.setAttribute( QStringLiteral( "strength" ), rel.strength() );
+ relationElement.setAttribute( QStringLiteral( "layerId" ), rel.referencedLayer()->id() );
+ relationElement.setAttribute( QStringLiteral( "layerName" ), rel.referencedLayer()->name() );
+ relationElement.setAttribute( QStringLiteral( "dataSource" ), resolver.writePath( rel.referencedLayer()->publicSource() ) );
+ relationElement.setAttribute( QStringLiteral( "providerKey" ), rel.referencedLayer()->providerType() );
+
+ const QList constFieldPairs { rel.fieldPairs() };
+ for ( const QgsRelation::FieldPair &fp : constFieldPairs )
+ {
+ QDomElement fieldPair = doc.createElement( QStringLiteral( "fieldPair" ) );
+ relationElement.appendChild( fieldPair );
+ fieldPair.setAttribute( QStringLiteral( "referenced" ), fp.referencedField() );
+ fieldPair.setAttribute( QStringLiteral( "referencing" ), fp.referencingField() );
+ }
+
+ }
+
+ // Store referencing layers: relations where "this" is the parent layer (the referenced part where the FK points to)
+ QDomElement referencingLayersElement = doc.createElement( QStringLiteral( "referencingLayers" ) );
+ node.appendChild( referencingLayersElement );
+
+ const auto constReferencedRelations { QgsProject::instance()->relationManager()->referencedRelations( this ) };
+ for ( const auto &rel : constReferencedRelations )
+ {
+
+ QDomElement relationElement = doc.createElement( QStringLiteral( "relation" ) );
+ referencingLayersElement.appendChild( relationElement );
+
+ relationElement.setAttribute( QStringLiteral( "id" ), rel.id() );
+ relationElement.setAttribute( QStringLiteral( "name" ), rel.name() );
+ relationElement.setAttribute( QStringLiteral( "strength" ), rel.strength() );
+ relationElement.setAttribute( QStringLiteral( "layerId" ), rel.referencingLayer()->id() );
+ relationElement.setAttribute( QStringLiteral( "layerName" ), rel.referencingLayer()->name() );
+ relationElement.setAttribute( QStringLiteral( "dataSource" ), resolver.writePath( rel.referencingLayer()->publicSource() ) );
+ relationElement.setAttribute( QStringLiteral( "providerKey" ), rel.referencingLayer()->providerType() );
+
+ const QList constFieldPairs { rel.fieldPairs() };
+ for ( const QgsRelation::FieldPair &fp : constFieldPairs )
+ {
+ QDomElement fieldPair = doc.createElement( QStringLiteral( "fieldPair" ) );
+ relationElement.appendChild( fieldPair );
+ fieldPair.setAttribute( QStringLiteral( "referenced" ), fp.referencedField() );
+ fieldPair.setAttribute( QStringLiteral( "referencing" ), fp.referencingField() );
+ }
+
+ }
+ }
+
if ( categories.testFlag( Fields ) )
{
QDomElement fieldConfigurationElement = doc.createElement( QStringLiteral( "fieldConfiguration" ) );
@@ -4766,6 +4909,11 @@ QList QgsVectorLayer::referencingRelations( int idx ) const
return QgsProject::instance()->relationManager()->referencingRelations( this, idx );
}
+QList QgsVectorLayer::weakRelations() const
+{
+ return mWeakRelations;
+}
+
int QgsVectorLayer::listStylesInDatabase( QStringList &ids, QStringList &names, QStringList &descriptions, QString &msgError )
{
return QgsProviderRegistry::instance()->listStyles( mProviderKey, mDataSource, ids, names, descriptions, msgError );
diff --git a/src/core/qgsvectorlayer.h b/src/core/qgsvectorlayer.h
index 28d4525ba78..b86ca83468b 100644
--- a/src/core/qgsvectorlayer.h
+++ b/src/core/qgsvectorlayer.h
@@ -62,6 +62,7 @@ class QgsMapToPixel;
class QgsRectangle;
class QgsRectangle;
class QgsRelation;
+class QgsWeakRelation;
class QgsRelationManager;
class QgsSingleSymbolRenderer;
class QgsStoredExpressionManager;
@@ -1872,6 +1873,15 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
*/
QList referencingRelations( int idx ) const;
+ /**
+ * Returns the layer's weak relations as specified in the layer's style.
+ * \returns A list of weak relations
+ * \note not available in Python bindings
+ * \since QGIS 3.12
+ */
+ QList weakRelations( ) const SIP_SKIP;
+
+
//! Buffer with uncommitted editing operations. Only valid after editing has been turned on.
Q_INVOKABLE QgsVectorLayerEditBuffer *editBuffer() { return mEditBuffer; }
@@ -2827,6 +2837,8 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
//! To avoid firing multiple time dataChanged signal on circular layer circular dependencies
bool mDataChangedFired = false;
+
+ QList mWeakRelations;
};
diff --git a/src/core/qgsweakrelation.cpp b/src/core/qgsweakrelation.cpp
new file mode 100644
index 00000000000..45782f0f9d5
--- /dev/null
+++ b/src/core/qgsweakrelation.cpp
@@ -0,0 +1,75 @@
+/***************************************************************************
+ qgsweakrelation.cpp - QgsWeakRelation
+
+ ---------------------
+ begin : 5.12.2019
+ copyright : (C) 2019 by Alessandro Pasotti
+ email : elpaso at itopen dot it
+ ***************************************************************************
+ * *
+ * 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 "qgsweakrelation.h"
+
+
+QgsWeakRelation::QgsWeakRelation( const QString &relationId, const QString &relationName, const QgsRelation::RelationStrength strength,
+ const QString &referencingLayerId, const QString &referencingLayerName, const QString &referencingLayerSource, const QString &referencingLayerProviderKey,
+ const QString &referencedLayerId, const QString &referencedLayerName, const QString &referencedLayerSource, const QString &referencedLayerProviderKey,
+ const QList &fieldPairs )
+ : mReferencingLayer( referencingLayerId, referencingLayerName, referencingLayerSource, referencingLayerProviderKey )
+ , mReferencedLayer( referencedLayerId, referencedLayerName, referencedLayerSource, referencedLayerProviderKey )
+ , mRelationId( relationId )
+ , mRelationName( relationName )
+ , mStrength( strength )
+ , mFieldPairs( fieldPairs )
+{
+}
+
+QgsRelation QgsWeakRelation::resolvedRelation( const QgsProject *project, QgsVectorLayerRef::MatchType matchType ) const
+{
+ QgsRelation relation;
+ relation.setId( mRelationId );
+ relation.setName( mRelationName );
+ relation.setStrength( mStrength );
+ QgsVectorLayerRef referencedLayerRef { mReferencedLayer };
+ QgsMapLayer *referencedLayer { referencedLayerRef.resolveWeakly( project, matchType ) };
+ if ( referencedLayer )
+ {
+ relation.setReferencedLayer( referencedLayer->id() );
+ }
+ QgsVectorLayerRef referencingLayerRef { mReferencingLayer };
+ QgsMapLayer *referencingLayer { referencingLayerRef.resolveWeakly( project, matchType ) };
+ if ( referencingLayer )
+ {
+ relation.setReferencingLayer( referencingLayer->id() );
+ }
+ for ( const auto &fp : qgis::as_const( mFieldPairs ) )
+ {
+ relation.addFieldPair( fp );
+ }
+ return relation;
+}
+
+QgsVectorLayerRef QgsWeakRelation::referencingLayer() const
+{
+ return mReferencingLayer;
+}
+
+QgsVectorLayerRef QgsWeakRelation::referencedLayer() const
+{
+ return mReferencedLayer;
+}
+
+QgsRelation::RelationStrength QgsWeakRelation::strength() const
+{
+ return mStrength;
+}
+
+QList QgsWeakRelation::fieldPairs() const
+{
+ return mFieldPairs;
+}
diff --git a/src/core/qgsweakrelation.h b/src/core/qgsweakrelation.h
new file mode 100644
index 00000000000..1f31e0706e0
--- /dev/null
+++ b/src/core/qgsweakrelation.h
@@ -0,0 +1,101 @@
+/***************************************************************************
+ qgsweakrelation.h - QgsWeakRelation
+
+ ---------------------
+ begin : 5.12.2019
+ copyright : (C) 2019 by Alessandro Pasotti
+ email : elpaso at itopen dot it
+ ***************************************************************************
+ * *
+ * 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 QGSWEAKRELATION_H
+#define QGSWEAKRELATION_H
+
+#define SIP_NO_FILE
+
+#include "qgis_core.h"
+#include "qgsrelation.h"
+#include "qgsvectorlayerref.h"
+
+/**
+ * The QgsWeakRelation class represent a QgsRelation with possibly
+ * unresolved layers or unmatched fields.
+ *
+ * This class is used to store relation information attached to a
+ * layer style, a method to attempt relation resolution is also
+ * implemented and can be used to create a QgsRelation after the
+ * dependent layers are loaded and available.
+ *
+ * \note not available in Python bindings
+ * \ingroup core
+ * \since QGIS 3.12
+ */
+class CORE_EXPORT QgsWeakRelation
+{
+ public:
+
+ /**
+ * Creates a QgsWeakRelation
+ */
+ QgsWeakRelation( const QString &relationId,
+ const QString &relationName,
+ const QgsRelation::RelationStrength strength,
+ const QString &referencingLayerId,
+ const QString &referencingLayerName,
+ const QString &referencingLayerSource,
+ const QString &referencingLayerProviderKey,
+ const QString &referencedLayerId,
+ const QString &referencedLayerName,
+ const QString &referencedLayerSource,
+ const QString &referencedLayerProviderKey,
+ const QList &fieldPairs
+ );
+
+ /**
+ * Resolves a weak relation in the given \a project returning a possibly invalid QgsRelation
+ * and without performing any kind of validity check.
+ *
+ * \note Client code should never assume that the returned relation is valid and the
+ * layer components are not NULL.
+ */
+ QgsRelation resolvedRelation( const QgsProject *project, QgsVectorLayerRef::MatchType matchType = QgsVectorLayerRef::MatchType::All ) const;
+
+ /**
+ * Returns a weak reference to the referencing layer
+ */
+ QgsVectorLayerRef referencingLayer() const;
+
+ /**
+ * Returns a weak reference to the referenced layer
+ */
+ QgsVectorLayerRef referencedLayer() const;
+
+ /**
+ * Returns the strength of the relation
+ */
+ QgsRelation::RelationStrength strength() const;
+
+ /**
+ * Returns the list of field pairs
+ */
+ QList fieldPairs() const;
+
+ private:
+
+ QgsVectorLayerRef mReferencingLayer;
+ QgsVectorLayerRef mReferencedLayer;
+ QString mRelationId;
+ QString mRelationName;
+ QgsRelation::RelationStrength mStrength = QgsRelation::RelationStrength::Association;
+ QList mFieldPairs;
+
+ friend class TestQgsWeakRelation;
+
+};
+
+#endif // QGSWEAKRELATION_H
diff --git a/tests/src/core/CMakeLists.txt b/tests/src/core/CMakeLists.txt
index 4cb8c63638d..e052a510569 100644
--- a/tests/src/core/CMakeLists.txt
+++ b/tests/src/core/CMakeLists.txt
@@ -241,6 +241,7 @@ SET(TESTS
testqobjectuniqueptr.cpp
testqgspostgresstringutils.cpp
testqgsstoredexpressionmanager.cpp
+ testqgsweakrelation.cpp
)
IF(WITH_QTWEBKIT)
diff --git a/tests/src/core/testqgsweakrelation.cpp b/tests/src/core/testqgsweakrelation.cpp
new file mode 100644
index 00000000000..703dbc67602
--- /dev/null
+++ b/tests/src/core/testqgsweakrelation.cpp
@@ -0,0 +1,106 @@
+/***************************************************************************
+ testqgsweakrelation.cpp
+ ----------------
+ Date : December 2019
+ Copyright : (C) 2019 Alessandro Pasotti
+ Email : elpaso at itopen dot it
+ ***************************************************************************
+ * *
+ * 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 "qgsapplication.h"
+#include "qgsvectorlayer.h"
+#include "qgsproject.h"
+#include "qgsweakrelation.h"
+#include "qgsrelation.h"
+
+class TestQgsWeakRelation: public QObject
+{
+ Q_OBJECT
+
+ 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 testResolved(); // Test if relation can be resolved
+
+ private:
+};
+
+void TestQgsWeakRelation::initTestCase()
+{
+ // Set up the QgsSettings environment
+ QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) );
+ QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) );
+ QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) );
+ QgsApplication::init();
+ QgsApplication::initQgis();
+}
+
+void TestQgsWeakRelation::cleanupTestCase()
+{
+ QgsApplication::exitQgis();
+}
+
+void TestQgsWeakRelation::init()
+{
+ QLocale::setDefault( QLocale::English );
+}
+
+void TestQgsWeakRelation::cleanup()
+{
+ QLocale::setDefault( QLocale::English );
+}
+
+void TestQgsWeakRelation::testResolved()
+{
+ QList fieldPairs {{ "fk_province", "pk" }};
+
+ QgsWeakRelation weakRel( QStringLiteral( "my_relation_id" ),
+ QStringLiteral( "my_relation_name" ),
+ QgsRelation::RelationStrength::Association,
+ QStringLiteral( "referencingLayerId" ),
+ QStringLiteral( "referencingLayerName" ),
+ QStringLiteral( "Point?crs=epsg:4326&field=pk:int&field=fk_province:int&field=fk_municipality:int" ),
+ QStringLiteral( "memory" ),
+ QStringLiteral( "referencedLayerId" ),
+ QStringLiteral( "referencedLayerName" ),
+ QStringLiteral( "Polygon?crs=epsg:4326&field=pk:int&field=province:int&field=municipality:string" ),
+ QStringLiteral( "memory" ),
+ fieldPairs
+ );
+ QVERIFY( ! weakRel.resolvedRelation( QgsProject::instance(), QgsVectorLayerRef::MatchType::Name ).isValid() );
+
+ // create a vector layer
+ QgsVectorLayer referencedLayer( QStringLiteral( "Polygon?crs=epsg:4326&field=pk:int&field=province:int&field=municipality:string" ), QStringLiteral( "referencedLayerName" ), QStringLiteral( "memory" ) );
+ QgsProject::instance()->addMapLayer( &referencedLayer, false, false );
+ QVERIFY( ! weakRel.resolvedRelation( QgsProject::instance(), QgsVectorLayerRef::MatchType::Name ).isValid() );
+
+ QgsVectorLayer referencingLayer( QStringLiteral( "Point?crs=epsg:4326&field=pk:int&field=fk_province:int&field=fk_municipality:int" ), QStringLiteral( "referencingLayerName" ), QStringLiteral( "memory" ) );
+ QgsProject::instance()->addMapLayer( &referencingLayer, false, false );
+ QVERIFY( weakRel.resolvedRelation( QgsProject::instance(), QgsVectorLayerRef::MatchType::Name ).isValid() );
+
+ QVERIFY( weakRel.resolvedRelation( QgsProject::instance(), static_cast( QgsVectorLayerRef::MatchType::Name | QgsVectorLayerRef::MatchType::Provider ) ).isValid() );
+
+ // This fails because memory provider stores an UUID in the data source definition ...
+ QVERIFY( !weakRel.resolvedRelation( QgsProject::instance(), static_cast( QgsVectorLayerRef::MatchType::Name | QgsVectorLayerRef::MatchType::Source ) ).isValid() );
+
+ // ... let's fix it
+ weakRel.mReferencedLayer.source = referencedLayer.publicSource();
+ weakRel.mReferencingLayer.source = referencingLayer.publicSource();
+ QVERIFY( weakRel.resolvedRelation( QgsProject::instance(), static_cast( QgsVectorLayerRef::MatchType::Name | QgsVectorLayerRef::MatchType::Source ) ).isValid() );
+
+ // Just to be sure
+ QVERIFY( weakRel.resolvedRelation( QgsProject::instance() ).isValid() );
+}
+
+QGSTEST_MAIN( TestQgsWeakRelation )
+#include "testqgsweakrelation.moc"