QGIS API Documentation 4.3.0-Master (0cfde48c85b)
Loading...
Searching...
No Matches
qgsrelationreferencefieldformatter.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsrelationreferencefieldformatter.cpp - QgsRelationReferenceFieldFormatter
3
4 ---------------------
5 begin : 3.12.2016
6 copyright : (C) 2016 by Matthias Kuhn
7 email : matthias@opengis.ch
8 ***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
17
20#include "qgsmessagelog.h"
21#include "qgsproject.h"
22#include "qgsrelation.h"
23#include "qgsrelationmanager.h"
24#include "qgsvectorlayer.h"
25
26#include <QString>
27
28using namespace Qt::StringLiterals;
29
31{
32 setFlags( flags() | QgsFieldFormatter::CanProvideAvailableValues );
33}
34
36{
37 return u"RelationReference"_s;
38}
39
40QString QgsRelationReferenceFieldFormatter::representValue( QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config, const QVariant &cache, const QVariant &value ) const
41{
42 if ( cache.isValid() )
43 {
44 // See regression #66339: the value type should be converted to the type of the key in the cache,
45 // otherwise we might not find the value in the cache, even if it is there, because of type mismatch.
46 // This can happen for example when the value is a int and the key in the cache is a long long.
47 const QMap<QVariant, QString> cacheMap = cache.value<QMap<QVariant, QString>>();
48 if ( cacheMap.size() > 0 )
49 {
50 const QMetaType keyType = value.metaType();
51 const QMetaType cacheKeyType = cacheMap.firstKey().metaType();
52 if ( keyType != cacheKeyType && value.canConvert( cacheKeyType ) )
53 {
54 QVariant key = value;
55 if ( key.convert( cacheKeyType ) )
56 {
57 return cacheMap.value( key );
58 }
59 }
60 }
61 return cacheMap.value( value );
62 }
63
64 const QString fieldName = fieldIndex < layer->fields().size() ? layer->fields().at( fieldIndex ).name() : QObject::tr( "<unknown>" );
65
66 // Some sanity checks
67 if ( !config.contains( u"Relation"_s ) )
68 {
69 QgsMessageLog::logMessage( QObject::tr( "Layer %1, field %2: Missing Relation in configuration" ).arg( layer->name(), fieldName ) );
70 return value.toString();
71 }
72
73 const QString relationName = config[u"Relation"_s].toString();
74
75 QgsRelation relation;
76 if ( QgsProject *project = layer->project() )
77 {
78 relation = project->relationManager()->relation( relationName );
79 }
80 // TODO QGIS 5.0 -- remove this fallback for layers without an associated project
81 else
82 {
83 relation = QgsProject::instance()->relationManager()->relation( relationName ); // skip-keyword-check
84 }
85
86 if ( !relation.isValid() )
87 {
88 QgsMessageLog::logMessage( QObject::tr( "Layer %1, field %2: Invalid relation %3" ).arg( layer->name(), fieldName, relationName ) );
89 return value.toString();
90 }
91 QgsVectorLayer *referencingLayer = relation.referencingLayer();
92 if ( layer != referencingLayer )
93 {
94 QgsMessageLog::logMessage( QObject::tr( "Layer %1, field %2: representValue() with inconsistent layer parameter w.r.t relation referencingLayer" ).arg( layer->name(), fieldName ) );
95 return value.toString();
96 }
97 const int referencingFieldIdx = referencingLayer->fields().lookupField( relation.fieldPairs().at( 0 ).first );
98 if ( referencingFieldIdx != fieldIndex )
99 {
100 QgsMessageLog::logMessage( QObject::tr( "Layer %1, field %2: representValue() with inconsistent fieldIndex parameter w.r.t relation referencingFieldIdx" ).arg( layer->name(), fieldName ) );
101 return value.toString();
102 }
103 QgsVectorLayer *referencedLayer = relation.referencedLayer();
104 if ( !referencedLayer )
105 {
106 QgsMessageLog::logMessage( QObject::tr( "Layer %1, field %2: Cannot find referenced layer" ).arg( layer->name(), fieldName ) );
107 return value.toString();
108 }
109
110 // Attributes from the referencing layer
111 QgsAttributes attrs = QgsAttributes( layer->fields().count() );
112 // Set the value on the foreign key field of the referencing record
113 attrs[referencingFieldIdx] = value;
114
115 const QgsFeatureRequest request = relation.getReferencedFeatureRequest( attrs );
116 QgsFeature feature;
117 referencedLayer->getFeatures( request ).nextFeature( feature );
118 if ( !feature.isValid() )
119 return value.toString();
120
121 QgsExpression expr( referencedLayer->displayExpression() );
123 context.setFeature( feature );
124 QString title = expr.evaluate( &context ).toString();
125 if ( expr.hasEvalError() )
126 {
127 const int referencedFieldIdx = referencedLayer->fields().lookupField( relation.fieldPairs().at( 0 ).second );
128 title = feature.attribute( referencedFieldIdx ).toString();
129 }
130 return title;
131}
132
133QVariant QgsRelationReferenceFieldFormatter::sortValue( QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config, const QVariant &cache, const QVariant &value ) const
134{
135 return representValue( layer, fieldIndex, config, cache, value );
136}
137
138QVariant QgsRelationReferenceFieldFormatter::createCache( QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config ) const
139{
140 Q_UNUSED( fieldIndex )
141 QMap<QVariant, QString> cache;
142
143 if ( !layer )
144 {
145 return QVariant();
146 }
147
148 const QString fieldName = fieldIndex < layer->fields().size() ? layer->fields().at( fieldIndex ).name() : QObject::tr( "<unknown>" );
149
150 // Some sanity checks
151 if ( !config.contains( u"Relation"_s ) )
152 {
153 QgsMessageLog::logMessage( QObject::tr( "Layer %1, field %2: Missing Relation in configuration" ).arg( layer->name(), fieldName ) );
154 return QVariant();
155 }
156 const QString relationName = config[u"Relation"_s].toString();
157
158 QgsRelation relation;
159 if ( QgsProject *project = layer->project() )
160 {
161 relation = project->relationManager()->relation( config[u"Relation"_s].toString() ); // skip-keyword-check
162 }
163 // TODO QGIS 5.0 -- remove this fallback for layers without an associated project
164 else
165 {
166 relation = QgsProject::instance()->relationManager()->relation( relationName ); // skip-keyword-check
167 }
168
169
170 if ( !relation.isValid() )
171 {
172 QgsMessageLog::logMessage( QObject::tr( "Layer %1, field %2: Invalid relation %3" ).arg( layer->name(), fieldName, relationName ) );
173 return QVariant();
174 }
175 QgsVectorLayer *referencingLayer = relation.referencingLayer();
176 if ( layer != referencingLayer )
177 {
178 QgsMessageLog::logMessage( QObject::tr( "Layer %1, field %2: representValue() with inconsistent layer parameter w.r.t relation referencingLayer" ).arg( layer->name(), fieldName ) );
179 return QVariant();
180 }
181 QgsVectorLayer *referencedLayer = relation.referencedLayer();
182 if ( !referencedLayer )
183 {
184 QgsMessageLog::logMessage( QObject::tr( "Layer %1, field %2: Cannot find referenced layer" ).arg( layer->name(), fieldName ) );
185 return QVariant();
186 }
187
188 const int referencedFieldIdx = referencedLayer->fields().lookupField( relation.fieldPairs().at( 0 ).second );
189 if ( referencedFieldIdx == -1 )
190 {
192 QObject::tr( "Layer %1, field %2: Invalid referenced field (%3) configured in relation %4" ).arg( layer->name(), fieldName, relation.fieldPairs().at( 0 ).second, relation.name() )
193 );
194 return QVariant();
195 }
196
197 QgsExpression expr( referencedLayer->displayExpression() );
198
199 QgsFeatureRequest request;
201 QgsAttributeList requiredAttributes = qgis::setToList( expr.referencedAttributeIndexes( referencedLayer->fields() ) );
202 requiredAttributes << referencedFieldIdx;
203 request.setSubsetOfAttributes( requiredAttributes );
204 QgsFeature feature;
205 auto iterator = referencedLayer->getFeatures( request );
206
208
209 expr.prepare( &context );
210
211 while ( iterator.nextFeature( feature ) )
212 {
213 context.setFeature( feature );
214 QString title = expr.evaluate( &context ).toString();
215
216 if ( expr.hasEvalError() )
217 {
218 title = feature.attribute( referencedFieldIdx ).toString();
219 }
220
221 cache.insert( feature.attribute( referencedFieldIdx ), title );
222 }
223
224 return QVariant::fromValue<QMap<QVariant, QString>>( cache );
225}
226
227
228QList<QgsVectorLayerRef> QgsRelationReferenceFieldFormatter::layerDependencies( const QVariantMap &config ) const
229{
230 // Old projects, create before the weak relations were introduced and stored with the
231 // widget configuration do not have the referenced layer details but only the "Relation" id,
232 // for these projects automatic loading of broken references is not supported.
233 if ( config.value( u"ReferencedLayerId"_s ).toString().isEmpty() )
234 {
235 return {};
236 }
237
238 const QList<QgsVectorLayerRef> result { { QgsVectorLayerRef(
239 config.value( u"ReferencedLayerId"_s ).toString(),
240 config.value( u"ReferencedLayerName"_s ).toString(),
241 config.value( u"ReferencedLayerDataSource"_s ).toString(),
242 config.value( u"ReferencedLayerProviderKey"_s ).toString()
243 ) } };
244 return result;
245}
246
247QVariantList QgsRelationReferenceFieldFormatter::availableValues( const QVariantMap &config, int countLimit, const QgsFieldFormatterContext &context ) const
248{
249 QVariantList values;
250 if ( auto *lProject = context.project() )
251 {
252 const QgsVectorLayer *referencedLayer = lProject->relationManager()->relation( config[u"Relation"_s].toString() ).referencedLayer();
253 if ( referencedLayer )
254 {
255 const int fieldIndex = lProject->relationManager()->relation( config[u"Relation"_s].toString() ).referencedFields().first();
256 values = qgis::setToList( referencedLayer->uniqueValues( fieldIndex, countLimit ) );
257 }
258 }
259 return values;
260}
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition qgis.h:2380
A vector of attributes.
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
Handles parsing and evaluation of expressions (formerly called "search strings").
bool prepare(const QgsExpressionContext *context)
Gets the expression ready for evaluation - find out column indexes.
bool hasEvalError() const
Returns true if an error occurred when evaluating last input.
QVariant evaluate()
Evaluate the feature and return the result.
QSet< int > referencedAttributeIndexes(const QgsFields &fields) const
Returns a list of field name indexes obtained from the provided fields.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
Wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFlags(Qgis::FeatureRequestFlags flags)
Sets flags that affect how features will be fetched.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:60
bool isValid() const
Returns the validity of this feature.
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
A context for field formatter containing information like the project.
QgsProject * project() const
Returns the project used in field formatter.
Flags flags() const
Returns the flags.
void setFlags(const Flags &flags)
Sets the flags.
QString name
Definition qgsfield.h:65
int count
Definition qgsfields.h:49
int size() const
Returns number of items.
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Q_INVOKABLE int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
QString name
Definition qgsmaplayer.h:87
QgsProject * project() const
Returns the parent project if this map layer is added to a project.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:114
QgsRelationManager * relationManager
Definition qgsproject.h:125
static QgsProject * instance()
Returns the QgsProject singleton instance.
Q_INVOKABLE QgsRelation relation(const QString &id) const
Gets access to a relation by its id.
QList< QgsVectorLayerRef > layerDependencies(const QVariantMap &config) const override
Returns a list of weak layer references to other layers required by this formatter for the given conf...
QString representValue(QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config, const QVariant &cache, const QVariant &value) const override
Create a pretty String representation of the value.
QVariantList availableValues(const QVariantMap &config, int countLimit, const QgsFieldFormatterContext &context) const override
Returns a list of the values that would be possible to select with this widget type On a RelationRefe...
QVariant createCache(QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config) const override
Create a cache for a given field.
QString id() const override
Returns a unique id for this field formatter.
QgsRelationReferenceFieldFormatter()
Default constructor of field formatter for a relation reference field.
QVariant sortValue(QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config, const QVariant &cache, const QVariant &value) const override
If the default sort order should be overwritten for this widget, you can transform the value in here.
Represents a relationship between two vector layers.
Definition qgsrelation.h:42
QgsFeatureRequest getReferencedFeatureRequest(const QgsAttributes &attributes) const
Creates a request to return the feature on the referenced (parent) layer which is referenced by the p...
QString name
Definition qgsrelation.h:52
QgsVectorLayer * referencedLayer
Definition qgsrelation.h:50
QList< QgsRelation::FieldPair > fieldPairs() const
Returns the field pairs which form this relation The first element of each pair are the field names o...
QgsVectorLayer * referencingLayer
Definition qgsrelation.h:47
Represents a vector layer which manages a vector based dataset.
QString displayExpression
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const final
Queries the layer for features specified in request.
Q_INVOKABLE QSet< QVariant > uniqueValues(int fieldIndex, int limit=-1) const final
Calculates a list of unique values contained within an attribute in the layer.
QList< int > QgsAttributeList
Definition qgsfield.h:30
_LayerRef< QgsVectorLayer > QgsVectorLayerRef