Skip to content

Commit 3f2a781

Browse files
committed
Respect non-enforced constraints when editing/adding features
Warnings are shown, but features can be committed. Fields which fail an unenforced constraint are now shaded in yellow to differentiate from the red failure for enforced constraints.
1 parent fac5bc0 commit 3f2a781

27 files changed

+255
-95
lines changed

python/core/qgsvectorlayerutils.sip

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ class QgsVectorLayerUtils
2222
/**
2323
* Tests an attribute value to check whether it passes all constraints which are present on the corresponding field.
2424
* Returns true if the attribute value is valid for the field. Any constraint failures will be reported in the errors argument.
25-
* If the origin parameter is set then only constraints with a matching origin will be checked.
25+
* If the strength or origin parameter is set then only constraints with a matching strength/origin will be checked.
2626
*/
2727
static bool validateAttribute( const QgsVectorLayer* layer, const QgsFeature& feature, int attributeIndex, QStringList& errors /Out/,
28+
QgsFieldConstraints::ConstraintStrength strength = QgsFieldConstraints::ConstraintStrengthNotSet,
2829
QgsFieldConstraints::ConstraintOrigin origin = QgsFieldConstraints::ConstraintOriginNotSet );
2930

3031
};

python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip

+27-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,18 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper
2020
%End
2121

2222
public:
23+
24+
/**
25+
* Result of constraint checks.
26+
* @note added in QGIS 3.0
27+
*/
28+
enum ConstraintResult
29+
{
30+
ConstraintResultPass, //!< Widget passed constraints successfully
31+
ConstraintResultFailHard, //!< Widget failed at least one hard (enforced) constraint
32+
ConstraintResultFailSoft, //!< Widget failed at least one soft (non-enforced) constraint
33+
};
34+
2335
/**
2436
* Create a new widget wrapper
2537
*
@@ -108,9 +120,18 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper
108120
* false otherwise
109121
* @note added in QGIS 2.16
110122
* @see constraintFailureReason()
123+
* @see isBlockingCommit()
111124
*/
112125
bool isValidConstraint() const;
113126

127+
/**
128+
* Returns true if the widget is preventing the feature from being committed. This may be true as a result
129+
* of attribute values failing enforced field constraints.
130+
* @note added in QGIS 3.0
131+
* @see isValidConstraint()
132+
*/
133+
bool isBlockingCommit() const;
134+
114135
/**
115136
* Returns the reason why a constraint check has failed (or an empty string
116137
* if constraint check was successful).
@@ -135,7 +156,7 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper
135156
* @param err the error represented as a string. Empty if none.
136157
* @param status
137158
*/
138-
void constraintStatusChanged( const QString& constraint, const QString& err, bool status );
159+
void constraintStatusChanged( const QString& constraint, const QString &desc, const QString& err, ConstraintResult status );
139160

140161
public slots:
141162
/**
@@ -214,6 +235,10 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper
214235
*
215236
* This can be overwritten in subclasses to allow individual widgets to
216237
* change the visual cue.
238+
*
239+
* @param status The current constraint status.
240+
*
241+
* @note added in QGIS 2.16
217242
*/
218-
virtual void updateConstraintWidgetStatus( bool contraintValid );
243+
virtual void updateConstraintWidgetStatus( ConstraintResult status );
219244
};

python/gui/editorwidgets/qgsrelationreferencewidgetwrapper.sip

+2-12
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,6 @@ class QgsRelationReferenceWidgetWrapper : QgsEditorWidgetWrapper
2323
virtual void setEnabled( bool enabled );
2424

2525
protected:
26-
/**
27-
* This should update the widget with a visual cue if a constraint status
28-
* changed.
29-
*
30-
* By default a stylesheet will be applied on the widget that changes the
31-
* background color to red.
32-
*
33-
* This can be overwritten in subclasses to allow individual widgets to
34-
* change the visual cue.
35-
* @note added in QGIS 2.16
36-
*/
37-
void updateConstraintWidgetStatus( bool constraintValid );
26+
27+
void updateConstraintWidgetStatus( ConstraintResult status );
3828
};

src/core/qgsvectorlayerutils.cpp

+5-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ bool QgsVectorLayerUtils::valueExists( const QgsVectorLayer* layer, int fieldInd
5050
return false;
5151
}
5252

53-
bool QgsVectorLayerUtils::validateAttribute( const QgsVectorLayer* layer, const QgsFeature& feature, int attributeIndex, QStringList& errors, QgsFieldConstraints::ConstraintOrigin origin )
53+
bool QgsVectorLayerUtils::validateAttribute( const QgsVectorLayer* layer, const QgsFeature& feature, int attributeIndex, QStringList& errors,
54+
QgsFieldConstraints::ConstraintStrength strength, QgsFieldConstraints::ConstraintOrigin origin )
5455
{
5556
if ( !layer )
5657
return false;
@@ -66,6 +67,7 @@ bool QgsVectorLayerUtils::validateAttribute( const QgsVectorLayer* layer, const
6667
QgsFieldConstraints constraints = field.constraints();
6768

6869
if ( constraints.constraints() & QgsFieldConstraints::ConstraintExpression && !constraints.constraintExpression().isEmpty()
70+
&& ( strength == QgsFieldConstraints::ConstraintStrengthNotSet || strength == constraints.constraintStrength( QgsFieldConstraints::ConstraintExpression ) )
6971
&& ( origin == QgsFieldConstraints::ConstraintOriginNotSet || origin == constraints.constraintOrigin( QgsFieldConstraints::ConstraintExpression ) ) )
7072
{
7173
QgsExpressionContext context = layer->createExpressionContext();
@@ -90,6 +92,7 @@ bool QgsVectorLayerUtils::validateAttribute( const QgsVectorLayer* layer, const
9092
}
9193

9294
if ( constraints.constraints() & QgsFieldConstraints::ConstraintNotNull
95+
&& ( strength == QgsFieldConstraints::ConstraintStrengthNotSet || strength == constraints.constraintStrength( QgsFieldConstraints::ConstraintNotNull ) )
9396
&& ( origin == QgsFieldConstraints::ConstraintOriginNotSet || origin == constraints.constraintOrigin( QgsFieldConstraints::ConstraintNotNull ) ) )
9497
{
9598
valid = valid && !value.isNull();
@@ -101,6 +104,7 @@ bool QgsVectorLayerUtils::validateAttribute( const QgsVectorLayer* layer, const
101104
}
102105

103106
if ( constraints.constraints() & QgsFieldConstraints::ConstraintUnique
107+
&& ( strength == QgsFieldConstraints::ConstraintStrengthNotSet || strength == constraints.constraintStrength( QgsFieldConstraints::ConstraintUnique ) )
104108
&& ( origin == QgsFieldConstraints::ConstraintOriginNotSet || origin == constraints.constraintOrigin( QgsFieldConstraints::ConstraintUnique ) ) )
105109
{
106110
bool alreadyExists = QgsVectorLayerUtils::valueExists( layer, attributeIndex, value, QgsFeatureIds() << feature.id() );

src/core/qgsvectorlayerutils.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,10 @@ class CORE_EXPORT QgsVectorLayerUtils
3939
/**
4040
* Tests an attribute value to check whether it passes all constraints which are present on the corresponding field.
4141
* Returns true if the attribute value is valid for the field. Any constraint failures will be reported in the errors argument.
42-
* If the origin parameter is set then only constraints with a matching origin will be checked.
42+
* If the strength or origin parameter is set then only constraints with a matching strength/origin will be checked.
4343
*/
4444
static bool validateAttribute( const QgsVectorLayer* layer, const QgsFeature& feature, int attributeIndex, QStringList& errors,
45+
QgsFieldConstraints::ConstraintStrength strength = QgsFieldConstraints::ConstraintStrengthNotSet,
4546
QgsFieldConstraints::ConstraintOrigin origin = QgsFieldConstraints::ConstraintOriginNotSet );
4647

4748
};

src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp

+34-8
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
QgsEditorWidgetWrapper::QgsEditorWidgetWrapper( QgsVectorLayer* vl, int fieldIdx, QWidget* editor, QWidget* parent )
2525
: QgsWidgetWrapper( vl, editor, parent )
2626
, mValidConstraint( true )
27+
, mIsBlockingCommit( false )
2728
, mFieldIdx( fieldIdx )
2829
{
2930
}
@@ -98,12 +99,22 @@ void QgsEditorWidgetWrapper::valueChanged()
9899
emit valueChanged( value() );
99100
}
100101

101-
void QgsEditorWidgetWrapper::updateConstraintWidgetStatus( bool constraintValid )
102+
void QgsEditorWidgetWrapper::updateConstraintWidgetStatus( ConstraintResult constraintResult )
102103
{
103-
if ( constraintValid )
104-
widget()->setStyleSheet( QString() );
105-
else
106-
widget()->setStyleSheet( QStringLiteral( "background-color: #dd7777;" ) );
104+
switch ( constraintResult )
105+
{
106+
case ConstraintResultPass:
107+
widget()->setStyleSheet( QString() );
108+
break;
109+
110+
case ConstraintResultFailHard:
111+
widget()->setStyleSheet( QStringLiteral( "background-color: #dd7777;" ) );
112+
break;
113+
114+
case ConstraintResultFailSoft:
115+
widget()->setStyleSheet( QStringLiteral( "background-color: #ffd85d;" ) );
116+
break;
117+
}
107118
}
108119

109120
void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft, QgsFieldConstraints::ConstraintOrigin constraintOrigin )
@@ -150,7 +161,15 @@ void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft, QgsFieldCon
150161
}
151162

152163
QStringList errors;
153-
mValidConstraint = QgsVectorLayerUtils::validateAttribute( layer(), ft, mFieldIdx, errors, constraintOrigin );
164+
bool hardConstraintsOk = QgsVectorLayerUtils::validateAttribute( layer(), ft, mFieldIdx, errors, QgsFieldConstraints::ConstraintStrengthHard, constraintOrigin );
165+
166+
QStringList softErrors;
167+
bool softConstraintsOk = QgsVectorLayerUtils::validateAttribute( layer(), ft, mFieldIdx, softErrors, QgsFieldConstraints::ConstraintStrengthSoft, constraintOrigin );
168+
errors << softErrors;
169+
170+
mValidConstraint = hardConstraintsOk && softConstraintsOk;
171+
mIsBlockingCommit = !hardConstraintsOk;
172+
154173
mConstraintFailureReason = errors.join( ", " );
155174

156175
if ( toEmit )
@@ -164,8 +183,10 @@ void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft, QgsFieldCon
164183
else if ( !expressions.isEmpty() )
165184
expressionDesc = expressions.at( 0 );
166185

167-
updateConstraintWidgetStatus( mValidConstraint );
168-
emit constraintStatusChanged( expressionDesc, description, errStr, mValidConstraint );
186+
ConstraintResult result = !hardConstraintsOk ? ConstraintResultFailHard
187+
: ( !softConstraintsOk ? ConstraintResultFailSoft : ConstraintResultPass );
188+
updateConstraintWidgetStatus( result );
189+
emit constraintStatusChanged( expressionDesc, description, errStr, result );
169190
}
170191
}
171192

@@ -174,6 +195,11 @@ bool QgsEditorWidgetWrapper::isValidConstraint() const
174195
return mValidConstraint;
175196
}
176197

198+
bool QgsEditorWidgetWrapper::isBlockingCommit() const
199+
{
200+
return mIsBlockingCommit;
201+
}
202+
177203
QString QgsEditorWidgetWrapper::constraintFailureReason() const
178204
{
179205
return mConstraintFailureReason;

src/gui/editorwidgets/core/qgseditorwidgetwrapper.h

+27-3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,18 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
4040
{
4141
Q_OBJECT
4242
public:
43+
44+
/**
45+
* Result of constraint checks.
46+
* @note added in QGIS 3.0
47+
*/
48+
enum ConstraintResult
49+
{
50+
ConstraintResultPass = 0, //!< Widget passed constraints successfully
51+
ConstraintResultFailHard, //!< Widget failed at least one hard (enforced) constraint
52+
ConstraintResultFailSoft, //!< Widget failed at least one soft (non-enforced) constraint
53+
};
54+
4355
/**
4456
* Create a new widget wrapper
4557
*
@@ -130,9 +142,18 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
130142
* false otherwise
131143
* @note added in QGIS 2.16
132144
* @see constraintFailureReason()
145+
* @see isBlockingCommit()
133146
*/
134147
bool isValidConstraint() const;
135148

149+
/**
150+
* Returns true if the widget is preventing the feature from being committed. This may be true as a result
151+
* of attribute values failing enforced field constraints.
152+
* @note added in QGIS 3.0
153+
* @see isValidConstraint()
154+
*/
155+
bool isBlockingCommit() const;
156+
136157
/**
137158
* Returns the reason why a constraint check has failed (or an empty string
138159
* if constraint check was successful).
@@ -157,7 +178,7 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
157178
* @param err the error represented as a string. Empty if none.
158179
* @param status
159180
*/
160-
void constraintStatusChanged( const QString& constraint, const QString &desc, const QString& err, bool status );
181+
void constraintStatusChanged( const QString& constraint, const QString &desc, const QString& err, ConstraintResult status );
161182

162183
public slots:
163184
/**
@@ -237,11 +258,11 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
237258
* This can be overwritten in subclasses to allow individual widgets to
238259
* change the visual cue.
239260
*
240-
* @param constraintValid The current constraint status.
261+
* @param status The current constraint status.
241262
*
242263
* @note added in QGIS 2.16
243264
*/
244-
virtual void updateConstraintWidgetStatus( bool constraintValid );
265+
virtual void updateConstraintWidgetStatus( ConstraintResult status );
245266

246267
private:
247268

@@ -250,6 +271,9 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
250271
*/
251272
bool mValidConstraint;
252273

274+
//! True if widget is blocking feature commits
275+
bool mIsBlockingCommit;
276+
253277
//! Contains the string explanation of why a constraint check failed
254278
QString mConstraintFailureReason;
255279

src/gui/editorwidgets/qgscolorwidgetwrapper.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ void QgsColorWidgetWrapper::setValue( const QVariant& value )
8080
mColorButton->setColor( !value.isNull() ? QColor( value.toString() ) : QColor() );
8181
}
8282

83-
void QgsColorWidgetWrapper::updateConstraintWidgetStatus( bool /*constraintValid*/ )
83+
void QgsColorWidgetWrapper::updateConstraintWidgetStatus( ConstraintResult /*constraintValid*/ )
8484
{
8585
// nothing
8686
}

src/gui/editorwidgets/qgscolorwidgetwrapper.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class GUI_EXPORT QgsColorWidgetWrapper : public QgsEditorWidgetWrapper
4545
void setValue( const QVariant& value ) override;
4646

4747
private:
48-
void updateConstraintWidgetStatus( bool constraintValid ) override;
48+
void updateConstraintWidgetStatus( ConstraintResult status ) override;
4949

5050
QgsColorButton* mColorButton;
5151
};

src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.cpp

+15-5
Original file line numberDiff line numberDiff line change
@@ -188,13 +188,23 @@ void QgsExternalResourceWidgetWrapper::setEnabled( bool enabled )
188188
mQgsWidget->setReadOnly( !enabled );
189189
}
190190

191-
void QgsExternalResourceWidgetWrapper::updateConstraintWidgetStatus( bool constraintValid )
191+
void QgsExternalResourceWidgetWrapper::updateConstraintWidgetStatus( ConstraintResult status )
192192
{
193193
if ( mLineEdit )
194194
{
195-
if ( constraintValid )
196-
mLineEdit->setStyleSheet( QString() );
197-
else
198-
mLineEdit->setStyleSheet( QStringLiteral( "QgsFilterLineEdit { background-color: #dd7777; }" ) );
195+
switch ( status )
196+
{
197+
case ConstraintResultPass:
198+
mLineEdit->setStyleSheet( QString() );
199+
break;
200+
201+
case ConstraintResultFailHard:
202+
mLineEdit->setStyleSheet( QStringLiteral( "QgsFilterLineEdit { background-color: #dd7777; }" ) );
203+
break;
204+
205+
case ConstraintResultFailSoft:
206+
mLineEdit->setStyleSheet( QStringLiteral( "QgsFilterLineEdit { background-color: #ffd85d; }" ) );
207+
break;
208+
}
199209
}
200210
}

src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class GUI_EXPORT QgsExternalResourceWidgetWrapper : public QgsEditorWidgetWrappe
5454
void setEnabled( bool enabled ) override;
5555

5656
private:
57-
void updateConstraintWidgetStatus( bool constraintValid ) override;
57+
void updateConstraintWidgetStatus( ConstraintResult status ) override;
5858

5959
QLineEdit* mLineEdit;
6060
QLabel* mLabel;

src/gui/editorwidgets/qgsfilenamewidgetwrapper.cpp

+13-5
Original file line numberDiff line numberDiff line change
@@ -152,15 +152,23 @@ void QgsFileNameWidgetWrapper::selectFileName()
152152
mLineEdit->setText( fileName );
153153
}
154154

155-
void QgsFileNameWidgetWrapper::updateConstraintWidgetStatus( bool constraintValid )
155+
void QgsFileNameWidgetWrapper::updateConstraintWidgetStatus( ConstraintResult status )
156156
{
157157
if ( mLineEdit )
158158
{
159-
if ( constraintValid )
160-
mLineEdit->setStyleSheet( QString() );
161-
else
159+
switch ( status )
162160
{
163-
mLineEdit->setStyleSheet( QStringLiteral( "QgsFilterLineEdit { background-color: #dd7777; }" ) );
161+
case ConstraintResultPass:
162+
mLineEdit->setStyleSheet( QString() );
163+
break;
164+
165+
case ConstraintResultFailHard:
166+
mLineEdit->setStyleSheet( QStringLiteral( "QgsFilterLineEdit { background-color: #dd7777; }" ) );
167+
break;
168+
169+
case ConstraintResultFailSoft:
170+
mLineEdit->setStyleSheet( QStringLiteral( "QgsFilterLineEdit { background-color: #ffd85d; }" ) );
171+
break;
164172
}
165173
}
166174
}

src/gui/editorwidgets/qgsfilenamewidgetwrapper.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class GUI_EXPORT QgsFileNameWidgetWrapper : public QgsEditorWidgetWrapper
5151
void setValue( const QVariant& value ) override;
5252

5353
private:
54-
void updateConstraintWidgetStatus( bool constraintValid ) override;
54+
void updateConstraintWidgetStatus( ConstraintResult status ) override;
5555

5656
QLineEdit* mLineEdit;
5757
QPushButton* mPushButton;

src/gui/editorwidgets/qgskeyvaluewidgetwrapper.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ void QgsKeyValueWidgetWrapper::setValue( const QVariant& value )
7272
mWidget->setMap( value.toMap() );
7373
}
7474

75-
void QgsKeyValueWidgetWrapper::updateConstraintWidgetStatus( bool /*constraintValid*/ )
75+
void QgsKeyValueWidgetWrapper::updateConstraintWidgetStatus( ConstraintResult /*constraintValid*/ )
7676
{
7777
// Nothing
7878
}

src/gui/editorwidgets/qgskeyvaluewidgetwrapper.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class GUI_EXPORT QgsKeyValueWidgetWrapper : public QgsEditorWidgetWrapper
5151
void onValueChanged();
5252

5353
private:
54-
void updateConstraintWidgetStatus( bool constraintValid ) override;
54+
void updateConstraintWidgetStatus( ConstraintResult status ) override;
5555

5656
QgsKeyValueWidget* mWidget;
5757
};

0 commit comments

Comments
 (0)