QGIS API Documentation  3.21.0-Master (5b68dc587e)
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 
18 #include "qgsapplication.h"
19 #include "qgsfeatureiterator.h"
20 #include "qgslogger.h"
21 #include "qgsproject.h"
22 #include "qgsvectorlayer.h"
25 
27  : d( new QgsPolymorphicRelationPrivate() )
28 {
29 }
30 
32  : d( new QgsPolymorphicRelationPrivate() )
33  , mContext( context )
34 {
35 }
36 
38 
40  : d( other.d )
41  , mContext( other.mContext )
42 {
43 }
44 
46 {
47  d = other.d;
48  mContext = other.mContext;
49  return *this;
50 }
51 
53 {
54  Q_UNUSED( context );
55  QDomElement elem = node.toElement();
56 
57  if ( elem.tagName() != QLatin1String( "relation" ) )
58  {
59  QgsLogger::warning( QApplication::translate( "QgsPolymorphicRelation", "Cannot create relation. Unexpected tag '%1'" ).arg( elem.tagName() ) );
60  }
61 
62  QgsPolymorphicRelation relation( relationContext );
63 
64  QString referencingLayerId = elem.attribute( QStringLiteral( "referencingLayer" ) );
65  QString referencedLayerField = elem.attribute( QStringLiteral( "referencedLayerField" ) );
66  QString referencedLayerExpression = elem.attribute( QStringLiteral( "referencedLayerExpression" ) );
67  QString id = elem.attribute( QStringLiteral( "id" ) );
68  QString name = elem.attribute( QStringLiteral( "name" ) );
69  QString relationStrength = elem.attribute( QStringLiteral( "relationStrength" ) );
70  QStringList referencedLayerIds = elem.attribute( QStringLiteral( "referencedLayerIds" ) ).split( "," );
71 
72  QMap<QString, QgsMapLayer *> mapLayers = relationContext.project()->mapLayers();
73 
74  relation.d->mReferencingLayerId = referencingLayerId;
75  relation.d->mReferencingLayer = qobject_cast<QgsVectorLayer *>( mapLayers[referencingLayerId] );
76  relation.d->mReferencedLayerField = referencedLayerField;
77  relation.d->mReferencedLayerExpression = referencedLayerExpression;
78  relation.d->mReferencedLayerIds = referencedLayerIds;
79  relation.d->mRelationId = id;
80  relation.d->mRelationName = name;
81  relation.d->mRelationStrength = qgsEnumKeyToValue<QgsRelation::RelationStrength>( relationStrength, QgsRelation::RelationStrength::Association );
82 
83  QDomNodeList references = elem.elementsByTagName( QStringLiteral( "fieldRef" ) );
84  for ( int i = 0; i < references.size(); ++i )
85  {
86  QDomElement refEl = references.at( i ).toElement();
87 
88  QString referencingField = refEl.attribute( QStringLiteral( "referencingField" ) );
89  QString referencedField = refEl.attribute( QStringLiteral( "referencedField" ) );
90 
91  relation.addFieldPair( referencingField, referencedField );
92  }
93 
94  relation.updateRelationStatus();
95 
96  return relation;
97 }
98 
99 void QgsPolymorphicRelation::writeXml( QDomNode &node, QDomDocument &doc ) const
100 {
101  QDomElement elem = doc.createElement( QStringLiteral( "relation" ) );
102  elem.setAttribute( QStringLiteral( "id" ), d->mRelationId );
103  elem.setAttribute( QStringLiteral( "name" ), d->mRelationName );
104  elem.setAttribute( QStringLiteral( "referencingLayer" ), d->mReferencingLayerId );
105  elem.setAttribute( QStringLiteral( "referencedLayerField" ), d->mReferencedLayerField );
106  elem.setAttribute( QStringLiteral( "referencedLayerExpression" ), d->mReferencedLayerExpression );
107  elem.setAttribute( QStringLiteral( "referencedLayerIds" ), d->mReferencedLayerIds.join( "," ) );
108  elem.setAttribute( QStringLiteral( "relationStrength" ), qgsEnumValueToKey<QgsRelation::RelationStrength>( d->mRelationStrength ) );
109 
110  // 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
111  for ( const QString &layerId : std::as_const( d->mReferencedLayerIds ) )
112  Q_ASSERT( ! layerId.contains( "," ) );
113 
114  for ( const QgsRelation::FieldPair &pair : std::as_const( d->mFieldPairs ) )
115  {
116  QDomElement referenceElem = doc.createElement( QStringLiteral( "fieldRef" ) );
117  referenceElem.setAttribute( QStringLiteral( "referencingField" ), pair.first );
118  referenceElem.setAttribute( QStringLiteral( "referencedField" ), pair.second );
119  elem.appendChild( referenceElem );
120  }
121 
122  node.appendChild( elem );
123 }
124 
125 void QgsPolymorphicRelation::setId( const QString &id )
126 {
127  if ( d->mRelationId == id )
128  return;
129 
130  d.detach();
131  d->mRelationId = id;
132 
134 }
135 
137 {
138  d.detach();
139  d->mReferencingLayerId = id;
140 
142 }
143 
144 void QgsPolymorphicRelation::addFieldPair( const QString &referencingField, const QString &referencedField )
145 {
146  d.detach();
147  d->mFieldPairs << QgsRelation::FieldPair( referencingField, referencedField );
149 }
150 
152 {
153  d.detach();
154  d->mFieldPairs << fieldPair;
156 }
157 
159 {
160  return d->mRelationId;
161 }
162 
164 {
165  d->mRelationId = QStringLiteral( "%1_%2_%3_%4" )
166  .arg( referencingLayerId(),
167  d->mFieldPairs.at( 0 ).referencingField(),
169  d->mFieldPairs.at( 0 ).referencedField() );
171 }
172 
174 {
175  return d->mReferencingLayerId;
176 }
177 
179 {
180  return d->mReferencingLayer;
181 }
182 
183 QList<QgsRelation::FieldPair> QgsPolymorphicRelation::fieldPairs() const
184 {
185  return d->mFieldPairs;
186 }
187 
189 {
190  QgsAttributeList attrs;
191 
192  if ( d->mReferencedLayerIds.contains( layerId ) )
193  {
194  QgsVectorLayer *vl = static_cast<QgsVectorLayer *>( QgsProject::instance()->mapLayer( layerId ) );
195 
196  if ( vl && vl->isValid() )
197  {
198  for ( const QgsRelation::FieldPair &pair : std::as_const( d->mFieldPairs ) )
199  {
200  attrs << vl->fields().lookupField( pair.second );
201  }
202  }
203  }
204 
205  return attrs;
206 }
207 
209 {
210  QgsAttributeList attrs;
211 
212  for ( const QgsRelation::FieldPair &pair : std::as_const( d->mFieldPairs ) )
213  {
214  attrs << d->mReferencingLayer->fields().lookupField( pair.first );
215  }
216  return attrs;
217 
218 }
219 
221 {
222  return d->mValid && !d->mReferencingLayer.isNull() && !d->mReferencedLayerField.isNull() && d->mReferencingLayer.data()->isValid() && !d->mReferencedLayerExpression.isNull();
223 }
224 
226 {
227  return d->mReferencedLayerField == other.d->mReferencedLayerField
228  && d->mReferencedLayerExpression == other.d->mReferencedLayerExpression
229  && d->mReferencingLayerId == other.d->mReferencingLayerId
230  && d->mFieldPairs == other.d->mFieldPairs;
231 }
232 
234 {
235  const QMap<QString, QgsMapLayer *> &mapLayers = mContext.project()->mapLayers();
236 
237  d->mValid = true;
238  d->mReferencingLayer = mapLayers.contains( d->mReferencingLayerId )
239  ? qobject_cast<QgsVectorLayer *>( mapLayers[d->mReferencingLayerId] )
240  : nullptr;
241  d->mReferencedLayersMap.clear();
242 
243  if ( d->mRelationId.isEmpty() )
244  {
245  QgsDebugMsg( QStringLiteral( "Invalid relation: no ID" ) );
246  d->mValid = false;
247  return;
248  }
249 
250  if ( !d->mReferencingLayer )
251  {
252  QgsDebugMsgLevel( QStringLiteral( "Invalid relation: referencing layer does not exist. ID: %1" ).arg( d->mReferencingLayerId ), 4 );
253  d->mValid = false;
254  return;
255  }
256 
257  if ( d->mReferencingLayer->fields().lookupField( d->mReferencedLayerField ) == -1 )
258  {
259  QgsDebugMsgLevel( QStringLiteral( "Invalid relation: referenced layer field \"%1\" does not exist in layer with ID: %2" ).arg( d->mReferencedLayerField, d->mReferencingLayerId ), 4 );
260  d->mValid = false;
261  return;
262  }
263 
264  if ( d->mReferencedLayerExpression.trimmed().isNull() )
265  {
266  QgsDebugMsgLevel( QStringLiteral( "Invalid relation: referenced layer expression \"%1\" is missing" ).arg( d->mReferencedLayerExpression ), 4 );
267  d->mValid = false;
268  return;
269  }
270 
271  const QStringList referencedLayerIds = d->mReferencedLayerIds;
272  for ( const QString &referencedLayerId : referencedLayerIds )
273  {
274  d->mReferencedLayersMap.insert( referencedLayerId, qobject_cast<QgsVectorLayer *>( mapLayers[referencedLayerId] ) );
275 
276  if ( !d->mReferencedLayersMap[referencedLayerId] || !d->mReferencedLayersMap[referencedLayerId]->isValid() )
277  {
278  QgsDebugMsgLevel( QStringLiteral( "Invalid relation: referenced layer does not exist. ID: %1" ).arg( d->mReferencedLayersMap[referencedLayerId]->id() ), 4 );
279  d->mValid = false;
280  return;
281  }
282  }
283 
284  if ( d->mFieldPairs.count() == 0 )
285  {
286  QgsDebugMsgLevel( QStringLiteral( "Invalid relation: no pair of field is specified." ), 4 );
287  d->mValid = false;
288  return;
289  }
290 
291  for ( const QgsRelation::FieldPair &pair : std::as_const( d->mFieldPairs ) )
292  {
293  if ( d->mReferencingLayer->fields().lookupField( pair.first ) == -1 )
294  {
295  QgsDebugMsg( QStringLiteral( "Invalid relation: field %1 does not exist in referencing layer %2" ).arg( pair.first, d->mReferencingLayer->name() ) );
296  d->mValid = false;
297  return;
298  }
299 
300  for ( const QString &referencedLayerId : referencedLayerIds )
301  {
302  if ( d->mReferencedLayersMap[referencedLayerId]->fields().lookupField( pair.second ) == -1 )
303  {
304  QgsDebugMsg( QStringLiteral( "Invalid relation: field %1 does not exist in referenced layer %2" ).arg( pair.second, d->mReferencedLayersMap[referencedLayerId]->name() ) );
305  d->mValid = false;
306  return;
307  }
308  }
309  }
310 }
311 
312 void QgsPolymorphicRelation::setName( const QString &name )
313 {
314  if ( d->mRelationName == name && !name.isEmpty() )
315  return;
316 
317  d.detach();
318  d->mRelationName = name;
320 }
321 
323 {
324  if ( d->mRelationName.isEmpty() )
325  return QObject::tr( "Polymorphic relations for \"%1\"" ).arg( d->mReferencingLayer ? d->mReferencingLayer->name() : QStringLiteral( "<NO LAYER>" ) );
326 
327  return d->mRelationName;
328 }
329 
330 void QgsPolymorphicRelation::setReferencedLayerField( const QString &referencedLayerField )
331 {
332  d.detach();
333  d->mReferencedLayerField = referencedLayerField;
335 }
336 
338 {
339  return d->mReferencedLayerField;
340 }
341 
342 void QgsPolymorphicRelation::setReferencedLayerExpression( const QString &referencedLayerExpression )
343 {
344  d.detach();
345  d->mReferencedLayerExpression = referencedLayerExpression;
347 }
348 
350 {
351  return d->mReferencedLayerExpression;
352 }
353 
354 void QgsPolymorphicRelation::setReferencedLayerIds( const QStringList &referencedLayerIds )
355 {
356  d.detach();
357  d->mReferencedLayerIds = referencedLayerIds;
359 }
360 
362 {
363  return d->mReferencedLayerIds;
364 }
365 
367 {
368  return d->mRelationStrength;
369 }
370 
372 {
373  d.detach();
374  d->mRelationStrength = relationStrength;
376 }
377 
379 {
380  QList<QgsRelation> relations;
381 
382  if ( !isValid() )
383  return relations;
384 
385  const QStringList referencedLayerIds = d->mReferencedLayerIds;
386 
387  for ( const QString &referencedLayerId : referencedLayerIds )
388  {
389  QgsRelation relation;
390  QString referencedLayerName = d->mReferencedLayersMap[referencedLayerId]->name();
391 
392  relation.setId( QStringLiteral( "%1_%2" ).arg( d->mRelationId, referencedLayerName ) );
393  relation.setReferencedLayer( referencedLayerId );
394  relation.setReferencingLayer( d->mReferencingLayerId );
395  relation.setName( QStringLiteral( "Generated for \"%1\"" ).arg( referencedLayerName ) );
396  relation.setPolymorphicRelationId( d->mRelationId );
397  relation.setStrength( d->mRelationStrength );
398 
399  const QList<QgsRelation::FieldPair> constFieldPairs = fieldPairs();
400  for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs )
401  relation.addFieldPair( fieldPair );
402 
403  if ( !relation.isValid() )
404  continue;
405 
406  relations << relation;
407  }
408 
409  return relations;
410 }
411 
413 {
414  if ( !layer || !layer->isValid() )
415  return QString();
416 
418  QgsExpression expr( d->mReferencedLayerExpression );
419 
420  return expr.evaluate( &context ).toString();
421 }
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:344
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...
void setRelationStrength(QgsRelation::RelationStrength relationStrength)
Sets the relation strength for all the generated normal relations.
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.
QgsRelation::RelationStrength strength() const
Returns the relation strength for all the generated normal relations.
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.
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.
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:467
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:89
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 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.
RelationStrength
enum for the relation strength Association, Composition
Definition: qgsrelation.h:71
void setStrength(RelationStrength strength)
Set a strength for this relation.
bool isValid
Definition: qgsrelation.h:50
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