Skip to content

Commit 6ed2cf2

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() Sponsored by DB Fahrwegdienste GmbH
1 parent 9c62eec commit 6ed2cf2

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
@@ -1317,6 +1317,42 @@ class QgsVectorLayer : QgsMapLayer
13171317
/** Caches joined attributes if required (and not already done) */
13181318
void createJoinCaches();
13191319

1320+
/** Returns the calculated default value for the specified field index. The default
1321+
* value may be taken from a client side default value expression (see setDefaultValueExpression())
1322+
* or taken from the underlying data provider.
1323+
* @param index field index
1324+
* @param feature optional feature to use for default value evaluation. If passed,
1325+
* then properties from the feature (such as geometry) can be used when calculating
1326+
* the default value.
1327+
* @param context optional expression context to evaluate expressions again. If not
1328+
* specified, a default context will be created
1329+
* @return calculated default value
1330+
* @note added in QGIS 3.0
1331+
* @see setDefaultValueExpression()
1332+
*/
1333+
QVariant defaultValue( int index, const QgsFeature& feature = QgsFeature(),
1334+
QgsExpressionContext* context = nullptr ) const;
1335+
1336+
/** Sets an expression to use when calculating the default value for a field.
1337+
* @param index field index
1338+
* @param expression expression to evaluate when calculating default values for field. Pass
1339+
* an empty expression to clear the default.
1340+
* @note added in QGIS 3.0
1341+
* @see defaultValue()
1342+
* @see defaultValueExpression()
1343+
*/
1344+
void setDefaultValueExpression( int index, const QString& expression );
1345+
1346+
/** Returns the expression used when calculating the default value for a field.
1347+
* @param index field index
1348+
* @returns expression evaluated when calculating default values for field, or an
1349+
* empty string if no default is set
1350+
* @note added in QGIS 3.0
1351+
* @see defaultValue()
1352+
* @see setDefaultValueExpression()
1353+
*/
1354+
QString defaultValueExpression( int index ) const;
1355+
13201356
/** Calculates a list of unique values contained within an attribute in the layer. Note that
13211357
* in some circumstances when unsaved changes are present for the layer then the returned list
13221358
* 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
@@ -94,14 +94,14 @@ class APP_EXPORT QgsAttributeTypeDialog: public QDialog, private Ui::QgsAttribut
9494
*/
9595
bool notNull() const;
9696

97-
/*
97+
/**
9898
* Setter for constraint expression description
9999
* @param desc the expression description
100100
* @note added in QGIS 2.16
101101
**/
102102
void setExpressionDescription( const QString &desc );
103103

104-
/*
104+
/**
105105
* Getter for constraint expression description
106106
* @return the expression description
107107
* @note added in QGIS 2.16

src/core/qgsvectorlayer.cpp

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

16801680
readStyleManager( layer_node );
16811681

1682+
// default expressions
1683+
mDefaultExpressionMap.clear();
1684+
QDomNode defaultsNode = layer_node.namedItem( "defaults" );
1685+
if ( !defaultsNode.isNull() )
1686+
{
1687+
QDomNodeList defaultNodeList = defaultsNode.toElement().elementsByTagName( "default" );
1688+
for ( int i = 0; i < defaultNodeList.size(); ++i )
1689+
{
1690+
QDomElement defaultElem = defaultNodeList.at( i ).toElement();
1691+
1692+
QString field = defaultElem.attribute( "field", QString() );
1693+
QString expression = defaultElem.attribute( "expression", QString() );
1694+
if ( field.isEmpty() || expression.isEmpty() )
1695+
continue;
1696+
1697+
int index = mUpdatedFields.fieldNameIndex( field );
1698+
if ( index < 0 )
1699+
continue;
1700+
1701+
mDefaultExpressionMap.insert( index, expression );
1702+
}
1703+
}
16821704

16831705
setLegend( QgsMapLayerLegend::defaultVectorLegend( this ) );
16841706

@@ -1880,6 +1902,26 @@ bool QgsVectorLayer::writeXml( QDomNode & layer_node,
18801902
// save expression fields
18811903
mExpressionFieldBuffer->writeXml( layer_node, document );
18821904

1905+
//default expressions
1906+
if ( !mDefaultExpressionMap.isEmpty() )
1907+
{
1908+
QDomElement defaultsElem = document.createElement( "defaults" );
1909+
QMap<int, QString>::const_iterator it = mDefaultExpressionMap.constBegin();
1910+
for ( ; it != mDefaultExpressionMap.constEnd(); ++it )
1911+
{
1912+
if ( it.key() >= mUpdatedFields.count() )
1913+
continue;
1914+
1915+
QString fieldName = mUpdatedFields.at( it.key() ).name();
1916+
1917+
QDomElement defaultElem = document.createElement( "default" );
1918+
defaultElem.setAttribute( "field", fieldName );
1919+
defaultElem.setAttribute( "expression", it.value() );
1920+
defaultsElem.appendChild( defaultElem );
1921+
}
1922+
layer_node.appendChild( defaultsElem );
1923+
}
1924+
18831925
writeStyleManager( layer_node, document );
18841926

18851927
// renderer specific settings
@@ -3133,6 +3175,69 @@ void QgsVectorLayer::createJoinCaches()
31333175
}
31343176
}
31353177

3178+
QVariant QgsVectorLayer::defaultValue( int index, const QgsFeature& feature, QgsExpressionContext* context ) const
3179+
{
3180+
QString expression = mDefaultExpressionMap.value( index, QString() );
3181+
if ( expression.isEmpty() )
3182+
return mDataProvider->defaultValue( index );
3183+
3184+
QgsExpressionContext* evalContext = context;
3185+
QScopedPointer< QgsExpressionContext > tempContext;
3186+
if ( !evalContext )
3187+
{
3188+
// no context passed, so we create a default one
3189+
tempContext.reset( new QgsExpressionContext() );
3190+
tempContext->appendScope( QgsExpressionContextUtils::globalScope() );
3191+
tempContext->appendScope( QgsExpressionContextUtils::projectScope() );
3192+
tempContext->appendScope( QgsExpressionContextUtils::layerScope( this ) );
3193+
evalContext = tempContext.data();
3194+
}
3195+
3196+
if ( feature.isValid() )
3197+
{
3198+
QgsExpressionContextScope* featScope = new QgsExpressionContextScope();
3199+
featScope->setFeature( feature );
3200+
featScope->setFields( *feature.fields() );
3201+
evalContext->appendScope( featScope );
3202+
}
3203+
3204+
QVariant val;
3205+
QgsExpression exp( expression );
3206+
exp.prepare( evalContext );
3207+
if ( exp.hasEvalError() )
3208+
{
3209+
QgsLogger::warning( "Error evaluating default value: " + exp.evalErrorString() );
3210+
}
3211+
else
3212+
{
3213+
val = exp.evaluate( evalContext );
3214+
}
3215+
3216+
if ( feature.isValid() )
3217+
{
3218+
delete evalContext->popScope();
3219+
}
3220+
3221+
return val;
3222+
}
3223+
3224+
void QgsVectorLayer::setDefaultValueExpression( int index, const QString& expression )
3225+
{
3226+
if ( expression.isEmpty() )
3227+
{
3228+
mDefaultExpressionMap.remove( index );
3229+
}
3230+
else
3231+
{
3232+
mDefaultExpressionMap.insert( index, expression );
3233+
}
3234+
}
3235+
3236+
QString QgsVectorLayer::defaultValueExpression( int index ) const
3237+
{
3238+
return mDefaultExpressionMap.value( index, QString() );
3239+
}
3240+
31363241
void QgsVectorLayer::uniqueValues( int index, QList<QVariant> &uniqueValues, int limit )
31373242
{
31383243
uniqueValues.clear();

src/core/qgsvectorlayer.h

+39
Original file line numberDiff line numberDiff line change
@@ -1707,6 +1707,42 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer
17071707
/** Caches joined attributes if required (and not already done) */
17081708
void createJoinCaches();
17091709

1710+
/** Returns the calculated default value for the specified field index. The default
1711+
* value may be taken from a client side default value expression (see setDefaultValueExpression())
1712+
* or taken from the underlying data provider.
1713+
* @param index field index
1714+
* @param feature optional feature to use for default value evaluation. If passed,
1715+
* then properties from the feature (such as geometry) can be used when calculating
1716+
* the default value.
1717+
* @param context optional expression context to evaluate expressions again. If not
1718+
* specified, a default context will be created
1719+
* @return calculated default value
1720+
* @note added in QGIS 3.0
1721+
* @see setDefaultValueExpression()
1722+
*/
1723+
QVariant defaultValue( int index, const QgsFeature& feature = QgsFeature(),
1724+
QgsExpressionContext* context = nullptr ) const;
1725+
1726+
/** Sets an expression to use when calculating the default value for a field.
1727+
* @param index field index
1728+
* @param expression expression to evaluate when calculating default values for field. Pass
1729+
* an empty expression to clear the default.
1730+
* @note added in QGIS 3.0
1731+
* @see defaultValue()
1732+
* @see defaultValueExpression()
1733+
*/
1734+
void setDefaultValueExpression( int index, const QString& expression );
1735+
1736+
/** Returns the expression used when calculating the default value for a field.
1737+
* @param index field index
1738+
* @returns expression evaluated when calculating default values for field, or an
1739+
* empty string if no default is set
1740+
* @note added in QGIS 3.0
1741+
* @see defaultValue()
1742+
* @see setDefaultValueExpression()
1743+
*/
1744+
QString defaultValueExpression( int index ) const;
1745+
17101746
/** Calculates a list of unique values contained within an attribute in the layer. Note that
17111747
* in some circumstances when unsaved changes are present for the layer then the returned list
17121748
* may contain outdated values (for instance when the attribute value in a saved feature has
@@ -2173,6 +2209,9 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer
21732209
/** Map that stores the aliases for attributes. Key is the attribute name and value the alias for that attribute*/
21742210
QMap< QString, QString > mAttributeAliasMap;
21752211

2212+
//! Map which stores default value expressions for fields
2213+
QMap< int, QString > mDefaultExpressionMap;
2214+
21762215
/** Holds the configuration for the edit form */
21772216
QgsEditFormConfig* mEditFormConfig;
21782217

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
QgsVectorLayer,
@@ -35,7 +36,11 @@
3536
QgsCoordinateReferenceSystem,
3637
QgsProject,
3738
QgsUnitTypes,
38-
QgsAggregateCalculator)
39+
QgsAggregateCalculator,
40+
QgsPointV2,
41+
QgsExpressionContext,
42+
QgsExpressionContextScope,
43+
QgsExpressionContextUtils)
3944
from qgis.testing import start_app, unittest
4045
from utilities import unitTestDataPath
4146
start_app()
@@ -1455,6 +1460,94 @@ def test_setRendererV2(self):
14551460
self.assertTrue(self.rendererChanged)
14561461
self.assertEqual(layer.rendererV2(), r)
14571462

1463+
def testGetSetDefaults(self):
1464+
""" test getting and setting default expressions """
1465+
layer = createLayerWithOnePoint()
1466+
1467+
self.assertFalse(layer.defaultValueExpression(0))
1468+
self.assertFalse(layer.defaultValueExpression(1))
1469+
self.assertFalse(layer.defaultValueExpression(2))
1470+
1471+
layer.setDefaultValueExpression(0, "'test'")
1472+
self.assertEqual(layer.defaultValueExpression(0), "'test'")
1473+
self.assertFalse(layer.defaultValueExpression(1))
1474+
self.assertFalse(layer.defaultValueExpression(2))
1475+
1476+
layer.setDefaultValueExpression(1, "2+2")
1477+
self.assertEqual(layer.defaultValueExpression(0), "'test'")
1478+
self.assertEqual(layer.defaultValueExpression(1), "2+2")
1479+
self.assertFalse(layer.defaultValueExpression(2))
1480+
1481+
def testSaveRestoreDefaults(self):
1482+
""" test saving and restoring default expressions from xml"""
1483+
layer = createLayerWithOnePoint()
1484+
1485+
# no default expressions
1486+
doc = QDomDocument("testdoc")
1487+
elem = doc.createElement("maplayer")
1488+
self.assertTrue(layer.writeXml(elem, doc))
1489+
1490+
layer2 = createLayerWithOnePoint()
1491+
self.assertTrue(layer2.readXml(elem))
1492+
self.assertFalse(layer2.defaultValueExpression(0))
1493+
self.assertFalse(layer2.defaultValueExpression(1))
1494+
1495+
# set some default expressions
1496+
layer.setDefaultValueExpression(0, "'test'")
1497+
layer.setDefaultValueExpression(1, "2+2")
1498+
1499+
doc = QDomDocument("testdoc")
1500+
elem = doc.createElement("maplayer")
1501+
self.assertTrue(layer.writeXml(elem, doc))
1502+
1503+
layer3 = createLayerWithOnePoint()
1504+
self.assertTrue(layer3.readXml(elem))
1505+
self.assertEqual(layer3.defaultValueExpression(0), "'test'")
1506+
self.assertEqual(layer3.defaultValueExpression(1), "2+2")
1507+
1508+
def testEvaluatingDefaultExpressions(self):
1509+
""" tests calculation of default values"""
1510+
layer = createLayerWithOnePoint()
1511+
layer.setDefaultValueExpression(0, "'test'")
1512+
layer.setDefaultValueExpression(1, "2+2")
1513+
self.assertEqual(layer.defaultValue(0), 'test')
1514+
self.assertEqual(layer.defaultValue(1), 4)
1515+
1516+
# using feature
1517+
layer.setDefaultValueExpression(1, '$id * 2')
1518+
feature = QgsFeature(4)
1519+
feature.setValid(True)
1520+
feature.setFields(layer.fields())
1521+
# no feature:
1522+
self.assertFalse(layer.defaultValue(1))
1523+
# with feature:
1524+
self.assertEqual(layer.defaultValue(0, feature), 'test')
1525+
self.assertEqual(layer.defaultValue(1, feature), 8)
1526+
1527+
# using feature geometry
1528+
layer.setDefaultValueExpression(1, '$x * 2')
1529+
feature.setGeometry(QgsGeometry(QgsPointV2(6, 7)))
1530+
self.assertEqual(layer.defaultValue(1, feature), 12)
1531+
1532+
# using contexts
1533+
scope = QgsExpressionContextScope()
1534+
scope.setVariable('var1', 16)
1535+
context = QgsExpressionContext()
1536+
context.appendScope(scope)
1537+
layer.setDefaultValueExpression(1, '$id + @var1')
1538+
self.assertEqual(layer.defaultValue(1, feature, context), 20)
1539+
1540+
# if no scope passed, should use a default constructed one including layer variables
1541+
QgsExpressionContextUtils.setLayerVariable(layer, 'var2', 4)
1542+
QgsExpressionContextUtils.setProjectVariable('var3', 8)
1543+
layer.setDefaultValueExpression(1, 'to_int(@var2) + to_int(@var3) + $id')
1544+
self.assertEqual(layer.defaultValue(1, feature), 16)
1545+
1546+
# bad expression
1547+
layer.setDefaultValueExpression(1, 'not a valid expression')
1548+
self.assertFalse(layer.defaultValue(1))
1549+
1550+
14581551
# TODO:
14591552
# - fetch rect: feat with changed geometry: 1. in rect, 2. out of rect
14601553
# - more join tests

0 commit comments

Comments
 (0)