QGIS API Documentation 3.43.0-Master (a93bf8b6462)
qgsvaluerelationfieldformatter.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsvaluerelationfieldformatter.cpp - QgsValueRelationFieldFormatter
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
18#include "qgis.h"
19#include "qgsproject.h"
20#include "qgsvectorlayer.h"
22#include "qgsapplication.h"
24#include "qgsvectorlayerref.h"
26#include "qgsmessagelog.h"
27#include "qgsvariantutils.h"
28
29#include <nlohmann/json.hpp>
30using namespace nlohmann;
31
32#include <QSettings>
33
35{
36 setFlags( flags() | QgsFieldFormatter::CanProvideAvailableValues );
37}
38
40{
41 return QStringLiteral( "ValueRelation" );
42}
43
44QString QgsValueRelationFieldFormatter::representValue( QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config, const QVariant &cache, const QVariant &value ) const
45{
46 ValueRelationCache vrCache;
47
48 if ( cache.isValid() )
49 {
51 }
52 else
53 {
55 }
56
57 if ( config.value( QStringLiteral( "AllowMulti" ) ).toBool() )
58 {
59 QStringList keyList;
60
61 if ( layer->fields().at( fieldIndex ).type() == QMetaType::Type::QVariantMap )
62 {
63 //because of json it's stored as QVariantList
64 keyList = value.toStringList();
65 }
66 else
67 {
68 keyList = valueToStringList( value );
69 }
70
71 QStringList valueList;
72
73 for ( const QgsValueRelationFieldFormatter::ValueRelationItem &item : std::as_const( vrCache ) )
74 {
75 if ( keyList.contains( item.key.toString() ) )
76 {
77 valueList << item.value;
78 }
79 }
80
81 return valueList.join( QLatin1String( ", " ) ).prepend( '{' ).append( '}' );
82 }
83 else
84 {
85 if ( QgsVariantUtils::isNull( value ) )
86 {
88 }
89
90 for ( const QgsValueRelationFieldFormatter::ValueRelationItem &item : std::as_const( vrCache ) )
91 {
92 if ( item.key == value )
93 {
94 return item.value;
95 }
96 }
97 }
98
99 return QStringLiteral( "(%1)" ).arg( value.toString() );
100}
101
102QVariant QgsValueRelationFieldFormatter::sortValue( QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config, const QVariant &cache, const QVariant &value ) const
103{
104 return QgsVariantUtils::isNull( value ) ? QString() : representValue( layer, fieldIndex, config, cache, value );
105}
106
107QVariant QgsValueRelationFieldFormatter::createCache( QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config ) const
108{
109 Q_UNUSED( layer )
110 Q_UNUSED( fieldIndex )
111 return QVariant::fromValue<ValueRelationCache>( createCache( config ) );
112
113}
114
116 const QVariantMap &config,
117 const QgsFeature &formFeature,
118 const QgsFeature &parentFormFeature )
119{
120 ValueRelationCache cache;
121
122 const QgsVectorLayer *layer = resolveLayer( config, QgsProject::instance() ); // skip-keyword-check
123
124 if ( !layer )
125 return cache;
126
127 QgsFields fields = layer->fields();
128 const int keyIdx = fields.indexOf( config.value( QStringLiteral( "Key" ) ).toString() );
129 const int valueIdx = fields.indexOf( config.value( QStringLiteral( "Value" ) ).toString() );
130
131 QgsFeatureRequest request;
132
134 QgsAttributeIds subsetOfAttributes { keyIdx, valueIdx };
135
136 const int groupIdx = fields.lookupField( config.value( QStringLiteral( "Group" ) ).toString() );
137 if ( groupIdx > -1 )
138 {
139 subsetOfAttributes << groupIdx;
140 }
141
142 const bool orderByField { config.value( QStringLiteral( "OrderByField" ) ).toBool() };
143 const int fieldIdx { orderByField ? layer->fields().lookupField( config.value( QStringLiteral( "OrderByFieldName" ) ).toString() ) : -1 };
144 const bool reverseSort { config.value( QStringLiteral( "OrderByDescending" ) ).toBool() };
145 if ( fieldIdx != -1 )
146 {
147 subsetOfAttributes << fieldIdx;
148 }
149
150 const QString descriptionExpressionString = config.value( "Description" ).toString();
151 QgsExpression descriptionExpression( descriptionExpressionString );
153 descriptionExpression.prepare( &context );
154 subsetOfAttributes += descriptionExpression.referencedAttributeIndexes( layer->fields() );
155 request.setSubsetOfAttributes( qgis::setToList( subsetOfAttributes ) );
156
157 const QString filterExpression = config.value( QStringLiteral( "FilterExpression" ) ).toString();
158
159 // Skip the filter and build a full cache if the form scope is required and the feature
160 // is not valid or the attributes required for the filter have no valid value
161 // Note: parent form scope is not checked for usability because it's supposed to
162 // be used into a coalesce that retrieve the current value of the parent
163 // from the parent layer when used outside of an embedded form
164 if ( ! filterExpression.isEmpty() && ( !( expressionRequiresFormScope( filterExpression ) )
165 || expressionIsUsable( filterExpression, formFeature ) ) )
166 {
167 QgsExpressionContext filterContext = context;
168 if ( formFeature.isValid( ) && QgsValueRelationFieldFormatter::expressionRequiresFormScope( filterExpression ) )
169 filterContext.appendScope( QgsExpressionContextUtils::formScope( formFeature ) );
170 if ( parentFormFeature.isValid() && QgsValueRelationFieldFormatter::expressionRequiresParentFormScope( filterExpression ) )
171 filterContext.appendScope( QgsExpressionContextUtils::parentFormScope( parentFormFeature ) );
172 request.setExpressionContext( filterContext );
173 request.setFilterExpression( filterExpression );
174 }
175
176 QgsFeatureIterator fit = layer->getFeatures( request );
177 QMap<QVariant, QVariant> orderByFieldValues;
178
179 QgsFeature f;
180 while ( fit.nextFeature( f ) )
181 {
182 QString description;
183 if ( descriptionExpression.isValid() )
184 {
185 context.setFeature( f );
186 description = descriptionExpression.evaluate( &context ).toString();
187 }
188 const QVariant group = groupIdx > -1 ? f.attribute( groupIdx ) : QVariant();
189 const QVariant keyValue = f.attribute( keyIdx );
190 if ( fieldIdx != -1 )
191 {
192 orderByFieldValues.insert( keyValue, f.attribute( fieldIdx ) );
193 }
194 cache.append( ValueRelationItem( keyValue, f.attribute( valueIdx ).toString(), description, group ) );
195 }
196
197
198 if ( config.value( QStringLiteral( "OrderByValue" ) ).toBool() )
199 {
200 std::sort( cache.begin(), cache.end(), [&reverseSort]( const QgsValueRelationFieldFormatter::ValueRelationItem & p1, const QgsValueRelationFieldFormatter::ValueRelationItem & p2 ) -> bool
201 {
202 if ( reverseSort )
203 return p1.group == p2.group ? qgsVariantGreaterThan( p1.value, p2.value ) : qgsVariantGreaterThan( p1.group, p2.group );
204 else
205 return p1.group == p2.group ? qgsVariantLessThan( p1.value, p2.value ) : qgsVariantLessThan( p1.group, p2.group );
206 } );
207 }
208 // Order by field
209 else if ( fieldIdx != -1 )
210 {
211 std::sort( cache.begin(), cache.end(), [&reverseSort, &orderByFieldValues]( const QgsValueRelationFieldFormatter::ValueRelationItem & p1, const QgsValueRelationFieldFormatter::ValueRelationItem & p2 ) -> bool
212 {
213 if ( reverseSort )
214 return p1.group == p2.group ? qgsVariantGreaterThan( orderByFieldValues.value( p1.key ), orderByFieldValues.value( p2.key ) ) : qgsVariantGreaterThan( p1.group, p2.group );
215 else
216 return p1.group == p2.group ? qgsVariantLessThan( orderByFieldValues.value( p1.key ), orderByFieldValues.value( p2.key ) ) : qgsVariantLessThan( p1.group, p2.group );
217
218 } );
219 }
220 // OrderByKey is the default
221 else
222 {
223 std::sort( cache.begin(), cache.end(), [&reverseSort]( const QgsValueRelationFieldFormatter::ValueRelationItem & p1, const QgsValueRelationFieldFormatter::ValueRelationItem & p2 ) -> bool
224 {
225 if ( reverseSort )
226 return p1.group == p2.group ? qgsVariantGreaterThan( p1.key, p2.key ) : qgsVariantGreaterThan( p1.group, p2.group );
227 else
228 return p1.group == p2.group ? qgsVariantLessThan( p1.key, p2.key ) : qgsVariantLessThan( p1.group, p2.group );
229 } );
230 }
231
232 return cache;
233}
234
235
236QList<QgsVectorLayerRef> QgsValueRelationFieldFormatter::layerDependencies( const QVariantMap &config ) const
237{
238 QList<QgsVectorLayerRef> result;
239 const QString layerId { config.value( QStringLiteral( "Layer" ) ).toString() };
240 const QString layerName { config.value( QStringLiteral( "LayerName" ) ).toString() };
241 const QString providerName { config.value( QStringLiteral( "LayerProviderName" ) ).toString() };
242 const QString layerSource { config.value( QStringLiteral( "LayerSource" ) ).toString() };
243 if ( ! layerId.isEmpty() && ! layerName.isEmpty() && ! providerName.isEmpty() && ! layerSource.isEmpty() )
244 {
245 result.append( QgsVectorLayerRef( layerId, layerName, layerSource, providerName ) );
246 }
247 return result;
248}
249
250QVariantList QgsValueRelationFieldFormatter::availableValues( const QVariantMap &config, int countLimit, const QgsFieldFormatterContext &context ) const
251{
252 QVariantList values;
253
254 if ( auto *lProject = context.project() )
255 {
256 const QgsVectorLayer *referencedLayer = qobject_cast<QgsVectorLayer *>( lProject->mapLayer( config[QStringLiteral( "Layer" )].toString() ) );
257 if ( referencedLayer )
258 {
259 int fieldIndex = referencedLayer->fields().indexOf( config.value( QStringLiteral( "Key" ) ).toString() );
260 values = qgis::setToList( referencedLayer->uniqueValues( fieldIndex, countLimit ) );
261 }
262 }
263 return values;
264}
265
266QStringList QgsValueRelationFieldFormatter::valueToStringList( const QVariant &value )
267{
268 QStringList checkList;
269 if ( value.userType() == QMetaType::Type::QStringList )
270 {
271 checkList = value.toStringList();
272 }
273 else
274 {
275 QVariantList valuesList;
276 if ( value.userType() == QMetaType::Type::QString )
277 {
278 // This must be an array representation
279 auto newVal { value };
280 if ( newVal.toString().trimmed().startsWith( '{' ) )
281 {
282 //normal case
283 valuesList = QgsPostgresStringUtils::parseArray( newVal.toString() );
284 }
285 else if ( newVal.toString().trimmed().startsWith( '[' ) )
286 {
287 //fallback, in case it's a json array
288 try
289 {
290 for ( auto &element : json::parse( newVal.toString().toStdString() ) )
291 {
292 if ( element.is_number_integer() )
293 {
294 valuesList.push_back( element.get<int>() );
295 }
296 else if ( element.is_number_unsigned() )
297 {
298 valuesList.push_back( element.get<unsigned>() );
299 }
300 else if ( element.is_string() )
301 {
302 valuesList.push_back( QString::fromStdString( element.get<std::string>() ) );
303 }
304 }
305 }
306 catch ( json::parse_error &ex )
307 {
308 QgsMessageLog::logMessage( QObject::tr( "Cannot parse JSON like string '%1' Error: %2" ).arg( newVal.toString(), ex.what() ) );
309 }
310 }
311 }
312 else if ( value.userType() == QMetaType::Type::QVariantList )
313 {
314 valuesList = value.toList( );
315 }
316
317 checkList.reserve( valuesList.size() );
318 for ( const QVariant &listItem : std::as_const( valuesList ) )
319 {
320 QString v( listItem.toString( ) );
321 if ( ! v.isEmpty() )
322 checkList.append( v );
323 }
324 }
325 return checkList;
326}
327
328
329QSet<QString> QgsValueRelationFieldFormatter::expressionFormVariables( const QString &expression )
330{
331 std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::formScope() );
332 QSet< QString > formVariables = qgis::listToSet( scope->variableNames() );
333 const QSet< QString > usedVariables = QgsExpression( expression ).referencedVariables();
334 formVariables.intersect( usedVariables );
335 return formVariables;
336}
337
339{
340 std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::parentFormScope() );
341 QSet< QString > formVariables = qgis::listToSet( scope->variableNames() );
342 const QSet< QString > usedVariables = QgsExpression( expression ).referencedVariables();
343 formVariables.intersect( usedVariables );
344 return formVariables;
345}
346
348{
349 return !( expressionFormAttributes( expression ).isEmpty() && expressionFormVariables( expression ).isEmpty() );
350}
351
353{
354 return !( expressionParentFormAttributes( expression ).isEmpty() && expressionParentFormVariables( expression ).isEmpty() );
355}
356
358{
359 QSet<QString> attributes;
360 QgsExpression exp( expression );
361 std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::parentFormScope() );
362 // List of form function names used in the expression
363 const QSet<QString> formFunctions( qgis::listToSet( scope->functionNames() )
364 .intersect( exp.referencedFunctions( ) ) );
365 const QList<const QgsExpressionNodeFunction *> expFunctions( exp.findNodes<QgsExpressionNodeFunction>() );
366 QgsExpressionContext context;
367 for ( const auto &f : expFunctions )
368 {
369 QgsExpressionFunction *fd = QgsExpression::QgsExpression::Functions()[f->fnIndex()];
370 if ( formFunctions.contains( fd->name( ) ) )
371 {
372 const QList<QgsExpressionNode *> cExpressionNodes { f->args( )->list() };
373 for ( const auto &param : std::as_const( cExpressionNodes ) )
374 {
375 attributes.insert( param->eval( &exp, &context ).toString() );
376 }
377 }
378 }
379 return attributes;
380}
381
382QSet<QString> QgsValueRelationFieldFormatter::expressionFormAttributes( const QString &expression )
383{
384 QSet<QString> attributes;
385 QgsExpression exp( expression );
386 std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::formScope() );
387 // List of form function names used in the expression
388 const QSet<QString> formFunctions( qgis::listToSet( scope->functionNames() )
389 .intersect( exp.referencedFunctions( ) ) );
390 const QList<const QgsExpressionNodeFunction *> expFunctions( exp.findNodes<QgsExpressionNodeFunction>() );
391 QgsExpressionContext context;
392 for ( const auto &f : expFunctions )
393 {
394 QgsExpressionFunction *fd = QgsExpression::QgsExpression::Functions()[f->fnIndex()];
395 if ( formFunctions.contains( fd->name( ) ) )
396 {
397 const QList<QgsExpressionNode *> cExpressionNodes { f->args( )->list() };
398 for ( const auto &param : std::as_const( cExpressionNodes ) )
399 {
400 attributes.insert( param->eval( &exp, &context ).toString() );
401 }
402 }
403 }
404 return attributes;
405}
406
408 const QgsFeature &feature,
409 const QgsFeature &parentFeature )
410{
411 const QSet<QString> attrs = expressionFormAttributes( expression );
412 for ( auto it = attrs.constBegin() ; it != attrs.constEnd(); it++ )
413 {
414 if ( feature.fieldNameIndex( *it ) < 0 )
415 return false;
416 }
417
418 if ( ! expressionFormVariables( expression ).isEmpty() && feature.geometry().isEmpty( ) )
419 return false;
420
421 if ( parentFeature.isValid() )
422 {
423 const QSet<QString> parentAttrs = expressionParentFormAttributes( expression );
424 for ( auto it = parentAttrs.constBegin() ; it != parentAttrs.constEnd(); it++ )
425 {
426 if ( ! parentFeature.attribute( *it ).isValid() )
427 return false;
428 }
429 if ( ! expressionParentFormVariables( expression ).isEmpty() && parentFeature.geometry().isEmpty( ) )
430 return false;
431 }
432 return true;
433}
434
435QgsVectorLayer *QgsValueRelationFieldFormatter::resolveLayer( const QVariantMap &config, const QgsProject *project )
436{
437 QgsVectorLayerRef ref { config.value( QStringLiteral( "Layer" ) ).toString(),
438 config.value( QStringLiteral( "LayerName" ) ).toString(),
439 config.value( QStringLiteral( "LayerSource" ) ).toString(),
440 config.value( QStringLiteral( "LayerProviderName" ) ).toString() };
441 return ref.resolveByIdOrNameOnly( project );
442}
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
static QString nullRepresentation()
Returns the string used to represent the value NULL throughout QGIS.
static QgsExpressionContextScope * parentFormScope(const QgsFeature &formFeature=QgsFeature(), const QString &formMode=QString())
Creates a new scope which contains functions and variables from the current parent attribute form/tab...
static QgsExpressionContextScope * formScope(const QgsFeature &formFeature=QgsFeature(), const QString &formMode=QString())
Creates a new scope which contains functions and variables from the current attribute form/table form...
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 appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
An abstract base class for defining QgsExpression functions.
QString name() const
The name of the function.
An expression node for expression functions.
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.
QList< const T * > findNodes() const
Returns a list of all nodes of the given class which are used in this expression.
QSet< QString > referencedVariables() const
Returns a list of all variables which are used in this expression.
QSet< QString > referencedFunctions() const
Returns a list of the names of all functions which are used in this expression.
QVariant evaluate()
Evaluate the feature and return the result.
bool isValid() const
Checks if this expression is valid.
QSet< int > referencedAttributeIndexes(const QgsFields &fields) const
Returns a list of field name indexes obtained from the provided fields.
Wrapper for iterator of features from vector data provider or vector layer.
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.
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
QgsFeatureRequest & setExpressionContext(const QgsExpressionContext &context)
Sets the expression context used to evaluate filter expressions.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
int fieldNameIndex(const QString &fieldName) const
Utility method to get attribute index from name.
QgsGeometry geometry
Definition qgsfeature.h:69
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.
QMetaType::Type type
Definition qgsfield.h:60
Container of fields for a vector layer.
Definition qgsfields.h:46
Q_INVOKABLE int indexOf(const QString &fieldName) const
Gets the field index from the field name.
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.
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
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())
Adds a message to the log instance (and creates it if necessary).
static QVariantList parseArray(const QString &string)
Returns a QVariantList created out of a string containing an array in postgres array format {1,...
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:107
static QgsProject * instance()
Returns the QgsProject singleton instance.
static QgsVectorLayer * resolveLayer(const QVariantMap &config, const QgsProject *project)
Returns the (possibly nullptr) layer from the widget's config and project.
static QSet< QString > expressionFormVariables(const QString &expression)
Returns a list of variables required by the form context expression.
static bool expressionRequiresFormScope(const QString &expression)
Check if the expression requires a form scope (i.e.
QString id() const override
Returns a unique id for this field formatter.
static bool expressionRequiresParentFormScope(const QString &expression)
Check if the expression requires a parent form scope (i.e.
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.
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.
QVariant createCache(QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config) const override
Create a cache for a given field.
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...
static QSet< QString > expressionParentFormVariables(const QString &expression)
Returns a list of variables required by the parent form's form context expression.
static QSet< QString > expressionParentFormAttributes(const QString &expression)
Returns a list of attributes required by the parent form's form context expression.
static QStringList valueToStringList(const QVariant &value)
Utility to convert a list or a string representation of an (hstore style: {1,2...}) list in value to ...
static QSet< QString > expressionFormAttributes(const QString &expression)
Returns a list of attributes required by the form context expression.
static bool expressionIsUsable(const QString &expression, const QgsFeature &feature, const QgsFeature &parentFeature=QgsFeature())
Check whether the feature has all values required by the expression, optionally checks for parentFeat...
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...
QVector< QgsValueRelationFieldFormatter::ValueRelationItem > ValueRelationCache
QgsValueRelationFieldFormatter()
Constructor for QgsValueRelationFieldFormatter.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
Represents a vector layer which manages a vector based dataset.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QSet< QVariant > uniqueValues(int fieldIndex, int limit=-1) const FINAL
Calculates a list of unique values contained within an attribute in the layer.
QSet< int > QgsAttributeIds
_LayerRef< QgsVectorLayer > QgsVectorLayerRef
TYPE * resolveByIdOrNameOnly(const QgsProject *project)
Resolves the map layer by attempting to find a matching layer in a project using a weak match.