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