Enforce unique constraints in attribute form

This commit is contained in:
Nyall Dawson 2016-10-26 06:27:07 +10:00
parent b7d0fd6f90
commit 1cecf37b40
4 changed files with 90 additions and 17 deletions

View File

@ -104,12 +104,21 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper
/** /**
* Get the current constraint status. * 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 * false otherwise
* @note added in QGIS 2.16 * @note added in QGIS 2.16
* @see constraintFailureReason()
*/ */
bool isValidConstraint() const; 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: signals:
/** /**
* Emit this signal, whenever the value changed. * Emit this signal, whenever the value changed.

View File

@ -17,6 +17,7 @@
#include "qgsvectorlayer.h" #include "qgsvectorlayer.h"
#include "qgsvectordataprovider.h" #include "qgsvectordataprovider.h"
#include "qgsfields.h" #include "qgsfields.h"
#include "qgsvectorlayerutils.h"
#include <QTableView> #include <QTableView>
@ -108,14 +109,19 @@ void QgsEditorWidgetWrapper::updateConstraintWidgetStatus( bool constraintValid
void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft ) void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft )
{ {
bool toEmit( false ); bool toEmit( false );
QString errStr( tr( "predicate is True" ) );
QString expression = layer()->editFormConfig().constraintExpression( mFieldIdx ); QString expression = layer()->editFormConfig().constraintExpression( mFieldIdx );
QString description; QStringList expressions, descriptions;
QVariant value = ft.attribute( mFieldIdx ); QVariant value = ft.attribute( mFieldIdx );
QString fieldName = ft.fields().count() > mFieldIdx ? ft.fields().field( mFieldIdx ).name() : QString();
mConstraintFailureReason.clear();
QStringList errors;
if ( ! expression.isEmpty() ) if ( ! expression.isEmpty() )
{ {
description = layer()->editFormConfig().constraintDescription( mFieldIdx ); expressions << expression;
descriptions << layer()->editFormConfig().constraintDescription( mFieldIdx );
QgsExpressionContext context = layer()->createExpressionContext(); QgsExpressionContext context = layer()->createExpressionContext();
context.setFeature( ft ); context.setFeature( ft );
@ -125,11 +131,17 @@ void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft )
mValidConstraint = expr.evaluate( &context ).toBool(); mValidConstraint = expr.evaluate( &context ).toBool();
if ( expr.hasParserError() ) if ( expr.hasParserError() )
errStr = expr.parserErrorString(); {
errors << tr( "Parser error: %1" ).arg( expr.parserErrorString() );
}
else if ( expr.hasEvalError() ) else if ( expr.hasEvalError() )
errStr = expr.evalErrorString(); {
errors << tr( "Evaluation error: %1" ).arg( expr.evalErrorString() );
}
else if ( ! mValidConstraint ) else if ( ! mValidConstraint )
errStr = tr( "predicate is False" ); {
errors << tr( "%1 check failed" ).arg( layer()->editFormConfig().constraintDescription( mFieldIdx ) );
}
toEmit = true; toEmit = true;
} }
@ -138,30 +150,66 @@ void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft )
if ( layer()->fieldConstraints( mFieldIdx ) & QgsField::ConstraintNotNull ) if ( layer()->fieldConstraints( mFieldIdx ) & QgsField::ConstraintNotNull )
{ {
descriptions << QStringLiteral( "NotNull" );
if ( !expression.isEmpty() ) if ( !expression.isEmpty() )
{ {
QString fieldName = ft.fields().field( mFieldIdx ).name(); expressions << fieldName + " IS NOT NULL";
expression = "( " + expression + " ) AND ( " + fieldName + " IS NOT NULL)";
description = "( " + description + " ) AND NotNull";
} }
else else
{ {
description = QStringLiteral( "NotNull" ); expressions << QStringLiteral( "NotNull" );
expression = QStringLiteral( "NotNull" );
} }
mValidConstraint = mValidConstraint && !value.isNull(); mValidConstraint = mValidConstraint && !value.isNull();
if ( 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; toEmit = true;
} }
if ( toEmit ) 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 ); updateConstraintWidgetStatus( mValidConstraint );
emit constraintStatusChanged( expression, description, errStr, mValidConstraint ); emit constraintStatusChanged( expressionDesc, description, errStr, mValidConstraint );
} }
} }
@ -170,6 +218,11 @@ bool QgsEditorWidgetWrapper::isValidConstraint() const
return mValidConstraint; return mValidConstraint;
} }
QString QgsEditorWidgetWrapper::constraintFailureReason() const
{
return mConstraintFailureReason;
}
bool QgsEditorWidgetWrapper::isInTable( const QWidget* parent ) bool QgsEditorWidgetWrapper::isInTable( const QWidget* parent )
{ {
if ( !parent ) return false; if ( !parent ) return false;

View File

@ -124,12 +124,21 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
/** /**
* Get the current constraint status. * 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 * false otherwise
* @note added in QGIS 2.16 * @note added in QGIS 2.16
* @see constraintFailureReason()
*/ */
bool isValidConstraint() const; 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: signals:
/** /**
* Emit this signal, whenever the value changed. * Emit this signal, whenever the value changed.
@ -239,6 +248,9 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
*/ */
bool mValidConstraint; bool mValidConstraint;
//! Contains the string explanation of why a constraint check failed
QString mConstraintFailureReason;
int mFieldIdx; int mFieldIdx;
QgsFeature mFeature; QgsFeature mFeature;
mutable QVariant mDefaultValue; // Cache default value, we don't want to retrieve different serial numbers if called repeatedly mutable QVariant mDefaultValue; // Cache default value, we don't want to retrieve different serial numbers if called repeatedly

View File

@ -824,8 +824,7 @@ bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields,
{ {
invalidFields.append( eww->field().name() ); invalidFields.append( eww->field().name() );
QString desc = eww->layer()->editFormConfig().constraintDescription( eww->fieldIdx() ); descriptions.append( eww->constraintFailureReason() );
descriptions.append( desc );
valid = false; // continue to get all invalif fields valid = false; // continue to get all invalif fields
} }