Skip to content

Commit 4d5bae2

Browse files
committed
[FEATURE] Client side default field values
Allows an expression to be set for a vector layer field which is used to evaluate a default value for this field. A new method, QgsVectorLayer::defaultValue( int index, const QgsFeature& feature = QgsFeature(), QgsExpressionContext* context = nullptr ) has been added which evaluates the default value for a given field using the optionally passed feature and expression context. This allows default values to utilise properties of the feature which exist at the time of calling, such as digitized geometries. The expression context parameter allows variables to be used in default value expressions, making it easier to eg insert a user's name, current datetime, project path, etc Default values are set using QgsVectorLayer::setDefaultValueExpression() and retrieved using defaultValueExpression()
1 parent 7dea970 commit 4d5bae2

File tree

5 files changed

+276
-3
lines changed

5 files changed

+276
-3
lines changed

python/core/qgsvectorlayer.sip

+36
Original file line numberDiff line numberDiff line change
@@ -1202,6 +1202,42 @@ class QgsVectorLayer : QgsMapLayer
12021202
// marked as const as these are just caches, and need to be created from const accessors
12031203
void createJoinCaches() const;
12041204

1205+
/** Returns the calculated default value for the specified field index. The default
1206+
* value may be taken from a client side default value expression (see setDefaultValueExpression())
1207+
* or taken from the underlying data provider.
1208+
* @param index field index
1209+
* @param feature optional feature to use for default value evaluation. If passed,
1210+
* then properties from the feature (such as geometry) can be used when calculating
1211+
* the default value.
1212+
* @param context optional expression context to evaluate expressions again. If not
1213+
* specified, a default context will be created
1214+
* @return calculated default value
1215+
* @note added in QGIS 3.0
1216+
* @see setDefaultValueExpression()
1217+
*/
1218+
QVariant defaultValue( int index, const QgsFeature& feature = QgsFeature(),
1219+
QgsExpressionContext* context = nullptr ) const;
1220+
1221+
/** Sets an expression to use when calculating the default value for a field.
1222+
* @param index field index
1223+
* @param expression expression to evaluate when calculating default values for field. Pass
1224+
* an empty expression to clear the default.
1225+
* @note added in QGIS 3.0
1226+
* @see defaultValue()
1227+
* @see defaultValueExpression()
1228+
*/
1229+
void setDefaultValueExpression( int index, const QString& expression );
1230+
1231+
/** Returns the expression used when calculating the default value for a field.
1232+
* @param index field index
1233+
* @returns expression evaluated when calculating default values for field, or an
1234+
* empty string if no default is set
1235+
* @note added in QGIS 3.0
1236+
* @see defaultValue()
1237+
* @see setDefaultValueExpression()
1238+
*/
1239+
QString defaultValueExpression( int index ) const;
1240+
12051241
/** Calculates a list of unique values contained within an attribute in the layer. Note that
12061242
* in some circumstances when unsaved changes are present for the layer then the returned list
12071243
* may contain outdated values (for instance when the attribute value in a saved feature has

src/app/qgsattributetypedialog.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,14 @@ class APP_EXPORT QgsAttributeTypeDialog: public QDialog, private Ui::QgsAttribut
7878
*/
7979
bool notNull() const;
8080

81-
/*
81+
/**
8282
* Setter for constraint expression description
8383
* @param desc the expression description
8484
* @note added in QGIS 2.16
8585
**/
8686
void setExpressionDescription( const QString &desc );
8787

88-
/*
88+
/**
8989
* Getter for constraint expression description
9090
* @return the expression description
9191
* @note added in QGIS 2.16

src/core/qgsvectorlayer.cpp

+105
Original file line numberDiff line numberDiff line change
@@ -1436,6 +1436,28 @@ bool QgsVectorLayer::readXml( const QDomNode& layer_node )
14361436

14371437
readStyleManager( layer_node );
14381438

1439+
// default expressions
1440+
mDefaultExpressionMap.clear();
1441+
QDomNode defaultsNode = layer_node.namedItem( "defaults" );
1442+
if ( !defaultsNode.isNull() )
1443+
{
1444+
QDomNodeList defaultNodeList = defaultsNode.toElement().elementsByTagName( "default" );
1445+
for ( int i = 0; i < defaultNodeList.size(); ++i )
1446+
{
1447+
QDomElement defaultElem = defaultNodeList.at( i ).toElement();
1448+
1449+
QString field = defaultElem.attribute( "field", QString() );
1450+
QString expression = defaultElem.attribute( "expression", QString() );
1451+
if ( field.isEmpty() || expression.isEmpty() )
1452+
continue;
1453+
1454+
int index = mUpdatedFields.fieldNameIndex( field );
1455+
if ( index < 0 )
1456+
continue;
1457+
1458+
mDefaultExpressionMap.insert( index, expression );
1459+
}
1460+
}
14391461

14401462
setLegend( QgsMapLayerLegend::defaultVectorLegend( this ) );
14411463

@@ -1617,6 +1639,26 @@ bool QgsVectorLayer::writeXml( QDomNode & layer_node,
16171639
}
16181640
layer_node.appendChild( dependenciesElement );
16191641

1642+
//default expressions
1643+
if ( !mDefaultExpressionMap.isEmpty() )
1644+
{
1645+
QDomElement defaultsElem = document.createElement( "defaults" );
1646+
QMap<int, QString>::const_iterator it = mDefaultExpressionMap.constBegin();
1647+
for ( ; it != mDefaultExpressionMap.constEnd(); ++it )
1648+
{
1649+
if ( it.key() >= mUpdatedFields.count() )
1650+
continue;
1651+
1652+
QString fieldName = mUpdatedFields.at( it.key() ).name();
1653+
1654+
QDomElement defaultElem = document.createElement( "default" );
1655+
defaultElem.setAttribute( "field", fieldName );
1656+
defaultElem.setAttribute( "expression", it.value() );
1657+
defaultsElem.appendChild( defaultElem );
1658+
}
1659+
layer_node.appendChild( defaultsElem );
1660+
}
1661+
16201662
writeStyleManager( layer_node, document );
16211663

16221664
// renderer specific settings
@@ -2799,6 +2841,69 @@ void QgsVectorLayer::createJoinCaches() const
27992841
}
28002842
}
28012843

2844+
QVariant QgsVectorLayer::defaultValue( int index, const QgsFeature& feature, QgsExpressionContext* context ) const
2845+
{
2846+
QString expression = mDefaultExpressionMap.value( index, QString() );
2847+
if ( expression.isEmpty() )
2848+
return mDataProvider->defaultValue( index );
2849+
2850+
QgsExpressionContext* evalContext = context;
2851+
QScopedPointer< QgsExpressionContext > tempContext;
2852+
if ( !evalContext )
2853+
{
2854+
// no context passed, so we create a default one
2855+
tempContext.reset( new QgsExpressionContext() );
2856+
tempContext->appendScope( QgsExpressionContextUtils::globalScope() );
2857+
tempContext->appendScope( QgsExpressionContextUtils::projectScope() );
2858+
tempContext->appendScope( QgsExpressionContextUtils::layerScope( this ) );
2859+
evalContext = tempContext.data();
2860+
}
2861+
2862+
if ( feature.isValid() )
2863+
{
2864+
QgsExpressionContextScope* featScope = new QgsExpressionContextScope();
2865+
featScope->setFeature( feature );
2866+
featScope->setFields( feature.fields() );
2867+
evalContext->appendScope( featScope );
2868+
}
2869+
2870+
QVariant val;
2871+
QgsExpression exp( expression );
2872+
exp.prepare( evalContext );
2873+
if ( exp.hasEvalError() )
2874+
{
2875+
QgsLogger::warning( "Error evaluating default value: " + exp.evalErrorString() );
2876+
}
2877+
else
2878+
{
2879+
val = exp.evaluate( evalContext );
2880+
}
2881+
2882+
if ( feature.isValid() )
2883+
{
2884+
delete evalContext->popScope();
2885+
}
2886+
2887+
return val;
2888+
}
2889+
2890+
void QgsVectorLayer::setDefaultValueExpression( int index, const QString& expression )
2891+
{
2892+
if ( expression.isEmpty() )
2893+
{
2894+
mDefaultExpressionMap.remove( index );
2895+
}
2896+
else
2897+
{
2898+
mDefaultExpressionMap.insert( index, expression );
2899+
}
2900+
}
2901+
2902+
QString QgsVectorLayer::defaultValueExpression( int index ) const
2903+
{
2904+
return mDefaultExpressionMap.value( index, QString() );
2905+
}
2906+
28022907
void QgsVectorLayer::uniqueValues( int index, QList<QVariant> &uniqueValues, int limit ) const
28032908
{
28042909
uniqueValues.clear();

src/core/qgsvectorlayer.h

+39
Original file line numberDiff line numberDiff line change
@@ -1331,6 +1331,42 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
13311331
// marked as const as these are just caches, and need to be created from const accessors
13321332
void createJoinCaches() const;
13331333

1334+
/** Returns the calculated default value for the specified field index. The default
1335+
* value may be taken from a client side default value expression (see setDefaultValueExpression())
1336+
* or taken from the underlying data provider.
1337+
* @param index field index
1338+
* @param feature optional feature to use for default value evaluation. If passed,
1339+
* then properties from the feature (such as geometry) can be used when calculating
1340+
* the default value.
1341+
* @param context optional expression context to evaluate expressions again. If not
1342+
* specified, a default context will be created
1343+
* @return calculated default value
1344+
* @note added in QGIS 3.0
1345+
* @see setDefaultValueExpression()
1346+
*/
1347+
QVariant defaultValue( int index, const QgsFeature& feature = QgsFeature(),
1348+
QgsExpressionContext* context = nullptr ) const;
1349+
1350+
/** Sets an expression to use when calculating the default value for a field.
1351+
* @param index field index
1352+
* @param expression expression to evaluate when calculating default values for field. Pass
1353+
* an empty expression to clear the default.
1354+
* @note added in QGIS 3.0
1355+
* @see defaultValue()
1356+
* @see defaultValueExpression()
1357+
*/
1358+
void setDefaultValueExpression( int index, const QString& expression );
1359+
1360+
/** Returns the expression used when calculating the default value for a field.
1361+
* @param index field index
1362+
* @returns expression evaluated when calculating default values for field, or an
1363+
* empty string if no default is set
1364+
* @note added in QGIS 3.0
1365+
* @see defaultValue()
1366+
* @see setDefaultValueExpression()
1367+
*/
1368+
QString defaultValueExpression( int index ) const;
1369+
13341370
/** Calculates a list of unique values contained within an attribute in the layer. Note that
13351371
* in some circumstances when unsaved changes are present for the layer then the returned list
13361372
* may contain outdated values (for instance when the attribute value in a saved feature has
@@ -1865,6 +1901,9 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
18651901
/** Map that stores the aliases for attributes. Key is the attribute name and value the alias for that attribute*/
18661902
QMap< QString, QString > mAttributeAliasMap;
18671903

1904+
//! Map which stores default value expressions for fields
1905+
QMap< int, QString > mDefaultExpressionMap;
1906+
18681907
/** Holds the configuration for the edit form */
18691908
QgsEditFormConfig mEditFormConfig;
18701909

tests/src/python/test_qgsvectorlayer.py

+94-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
from qgis.PyQt.QtCore import QVariant
2020
from qgis.PyQt.QtGui import QPainter
21+
from qgis.PyQt.QtXml import (QDomDocument, QDomElement)
2122

2223
from qgis.core import (Qgis,
2324
QgsWkbTypes,
@@ -36,7 +37,11 @@
3637
QgsCoordinateReferenceSystem,
3738
QgsProject,
3839
QgsUnitTypes,
39-
QgsAggregateCalculator)
40+
QgsAggregateCalculator,
41+
QgsPointV2,
42+
QgsExpressionContext,
43+
QgsExpressionContextScope,
44+
QgsExpressionContextUtils)
4045
from qgis.testing import start_app, unittest
4146
from utilities import unitTestDataPath
4247
start_app()
@@ -1602,6 +1607,94 @@ def test_setRenderer(self):
16021607
self.assertTrue(self.rendererChanged)
16031608
self.assertEqual(layer.renderer(), r)
16041609

1610+
def testGetSetDefaults(self):
1611+
""" test getting and setting default expressions """
1612+
layer = createLayerWithOnePoint()
1613+
1614+
self.assertFalse(layer.defaultValueExpression(0))
1615+
self.assertFalse(layer.defaultValueExpression(1))
1616+
self.assertFalse(layer.defaultValueExpression(2))
1617+
1618+
layer.setDefaultValueExpression(0, "'test'")
1619+
self.assertEqual(layer.defaultValueExpression(0), "'test'")
1620+
self.assertFalse(layer.defaultValueExpression(1))
1621+
self.assertFalse(layer.defaultValueExpression(2))
1622+
1623+
layer.setDefaultValueExpression(1, "2+2")
1624+
self.assertEqual(layer.defaultValueExpression(0), "'test'")
1625+
self.assertEqual(layer.defaultValueExpression(1), "2+2")
1626+
self.assertFalse(layer.defaultValueExpression(2))
1627+
1628+
def testSaveRestoreDefaults(self):
1629+
""" test saving and restoring default expressions from xml"""
1630+
layer = createLayerWithOnePoint()
1631+
1632+
# no default expressions
1633+
doc = QDomDocument("testdoc")
1634+
elem = doc.createElement("maplayer")
1635+
self.assertTrue(layer.writeXml(elem, doc))
1636+
1637+
layer2 = createLayerWithOnePoint()
1638+
self.assertTrue(layer2.readXml(elem))
1639+
self.assertFalse(layer2.defaultValueExpression(0))
1640+
self.assertFalse(layer2.defaultValueExpression(1))
1641+
1642+
# set some default expressions
1643+
layer.setDefaultValueExpression(0, "'test'")
1644+
layer.setDefaultValueExpression(1, "2+2")
1645+
1646+
doc = QDomDocument("testdoc")
1647+
elem = doc.createElement("maplayer")
1648+
self.assertTrue(layer.writeXml(elem, doc))
1649+
1650+
layer3 = createLayerWithOnePoint()
1651+
self.assertTrue(layer3.readXml(elem))
1652+
self.assertEqual(layer3.defaultValueExpression(0), "'test'")
1653+
self.assertEqual(layer3.defaultValueExpression(1), "2+2")
1654+
1655+
def testEvaluatingDefaultExpressions(self):
1656+
""" tests calculation of default values"""
1657+
layer = createLayerWithOnePoint()
1658+
layer.setDefaultValueExpression(0, "'test'")
1659+
layer.setDefaultValueExpression(1, "2+2")
1660+
self.assertEqual(layer.defaultValue(0), 'test')
1661+
self.assertEqual(layer.defaultValue(1), 4)
1662+
1663+
# using feature
1664+
layer.setDefaultValueExpression(1, '$id * 2')
1665+
feature = QgsFeature(4)
1666+
feature.setValid(True)
1667+
feature.setFields(layer.fields())
1668+
# no feature:
1669+
self.assertFalse(layer.defaultValue(1))
1670+
# with feature:
1671+
self.assertEqual(layer.defaultValue(0, feature), 'test')
1672+
self.assertEqual(layer.defaultValue(1, feature), 8)
1673+
1674+
# using feature geometry
1675+
layer.setDefaultValueExpression(1, '$x * 2')
1676+
feature.setGeometry(QgsGeometry(QgsPointV2(6, 7)))
1677+
self.assertEqual(layer.defaultValue(1, feature), 12)
1678+
1679+
# using contexts
1680+
scope = QgsExpressionContextScope()
1681+
scope.setVariable('var1', 16)
1682+
context = QgsExpressionContext()
1683+
context.appendScope(scope)
1684+
layer.setDefaultValueExpression(1, '$id + @var1')
1685+
self.assertEqual(layer.defaultValue(1, feature, context), 20)
1686+
1687+
# if no scope passed, should use a default constructed one including layer variables
1688+
QgsExpressionContextUtils.setLayerVariable(layer, 'var2', 4)
1689+
QgsExpressionContextUtils.setProjectVariable('var3', 8)
1690+
layer.setDefaultValueExpression(1, 'to_int(@var2) + to_int(@var3) + $id')
1691+
self.assertEqual(layer.defaultValue(1, feature), 16)
1692+
1693+
# bad expression
1694+
layer.setDefaultValueExpression(1, 'not a valid expression')
1695+
self.assertFalse(layer.defaultValue(1))
1696+
1697+
16051698
# TODO:
16061699
# - fetch rect: feat with changed geometry: 1. in rect, 2. out of rect
16071700
# - more join tests

0 commit comments

Comments
 (0)