Skip to content
Permalink
Browse files

[feature] Form context expressions in value relation widget

The value relation widget filter expression can now use two
new functions/variables that have access to the current
values and geometry of the form being edited.

This allows for dynamic filtering (drill-down) as explained
in the crowdfunding page:
https://north-road.com/drill-down-cascading-forms/

The new functions/variables are:

Function:
get_current_form_field_value( 'FIELD_NAME' )

Variable:
@current_form_geometry
  • Loading branch information
elpaso committed Mar 12, 2018
1 parent 4d36f37 commit 83328ae5962816bca5d6728c18a034357e346618
@@ -8,6 +8,7 @@




class QgsValueRelationFieldFormatter : QgsFieldFormatter
{
%Docstring
@@ -56,25 +57,76 @@ Constructor for QgsValueRelationFieldFormatter.
virtual QVariant createCache( QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config ) const;


static QgsValueRelationFieldFormatter::ValueRelationCache createCache( const QVariantMap &config );
static QStringList valueToStringList( const QVariant &value );
%Docstring
Utility to convert an array or a string representation of and array ``value`` to a string list

:param value: The value to be converted

:return: A string list

.. versionadded:: 3.2
%End

static QgsValueRelationFieldFormatter::ValueRelationCache createCache( const QVariantMap &config, const QgsFeature &formFeature = QgsFeature() );
%Docstring
Create a cache for a value relation field.
This can be used to keep the value map in the local memory
if doing multiple lookups in a loop.

:param config: The widget configuration
:param formFeature: The feature currently being edited with current attribute values

:return: A kvp list of values for the widget

.. versionadded:: 3.0
%End

static QStringList valueToStringList( const QVariant &value );
static bool expressionRequiresFormScope( const QString &expression );
%Docstring
Utility to convert an array or a string representation of and array ``value`` to a string list
Check if the ``expression`` requires a form scope (i.e. if it uses fields
or geometry of the currently edited feature).

:param value: The value to be converted
:param expression: The widget's filter expression

:return: A string list
:return: true if the expression requires a form scope

.. versionadded:: 3.2
%End

static QSet<QString> expressionFormAttributes( const QString &expression );
%Docstring
Return a list of attributes required by the form context ``expression``

:param expression: Form filter expression

:return: list of attributes required by the expression

.. versionadded:: 3.2
%End

static QSet<QString> expressionFormVariables( const QString &expression );
%Docstring
Return a list of variables required by the form context ``expression``

:param expression: Form filter expression

:return: list of variables required by the expression

.. versionadded:: 3.2
%End

static bool expressionIsUsable( const QString &expression, const QgsFeature &feature );
%Docstring
Check wether the ``feature`` has all values required by the ``expression``

@return True if the expression can be used

.. versionadded:: 3.2
%End

static QString FORM_SCOPE_FUNCTIONS_RE;

};


@@ -763,6 +763,14 @@ Creates a new scope which contains variables and functions relating to the globa
For instance, QGIS version numbers and variables specified through QGIS options.

.. seealso:: :py:func:`setGlobalVariable`
%End

static QgsExpressionContextScope *formScope( const QgsFeature &formFeature = QgsFeature( ) ) /Factory/;
%Docstring
Creates a new scope which contains functions and variables from the current attribute form/table feature.
The variables and values in this scope will reflect the current state of the form/row being edited.

.. versionadded:: 3.2
%End

static void setGlobalVariable( const QString &name, const QVariant &value );
@@ -279,6 +279,10 @@ change the visual cue.
.. versionadded:: 2.16
%End

protected:



};


@@ -180,6 +180,25 @@ QGIS forms

const QgsAttributeEditorContext *parentContext() const;

const QgsFeature formFeature() const;
%Docstring
Return current feature from the currently edited form or table row

.. seealso:: :py:func:`setFormFeature`

.. versionadded:: 3.2
%End

void setFormFeature( const QgsFeature &feature );
%Docstring
Set current ``feature`` for the currently edited form or table row

.. seealso:: :py:func:`formFeature`

.. versionadded:: 3.2
%End


};

/************************************************************************
@@ -141,7 +141,8 @@ on all attribute widgets.

void attributeChanged( const QString &attribute, const QVariant &value ) /Deprecated/;
%Docstring
Notifies about changes of attributes
Notifies about changes of attributes, this signal is not emitted when the value is set
back to the original one.

:param attribute: The name of the attribute that changed.
:param value: The new value of the attribute.
@@ -0,0 +1,7 @@
{
"name": "get_current_form_field_value",
"type": "function",
"description": "Returns the current value of a field in the form or table row currently being edited.",
"arguments": [ {"arg":"field_name","description":"a field name in the current form or table row"}],
"examples": [ { "expression":"get_current_form_field_value( 'FIELD_NAME' )","returns":"The current value of field 'FIELD_NAME'."} ]
}
@@ -771,6 +771,9 @@ void QgsExpression::initVariableHelp()

//provider notification
sVariableHelpTexts.insert( QStringLiteral( "notification_message" ), QCoreApplication::translate( "notification_message", "Content of the notification message sent by the provider (available only for actions triggered by provider notifications)." ) );

//form context variable
sVariableHelpTexts.insert( QStringLiteral( "current_form_geometry" ), QCoreApplication::translate( "current_form_geometry", "Represents the geometry of the feature currently being edited in the form or the table row. Can be used for in a form/row context to filter the related features." ) );
}

QString QgsExpression::variableHelpText( const QString &variableName )
@@ -21,6 +21,8 @@

#include <QSettings>

QString QgsValueRelationFieldFormatter::FORM_SCOPE_FUNCTIONS_RE = QStringLiteral( "%1\\s*\\(\\s*'([^']+)'\\s*\\)" );

bool orderByKeyLessThan( const QgsValueRelationFieldFormatter::ValueRelationItem &p1, const QgsValueRelationFieldFormatter::ValueRelationItem &p2 )
{
return qgsVariantLessThan( p1.key, p2.key );
@@ -99,7 +101,7 @@ QVariant QgsValueRelationFieldFormatter::createCache( QgsVectorLayer *layer, int

}

QgsValueRelationFieldFormatter::ValueRelationCache QgsValueRelationFieldFormatter::createCache( const QVariantMap &config )
QgsValueRelationFieldFormatter::ValueRelationCache QgsValueRelationFieldFormatter::createCache( const QVariantMap &config, const QgsFeature &formFeature )
{
ValueRelationCache cache;

@@ -116,11 +118,19 @@ QgsValueRelationFieldFormatter::ValueRelationCache QgsValueRelationFieldFormatte

request.setFlags( QgsFeatureRequest::NoGeometry );
request.setSubsetOfAttributes( QgsAttributeList() << ki << vi );
if ( !config.value( QStringLiteral( "FilterExpression" ) ).toString().isEmpty() )

const QString expression = config.value( QStringLiteral( "FilterExpression" ) ).toString();

// Skip the filter and build a full cache if the form scope is required and the feature
// is not valid or the attributes required for the filter have no valid value
if ( ! expression.isEmpty() && ( ! expressionRequiresFormScope( expression )
|| expressionIsUsable( expression, formFeature ) ) )
{
QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( layer ) );
if ( formFeature.isValid( ) )
context.appendScope( QgsExpressionContextUtils::formScope( formFeature ) );
request.setExpressionContext( context );
request.setFilterExpression( config.value( QStringLiteral( "FilterExpression" ) ).toString() );
request.setFilterExpression( expression );
}

QgsFeatureIterator fit = layer->getFeatures( request );
@@ -162,3 +172,58 @@ QStringList QgsValueRelationFieldFormatter::valueToStringList( const QVariant &v
}
return checkList;
}


QSet<QString> QgsValueRelationFieldFormatter::expressionFormVariables( const QString &expression )
{
const QStringList formVariables( QgsExpressionContextUtils::formScope()->variableNames() );
QSet<QString> variables;

for ( auto it = formVariables.constBegin(); it != formVariables.constEnd(); it++ )
{
if ( expression.contains( *it ) )
{
variables.insert( *it );
}
}
return variables;
}

bool QgsValueRelationFieldFormatter::expressionRequiresFormScope( const QString &expression )
{
return !( expressionFormAttributes( expression ).isEmpty() && expressionFormVariables( expression ).isEmpty() );
}

QSet<QString> QgsValueRelationFieldFormatter::expressionFormAttributes( const QString &expression )
{
QSet<QString> attributes;
const QStringList formFunctions( QgsExpressionContextUtils::formScope()->functionNames() );
QRegularExpression re;
for ( const auto &fname : formFunctions )
{
if ( QgsExpressionContextUtils::formScope()->function( fname )->parameters().count( ) != 0 )
{
re.setPattern( QgsValueRelationFieldFormatter::FORM_SCOPE_FUNCTIONS_RE.arg( fname ) );
QRegularExpressionMatchIterator i = re.globalMatch( expression );
while ( i.hasNext() )
{
QRegularExpressionMatch match = i.next();
attributes.insert( match.captured( 1 ) );
}
}
}
return attributes;
}

bool QgsValueRelationFieldFormatter::expressionIsUsable( const QString &expression, const QgsFeature &feature )
{
const QSet<QString> attrs = expressionFormAttributes( expression );
for ( auto it = attrs.constBegin() ; it != attrs.constEnd(); it++ )
{
if ( ! feature.attribute( *it ).isValid() )
return false;
}
if ( ! expressionFormVariables( expression ).isEmpty() && feature.geometry().isEmpty( ) )
return false;
return true;
}
@@ -18,10 +18,13 @@

#include "qgis_core.h"
#include "qgsfieldformatter.h"
#include "qgsexpression.h"
#include "qgsexpressioncontext.h"

#include <QVector>
#include <QVariant>


/**
* \ingroup core
* Field formatter for a value relation field.
@@ -62,23 +65,71 @@ class CORE_EXPORT QgsValueRelationFieldFormatter : public QgsFieldFormatter

QVariant createCache( QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config ) const override;

/**
* Utility to convert an array or a string representation of and array \a value to a string list
*
* \param value The value to be converted
* \return A string list
* \since QGIS 3.2
*/
static QStringList valueToStringList( const QVariant &value );

/**
* Create a cache for a value relation field.
* This can be used to keep the value map in the local memory
* if doing multiple lookups in a loop.
* \param config The widget configuration
* \param formFeature The feature currently being edited with current attribute values
* \return A kvp list of values for the widget
*
* \since QGIS 3.0
*/
static QgsValueRelationFieldFormatter::ValueRelationCache createCache( const QVariantMap &config );
static QgsValueRelationFieldFormatter::ValueRelationCache createCache( const QVariantMap &config, const QgsFeature &formFeature = QgsFeature() );

/**
* Utility to convert an array or a string representation of and array \a value to a string list
* Check if the \a expression requires a form scope (i.e. if it uses fields
* or geometry of the currently edited feature).
*
* \param value The value to be converted
* \return A string list
* \param expression The widget's filter expression
* \return true if the expression requires a form scope
* \since QGIS 3.2
*/
static QStringList valueToStringList( const QVariant &value );
static bool expressionRequiresFormScope( const QString &expression );

/**
* Return a list of attributes required by the form context \a expression
*
* \param expression Form filter expression
* \return list of attributes required by the expression
* \since QGIS 3.2
*/
static QSet<QString> expressionFormAttributes( const QString &expression );

/**
* Return a list of variables required by the form context \a expression
*
* \param expression Form filter expression
* \return list of variables required by the expression
* \since QGIS 3.2
*/
static QSet<QString> expressionFormVariables( const QString &expression );

/**
* Check wether the \a feature has all values required by the \a expression
*
* @return True if the expression can be used
* \since QGIS 3.2
*/
static bool expressionIsUsable( const QString &expression, const QgsFeature &feature );

/**
* Regular expression to find dynamic filtering based on form field values
* \see GetCurrentFormFieldValue()
*
* \since QGIS 3.2
*/
static QString FORM_SCOPE_FUNCTIONS_RE;

};

Q_DECLARE_METATYPE( QgsValueRelationFieldFormatter::ValueRelationCache )

0 comments on commit 83328ae

Please sign in to comment.
You can’t perform that action at this time.