From 1cecf37b40d632e55ff104b2da2fea75cc56b1da Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 26 Oct 2016 06:27:07 +1000 Subject: [PATCH] Enforce unique constraints in attribute form --- .../core/qgseditorwidgetwrapper.sip | 11 ++- .../core/qgseditorwidgetwrapper.cpp | 79 ++++++++++++++++--- .../core/qgseditorwidgetwrapper.h | 14 +++- src/gui/qgsattributeform.cpp | 3 +- 4 files changed, 90 insertions(+), 17 deletions(-) diff --git a/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip b/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip index 5003d16d55d..5bdcbddd798 100644 --- a/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip +++ b/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip @@ -104,12 +104,21 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper /** * Get the current constraint status. - * @return true if the constraint is valid or if there's not constraint, + * @return true if the constraint is valid or if there's no constraint, * false otherwise * @note added in QGIS 2.16 + * @see constraintFailureReason() */ bool isValidConstraint() const; + /** + * Returns the reason why a constraint check has failed (or an empty string + * if constraint check was successful). + * @see isValidConstraint() + * @note added in QGIS 3.0 + */ + QString constraintFailureReason() const; + signals: /** * Emit this signal, whenever the value changed. diff --git a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp index b55621c0823..3241e98d706 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp +++ b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp @@ -17,6 +17,7 @@ #include "qgsvectorlayer.h" #include "qgsvectordataprovider.h" #include "qgsfields.h" +#include "qgsvectorlayerutils.h" #include @@ -108,14 +109,19 @@ void QgsEditorWidgetWrapper::updateConstraintWidgetStatus( bool constraintValid void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft ) { bool toEmit( false ); - QString errStr( tr( "predicate is True" ) ); QString expression = layer()->editFormConfig().constraintExpression( mFieldIdx ); - QString description; + QStringList expressions, descriptions; QVariant value = ft.attribute( mFieldIdx ); + QString fieldName = ft.fields().count() > mFieldIdx ? ft.fields().field( mFieldIdx ).name() : QString(); + + mConstraintFailureReason.clear(); + + QStringList errors; if ( ! expression.isEmpty() ) { - description = layer()->editFormConfig().constraintDescription( mFieldIdx ); + expressions << expression; + descriptions << layer()->editFormConfig().constraintDescription( mFieldIdx ); QgsExpressionContext context = layer()->createExpressionContext(); context.setFeature( ft ); @@ -125,11 +131,17 @@ void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft ) mValidConstraint = expr.evaluate( &context ).toBool(); if ( expr.hasParserError() ) - errStr = expr.parserErrorString(); + { + errors << tr( "Parser error: %1" ).arg( expr.parserErrorString() ); + } else if ( expr.hasEvalError() ) - errStr = expr.evalErrorString(); + { + errors << tr( "Evaluation error: %1" ).arg( expr.evalErrorString() ); + } else if ( ! mValidConstraint ) - errStr = tr( "predicate is False" ); + { + errors << tr( "%1 check failed" ).arg( layer()->editFormConfig().constraintDescription( mFieldIdx ) ); + } toEmit = true; } @@ -138,30 +150,66 @@ void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft ) if ( layer()->fieldConstraints( mFieldIdx ) & QgsField::ConstraintNotNull ) { + descriptions << QStringLiteral( "NotNull" ); if ( !expression.isEmpty() ) { - QString fieldName = ft.fields().field( mFieldIdx ).name(); - expression = "( " + expression + " ) AND ( " + fieldName + " IS NOT NULL)"; - description = "( " + description + " ) AND NotNull"; + expressions << fieldName + " IS NOT NULL"; } else { - description = QStringLiteral( "NotNull" ); - expression = QStringLiteral( "NotNull" ); + expressions << QStringLiteral( "NotNull" ); } mValidConstraint = mValidConstraint && !value.isNull(); if ( value.isNull() ) - errStr = tr( "predicate is False" ); + { + errors << tr( "Value is NULL" ); + } + + toEmit = true; + } + + if ( layer()->fieldConstraints( mFieldIdx ) & QgsField::ConstraintUnique ) + { + descriptions << QStringLiteral( "Unique" ); + if ( !expression.isEmpty() ) + { + expressions << fieldName + " IS UNIQUE"; + } + else + { + expression = QStringLiteral( "Unique" ); + } + + bool alreadyExists = QgsVectorLayerUtils::valueExists( layer(), mFieldIdx, value, QgsFeatureIds() << ft.id() ); + mValidConstraint = mValidConstraint && !alreadyExists; + + if ( alreadyExists ) + { + errors << tr( "Value is not unique" ); + } toEmit = true; } if ( toEmit ) { + QString errStr = errors.isEmpty() ? tr( "Constraint checks passed" ) : errors.join( '\n' ); + mConstraintFailureReason = errors.join( ", " ); + QString description; + if ( descriptions.size() > 1 ) + description = "( " + descriptions.join( " ) AND ( " ) + " )"; + else if ( !descriptions.isEmpty() ) + description = descriptions.at( 0 ); + QString expressionDesc; + if ( expressions.size() > 1 ) + expressionDesc = "( " + expressions.join( " ) AND ( " ) + " )"; + else if ( !expressions.isEmpty() ) + expressionDesc = expressions.at( 0 ); + updateConstraintWidgetStatus( mValidConstraint ); - emit constraintStatusChanged( expression, description, errStr, mValidConstraint ); + emit constraintStatusChanged( expressionDesc, description, errStr, mValidConstraint ); } } @@ -170,6 +218,11 @@ bool QgsEditorWidgetWrapper::isValidConstraint() const return mValidConstraint; } +QString QgsEditorWidgetWrapper::constraintFailureReason() const +{ + return mConstraintFailureReason; +} + bool QgsEditorWidgetWrapper::isInTable( const QWidget* parent ) { if ( !parent ) return false; diff --git a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h index f5894f08020..c013c31b4ae 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h +++ b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h @@ -124,12 +124,21 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper /** * Get the current constraint status. - * @return true if the constraint is valid or if there's not constraint, + * @return true if the constraint is valid or if there's no constraint, * false otherwise * @note added in QGIS 2.16 + * @see constraintFailureReason() */ bool isValidConstraint() const; + /** + * Returns the reason why a constraint check has failed (or an empty string + * if constraint check was successful). + * @see isValidConstraint() + * @note added in QGIS 3.0 + */ + QString constraintFailureReason() const; + signals: /** * Emit this signal, whenever the value changed. @@ -239,6 +248,9 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper */ bool mValidConstraint; + //! Contains the string explanation of why a constraint check failed + QString mConstraintFailureReason; + int mFieldIdx; QgsFeature mFeature; mutable QVariant mDefaultValue; // Cache default value, we don't want to retrieve different serial numbers if called repeatedly diff --git a/src/gui/qgsattributeform.cpp b/src/gui/qgsattributeform.cpp index f4069e64e6c..6582131dc02 100644 --- a/src/gui/qgsattributeform.cpp +++ b/src/gui/qgsattributeform.cpp @@ -824,8 +824,7 @@ bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields, { invalidFields.append( eww->field().name() ); - QString desc = eww->layer()->editFormConfig().constraintDescription( eww->fieldIdx() ); - descriptions.append( desc ); + descriptions.append( eww->constraintFailureReason() ); valid = false; // continue to get all invalif fields }