QGIS API Documentation  3.27.0-Master (11ef3e5184)
qgspolymorphicrelation.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgspolymorphicrelation.h
3  --------------------------------------
4  Date : December 2020
5  Copyright : (C) 2020 Ivan Ivanov
6  Email : ivan at opengis dot ch
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 #include "qgspolymorphicrelation.h"
17 #include "qgslogger.h"
18 #include "qgsproject.h"
19 #include "qgsvectorlayer.h"
21 
22 #include <QApplication>
23 
25  : d( new QgsPolymorphicRelationPrivate() )
26 {
27 }
28 
30  : d( new QgsPolymorphicRelationPrivate() )
31  , mContext( context )
32 {
33 }
34 
36 
38  : d( other.d )
39  , mContext( other.mContext )
40 {
41 }
42 
44 {
45  d = other.d;
46  mContext = other.mContext;
47  return *this;
48 }
49 
51 {
52  Q_UNUSED( context );
53  QDomElement elem = node.toElement();
54 
55  if ( elem.tagName() != QLatin1String( "relation" ) )
56  {
57  QgsLogger::warning( QApplication::translate( "QgsPolymorphicRelation", "Cannot create relation. Unexpected tag '%1'" ).arg( elem.tagName() ) );
58  }
59 
60  QgsPolymorphicRelation relation( relationContext );
61 
62  QString referencingLayerId = elem.attribute( QStringLiteral( "referencingLayer" ) );
63  QString referencedLayerField = elem.attribute( QStringLiteral( "referencedLayerField" ) );
64  QString referencedLayerExpression = elem.attribute( QStringLiteral( "referencedLayerExpression" ) );
65  QString id = elem.attribute( QStringLiteral( "id" ) );
66  QString name = elem.attribute( QStringLiteral( "name" ) );
67  QString relationStrength = elem.attribute( QStringLiteral( "relationStrength" ) );
68  QStringList referencedLayerIds = elem.attribute( QStringLiteral( "referencedLayerIds" ) ).split( "," );
69 
70  QMap<QString, QgsMapLayer *> mapLayers = relationContext.project()->mapLayers();
71 
72  relation.d->mReferencingLayerId = referencingLayerId;
73  relation.d->mReferencingLayer = qobject_cast<QgsVectorLayer *>( mapLayers[referencingLayerId] );
74  relation.d->mReferencedLayerField = referencedLayerField;
75  relation.d->mReferencedLayerExpression = referencedLayerExpression;
76  relation.d->mReferencedLayerIds = referencedLayerIds;
77  relation.d->mRelationId = id;
78  relation.d->mRelationName = name;
79  relation.d->mRelationStrength = qgsEnumKeyToValue<Qgis::RelationshipStrength>( relationStrength, Qgis::RelationshipStrength::Association );
80 
81  QDomNodeList references = elem.elementsByTagName( QStringLiteral( "fieldRef" ) );
82  for ( int i = 0; i < references.size(); ++i )
83  {
84  QDomElement refEl = references.at( i ).toElement();
85 
86  QString referencingField = refEl.attribute( QStringLiteral( "referencingField" ) );
87  QString referencedField = refEl.attribute( QStringLiteral( "referencedField" ) );
88 
89  relation.addFieldPair( referencingField, referencedField );
90  }
91 
92  relation.updateRelationStatus();
93 
94  return relation;
95 }
96 
97 void QgsPolymorphicRelation::writeXml( QDomNode &node, QDomDocument &doc ) const
98 {
99  QDomElement elem = doc.createElement( QStringLiteral( "relation" ) );
100  elem.setAttribute( QStringLiteral( "id" ), d->mRelationId );
101  elem.setAttribute( QStringLiteral( "name" ), d->mRelationName );
102  elem.setAttribute( QStringLiteral( "referencingLayer" ), d->mReferencingLayerId );
103  elem.setAttribute( QStringLiteral( "referencedLayerField" ), d->mReferencedLayerField );
104  elem.setAttribute( QStringLiteral( "referencedLayerExpression" ), d->mReferencedLayerExpression );
105  elem.setAttribute( QStringLiteral( "referencedLayerIds" ), d->mReferencedLayerIds.join( "," ) );
106  elem.setAttribute( QStringLiteral( "relationStrength" ), qgsEnumValueToKey<Qgis::RelationshipStrength>( d->mRelationStrength ) );
107 
108  // note that a layer id can store a comma in theory. Luckyly, this is not easy to achieve, e.g. you need to modify the .qgs file manually
109  for ( const QString &layerId : std::as_const( d->mReferencedLayerIds ) )
110  Q_ASSERT( ! layerId.contains( "," ) );
111 
112  for ( const QgsRelation::FieldPair &pair : std::as_const( d->mFieldPairs ) )
113  {
114  QDomElement referenceElem = doc.createElement( QStringLiteral( "fieldRef" ) );
115  referenceElem.setAttribute( QStringLiteral( "referencingField" ), pair.first );
116  referenceElem.setAttribute( QStringLiteral( "referencedField" ), pair.second );
117  elem.appendChild( referenceElem );
118  }
119 
120  node.appendChild( elem );
121 }
122 
123 void QgsPolymorphicRelation::setId( const QString &id )
124 {
125  if ( d->mRelationId == id )
126  return;
127 
128  d.detach();
129  d->mRelationId = id;
130 
132 }
133 
135 {
136  d.detach();
137  d->mReferencingLayerId = id;
138 
140 }
141 
142 void QgsPolymorphicRelation::addFieldPair( const QString &referencingField, const QString &referencedField )
143 {
144  d.detach();
145  d->mFieldPairs << QgsRelation::FieldPair( referencingField, referencedField );
147 }
148 
150 {
151  d.detach();
152  d->mFieldPairs << fieldPair;
154 }
155 
157 {
158  return d->mRelationId;
159 }
160 
162 {
163  d->mRelationId = QStringLiteral( "%1_%2_%3_%4" )
164  .arg( referencingLayerId(),
165  d->mFieldPairs.at( 0 ).referencingField(),
167  d->mFieldPairs.at( 0 ).referencedField() );
169 }
170 
172 {
173  return d->mReferencingLayerId;
174 }
175 
177 {
178  return d->mReferencingLayer;
179 }
180 
181 QList<QgsRelation::FieldPair> QgsPolymorphicRelation::fieldPairs() const
182 {
183  return d->mFieldPairs;
184 }
185 
187 {
188  QgsAttributeList attrs;
189 
190  if ( d->mReferencedLayerIds.contains( layerId ) )
191  {
192  QgsVectorLayer *vl = static_cast<QgsVectorLayer *>( QgsProject::instance()->mapLayer( layerId ) );
193 
194  if ( vl && vl->isValid() )
195  {
196  for ( const QgsRelation::FieldPair &pair : std::as_const( d->mFieldPairs ) )
197  {
198  attrs << vl->fields().lookupField( pair.second );
199  }
200  }
201  }
202 
203  return attrs;
204 }
205 
207 {
208  QgsAttributeList attrs;
209 
210  for ( const QgsRelation::FieldPair &pair : std::as_const( d->mFieldPairs ) )
211  {
212  attrs << d->mReferencingLayer->fields().lookupField( pair.first );
213  }
214  return attrs;
215 
216 }
217 
219 {
220  return d->mValid && !d->mReferencingLayer.isNull() && !d->mReferencedLayerField.isNull() && d->mReferencingLayer.data()->isValid() && !d->mReferencedLayerExpression.isNull();
221 }
222 
224 {
225  return d->mReferencedLayerField == other.d->mReferencedLayerField
226  && d->mReferencedLayerExpression == other.d->mReferencedLayerExpression
227  && d->mReferencingLayerId == other.d->mReferencingLayerId
228  && d->mFieldPairs == other.d->mFieldPairs;
229 }
230 
232 {
233  const QMap<QString, QgsMapLayer *> &mapLayers = mContext.project()->mapLayers();
234 
235  d->mValid = true;
236  d->mReferencingLayer = mapLayers.contains( d->mReferencingLayerId )
237  ? qobject_cast<QgsVectorLayer *>( mapLayers[d->mReferencingLayerId] )
238  : nullptr;
239  d->mReferencedLayersMap.clear();
240 
241  if ( d->mRelationId.isEmpty() )
242  {
243  QgsDebugMsg( QStringLiteral( "Invalid relation: no ID" ) );
244  d->mValid = false;
245  return;
246  }
247 
248  if ( !d->mReferencingLayer )
249  {
250  QgsDebugMsgLevel( QStringLiteral( "Invalid relation: referencing layer does not exist. ID: %1" ).arg( d->mReferencingLayerId ), 4 );
251  d->mValid = false;
252  return;
253  }
254 
255  if ( d->mReferencingLayer->fields().lookupField( d->mReferencedLayerField ) == -1 )
256  {
257  QgsDebugMsgLevel( QStringLiteral( "Invalid relation: referenced layer field \"%1\" does not exist in layer with ID: %2" ).arg( d->mReferencedLayerField, d->mReferencingLayerId ), 4 );
258  d->mValid = false;
259  return;
260  }
261 
262  if ( d->mReferencedLayerExpression.trimmed().isNull() )
263  {
264  QgsDebugMsgLevel( QStringLiteral( "Invalid relation: referenced layer expression \"%1\" is missing" ).arg( d->mReferencedLayerExpression ), 4 );
265  d->mValid = false;
266  return;
267  }
268 
269  const QStringList referencedLayerIds = d->mReferencedLayerIds;
270  for ( const QString &referencedLayerId : referencedLayerIds )
271  {
272  d->mReferencedLayersMap.insert( referencedLayerId, qobject_cast<QgsVectorLayer *>( mapLayers[referencedLayerId] ) );
273 
274  if ( !d->mReferencedLayersMap[referencedLayerId] || !d->mReferencedLayersMap[referencedLayerId]->isValid() )
275  {
276  QgsDebugMsgLevel( QStringLiteral( "Invalid relation: referenced layer does not exist. ID: %1" ).arg( d->mReferencedLayersMap[referencedLayerId]->id() ), 4 );
277  d->mValid = false;
278  return;
279  }
280  }
281 
282  if ( d->mFieldPairs.count() == 0 )
283  {
284  QgsDebugMsgLevel( QStringLiteral( "Invalid relation: no pair of field is specified." ), 4 );
285  d->mValid = false;
286  return;
287  }
288 
289  for ( const QgsRelation::FieldPair &pair : std::as_const( d->mFieldPairs ) )
290  {
291  if ( d->mReferencingLayer->fields().lookupField( pair.first ) == -1 )
292  {
293  QgsDebugMsg( QStringLiteral( "Invalid relation: field %1 does not exist in referencing layer %2" ).arg( pair.first, d->mReferencingLayer->name() ) );
294  d->mValid = false;
295  return;
296  }
297 
298  for ( const QString &referencedLayerId : referencedLayerIds )
299  {
300  if ( d->mReferencedLayersMap[referencedLayerId]->fields().lookupField( pair.second ) == -1 )
301  {
302  QgsDebugMsg( QStringLiteral( "Invalid relation: field %1 does not exist in referenced layer %2" ).arg( pair.second, d->mReferencedLayersMap[referencedLayerId]->name() ) );
303  d->mValid = false;
304  return;
305  }
306  }
307  }
308 }
309 
310 void QgsPolymorphicRelation::setName( const QString &name )
311 {
312  if ( d->mRelationName == name && !name.isEmpty() )
313  return;
314 
315  d.detach();
316  d->mRelationName = name;
318 }
319 
321 {
322  if ( d->mRelationName.isEmpty() )
323  return QObject::tr( "Polymorphic relations for \"%1\"" ).arg( d->mReferencingLayer ? d->mReferencingLayer->name() : QStringLiteral( "<NO LAYER>" ) );
324 
325  return d->mRelationName;
326 }
327 
328 void QgsPolymorphicRelation::setReferencedLayerField( const QString &referencedLayerField )
329 {
330  d.detach();
331  d->mReferencedLayerField = referencedLayerField;
333 }
334 
336 {
337  return d->mReferencedLayerField;
338 }
339 
340 void QgsPolymorphicRelation::setReferencedLayerExpression( const QString &referencedLayerExpression )
341 {
342  d.detach();
343  d->mReferencedLayerExpression = referencedLayerExpression;
345 }
346 
348 {
349  return d->mReferencedLayerExpression;
350 }
351 
352 void QgsPolymorphicRelation::setReferencedLayerIds( const QStringList &referencedLayerIds )
353 {
354  d.detach();
355  d->mReferencedLayerIds = referencedLayerIds;
357 }
358 
360 {
361  return d->mReferencedLayerIds;
362 }
363 
365 {
366  return d->mRelationStrength;
367 }
368 
370 {
371  d.detach();
372  d->mRelationStrength = relationStrength;
374 }
375 
377 {
378  QList<QgsRelation> relations;
379 
380  if ( !isValid() )
381  return relations;
382 
383  const QStringList referencedLayerIds = d->mReferencedLayerIds;
384 
385  for ( const QString &referencedLayerId : referencedLayerIds )
386  {
387  QgsRelation relation;
388  QString referencedLayerName = d->mReferencedLayersMap[referencedLayerId]->name();
389 
390  relation.setId( QStringLiteral( "%1_%2" ).arg( d->mRelationId, referencedLayerName ) );
391  relation.setReferencedLayer( referencedLayerId );
392  relation.setReferencingLayer( d->mReferencingLayerId );
393  relation.setName( QStringLiteral( "Generated for \"%1\"" ).arg( referencedLayerName ) );
394  relation.setPolymorphicRelationId( d->mRelationId );
395  relation.setStrength( d->mRelationStrength );
396 
397  const QList<QgsRelation::FieldPair> constFieldPairs = fieldPairs();
398  for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs )
399  relation.addFieldPair( fieldPair );
400 
401  if ( !relation.isValid() )
402  continue;
403 
404  relations << relation;
405  }
406 
407  return relations;
408 }
409 
411 {
412  if ( !layer || !layer->isValid() )
413  return QString();
414 
416  QgsExpression expr( d->mReferencedLayerExpression );
417 
418  return expr.evaluate( &context ).toString();
419 }
RelationshipStrength
Relationship strength.
Definition: qgis.h:2215
@ Association
Loose relation, related elements are not part of the parent and a parent copy will not copy any child...
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
Class for parsing and evaluation of expressions (formerly called "search strings").
QVariant evaluate()
Evaluate the feature and return the result.
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:349
static void warning(const QString &msg)
Goes to qWarning.
Definition: qgslogger.cpp:122
bool isValid
Definition: qgsmaplayer.h:81
A polymorphic relation consists of the same properties like a normal relation except for the referenc...
QgsAttributeList referencedFields(const QString &layerId) const
Returns a list of attributes used to form the referenced fields (most likely primary key) on the refe...
QString layerRepresentation(const QgsVectorLayer *layer) const
Returns layer representation as evaluated string.
QList< QgsRelation > generateRelations() const
Returns a list of generated relations, based on the currently set referencedLayerIds()
void setReferencedLayerIds(const QStringList &childRelationIds)
Sets a list of layer ids to be used as potential referenced layers.
void setReferencedLayerExpression(const QString &expression)
Sets the expression to identify the parent layer.
void setReferencedLayerField(const QString &referencedLayerField)
Sets the field in the referencing layer where the referenced layer identifier is stored.
void generateId()
Generate a (project-wide) unique id for this relation.
void setReferencingLayer(const QString &id)
Set the referencing (child) layer id.
QList< QgsRelation::FieldPair > fieldPairs() const
Returns the field pairs which form this relation The first element of each pair are the field names o...
void writeXml(QDomNode &node, QDomDocument &doc) const
Writes a relation to an XML structure.
void addFieldPair(const QString &referencingField, const QString &referencedField)
Add a field pair which is part of this relation The first element of each pair are the field names of...
static QgsPolymorphicRelation createFromXml(const QDomNode &node, QgsReadWriteContext &context, const QgsRelationContext &relationContext=QgsRelationContext())
Creates a relation from an XML structure.
void setRelationStrength(Qgis::RelationshipStrength relationStrength)
Sets the relation strength for all the generated normal relations.
bool hasEqualDefinition(const QgsPolymorphicRelation &other) const
Compares the two QgsRelation, ignoring the name and the ID.
QString referencingLayerId() const
Access the referencing (child) layer's id This is the layer which has the field(s) which point to ano...
void updateRelationStatus()
Updates the validity status of this relation.
Qgis::RelationshipStrength strength() const
Returns the relation strength for all the generated normal relations.
QgsVectorLayer * referencingLayer
QStringList referencedLayerIds() const
Returns a list of layer ids to be used as potential referenced layers.
QgsPolymorphicRelation()
Default constructor.
QgsAttributeList referencingFields() const
Returns a list of attributes used to form the referencing fields (foreign key) on the referencing (ch...
void setName(const QString &name)
Set a name for this relation.
void setId(const QString &id)
Set an id for this relation.
QgsPolymorphicRelation & operator=(const QgsPolymorphicRelation &other)
Copies a relation.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:477
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
QMap< QString, QgsMapLayer * > mapLayers(const bool validOnly=false) const
Returns a map of all registered layers by layer ID.
The class is used as a container of context for various read/write operations on other objects.
Context for relations.
const QgsProject * project() const
Gets the associated project.
Defines a relation between matching fields of the two involved tables of a relation.
Definition: qgsrelation.h:67
void setId(const QString &id)
Set an id for this relation.
void setReferencedLayer(const QString &id)
Set the referenced (parent) layer id.
void setPolymorphicRelationId(const QString &polymorphicRelationId)
Sets the parent polymorphic relation id.
void setStrength(Qgis::RelationshipStrength strength)
Set a strength for this relation.
void addFieldPair(const QString &referencingField, const QString &referencedField)
Add a field pair which is part of this relation The first element of each pair are the field names of...
void setReferencingLayer(const QString &id)
Set the referencing (child) layer id.
bool isValid
Definition: qgsrelation.h:49
void setName(const QString &name)
Set a name for this relation.
Represents a vector layer which manages a vector based data sets.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QgsExpressionContext createExpressionContext() const FINAL
This method needs to be reimplemented in all classes which implement this interface and return an exp...
QList< int > QgsAttributeList
Definition: qgsfield.h:26
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38