QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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 {
45  return QStringLiteral( "ValueRelation" );
46 }
47 
48 QString QgsValueRelationFieldFormatter::representValue( QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config, const QVariant &cache, const QVariant &value ) const
49 {
50  ValueRelationCache vrCache;
51 
52  if ( cache.isValid() )
53  {
55  }
56  else
57  {
59  }
60 
61  if ( config.value( QStringLiteral( "AllowMulti" ) ).toBool() )
62  {
63  QStringList keyList;
64 
65  if ( layer->fields().at( fieldIndex ).type() == QVariant::Map )
66  {
67  //because of json it's stored as QVariantList
68  keyList = value.toStringList();
69  }
70  else
71  {
72  keyList = valueToStringList( value );
73  }
74 
75  QStringList valueList;
76 
77  for ( const QgsValueRelationFieldFormatter::ValueRelationItem &item : qgis::as_const( vrCache ) )
78  {
79  if ( keyList.contains( item.key.toString() ) )
80  {
81  valueList << item.value;
82  }
83  }
84 
85  return valueList.join( QStringLiteral( ", " ) ).prepend( '{' ).append( '}' );
86  }
87  else
88  {
89  if ( value.isNull() )
90  {
92  }
93 
94  for ( const QgsValueRelationFieldFormatter::ValueRelationItem &item : qgis::as_const( vrCache ) )
95  {
96  if ( item.key == value )
97  {
98  return item.value;
99  }
100  }
101  }
102 
103  return QStringLiteral( "(%1)" ).arg( value.toString() );
104 }
105 
106 QVariant QgsValueRelationFieldFormatter::sortValue( QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config, const QVariant &cache, const QVariant &value ) const
107 {
108  return representValue( layer, fieldIndex, config, cache, value );
109 }
110 
111 QVariant QgsValueRelationFieldFormatter::createCache( QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config ) const
112 {
113  Q_UNUSED( layer )
114  Q_UNUSED( fieldIndex )
115  return QVariant::fromValue<ValueRelationCache>( createCache( config ) );
116 
117 }
118 
120 {
121  ValueRelationCache cache;
122 
123  const QgsVectorLayer *layer = resolveLayer( config, QgsProject::instance() );
124 
125  if ( !layer )
126  return cache;
127 
128  QgsFields fields = layer->fields();
129  int ki = fields.indexOf( config.value( QStringLiteral( "Key" ) ).toString() );
130  int vi = fields.indexOf( config.value( QStringLiteral( "Value" ) ).toString() );
131 
132  QgsFeatureRequest request;
133 
135  request.setSubsetOfAttributes( QgsAttributeList() << ki << vi );
136 
137  const QString expression = config.value( QStringLiteral( "FilterExpression" ) ).toString();
138 
139  // Skip the filter and build a full cache if the form scope is required and the feature
140  // is not valid or the attributes required for the filter have no valid value
141  if ( ! expression.isEmpty() && ( ! expressionRequiresFormScope( expression )
142  || expressionIsUsable( expression, formFeature ) ) )
143  {
145  if ( formFeature.isValid( ) )
146  context.appendScope( QgsExpressionContextUtils::formScope( formFeature ) );
147  request.setExpressionContext( context );
148  request.setFilterExpression( expression );
149  }
150 
151  QgsFeatureIterator fit = layer->getFeatures( request );
152 
153  QgsFeature f;
154  while ( fit.nextFeature( f ) )
155  {
156  cache.append( ValueRelationItem( f.attribute( ki ), f.attribute( vi ).toString() ) );
157  }
158 
159  if ( config.value( QStringLiteral( "OrderByValue" ) ).toBool() )
160  {
161  std::sort( cache.begin(), cache.end(), orderByValueLessThan );
162  }
163  else
164  {
165  std::sort( cache.begin(), cache.end(), orderByKeyLessThan );
166  }
167 
168  return cache;
169 }
170 
171 QStringList QgsValueRelationFieldFormatter::valueToStringList( const QVariant &value )
172 {
173  QStringList checkList;
174  if ( value.type() == QVariant::StringList )
175  {
176  checkList = value.toStringList();
177  }
178  else
179  {
180  QVariantList valuesList;
181  if ( value.type() == QVariant::String )
182  {
183  // This must be an array representation
184  auto newVal { value };
185  if ( newVal.toString().trimmed().startsWith( '{' ) )
186  {
187  //normal case
188  valuesList = QgsPostgresStringUtils::parseArray( newVal.toString() );
189  }
190  else if ( newVal.toString().trimmed().startsWith( '[' ) )
191  {
192  //fallback, in case it's a json array
193  try
194  {
195  for ( auto &element : json::parse( newVal.toString().toStdString() ) )
196  {
197  if ( element.is_number_integer() )
198  {
199  valuesList.push_back( element.get<int>() );
200  }
201  else if ( element.is_number_unsigned() )
202  {
203  valuesList.push_back( element.get<unsigned>() );
204  }
205  else if ( element.is_string() )
206  {
207  valuesList.push_back( QString::fromStdString( element.get<std::string>() ) );
208  }
209  }
210  }
211  catch ( json::parse_error &ex )
212  {
213  QgsMessageLog::logMessage( QObject::tr( "Cannot parse JSON like string '%1' Error: %2" ).arg( newVal.toString(), ex.what() ) );
214  }
215  }
216  }
217  else if ( value.type() == QVariant::List )
218  {
219  valuesList = value.toList( );
220  }
221 
222  checkList.reserve( valuesList.size() );
223  for ( const QVariant &listItem : qgis::as_const( valuesList ) )
224  {
225  QString v( listItem.toString( ) );
226  if ( ! v.isEmpty() )
227  checkList.append( v );
228  }
229  }
230  return checkList;
231 }
232 
233 
234 QSet<QString> QgsValueRelationFieldFormatter::expressionFormVariables( const QString &expression )
235 {
236  std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::formScope() );
237  QSet< QString > formVariables = scope->variableNames().toSet();
238  const QSet< QString > usedVariables = QgsExpression( expression ).referencedVariables();
239  formVariables.intersect( usedVariables );
240  return formVariables;
241 }
242 
244 {
245  return !( expressionFormAttributes( expression ).isEmpty() && expressionFormVariables( expression ).isEmpty() );
246 }
247 
248 QSet<QString> QgsValueRelationFieldFormatter::expressionFormAttributes( const QString &expression )
249 {
250  QSet<QString> attributes;
251  QgsExpression exp( expression );
252  std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::formScope() );
253  // List of form function names used in the expression
254  const QSet<QString> formFunctions( scope->functionNames()
255  .toSet()
256  .intersect( exp.referencedFunctions( ) ) );
257  const QList<const QgsExpressionNodeFunction *> expFunctions( exp.findNodes<QgsExpressionNodeFunction>() );
258  QgsExpressionContext context;
259  for ( const auto &f : expFunctions )
260  {
261  QgsExpressionFunction *fd = QgsExpression::QgsExpression::Functions()[f->fnIndex()];
262  if ( formFunctions.contains( fd->name( ) ) )
263  {
264  for ( const auto &param : f->args( )->list() )
265  {
266  attributes.insert( param->eval( &exp, &context ).toString() );
267  }
268  }
269  }
270  return attributes;
271 }
272 
273 bool QgsValueRelationFieldFormatter::expressionIsUsable( const QString &expression, const QgsFeature &feature )
274 {
275  const QSet<QString> attrs = expressionFormAttributes( expression );
276  for ( auto it = attrs.constBegin() ; it != attrs.constEnd(); it++ )
277  {
278  if ( ! feature.attribute( *it ).isValid() )
279  return false;
280  }
281  if ( ! expressionFormVariables( expression ).isEmpty() && feature.geometry().isEmpty( ) )
282  return false;
283  return true;
284 }
285 
286 QgsVectorLayer *QgsValueRelationFieldFormatter::resolveLayer( const QVariantMap &config, const QgsProject *project )
287 {
288  QgsVectorLayerRef ref { config.value( QStringLiteral( "Layer" ) ).toString(),
289  config.value( QStringLiteral( "LayerName" ) ).toString(),
290  config.value( QStringLiteral( "LayerSource" ) ).toString(),
291  config.value( QStringLiteral( "LayerProviderName" ) ).toString() };
292  return ref.resolveByIdOrNameOnly( project );
293 }
294 
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:183
Class for parsing and evaluation of expressions (formerly called "search strings").
Wrapper for iterator of features from vector data provider or vector layer.
static QSet< QString > expressionFormVariables(const QString &expression)
Returns a list of variables required by the form context expression.
TYPE * resolveByIdOrNameOnly(const QgsProject *project)
Resolves the map layer by attempting to find a matching layer in a project using a weak match...
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
Container of fields for a vector layer.
Definition: qgsfields.h:42
static QVariantList parseArray(const QString &string)
Returns a QVariantList created out of a string containing an array in postgres array format {1...
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...
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
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 feat...
QVector< QgsValueRelationFieldFormatter::ValueRelationItem > ValueRelationCache
QSet< QString > referencedVariables() const
Returns a list of all variables which are used in this expression.
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:154
QgsField at(int i) const
Gets field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:163
QgsFeatureRequest & setExpressionContext(const QgsExpressionContext &context)
Sets the expression context used to evaluate filter expressions.
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
This class wraps a request for features to a vector layer (or directly its vector data provider)...
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
static QString nullRepresentation()
This string is used to represent the value NULL throughout QGIS.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts, annotations, canvases, etc.
Definition: qgsproject.h:89
static QgsVectorLayer * resolveLayer(const QVariantMap &config, const QgsProject *project)
Returns the (possibly NULL) layer from the widget&#39;s config and project.
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer&#39;s project and layer.
An expression node for expression functions.
QVariant createCache(QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config) const override
Create a cache for a given field.
static QSet< QString > expressionFormAttributes(const QString &expression)
Returns a list of attributes required by the form context expression.
bool orderByKeyLessThan(const QgsValueRelationFieldFormatter::ValueRelationItem &p1, const QgsValueRelationFieldFormatter::ValueRelationItem &p2)
QString name() const
The name of the function.
bool orderByValueLessThan(const QgsValueRelationFieldFormatter::ValueRelationItem &p1, const QgsValueRelationFieldFormatter::ValueRelationItem &p2)
A abstract base class for defining QgsExpression functions.
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.
int indexOf(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:207
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 ...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:442
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.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
static bool expressionIsUsable(const QString &expression, const QgsFeature &feature)
Check whether the feature has all values required by the expression.
QgsGeometry geometry
Definition: qgsfeature.h:67
QList< int > QgsAttributeList
Definition: qgsfield.h:27
bool nextFeature(QgsFeature &f)
Geometry is not required. It may still be returned if e.g. required for a filter condition.
QgsSQLStatement::Node * parse(const QString &str, QString &parserErrorMsg)
Represents a vector layer which manages a vector based data sets.
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:262
QList< const T * > findNodes() const
Returns a list of all nodes of the given class which are used in this expression. ...
QVariant::Type type
Definition: qgsfield.h:56
QSet< QString > referencedFunctions() const
Returns a list of the names of all functions which are used in this expression.
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.