Skip to content

Commit c98d380

Browse files
committedNov 2, 2016
Move responsibility for testing for attribute against constraints
to QgsVectorLayerUtils::validateAttribute() Also clean up some strings shown to the user when a constraint check fails
1 parent 2500d75 commit c98d380

File tree

6 files changed

+155
-58
lines changed

6 files changed

+155
-58
lines changed
 

‎python/core/qgsvectorlayerutils.sip

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,10 @@ class QgsVectorLayerUtils
1919
*/
2020
static bool valueExists( const QgsVectorLayer* layer, int fieldIndex, const QVariant& value, const QgsFeatureIds& ignoreIds = QgsFeatureIds() );
2121

22+
/**
23+
* Tests an attribute value to check whether it passes all constraints which are present on the corresponding field.
24+
* Returns true if the attribute value is valid for the field. Any constraint failures will be reported in the errors argument.
25+
*/
26+
static bool validateAttribute( const QgsVectorLayer* layer, const QgsFeature& feature, int attributeIndex, QStringList& errors /Out/ );
27+
2228
};

‎src/core/qgsvectorlayerutils.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,64 @@ bool QgsVectorLayerUtils::valueExists( const QgsVectorLayer* layer, int fieldInd
4949

5050
return false;
5151
}
52+
53+
bool QgsVectorLayerUtils::validateAttribute( const QgsVectorLayer* layer, const QgsFeature& feature, int attributeIndex, QStringList& errors )
54+
{
55+
if ( !layer )
56+
return false;
57+
58+
if ( attributeIndex < 0 || attributeIndex >= layer->fields().count() )
59+
return false;
60+
61+
QgsField field = layer->fields().at( attributeIndex );
62+
QVariant value = feature.attribute( attributeIndex );
63+
bool valid = true;
64+
errors.clear();
65+
66+
if ( field.constraints() & QgsField::ConstraintExpression && !field.constraintExpression().isEmpty() )
67+
{
68+
QgsExpressionContext context = layer->createExpressionContext();
69+
context.setFeature( feature );
70+
71+
QgsExpression expr( field.constraintExpression() );
72+
73+
valid = expr.evaluate( &context ).toBool();
74+
75+
if ( expr.hasParserError() )
76+
{
77+
errors << QObject::tr( "parser error: %1" ).arg( expr.parserErrorString() );
78+
}
79+
else if ( expr.hasEvalError() )
80+
{
81+
errors << QObject::tr( "evaluation error: %1" ).arg( expr.evalErrorString() );
82+
}
83+
else if ( !valid )
84+
{
85+
errors << QObject::tr( "%1 check failed" ).arg( field.constraintDescription() );
86+
}
87+
}
88+
89+
if ( field.constraints() & QgsField::ConstraintNotNull )
90+
{
91+
valid = valid && !value.isNull();
92+
93+
if ( value.isNull() )
94+
{
95+
errors << QObject::tr( "value is NULL" );
96+
}
97+
}
98+
99+
if ( field.constraints() & QgsField::ConstraintUnique )
100+
{
101+
bool alreadyExists = QgsVectorLayerUtils::valueExists( layer, attributeIndex, value, QgsFeatureIds() << feature.id() );
102+
valid = valid && !alreadyExists;
103+
104+
if ( alreadyExists )
105+
{
106+
errors << QObject::tr( "value is not unique" );
107+
}
108+
}
109+
110+
return valid;
111+
}
112+

‎src/core/qgsvectorlayerutils.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ class CORE_EXPORT QgsVectorLayerUtils
3636
*/
3737
static bool valueExists( const QgsVectorLayer* layer, int fieldIndex, const QVariant& value, const QgsFeatureIds& ignoreIds = QgsFeatureIds() );
3838

39+
/**
40+
* Tests an attribute value to check whether it passes all constraints which are present on the corresponding field.
41+
* Returns true if the attribute value is valid for the field. Any constraint failures will be reported in the errors argument.
42+
*/
43+
static bool validateAttribute( const QgsVectorLayer* layer, const QgsFeature& feature, int attributeIndex, QStringList& errors );
44+
3945
};
4046

4147
#endif // QGSVECTORLAYERUTILS_H

‎src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp

Lines changed: 10 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -113,61 +113,25 @@ void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft )
113113

114114
QString expression = field.constraintExpression();
115115
QStringList expressions, descriptions;
116-
QVariant value = ft.attribute( mFieldIdx );
117-
118-
mConstraintFailureReason.clear();
119-
120-
QStringList errors;
121116

122117
if ( ! expression.isEmpty() )
123118
{
124119
expressions << expression;
125120
descriptions << field.constraintDescription();
126-
127-
QgsExpressionContext context = layer()->createExpressionContext();
128-
context.setFeature( ft );
129-
130-
QgsExpression expr( expression );
131-
132-
mValidConstraint = expr.evaluate( &context ).toBool();
133-
134-
if ( expr.hasParserError() )
135-
{
136-
errors << tr( "Parser error: %1" ).arg( expr.parserErrorString() );
137-
}
138-
else if ( expr.hasEvalError() )
139-
{
140-
errors << tr( "Evaluation error: %1" ).arg( expr.evalErrorString() );
141-
}
142-
else if ( ! mValidConstraint )
143-
{
144-
errors << tr( "%1 check failed" ).arg( field.constraintDescription() );
145-
}
146-
147121
toEmit = true;
148122
}
149-
else
150-
mValidConstraint = true;
151123

152124
if ( field.constraints() & QgsField::ConstraintNotNull )
153125
{
154-
descriptions << QStringLiteral( "NotNull" );
126+
descriptions << QStringLiteral( "Not NULL" );
155127
if ( !expression.isEmpty() )
156128
{
157129
expressions << field.name() + " IS NOT NULL";
158130
}
159131
else
160132
{
161-
expressions << QStringLiteral( "NotNull" );
133+
expressions << QStringLiteral( "IS NOT NULL" );
162134
}
163-
164-
mValidConstraint = mValidConstraint && !value.isNull();
165-
166-
if ( value.isNull() )
167-
{
168-
errors << tr( "Value is NULL" );
169-
}
170-
171135
toEmit = true;
172136
}
173137

@@ -180,29 +144,20 @@ void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft )
180144
}
181145
else
182146
{
183-
expression = QStringLiteral( "Unique" );
184-
}
185-
186-
bool alreadyExists = QgsVectorLayerUtils::valueExists( layer(), mFieldIdx, value, QgsFeatureIds() << ft.id() );
187-
mValidConstraint = mValidConstraint && !alreadyExists;
188-
189-
if ( alreadyExists )
190-
{
191-
errors << tr( "Value is not unique" );
147+
expressions << QStringLiteral( "IS UNIQUE" );
192148
}
193-
194149
toEmit = true;
195150
}
196151

152+
QStringList errors;
153+
mValidConstraint = QgsVectorLayerUtils::validateAttribute( layer(), ft, mFieldIdx, errors );
154+
mConstraintFailureReason = errors.join( ", " );
155+
197156
if ( toEmit )
198157
{
199-
QString errStr = errors.isEmpty() ? tr( "Constraint checks passed" ) : errors.join( '\n' );
200-
mConstraintFailureReason = errors.join( ", " );
201-
QString description;
202-
if ( descriptions.size() > 1 )
203-
description = "( " + descriptions.join( " ) AND ( " ) + " )";
204-
else if ( !descriptions.isEmpty() )
205-
description = descriptions.at( 0 );
158+
QString errStr = errors.isEmpty() ? tr( "Constraint checks passed" ) : mConstraintFailureReason;
159+
160+
QString description = descriptions.join( ", " );
206161
QString expressionDesc;
207162
if ( expressions.size() > 1 )
208163
expressionDesc = "( " + expressions.join( " ) AND ( " ) + " )";

‎src/gui/qgsattributeform.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -901,8 +901,9 @@ void QgsAttributeForm::onConstraintStatusChanged( const QString& constraint,
901901

902902
if ( buddy )
903903
{
904-
QString tooltip = tr( "Description: " ) + description + "\n" +
905-
tr( "Raw expression: " ) + constraint + "\n" + tr( "Constraint: " ) + err;
904+
QString tooltip = QStringLiteral( "<b>" ) + tr( "Constraints: " ) + QStringLiteral( "</b>" ) + description +
905+
QStringLiteral( "<br /><b>" ) + tr( "Raw expression: " ) + QStringLiteral( "</b>" ) + constraint +
906+
QStringLiteral( "<br /><b>" ) + tr( "Result: " ) + QStringLiteral( "</b>" ) + err;
906907
buddy->setToolTip( tooltip );
907908

908909
if ( !buddy->property( "originalText" ).isValid() )

‎tests/src/python/test_qgsvectorlayerutils.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
QgsVectorLayerUtils,
2323
QgsField,
2424
QgsFields,
25-
QgsFeature
25+
QgsFeature,
26+
NULL
2627
)
2728
from qgis.testing import start_app, unittest
2829
from utilities import unitTestDataPath
@@ -83,6 +84,73 @@ def test_value_exists(self):
8384
self.assertFalse(QgsVectorLayerUtils.valueExists(layer, 1, 125, [99999, 3]))
8485
self.assertFalse(QgsVectorLayerUtils.valueExists(layer, 1, 125, [2, 4, 5, 3]))
8586

87+
def test_validate_attribute(self):
88+
""" test validating attributes against constraints """
89+
layer = createLayerWithOnePoint()
90+
91+
# field expression check
92+
layer.setConstraintExpression(1, 'fldint>5')
93+
94+
f = QgsFeature(2)
95+
f.setAttributes(["test123", 6])
96+
res, errors = QgsVectorLayerUtils.validateAttribute(layer, f, 1)
97+
self.assertTrue(res)
98+
self.assertEqual(len(errors), 0)
99+
f.setAttributes(["test123", 2])
100+
res, errors = QgsVectorLayerUtils.validateAttribute(layer, f, 1)
101+
self.assertFalse(res)
102+
self.assertEqual(len(errors), 1)
103+
print(errors)
104+
105+
# bad field expression check
106+
layer.setConstraintExpression(1, 'fldint>')
107+
res, errors = QgsVectorLayerUtils.validateAttribute(layer, f, 1)
108+
self.assertFalse(res)
109+
self.assertEqual(len(errors), 1)
110+
print(errors)
111+
112+
layer.setConstraintExpression(1, None)
113+
114+
# not null constraint
115+
f.setAttributes(["test123", NULL])
116+
res, errors = QgsVectorLayerUtils.validateAttribute(layer, f, 1)
117+
self.assertTrue(res)
118+
self.assertEqual(len(errors), 0)
119+
120+
layer.setFieldConstraints(1, QgsField.ConstraintNotNull)
121+
res, errors = QgsVectorLayerUtils.validateAttribute(layer, f, 1)
122+
self.assertFalse(res)
123+
self.assertEqual(len(errors), 1)
124+
print(errors)
125+
126+
# unique constraint
127+
f.setAttributes(["test123", 123])
128+
layer.setFieldConstraints(1, QgsField.Constraints())
129+
res, errors = QgsVectorLayerUtils.validateAttribute(layer, f, 1)
130+
self.assertTrue(res)
131+
self.assertEqual(len(errors), 0)
132+
layer.setFieldConstraints(1, QgsField.ConstraintUnique)
133+
res, errors = QgsVectorLayerUtils.validateAttribute(layer, f, 1)
134+
self.assertFalse(res)
135+
self.assertEqual(len(errors), 1)
136+
print(errors)
137+
138+
# check - same id should be ignored when testing for uniqueness
139+
f1 = QgsFeature(1)
140+
f1.setAttributes(["test123", 123])
141+
res, errors = QgsVectorLayerUtils.validateAttribute(layer, f1, 1)
142+
self.assertTrue(res)
143+
self.assertEqual(len(errors), 0)
144+
145+
# test double constraint failure
146+
layer.setConstraintExpression(1, 'fldint>5')
147+
layer.setFieldConstraints(1, QgsField.ConstraintNotNull)
148+
f.setAttributes(["test123", NULL])
149+
res, errors = QgsVectorLayerUtils.validateAttribute(layer, f, 1)
150+
self.assertFalse(res)
151+
self.assertEqual(len(errors), 2)
152+
print(errors)
153+
86154

87155
if __name__ == '__main__':
88156
unittest.main()

0 commit comments

Comments
 (0)
Please sign in to comment.