Skip to content
Permalink
Browse files

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.
  • Loading branch information
nyalldawson committed Nov 2, 2016
1 parent fac5bc0 commit 3f2a7810cf4610e253a75118fed671aaed78ff66
Showing with 255 additions and 95 deletions.
  1. +2 −1 python/core/qgsvectorlayerutils.sip
  2. +27 −2 python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip
  3. +2 −12 python/gui/editorwidgets/qgsrelationreferencewidgetwrapper.sip
  4. +5 −1 src/core/qgsvectorlayerutils.cpp
  5. +2 −1 src/core/qgsvectorlayerutils.h
  6. +34 −8 src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp
  7. +27 −3 src/gui/editorwidgets/core/qgseditorwidgetwrapper.h
  8. +1 −1 src/gui/editorwidgets/qgscolorwidgetwrapper.cpp
  9. +1 −1 src/gui/editorwidgets/qgscolorwidgetwrapper.h
  10. +15 −5 src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.cpp
  11. +1 −1 src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.h
  12. +13 −5 src/gui/editorwidgets/qgsfilenamewidgetwrapper.cpp
  13. +1 −1 src/gui/editorwidgets/qgsfilenamewidgetwrapper.h
  14. +1 −1 src/gui/editorwidgets/qgskeyvaluewidgetwrapper.cpp
  15. +1 −1 src/gui/editorwidgets/qgskeyvaluewidgetwrapper.h
  16. +2 −2 src/gui/editorwidgets/qgslistwidgetwrapper.cpp
  17. +1 −1 src/gui/editorwidgets/qgslistwidgetwrapper.h
  18. +13 −5 src/gui/editorwidgets/qgsphotowidgetwrapper.cpp
  19. +1 −1 src/gui/editorwidgets/qgsphotowidgetwrapper.h
  20. +15 −5 src/gui/editorwidgets/qgsrelationreferencewidgetwrapper.cpp
  21. +2 −12 src/gui/editorwidgets/qgsrelationreferencewidgetwrapper.h
  22. +13 −5 src/gui/editorwidgets/qgswebviewwidgetwrapper.cpp
  23. +1 −1 src/gui/editorwidgets/qgswebviewwidgetwrapper.h
  24. +16 −11 src/gui/qgsattributeform.cpp
  25. +3 −1 src/gui/qgsattributeform.h
  26. +36 −0 tests/src/gui/testqgsattributeform.cpp
  27. +19 −7 tests/src/python/test_qgsvectorlayerutils.py
@@ -22,9 +22,10 @@ class QgsVectorLayerUtils
/**
* Tests an attribute value to check whether it passes all constraints which are present on the corresponding field.
* Returns true if the attribute value is valid for the field. Any constraint failures will be reported in the errors argument.
* If the origin parameter is set then only constraints with a matching origin will be checked.
* If the strength or origin parameter is set then only constraints with a matching strength/origin will be checked.
*/
static bool validateAttribute( const QgsVectorLayer* layer, const QgsFeature& feature, int attributeIndex, QStringList& errors /Out/,
QgsFieldConstraints::ConstraintStrength strength = QgsFieldConstraints::ConstraintStrengthNotSet,
QgsFieldConstraints::ConstraintOrigin origin = QgsFieldConstraints::ConstraintOriginNotSet );

};
@@ -20,6 +20,18 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper
%End

public:

/**
* Result of constraint checks.
* @note added in QGIS 3.0
*/
enum ConstraintResult
{
ConstraintResultPass, //!< Widget passed constraints successfully
ConstraintResultFailHard, //!< Widget failed at least one hard (enforced) constraint
ConstraintResultFailSoft, //!< Widget failed at least one soft (non-enforced) constraint
};

/**
* Create a new widget wrapper
*
@@ -108,9 +120,18 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper
* false otherwise
* @note added in QGIS 2.16
* @see constraintFailureReason()
* @see isBlockingCommit()
*/
bool isValidConstraint() const;

/**
* Returns true if the widget is preventing the feature from being committed. This may be true as a result
* of attribute values failing enforced field constraints.
* @note added in QGIS 3.0
* @see isValidConstraint()
*/
bool isBlockingCommit() const;

/**
* Returns the reason why a constraint check has failed (or an empty string
* if constraint check was successful).
@@ -135,7 +156,7 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper
* @param err the error represented as a string. Empty if none.
* @param status
*/
void constraintStatusChanged( const QString& constraint, const QString& err, bool status );
void constraintStatusChanged( const QString& constraint, const QString &desc, const QString& err, ConstraintResult status );

public slots:
/**
@@ -214,6 +235,10 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper
*
* This can be overwritten in subclasses to allow individual widgets to
* change the visual cue.
*
* @param status The current constraint status.
*
* @note added in QGIS 2.16
*/
virtual void updateConstraintWidgetStatus( bool contraintValid );
virtual void updateConstraintWidgetStatus( ConstraintResult status );
};
@@ -23,16 +23,6 @@ class QgsRelationReferenceWidgetWrapper : QgsEditorWidgetWrapper
virtual void setEnabled( bool enabled );

protected:
/**
* This should update the widget with a visual cue if a constraint status
* changed.
*
* By default a stylesheet will be applied on the widget that changes the
* background color to red.
*
* This can be overwritten in subclasses to allow individual widgets to
* change the visual cue.
* @note added in QGIS 2.16
*/
void updateConstraintWidgetStatus( bool constraintValid );

void updateConstraintWidgetStatus( ConstraintResult status );
};
@@ -50,7 +50,8 @@ bool QgsVectorLayerUtils::valueExists( const QgsVectorLayer* layer, int fieldInd
return false;
}

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

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

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

if ( constraints.constraints() & QgsFieldConstraints::ConstraintUnique
&& ( strength == QgsFieldConstraints::ConstraintStrengthNotSet || strength == constraints.constraintStrength( QgsFieldConstraints::ConstraintUnique ) )
&& ( origin == QgsFieldConstraints::ConstraintOriginNotSet || origin == constraints.constraintOrigin( QgsFieldConstraints::ConstraintUnique ) ) )
{
bool alreadyExists = QgsVectorLayerUtils::valueExists( layer, attributeIndex, value, QgsFeatureIds() << feature.id() );
@@ -39,9 +39,10 @@ class CORE_EXPORT QgsVectorLayerUtils
/**
* Tests an attribute value to check whether it passes all constraints which are present on the corresponding field.
* Returns true if the attribute value is valid for the field. Any constraint failures will be reported in the errors argument.
* If the origin parameter is set then only constraints with a matching origin will be checked.
* If the strength or origin parameter is set then only constraints with a matching strength/origin will be checked.
*/
static bool validateAttribute( const QgsVectorLayer* layer, const QgsFeature& feature, int attributeIndex, QStringList& errors,
QgsFieldConstraints::ConstraintStrength strength = QgsFieldConstraints::ConstraintStrengthNotSet,
QgsFieldConstraints::ConstraintOrigin origin = QgsFieldConstraints::ConstraintOriginNotSet );

};
@@ -24,6 +24,7 @@
QgsEditorWidgetWrapper::QgsEditorWidgetWrapper( QgsVectorLayer* vl, int fieldIdx, QWidget* editor, QWidget* parent )
: QgsWidgetWrapper( vl, editor, parent )
, mValidConstraint( true )
, mIsBlockingCommit( false )
, mFieldIdx( fieldIdx )
{
}
@@ -98,12 +99,22 @@ void QgsEditorWidgetWrapper::valueChanged()
emit valueChanged( value() );
}

void QgsEditorWidgetWrapper::updateConstraintWidgetStatus( bool constraintValid )
void QgsEditorWidgetWrapper::updateConstraintWidgetStatus( ConstraintResult constraintResult )
{
if ( constraintValid )
widget()->setStyleSheet( QString() );
else
widget()->setStyleSheet( QStringLiteral( "background-color: #dd7777;" ) );
switch ( constraintResult )
{
case ConstraintResultPass:
widget()->setStyleSheet( QString() );
break;

case ConstraintResultFailHard:
widget()->setStyleSheet( QStringLiteral( "background-color: #dd7777;" ) );
break;

case ConstraintResultFailSoft:
widget()->setStyleSheet( QStringLiteral( "background-color: #ffd85d;" ) );
break;
}
}

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

QStringList errors;
mValidConstraint = QgsVectorLayerUtils::validateAttribute( layer(), ft, mFieldIdx, errors, constraintOrigin );
bool hardConstraintsOk = QgsVectorLayerUtils::validateAttribute( layer(), ft, mFieldIdx, errors, QgsFieldConstraints::ConstraintStrengthHard, constraintOrigin );

QStringList softErrors;
bool softConstraintsOk = QgsVectorLayerUtils::validateAttribute( layer(), ft, mFieldIdx, softErrors, QgsFieldConstraints::ConstraintStrengthSoft, constraintOrigin );
errors << softErrors;

mValidConstraint = hardConstraintsOk && softConstraintsOk;
mIsBlockingCommit = !hardConstraintsOk;

mConstraintFailureReason = errors.join( ", " );

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

updateConstraintWidgetStatus( mValidConstraint );
emit constraintStatusChanged( expressionDesc, description, errStr, mValidConstraint );
ConstraintResult result = !hardConstraintsOk ? ConstraintResultFailHard
: ( !softConstraintsOk ? ConstraintResultFailSoft : ConstraintResultPass );
updateConstraintWidgetStatus( result );
emit constraintStatusChanged( expressionDesc, description, errStr, result );
}
}

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

bool QgsEditorWidgetWrapper::isBlockingCommit() const
{
return mIsBlockingCommit;
}

QString QgsEditorWidgetWrapper::constraintFailureReason() const
{
return mConstraintFailureReason;
@@ -40,6 +40,18 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
{
Q_OBJECT
public:

/**
* Result of constraint checks.
* @note added in QGIS 3.0
*/
enum ConstraintResult
{
ConstraintResultPass = 0, //!< Widget passed constraints successfully
ConstraintResultFailHard, //!< Widget failed at least one hard (enforced) constraint
ConstraintResultFailSoft, //!< Widget failed at least one soft (non-enforced) constraint
};

/**
* Create a new widget wrapper
*
@@ -130,9 +142,18 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
* false otherwise
* @note added in QGIS 2.16
* @see constraintFailureReason()
* @see isBlockingCommit()
*/
bool isValidConstraint() const;

/**
* Returns true if the widget is preventing the feature from being committed. This may be true as a result
* of attribute values failing enforced field constraints.
* @note added in QGIS 3.0
* @see isValidConstraint()
*/
bool isBlockingCommit() const;

/**
* Returns the reason why a constraint check has failed (or an empty string
* if constraint check was successful).
@@ -157,7 +178,7 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
* @param err the error represented as a string. Empty if none.
* @param status
*/
void constraintStatusChanged( const QString& constraint, const QString &desc, const QString& err, bool status );
void constraintStatusChanged( const QString& constraint, const QString &desc, const QString& err, ConstraintResult status );

public slots:
/**
@@ -237,11 +258,11 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
* This can be overwritten in subclasses to allow individual widgets to
* change the visual cue.
*
* @param constraintValid The current constraint status.
* @param status The current constraint status.
*
* @note added in QGIS 2.16
*/
virtual void updateConstraintWidgetStatus( bool constraintValid );
virtual void updateConstraintWidgetStatus( ConstraintResult status );

private:

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

//! True if widget is blocking feature commits
bool mIsBlockingCommit;

//! Contains the string explanation of why a constraint check failed
QString mConstraintFailureReason;

@@ -80,7 +80,7 @@ void QgsColorWidgetWrapper::setValue( const QVariant& value )
mColorButton->setColor( !value.isNull() ? QColor( value.toString() ) : QColor() );
}

void QgsColorWidgetWrapper::updateConstraintWidgetStatus( bool /*constraintValid*/ )
void QgsColorWidgetWrapper::updateConstraintWidgetStatus( ConstraintResult /*constraintValid*/ )
{
// nothing
}
@@ -45,7 +45,7 @@ class GUI_EXPORT QgsColorWidgetWrapper : public QgsEditorWidgetWrapper
void setValue( const QVariant& value ) override;

private:
void updateConstraintWidgetStatus( bool constraintValid ) override;
void updateConstraintWidgetStatus( ConstraintResult status ) override;

QgsColorButton* mColorButton;
};
@@ -188,13 +188,23 @@ void QgsExternalResourceWidgetWrapper::setEnabled( bool enabled )
mQgsWidget->setReadOnly( !enabled );
}

void QgsExternalResourceWidgetWrapper::updateConstraintWidgetStatus( bool constraintValid )
void QgsExternalResourceWidgetWrapper::updateConstraintWidgetStatus( ConstraintResult status )
{
if ( mLineEdit )
{
if ( constraintValid )
mLineEdit->setStyleSheet( QString() );
else
mLineEdit->setStyleSheet( QStringLiteral( "QgsFilterLineEdit { background-color: #dd7777; }" ) );
switch ( status )
{
case ConstraintResultPass:
mLineEdit->setStyleSheet( QString() );
break;

case ConstraintResultFailHard:
mLineEdit->setStyleSheet( QStringLiteral( "QgsFilterLineEdit { background-color: #dd7777; }" ) );
break;

case ConstraintResultFailSoft:
mLineEdit->setStyleSheet( QStringLiteral( "QgsFilterLineEdit { background-color: #ffd85d; }" ) );
break;
}
}
}
@@ -54,7 +54,7 @@ class GUI_EXPORT QgsExternalResourceWidgetWrapper : public QgsEditorWidgetWrappe
void setEnabled( bool enabled ) override;

private:
void updateConstraintWidgetStatus( bool constraintValid ) override;
void updateConstraintWidgetStatus( ConstraintResult status ) override;

QLineEdit* mLineEdit;
QLabel* mLabel;
@@ -152,15 +152,23 @@ void QgsFileNameWidgetWrapper::selectFileName()
mLineEdit->setText( fileName );
}

void QgsFileNameWidgetWrapper::updateConstraintWidgetStatus( bool constraintValid )
void QgsFileNameWidgetWrapper::updateConstraintWidgetStatus( ConstraintResult status )
{
if ( mLineEdit )
{
if ( constraintValid )
mLineEdit->setStyleSheet( QString() );
else
switch ( status )
{
mLineEdit->setStyleSheet( QStringLiteral( "QgsFilterLineEdit { background-color: #dd7777; }" ) );
case ConstraintResultPass:
mLineEdit->setStyleSheet( QString() );
break;

case ConstraintResultFailHard:
mLineEdit->setStyleSheet( QStringLiteral( "QgsFilterLineEdit { background-color: #dd7777; }" ) );
break;

case ConstraintResultFailSoft:
mLineEdit->setStyleSheet( QStringLiteral( "QgsFilterLineEdit { background-color: #ffd85d; }" ) );
break;
}
}
}
@@ -51,7 +51,7 @@ class GUI_EXPORT QgsFileNameWidgetWrapper : public QgsEditorWidgetWrapper
void setValue( const QVariant& value ) override;

private:
void updateConstraintWidgetStatus( bool constraintValid ) override;
void updateConstraintWidgetStatus( ConstraintResult status ) override;

QLineEdit* mLineEdit;
QPushButton* mPushButton;
@@ -72,7 +72,7 @@ void QgsKeyValueWidgetWrapper::setValue( const QVariant& value )
mWidget->setMap( value.toMap() );
}

void QgsKeyValueWidgetWrapper::updateConstraintWidgetStatus( bool /*constraintValid*/ )
void QgsKeyValueWidgetWrapper::updateConstraintWidgetStatus( ConstraintResult /*constraintValid*/ )
{
// Nothing
}
@@ -51,7 +51,7 @@ class GUI_EXPORT QgsKeyValueWidgetWrapper : public QgsEditorWidgetWrapper
void onValueChanged();

private:
void updateConstraintWidgetStatus( bool constraintValid ) override;
void updateConstraintWidgetStatus( ConstraintResult status ) override;

QgsKeyValueWidget* mWidget;
};

0 comments on commit 3f2a781

Please sign in to comment.
You can’t perform that action at this time.