QGIS API Documentation 3.99.0-Master (26c88405ac0)
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 <nlohmann/json.hpp>
19
20#include "qgis.h"
21#include "qgsapplication.h"
24#include "qgsmessagelog.h"
26#include "qgsproject.h"
27#include "qgsvariantutils.h"
28#include "qgsvectorlayer.h"
29#include "qgsvectorlayerref.h"
30
31using namespace nlohmann;
32
33#include <QSettings>
34
36{
37 setFlags( flags() | QgsFieldFormatter::CanProvideAvailableValues );
38}
39
41{
42 return QStringLiteral( "ValueRelation" );
43}
44
45QString QgsValueRelationFieldFormatter::representValue( QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config, const QVariant &cache, const QVariant &value ) const
46{
47 ValueRelationCache vrCache;
48
49 if ( cache.isValid() )
50 {
52 }
53 else
54 {
56 }
57
58 if ( config.value( QStringLiteral( "AllowMulti" ) ).toBool() )
59 {
60 QStringList keyList;
61
62 if ( layer->fields().at( fieldIndex ).type() == QMetaType::Type::QVariantMap )
63 {
64 //because of json it's stored as QVariantList
65 keyList = value.toStringList();
66 }
67 else
68 {
69 keyList = valueToStringList( value );
70 }
71
72 QStringList valueList;
73
74 for ( const QgsValueRelationFieldFormatter::ValueRelationItem &item : std::as_const( vrCache ) )
75 {
76 if ( keyList.contains( item.key.toString() ) )
77 {
78 valueList << item.value;
79 }
80 }
81
82 return valueList.join( QLatin1String( ", " ) ).prepend( '{' ).append( '}' );
83 }
84 else
85 {
86 if ( QgsVariantUtils::isNull( value ) )
87 {
89 }
90
91 for ( const QgsValueRelationFieldFormatter::ValueRelationItem &item : std::as_const( vrCache ) )
92 {
93 if ( item.key == value )
94 {
95 return item.value;
96 }
97 }
98 }
99
100 return QStringLiteral( "(%1)" ).arg( value.toString() );
101}
102
103QVariant QgsValueRelationFieldFormatter::sortValue( QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config, const QVariant &cache, const QVariant &value ) const
104{
105 return QgsVariantUtils::isNull( value ) ? QString() : representValue( layer, fieldIndex, config, cache, value );
106}
107
108QVariant QgsValueRelationFieldFormatter::createCache( QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config ) const
109{
110 Q_UNUSED( layer )
111 Q_UNUSED( fieldIndex )
112 return QVariant::fromValue<ValueRelationCache>( createCache( config ) );
113
114}
115
117 const QVariantMap &config,
118 const QgsFeature &formFeature,
119 const QgsFeature &parentFormFeature )
120{
121 ValueRelationCache cache;
122
123 const QgsVectorLayer *layer = resolveLayer( config, QgsProject::instance() ); // skip-keyword-check
124
125 if ( !layer )
126 return cache;
127
128 QgsFields fields = layer->fields();
129 const int keyIdx = fields.indexOf( config.value( QStringLiteral( "Key" ) ).toString() );
130 const int valueIdx = fields.indexOf( config.value( QStringLiteral( "Value" ) ).toString() );
131
132 QgsFeatureRequest request;
133
135 QgsAttributeIds subsetOfAttributes { keyIdx, valueIdx };
136
137 const int groupIdx = fields.lookupField( config.value( QStringLiteral( "Group" ) ).toString() );
138 if ( groupIdx > -1 )
139 {
140 subsetOfAttributes << groupIdx;
141 }
142
143 const bool orderByField { config.value( QStringLiteral( "OrderByField" ) ).toBool() };
144 const int fieldIdx { orderByField ? layer->fields().lookupField( config.value( QStringLiteral( "OrderByFieldName" ) ).toString() ) : -1 };
145 const bool reverseSort { config.value( QStringLiteral( "OrderByDescending" ) ).toBool() };
146 if ( fieldIdx != -1 )
147 {
148 subsetOfAttributes << fieldIdx;
149 }
150
151 const QString descriptionExpressionString = config.value( "Description" ).toString();
152 QgsExpression descriptionExpression( descriptionExpressionString );
154 descriptionExpression.prepare( &context );
155 subsetOfAttributes += descriptionExpression.referencedAttributeIndexes( layer->fields() );
156 request.setSubsetOfAttributes( qgis::setToList( subsetOfAttributes ) );
157
158 const QString filterExpression = config.value( QStringLiteral( "FilterExpression" ) ).toString();
159
160 // Skip the filter and build a full cache if the form scope is required and the feature
161 // is not valid or the attributes required for the filter have no valid value
162 // Note: parent form scope is not checked for usability because it's supposed to
163 // be used into a coalesce that retrieve the current value of the parent
164 // from the parent layer when used outside of an embedded form
165 if ( ! filterExpression.isEmpty() && ( !( expressionRequiresFormScope( filterExpression ) )
166 || expressionIsUsable( filterExpression, formFeature ) ) )
167 {
168 QgsExpressionContext filterContext = context;
169 if ( formFeature.isValid( ) && QgsValueRelationFieldFormatter::expressionRequiresFormScope( filterExpression ) )
170 filterContext.appendScope( QgsExpressionContextUtils::formScope( formFeature ) );
171 if ( parentFormFeature.isValid() && QgsValueRelationFieldFormatter::expressionRequiresParentFormScope( filterExpression ) )
172 filterContext.appendScope( QgsExpressionContextUtils::parentFormScope( parentFormFeature ) );
173 request.setExpressionContext( filterContext );
174 request.setFilterExpression( filterExpression );
175 }
176
177 QgsFeatureIterator fit = layer->getFeatures( request );
178 QMap<QVariant, QVariant> orderByFieldValues;
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 const QVariant keyValue = f.attribute( keyIdx );
191 if ( fieldIdx != -1 )
192 {
193 orderByFieldValues.insert( keyValue, f.attribute( fieldIdx ) );
194 }
195 cache.append( ValueRelationItem( keyValue, f.attribute( valueIdx ).toString(), description, group ) );
196 }
197
198
199 if ( config.value( QStringLiteral( "OrderByValue" ) ).toBool() )
200 {
201 std::sort( cache.begin(), cache.end(), [&reverseSort]( const QgsValueRelationFieldFormatter::ValueRelationItem & p1, const QgsValueRelationFieldFormatter::ValueRelationItem & p2 ) -> bool
202 {
203 if ( reverseSort )
204 return p1.group == p2.group ? qgsVariantGreaterThan( p1.value, p2.value ) : qgsVariantGreaterThan( p1.group, p2.group );
205 else
206 return p1.group == p2.group ? qgsVariantLessThan( p1.value, p2.value ) : qgsVariantLessThan( p1.group, p2.group );
207 } );
208 }
209 // Order by field
210 else if ( fieldIdx != -1 )
211 {
212 std::sort( cache.begin(), cache.end(), [&reverseSort, &orderByFieldValues]( const QgsValueRelationFieldFormatter::ValueRelationItem & p1, const QgsValueRelationFieldFormatter::ValueRelationItem & p2 ) -> bool
213 {
214 if ( reverseSort )
215 return p1.group == p2.group ? qgsVariantGreaterThan( orderByFieldValues.value( p1.key ), orderByFieldValues.value( p2.key ) ) : qgsVariantGreaterThan( p1.group, p2.group );
216 else
217 return p1.group == p2.group ? qgsVariantLessThan( orderByFieldValues.value( p1.key ), orderByFieldValues.value( p2.key ) ) : qgsVariantLessThan( p1.group, p2.group );
218
219 } );
220 }
221 // OrderByKey is the default
222 else
223 {
224 std::sort( cache.begin(), cache.end(), [&reverseSort]( const QgsValueRelationFieldFormatter::ValueRelationItem & p1, const QgsValueRelationFieldFormatter::ValueRelationItem & p2 ) -> bool
225 {
226 if ( reverseSort )
227 return p1.group == p2.group ? qgsVariantGreaterThan( p1.key, p2.key ) : qgsVariantGreaterThan( p1.group, p2.group );
228 else
229 return p1.group == p2.group ? qgsVariantLessThan( p1.key, p2.key ) : qgsVariantLessThan( p1.group, p2.group );
230 } );
231 }
232
233 return cache;
234}
235
236
237QList<QgsVectorLayerRef> QgsValueRelationFieldFormatter::layerDependencies( const QVariantMap &config ) const
238{
239 QList<QgsVectorLayerRef> result;
240 const QString layerId { config.value( QStringLiteral( "Layer" ) ).toString() };
241 const QString layerName { config.value( QStringLiteral( "LayerName" ) ).toString() };
242 const QString providerName { config.value( QStringLiteral( "LayerProviderName" ) ).toString() };
243 const QString layerSource { config.value( QStringLiteral( "LayerSource" ) ).toString() };
244 if ( ! layerId.isEmpty() && ! layerName.isEmpty() && ! providerName.isEmpty() && ! layerSource.isEmpty() )
245 {
246 result.append( QgsVectorLayerRef( layerId, layerName, layerSource, providerName ) );
247 }
248 return result;
249}
250
251QVariantList QgsValueRelationFieldFormatter::availableValues( const QVariantMap &config, int countLimit, const QgsFieldFormatterContext &context ) const
252{
253 QVariantList values;
254
255 if ( auto *lProject = context.project() )
256 {
257 const QgsVectorLayer *referencedLayer = qobject_cast<QgsVectorLayer *>( lProject->mapLayer( config[QStringLiteral( "Layer" )].toString() ) );
258 if ( referencedLayer )
259 {
260 int fieldIndex = referencedLayer->fields().indexOf( config.value( QStringLiteral( "Key" ) ).toString() );
261 values = qgis::setToList( referencedLayer->uniqueValues( fieldIndex, countLimit ) );
262 }
263 }
264 return values;
265}
266
267QStringList QgsValueRelationFieldFormatter::valueToStringList( const QVariant &value )
268{
269 QStringList checkList;
270 if ( value.userType() == QMetaType::Type::QStringList )
271 {
272 checkList = value.toStringList();
273 }
274 else
275 {
276 QVariantList valuesList;
277 if ( value.userType() == QMetaType::Type::QString )
278 {
279 // This must be an array representation
280 auto newVal { value };
281 if ( newVal.toString().trimmed().startsWith( '{' ) )
282 {
283 //normal case
284 valuesList = QgsPostgresStringUtils::parseArray( newVal.toString() );
285 }
286 else if ( newVal.toString().trimmed().startsWith( '[' ) )
287 {
288 //fallback, in case it's a json array
289 try
290 {
291 for ( auto &element : json::parse( newVal.toString().toStdString() ) )
292 {
293 if ( element.is_number_integer() )
294 {
295 valuesList.push_back( element.get<int>() );
296 }
297 else if ( element.is_number_unsigned() )
298 {
299 valuesList.push_back( element.get<unsigned>() );
300 }
301 else if ( element.is_string() )
302 {
303 valuesList.push_back( QString::fromStdString( element.get<std::string>() ) );
304 }
305 }
306 }
307 catch ( json::parse_error &ex )
308 {
309 QgsMessageLog::logMessage( QObject::tr( "Cannot parse JSON like string '%1' Error: %2" ).arg( newVal.toString(), ex.what() ) );
310 }
311 }
312 }
313 else if ( value.userType() == QMetaType::Type::QVariantList )
314 {
315 valuesList = value.toList( );
316 }
317
318 checkList.reserve( valuesList.size() );
319 for ( const QVariant &listItem : std::as_const( valuesList ) )
320 {
321 QString v( listItem.toString( ) );
322 if ( ! v.isEmpty() )
323 checkList.append( v );
324 }
325 }
326 return checkList;
327}
328
329
330QSet<QString> QgsValueRelationFieldFormatter::expressionFormVariables( const QString &expression )
331{
332 std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::formScope() );
333 QSet< QString > formVariables = qgis::listToSet( scope->variableNames() );
334 const QSet< QString > usedVariables = QgsExpression( expression ).referencedVariables();
335 formVariables.intersect( usedVariables );
336 return formVariables;
337}
338
340{
341 std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::parentFormScope() );
342 QSet< QString > formVariables = qgis::listToSet( scope->variableNames() );
343 const QSet< QString > usedVariables = QgsExpression( expression ).referencedVariables();
344 formVariables.intersect( usedVariables );
345 return formVariables;
346}
347
349{
350 return !( expressionFormAttributes( expression ).isEmpty() && expressionFormVariables( expression ).isEmpty() );
351}
352
354{
355 return !( expressionParentFormAttributes( expression ).isEmpty() && expressionParentFormVariables( expression ).isEmpty() );
356}
357
359{
360 QSet<QString> attributes;
361 QgsExpression exp( expression );
362 std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::parentFormScope() );
363 // List of form function names used in the expression
364 const QSet<QString> formFunctions( qgis::listToSet( scope->functionNames() )
365 .intersect( exp.referencedFunctions( ) ) );
366 const QList<const QgsExpressionNodeFunction *> expFunctions( exp.findNodes<QgsExpressionNodeFunction>() );
367 QgsExpressionContext context;
368 for ( const auto &f : expFunctions )
369 {
370 QgsExpressionFunction *fd = QgsExpression::QgsExpression::Functions()[f->fnIndex()];
371 if ( formFunctions.contains( fd->name( ) ) )
372 {
373 const QList<QgsExpressionNode *> cExpressionNodes { f->args( )->list() };
374 for ( const auto &param : std::as_const( cExpressionNodes ) )
375 {
376 attributes.insert( param->eval( &exp, &context ).toString() );
377 }
378 }
379 }
380 return attributes;
381}
382
383QSet<QString> QgsValueRelationFieldFormatter::expressionFormAttributes( const QString &expression )
384{
385 QSet<QString> attributes;
386 QgsExpression exp( expression );
387 std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::formScope() );
388 // List of form function names used in the expression
389 const QSet<QString> formFunctions( qgis::listToSet( scope->functionNames() )
390 .intersect( exp.referencedFunctions( ) ) );
391 const QList<const QgsExpressionNodeFunction *> expFunctions( exp.findNodes<QgsExpressionNodeFunction>() );
392 QgsExpressionContext context;
393 for ( const auto &f : expFunctions )
394 {
395 QgsExpressionFunction *fd = QgsExpression::QgsExpression::Functions()[f->fnIndex()];
396 if ( formFunctions.contains( fd->name( ) ) )
397 {
398 const QList<QgsExpressionNode *> cExpressionNodes { f->args( )->list() };
399 for ( const auto &param : std::as_const( cExpressionNodes ) )
400 {
401 attributes.insert( param->eval( &exp, &context ).toString() );
402 }
403 }
404 }
405 return attributes;
406}
407
409 const QgsFeature &feature,
410 const QgsFeature &parentFeature )
411{
412 const QSet<QString> attrs = expressionFormAttributes( expression );
413 for ( auto it = attrs.constBegin() ; it != attrs.constEnd(); it++ )
414 {
415 if ( feature.fieldNameIndex( *it ) < 0 )
416 return false;
417 }
418
419 if ( ! expressionFormVariables( expression ).isEmpty() && feature.geometry().isEmpty( ) )
420 return false;
421
422 if ( parentFeature.isValid() )
423 {
424 const QSet<QString> parentAttrs = expressionParentFormAttributes( expression );
425 for ( auto it = parentAttrs.constBegin() ; it != parentAttrs.constEnd(); it++ )
426 {
427 if ( ! parentFeature.attribute( *it ).isValid() )
428 return false;
429 }
430 if ( ! expressionParentFormVariables( expression ).isEmpty() && parentFeature.geometry().isEmpty( ) )
431 return false;
432 }
433 return true;
434}
435
436QgsVectorLayer *QgsValueRelationFieldFormatter::resolveLayer( const QVariantMap &config, const QgsProject *project )
437{
438 QgsVectorLayerRef ref { config.value( QStringLiteral( "Layer" ) ).toString(),
439 config.value( QStringLiteral( "LayerName" ) ).toString(),
440 config.value( QStringLiteral( "LayerSource" ) ).toString(),
441 config.value( QStringLiteral( "LayerProviderName" ) ).toString() };
442 return ref.resolveByIdOrNameOnly( project );
443}
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition qgis.h:2196
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:61
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:109
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...}...
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.