QGIS API Documentation 3.37.0-Master (614ebf5f6b2)
Loading...
Searching...
No Matches
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
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
38
43
45{
46 setFlags( flags() | QgsFieldFormatter::CanProvideAvailableValues );
47}
48
50{
51 return QStringLiteral( "ValueRelation" );
52}
53
54QString QgsValueRelationFieldFormatter::representValue( QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config, const QVariant &cache, const QVariant &value ) const
55{
56 ValueRelationCache vrCache;
57
58 if ( cache.isValid() )
59 {
61 }
62 else
63 {
65 }
66
67 if ( config.value( QStringLiteral( "AllowMulti" ) ).toBool() )
68 {
69 QStringList keyList;
70
71 if ( layer->fields().at( fieldIndex ).type() == QMetaType::Type::QVariantMap )
72 {
73 //because of json it's stored as QVariantList
74 keyList = value.toStringList();
75 }
76 else
77 {
78 keyList = valueToStringList( value );
79 }
80
81 QStringList valueList;
82
83 for ( const QgsValueRelationFieldFormatter::ValueRelationItem &item : std::as_const( vrCache ) )
84 {
85 if ( keyList.contains( item.key.toString() ) )
86 {
87 valueList << item.value;
88 }
89 }
90
91 return valueList.join( QLatin1String( ", " ) ).prepend( '{' ).append( '}' );
92 }
93 else
94 {
95 if ( QgsVariantUtils::isNull( value ) )
96 {
98 }
99
100 for ( const QgsValueRelationFieldFormatter::ValueRelationItem &item : std::as_const( vrCache ) )
101 {
102 if ( item.key == value )
103 {
104 return item.value;
105 }
106 }
107 }
108
109 return QStringLiteral( "(%1)" ).arg( value.toString() );
110}
111
112QVariant QgsValueRelationFieldFormatter::sortValue( QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config, const QVariant &cache, const QVariant &value ) const
113{
114 return QgsVariantUtils::isNull( value ) ? QString() : representValue( layer, fieldIndex, config, cache, value );
115}
116
117QVariant QgsValueRelationFieldFormatter::createCache( QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config ) const
118{
119 Q_UNUSED( layer )
120 Q_UNUSED( fieldIndex )
121 return QVariant::fromValue<ValueRelationCache>( createCache( config ) );
122
123}
124
126 const QVariantMap &config,
127 const QgsFeature &formFeature,
128 const QgsFeature &parentFormFeature )
129{
130 ValueRelationCache cache;
131
132 const QgsVectorLayer *layer = resolveLayer( config, QgsProject::instance() );
133
134 if ( !layer )
135 return cache;
136
137 QgsFields fields = layer->fields();
138 const int keyIdx = fields.indexOf( config.value( QStringLiteral( "Key" ) ).toString() );
139 const int valueIdx = fields.indexOf( config.value( QStringLiteral( "Value" ) ).toString() );
140
141 QgsFeatureRequest request;
142
144 QgsAttributeIds subsetOfAttributes { keyIdx, valueIdx };
145
146 const int groupIdx = fields.lookupField( config.value( QStringLiteral( "Group" ) ).toString() );
147 if ( groupIdx > -1 )
148 {
149 subsetOfAttributes << groupIdx;
150 }
151
152 const QString descriptionExpressionString = config.value( "Description" ).toString();
153 QgsExpression descriptionExpression( descriptionExpressionString );
155 descriptionExpression.prepare( &context );
156 subsetOfAttributes += descriptionExpression.referencedAttributeIndexes( layer->fields() );
157 request.setSubsetOfAttributes( qgis::setToList( subsetOfAttributes ) );
158
159 const QString filterExpression = config.value( QStringLiteral( "FilterExpression" ) ).toString();
160
161 // Skip the filter and build a full cache if the form scope is required and the feature
162 // is not valid or the attributes required for the filter have no valid value
163 // Note: parent form scope is not checked for usability because it's supposed to
164 // be used into a coalesce that retrieve the current value of the parent
165 // from the parent layer when used outside of an embedded form
166 if ( ! filterExpression.isEmpty() && ( !( expressionRequiresFormScope( filterExpression ) )
167 || expressionIsUsable( filterExpression, formFeature ) ) )
168 {
169 QgsExpressionContext filterContext = context;
170 if ( formFeature.isValid( ) && QgsValueRelationFieldFormatter::expressionRequiresFormScope( filterExpression ) )
171 filterContext.appendScope( QgsExpressionContextUtils::formScope( formFeature ) );
172 if ( parentFormFeature.isValid() && QgsValueRelationFieldFormatter::expressionRequiresParentFormScope( filterExpression ) )
173 filterContext.appendScope( QgsExpressionContextUtils::parentFormScope( parentFormFeature ) );
174 request.setExpressionContext( filterContext );
175 request.setFilterExpression( filterExpression );
176 }
177
178 QgsFeatureIterator fit = layer->getFeatures( request );
179
180 QgsFeature f;
181 while ( fit.nextFeature( f ) )
182 {
183 QString description;
184 if ( descriptionExpression.isValid() )
185 {
186 context.setFeature( f );
187 description = descriptionExpression.evaluate( &context ).toString();
188 }
189 const QVariant group = groupIdx > -1 ? f.attribute( groupIdx ) : QVariant();
190 cache.append( ValueRelationItem( f.attribute( keyIdx ), f.attribute( valueIdx ).toString(), description, group ) );
191 }
192
193 if ( config.value( QStringLiteral( "OrderByValue" ) ).toBool() )
194 {
195 std::sort( cache.begin(), cache.end(), orderByValueLessThan );
196 }
197 else
198 {
199 std::sort( cache.begin(), cache.end(), orderByKeyLessThan );
200 }
201
202 return cache;
203}
204
205
206QList<QgsVectorLayerRef> QgsValueRelationFieldFormatter::layerDependencies( const QVariantMap &config ) const
207{
208 QList<QgsVectorLayerRef> result;
209 const QString layerId { config.value( QStringLiteral( "Layer" ) ).toString() };
210 const QString layerName { config.value( QStringLiteral( "LayerName" ) ).toString() };
211 const QString providerName { config.value( QStringLiteral( "LayerProviderName" ) ).toString() };
212 const QString layerSource { config.value( QStringLiteral( "LayerSource" ) ).toString() };
213 if ( ! layerId.isEmpty() && ! layerName.isEmpty() && ! providerName.isEmpty() && ! layerSource.isEmpty() )
214 {
215 result.append( QgsVectorLayerRef( layerId, layerName, layerSource, providerName ) );
216 }
217 return result;
218}
219
220QVariantList QgsValueRelationFieldFormatter::availableValues( const QVariantMap &config, int countLimit, const QgsFieldFormatterContext &context ) const
221{
222 QVariantList values;
223
224 if ( auto *lProject = context.project() )
225 {
226 const QgsVectorLayer *referencedLayer = qobject_cast<QgsVectorLayer *>( lProject->mapLayer( config[QStringLiteral( "Layer" )].toString() ) );
227 if ( referencedLayer )
228 {
229 int fieldIndex = referencedLayer->fields().indexOf( config.value( QStringLiteral( "Key" ) ).toString() );
230 values = qgis::setToList( referencedLayer->uniqueValues( fieldIndex, countLimit ) );
231 }
232 }
233 return values;
234}
235
236QStringList QgsValueRelationFieldFormatter::valueToStringList( const QVariant &value )
237{
238 QStringList checkList;
239 if ( value.userType() == QMetaType::Type::QStringList )
240 {
241 checkList = value.toStringList();
242 }
243 else
244 {
245 QVariantList valuesList;
246 if ( value.userType() == QMetaType::Type::QString )
247 {
248 // This must be an array representation
249 auto newVal { value };
250 if ( newVal.toString().trimmed().startsWith( '{' ) )
251 {
252 //normal case
253 valuesList = QgsPostgresStringUtils::parseArray( newVal.toString() );
254 }
255 else if ( newVal.toString().trimmed().startsWith( '[' ) )
256 {
257 //fallback, in case it's a json array
258 try
259 {
260 for ( auto &element : json::parse( newVal.toString().toStdString() ) )
261 {
262 if ( element.is_number_integer() )
263 {
264 valuesList.push_back( element.get<int>() );
265 }
266 else if ( element.is_number_unsigned() )
267 {
268 valuesList.push_back( element.get<unsigned>() );
269 }
270 else if ( element.is_string() )
271 {
272 valuesList.push_back( QString::fromStdString( element.get<std::string>() ) );
273 }
274 }
275 }
276 catch ( json::parse_error &ex )
277 {
278 QgsMessageLog::logMessage( QObject::tr( "Cannot parse JSON like string '%1' Error: %2" ).arg( newVal.toString(), ex.what() ) );
279 }
280 }
281 }
282 else if ( value.userType() == QMetaType::Type::QVariantList )
283 {
284 valuesList = value.toList( );
285 }
286
287 checkList.reserve( valuesList.size() );
288 for ( const QVariant &listItem : std::as_const( valuesList ) )
289 {
290 QString v( listItem.toString( ) );
291 if ( ! v.isEmpty() )
292 checkList.append( v );
293 }
294 }
295 return checkList;
296}
297
298
299QSet<QString> QgsValueRelationFieldFormatter::expressionFormVariables( const QString &expression )
300{
301 std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::formScope() );
302 QSet< QString > formVariables = qgis::listToSet( scope->variableNames() );
303 const QSet< QString > usedVariables = QgsExpression( expression ).referencedVariables();
304 formVariables.intersect( usedVariables );
305 return formVariables;
306}
307
309{
310 std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::parentFormScope() );
311 QSet< QString > formVariables = qgis::listToSet( scope->variableNames() );
312 const QSet< QString > usedVariables = QgsExpression( expression ).referencedVariables();
313 formVariables.intersect( usedVariables );
314 return formVariables;
315}
316
318{
319 return !( expressionFormAttributes( expression ).isEmpty() && expressionFormVariables( expression ).isEmpty() );
320}
321
323{
324 return !( expressionParentFormAttributes( expression ).isEmpty() && expressionParentFormVariables( expression ).isEmpty() );
325}
326
328{
329 QSet<QString> attributes;
330 QgsExpression exp( expression );
331 std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::parentFormScope() );
332 // List of form function names used in the expression
333 const QSet<QString> formFunctions( qgis::listToSet( scope->functionNames() )
334 .intersect( exp.referencedFunctions( ) ) );
335 const QList<const QgsExpressionNodeFunction *> expFunctions( exp.findNodes<QgsExpressionNodeFunction>() );
336 QgsExpressionContext context;
337 for ( const auto &f : expFunctions )
338 {
339 QgsExpressionFunction *fd = QgsExpression::QgsExpression::Functions()[f->fnIndex()];
340 if ( formFunctions.contains( fd->name( ) ) )
341 {
342 const QList<QgsExpressionNode *> cExpressionNodes { f->args( )->list() };
343 for ( const auto &param : std::as_const( cExpressionNodes ) )
344 {
345 attributes.insert( param->eval( &exp, &context ).toString() );
346 }
347 }
348 }
349 return attributes;
350}
351
352QSet<QString> QgsValueRelationFieldFormatter::expressionFormAttributes( const QString &expression )
353{
354 QSet<QString> attributes;
355 QgsExpression exp( expression );
356 std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::formScope() );
357 // List of form function names used in the expression
358 const QSet<QString> formFunctions( qgis::listToSet( scope->functionNames() )
359 .intersect( exp.referencedFunctions( ) ) );
360 const QList<const QgsExpressionNodeFunction *> expFunctions( exp.findNodes<QgsExpressionNodeFunction>() );
361 QgsExpressionContext context;
362 for ( const auto &f : expFunctions )
363 {
364 QgsExpressionFunction *fd = QgsExpression::QgsExpression::Functions()[f->fnIndex()];
365 if ( formFunctions.contains( fd->name( ) ) )
366 {
367 const QList<QgsExpressionNode *> cExpressionNodes { f->args( )->list() };
368 for ( const auto &param : std::as_const( cExpressionNodes ) )
369 {
370 attributes.insert( param->eval( &exp, &context ).toString() );
371 }
372 }
373 }
374 return attributes;
375}
376
378 const QgsFeature &feature,
379 const QgsFeature &parentFeature )
380{
381 const QSet<QString> attrs = expressionFormAttributes( expression );
382 for ( auto it = attrs.constBegin() ; it != attrs.constEnd(); it++ )
383 {
384 if ( feature.fieldNameIndex( *it ) < 0 )
385 return false;
386 }
387
388 if ( ! expressionFormVariables( expression ).isEmpty() && feature.geometry().isEmpty( ) )
389 return false;
390
391 if ( parentFeature.isValid() )
392 {
393 const QSet<QString> parentAttrs = expressionParentFormAttributes( expression );
394 for ( auto it = parentAttrs.constBegin() ; it != parentAttrs.constEnd(); it++ )
395 {
396 if ( ! parentFeature.attribute( *it ).isValid() )
397 return false;
398 }
399 if ( ! expressionParentFormVariables( expression ).isEmpty() && parentFeature.geometry().isEmpty( ) )
400 return false;
401 }
402 return true;
403}
404
405QgsVectorLayer *QgsValueRelationFieldFormatter::resolveLayer( const QVariantMap &config, const QgsProject *project )
406{
407 QgsVectorLayerRef ref { config.value( QStringLiteral( "Layer" ) ).toString(),
408 config.value( QStringLiteral( "LayerName" ) ).toString(),
409 config.value( QStringLiteral( "LayerSource" ) ).toString(),
410 config.value( QStringLiteral( "LayerProviderName" ) ).toString() };
411 return ref.resolveByIdOrNameOnly( project );
412}
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
static QString nullRepresentation()
This string is 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.
A abstract base class for defining QgsExpression functions.
QString name() const
The name of the function.
An expression node for expression functions.
Class for 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.
This class 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:56
int fieldNameIndex(const QString &fieldName) const
Utility method to get attribute index from name.
QgsGeometry geometry
Definition qgsfeature.h:67
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)
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 NULL) 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 data sets.
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.
bool qgsVariantLessThan(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether the first is less than the second.
Definition qgis.cpp:120
bool orderByKeyLessThan(const QgsValueRelationFieldFormatter::ValueRelationItem &p1, const QgsValueRelationFieldFormatter::ValueRelationItem &p2)
bool orderByValueLessThan(const QgsValueRelationFieldFormatter::ValueRelationItem &p1, const QgsValueRelationFieldFormatter::ValueRelationItem &p2)
QSet< int > QgsAttributeIds
_LayerRef< QgsVectorLayer > QgsVectorLayerRef
QVariant group
Value used to regroup items during sorting (since QGIS 3.38)
TYPE * resolveByIdOrNameOnly(const QgsProject *project)
Resolves the map layer by attempting to find a matching layer in a project using a weak match.