Skip to content

Commit bd9f672

Browse files
committedNov 2, 2016
Allow constraints to be added at a QgsVectorLayer level
1 parent 4efad04 commit bd9f672

File tree

5 files changed

+167
-0
lines changed

5 files changed

+167
-0
lines changed
 

‎python/core/qgsvectorlayer.sip

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,6 +1257,24 @@ class QgsVectorLayer : QgsMapLayer
12571257
*/
12581258
QString defaultValueExpression( int index ) const;
12591259

1260+
/**
1261+
* Returns any constraints which are present for a specified
1262+
* field index. These constraints may be inherited from the layer's data provider
1263+
* or may be set manually on the vector layer from within QGIS.
1264+
* @note added in QGIS 3.0
1265+
* @see setFieldConstraints()
1266+
*/
1267+
QgsField::Constraints fieldConstraints( int fieldIndex ) const;
1268+
1269+
/**
1270+
* Sets the constraints for a specified field index. Any constraints inherited from the layer's
1271+
* data provider will be kept intact and cannot be cleared. Ie, calling this method only allows for new
1272+
* constraints to be added on top of the existing provider constraints.
1273+
* @note added in QGIS 3.0
1274+
* @see fieldConstraints()
1275+
*/
1276+
void setFieldConstraints( int index, QgsField::Constraints constraints );
1277+
12601278
/** Calculates a list of unique values contained within an attribute in the layer. Note that
12611279
* in some circumstances when unsaved changes are present for the layer then the returned list
12621280
* may contain outdated values (for instance when the attribute value in a saved feature has

‎src/core/qgsvectorlayer.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1440,6 +1440,26 @@ bool QgsVectorLayer::readXml( const QDomNode& layer_node )
14401440
mDefaultExpressionMap.insert( field, expression );
14411441
}
14421442
}
1443+
1444+
// constraints
1445+
mFieldConstraints.clear();
1446+
QDomNode constraintsNode = layer_node.namedItem( "constraints" );
1447+
if ( !constraintsNode.isNull() )
1448+
{
1449+
QDomNodeList constraintNodeList = constraintsNode.toElement().elementsByTagName( "constraint" );
1450+
for ( int i = 0; i < constraintNodeList.size(); ++i )
1451+
{
1452+
QDomElement constraintElem = constraintNodeList.at( i ).toElement();
1453+
1454+
QString field = constraintElem.attribute( "field", QString() );
1455+
int constraints = constraintElem.attribute( "constraints", QString( "0" ) ).toInt();
1456+
if ( field.isEmpty() || constraints == 0 )
1457+
continue;
1458+
1459+
mFieldConstraints.insert( field, static_cast< QgsField::Constraints >( constraints ) );
1460+
}
1461+
}
1462+
14431463
updateFields();
14441464

14451465
QDomNode depsNode = layer_node.namedItem( QStringLiteral( "dataDependencies" ) );
@@ -1646,6 +1666,17 @@ bool QgsVectorLayer::writeXml( QDomNode & layer_node,
16461666
}
16471667
layer_node.appendChild( defaultsElem );
16481668

1669+
// constraints
1670+
QDomElement constraintsElem = document.createElement( "constraints" );
1671+
Q_FOREACH ( const QgsField& field, mFields )
1672+
{
1673+
QDomElement constraintElem = document.createElement( "constraint" );
1674+
constraintElem.setAttribute( "field", field.name() );
1675+
constraintElem.setAttribute( "constraints", field.constraints() );
1676+
constraintsElem.appendChild( constraintElem );
1677+
}
1678+
layer_node.appendChild( constraintsElem );
1679+
16491680
// change dependencies
16501681
QDomElement dataDependenciesElement = document.createElement( QStringLiteral( "dataDependencies" ) );
16511682
Q_FOREACH ( const QgsMapLayerDependency& dep, dependencies() )
@@ -2901,6 +2932,17 @@ void QgsVectorLayer::updateFields()
29012932

29022933
mFields[ index ].setDefaultValueExpression( defaultIt.value() );
29032934
}
2935+
QMap< QString, QgsField::Constraints >::const_iterator constraintIt = mFieldConstraints.constBegin();
2936+
for ( ; constraintIt != mFieldConstraints.constEnd(); ++constraintIt )
2937+
{
2938+
int index = mFields.lookupField( constraintIt.key() );
2939+
if ( index < 0 )
2940+
continue;
2941+
2942+
// always keep provider constraints intact
2943+
mFields[ index ].setConstraints( mFields.at( index ).constraints() | constraintIt.value() );
2944+
}
2945+
29042946
if ( oldFields != mFields )
29052947
{
29062948
emit updatedFields();
@@ -4205,3 +4247,19 @@ QgsField::Constraints QgsVectorLayer::fieldConstraints( int fieldIndex ) const
42054247

42064248
return constraints;
42074249
}
4250+
4251+
void QgsVectorLayer::setFieldConstraints( int index, QgsField::Constraints constraints )
4252+
{
4253+
if ( index < 0 || index >= mFields.count() )
4254+
return;
4255+
4256+
if ( constraints == 0 )
4257+
{
4258+
mFieldConstraints.remove( mFields.at( index ).name() );
4259+
}
4260+
else
4261+
{
4262+
mFieldConstraints.insert( mFields.at( index ).name(), constraints );
4263+
}
4264+
updateFields();
4265+
}

‎src/core/qgsvectorlayer.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,9 +1401,19 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
14011401
* field index. These constraints may be inherited from the layer's data provider
14021402
* or may be set manually on the vector layer from within QGIS.
14031403
* @note added in QGIS 3.0
1404+
* @see setFieldConstraints()
14041405
*/
14051406
QgsField::Constraints fieldConstraints( int fieldIndex ) const;
14061407

1408+
/**
1409+
* Sets the constraints for a specified field index. Any constraints inherited from the layer's
1410+
* data provider will be kept intact and cannot be cleared. Ie, calling this method only allows for new
1411+
* constraints to be added on top of the existing provider constraints.
1412+
* @note added in QGIS 3.0
1413+
* @see fieldConstraints()
1414+
*/
1415+
void setFieldConstraints( int index, QgsField::Constraints constraints );
1416+
14071417
/** Calculates a list of unique values contained within an attribute in the layer. Note that
14081418
* in some circumstances when unsaved changes are present for the layer then the returned list
14091419
* may contain outdated values (for instance when the attribute value in a saved feature has
@@ -1941,6 +1951,9 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
19411951
//! Map which stores default value expressions for fields
19421952
QgsStringMap mDefaultExpressionMap;
19431953

1954+
//! Map which stores constraints for fields
1955+
QMap< QString, QgsField::Constraints > mFieldConstraints;
1956+
19441957
//! Holds the configuration for the edit form
19451958
QgsEditFormConfig mEditFormConfig;
19461959

‎tests/src/python/test_provider_postgres.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,27 @@ def testUniqueConstraint(self):
480480
self.assertTrue(fields.at(2).constraints() & QgsField.ConstraintUnique)
481481
self.assertFalse(fields.at(3).constraints() & QgsField.ConstraintUnique)
482482

483+
def testConstraintOverwrite(self):
484+
""" test that Postgres provider constraints can't be overwritten by vector layer method """
485+
vl = QgsVectorLayer('%s table="qgis_test"."constraints" sql=' % (self.dbconn), "constraints", "postgres")
486+
self.assertTrue(vl.isValid())
487+
488+
self.assertTrue(vl.dataProvider().fieldConstraints(0) & QgsField.ConstraintNotNull)
489+
self.assertTrue(vl.fields().at(0).constraints() & QgsField.ConstraintNotNull)
490+
491+
# add a constraint at the layer level
492+
vl.setFieldConstraints(0, QgsField.ConstraintUnique)
493+
494+
# should be no change at provider level
495+
self.assertTrue(vl.dataProvider().fieldConstraints(0) & QgsField.ConstraintNotNull)
496+
497+
# but layer should still keep provider constraints...
498+
self.assertTrue(vl.fields().at(0).constraints() & QgsField.ConstraintNotNull)
499+
self.assertTrue(vl.fieldConstraints(0) & QgsField.ConstraintNotNull)
500+
# ...in addition to layer level constraint
501+
self.assertTrue(vl.fields().at(0).constraints() & QgsField.ConstraintUnique)
502+
self.assertTrue(vl.fieldConstraints(0) & QgsField.ConstraintUnique)
503+
483504
# See http://hub.qgis.org/issues/15188
484505
def testNumericPrecision(self):
485506
uri = 'point?field=f1:int'

‎tests/src/python/test_qgsvectorlayer.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,6 +1773,63 @@ def testEvaluatingDefaultExpressions(self):
17731773
layer.setDefaultValueExpression(1, 'not a valid expression')
17741774
self.assertFalse(layer.defaultValue(1))
17751775

1776+
def testGetSetConstraints(self):
1777+
""" test getting and setting field constraints """
1778+
layer = createLayerWithOnePoint()
1779+
1780+
self.assertFalse(layer.fieldConstraints(0))
1781+
self.assertFalse(layer.fieldConstraints(1))
1782+
self.assertFalse(layer.fieldConstraints(2))
1783+
1784+
layer.setFieldConstraints(0, QgsField.ConstraintNotNull)
1785+
self.assertEqual(layer.fieldConstraints(0), QgsField.ConstraintNotNull)
1786+
self.assertFalse(layer.fieldConstraints(1))
1787+
self.assertFalse(layer.fieldConstraints(2))
1788+
self.assertEqual(layer.fields().at(0).constraints(), QgsField.ConstraintNotNull)
1789+
1790+
layer.setFieldConstraints(1, QgsField.ConstraintNotNull | QgsField.ConstraintUnique)
1791+
self.assertEqual(layer.fieldConstraints(0), QgsField.ConstraintNotNull)
1792+
self.assertEqual(layer.fieldConstraints(1), QgsField.ConstraintNotNull | QgsField.ConstraintUnique)
1793+
self.assertFalse(layer.fieldConstraints(2))
1794+
self.assertEqual(layer.fields().at(0).constraints(), QgsField.ConstraintNotNull)
1795+
self.assertEqual(layer.fields().at(1).constraints(), QgsField.ConstraintNotNull | QgsField.ConstraintUnique)
1796+
1797+
layer.setFieldConstraints(1, QgsField.Constraints())
1798+
self.assertEqual(layer.fieldConstraints(0), QgsField.ConstraintNotNull)
1799+
self.assertFalse(layer.fieldConstraints(1))
1800+
self.assertFalse(layer.fieldConstraints(2))
1801+
self.assertEqual(layer.fields().at(0).constraints(), QgsField.ConstraintNotNull)
1802+
self.assertFalse(layer.fields().at(1).constraints())
1803+
1804+
def testSaveRestoreConstraints(self):
1805+
""" test saving and restoring constraints from xml"""
1806+
layer = createLayerWithOnePoint()
1807+
1808+
# no constraints
1809+
doc = QDomDocument("testdoc")
1810+
elem = doc.createElement("maplayer")
1811+
self.assertTrue(layer.writeXml(elem, doc))
1812+
1813+
layer2 = createLayerWithOnePoint()
1814+
self.assertTrue(layer2.readXml(elem))
1815+
self.assertFalse(layer2.fieldConstraints(0))
1816+
self.assertFalse(layer2.fieldConstraints(1))
1817+
1818+
# set some constraints
1819+
layer.setFieldConstraints(0, QgsField.ConstraintNotNull)
1820+
layer.setFieldConstraints(1, QgsField.ConstraintNotNull | QgsField.ConstraintUnique)
1821+
1822+
doc = QDomDocument("testdoc")
1823+
elem = doc.createElement("maplayer")
1824+
self.assertTrue(layer.writeXml(elem, doc))
1825+
1826+
layer3 = createLayerWithOnePoint()
1827+
self.assertTrue(layer3.readXml(elem))
1828+
self.assertEqual(layer3.fieldConstraints(0), QgsField.ConstraintNotNull)
1829+
self.assertEqual(layer3.fieldConstraints(1), QgsField.ConstraintNotNull | QgsField.ConstraintUnique)
1830+
self.assertEqual(layer3.fields().at(0).constraints(), QgsField.ConstraintNotNull)
1831+
self.assertEqual(layer3.fields().at(1).constraints(), QgsField.ConstraintNotNull | QgsField.ConstraintUnique)
1832+
17761833
def testGetFeatureLimitWithEdits(self):
17771834
""" test getting features with a limit, when edits are present """
17781835
layer = createLayerWithOnePoint()

0 commit comments

Comments
 (0)
Please sign in to comment.