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