Skip to content

Commit 1cecf37

Browse files
committedNov 2, 2016
Enforce unique constraints in attribute form
1 parent b7d0fd6 commit 1cecf37

File tree

4 files changed

+90
-17
lines changed

4 files changed

+90
-17
lines changed
 

‎python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,21 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper
104104

105105
/**
106106
* Get the current constraint status.
107-
* @return true if the constraint is valid or if there's not constraint,
107+
* @return true if the constraint is valid or if there's no constraint,
108108
* false otherwise
109109
* @note added in QGIS 2.16
110+
* @see constraintFailureReason()
110111
*/
111112
bool isValidConstraint() const;
112113

114+
/**
115+
* Returns the reason why a constraint check has failed (or an empty string
116+
* if constraint check was successful).
117+
* @see isValidConstraint()
118+
* @note added in QGIS 3.0
119+
*/
120+
QString constraintFailureReason() const;
121+
113122
signals:
114123
/**
115124
* Emit this signal, whenever the value changed.

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

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "qgsvectorlayer.h"
1818
#include "qgsvectordataprovider.h"
1919
#include "qgsfields.h"
20+
#include "qgsvectorlayerutils.h"
2021

2122
#include <QTableView>
2223

@@ -108,14 +109,19 @@ void QgsEditorWidgetWrapper::updateConstraintWidgetStatus( bool constraintValid
108109
void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft )
109110
{
110111
bool toEmit( false );
111-
QString errStr( tr( "predicate is True" ) );
112112
QString expression = layer()->editFormConfig().constraintExpression( mFieldIdx );
113-
QString description;
113+
QStringList expressions, descriptions;
114114
QVariant value = ft.attribute( mFieldIdx );
115+
QString fieldName = ft.fields().count() > mFieldIdx ? ft.fields().field( mFieldIdx ).name() : QString();
116+
117+
mConstraintFailureReason.clear();
118+
119+
QStringList errors;
115120

116121
if ( ! expression.isEmpty() )
117122
{
118-
description = layer()->editFormConfig().constraintDescription( mFieldIdx );
123+
expressions << expression;
124+
descriptions << layer()->editFormConfig().constraintDescription( mFieldIdx );
119125

120126
QgsExpressionContext context = layer()->createExpressionContext();
121127
context.setFeature( ft );
@@ -125,11 +131,17 @@ void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft )
125131
mValidConstraint = expr.evaluate( &context ).toBool();
126132

127133
if ( expr.hasParserError() )
128-
errStr = expr.parserErrorString();
134+
{
135+
errors << tr( "Parser error: %1" ).arg( expr.parserErrorString() );
136+
}
129137
else if ( expr.hasEvalError() )
130-
errStr = expr.evalErrorString();
138+
{
139+
errors << tr( "Evaluation error: %1" ).arg( expr.evalErrorString() );
140+
}
131141
else if ( ! mValidConstraint )
132-
errStr = tr( "predicate is False" );
142+
{
143+
errors << tr( "%1 check failed" ).arg( layer()->editFormConfig().constraintDescription( mFieldIdx ) );
144+
}
133145

134146
toEmit = true;
135147
}
@@ -138,30 +150,66 @@ void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft )
138150

139151
if ( layer()->fieldConstraints( mFieldIdx ) & QgsField::ConstraintNotNull )
140152
{
153+
descriptions << QStringLiteral( "NotNull" );
141154
if ( !expression.isEmpty() )
142155
{
143-
QString fieldName = ft.fields().field( mFieldIdx ).name();
144-
expression = "( " + expression + " ) AND ( " + fieldName + " IS NOT NULL)";
145-
description = "( " + description + " ) AND NotNull";
156+
expressions << fieldName + " IS NOT NULL";
146157
}
147158
else
148159
{
149-
description = QStringLiteral( "NotNull" );
150-
expression = QStringLiteral( "NotNull" );
160+
expressions << QStringLiteral( "NotNull" );
151161
}
152162

153163
mValidConstraint = mValidConstraint && !value.isNull();
154164

155165
if ( value.isNull() )
156-
errStr = tr( "predicate is False" );
166+
{
167+
errors << tr( "Value is NULL" );
168+
}
169+
170+
toEmit = true;
171+
}
172+
173+
if ( layer()->fieldConstraints( mFieldIdx ) & QgsField::ConstraintUnique )
174+
{
175+
descriptions << QStringLiteral( "Unique" );
176+
if ( !expression.isEmpty() )
177+
{
178+
expressions << fieldName + " IS UNIQUE";
179+
}
180+
else
181+
{
182+
expression = QStringLiteral( "Unique" );
183+
}
184+
185+
bool alreadyExists = QgsVectorLayerUtils::valueExists( layer(), mFieldIdx, value, QgsFeatureIds() << ft.id() );
186+
mValidConstraint = mValidConstraint && !alreadyExists;
187+
188+
if ( alreadyExists )
189+
{
190+
errors << tr( "Value is not unique" );
191+
}
157192

158193
toEmit = true;
159194
}
160195

161196
if ( toEmit )
162197
{
198+
QString errStr = errors.isEmpty() ? tr( "Constraint checks passed" ) : errors.join( '\n' );
199+
mConstraintFailureReason = errors.join( ", " );
200+
QString description;
201+
if ( descriptions.size() > 1 )
202+
description = "( " + descriptions.join( " ) AND ( " ) + " )";
203+
else if ( !descriptions.isEmpty() )
204+
description = descriptions.at( 0 );
205+
QString expressionDesc;
206+
if ( expressions.size() > 1 )
207+
expressionDesc = "( " + expressions.join( " ) AND ( " ) + " )";
208+
else if ( !expressions.isEmpty() )
209+
expressionDesc = expressions.at( 0 );
210+
163211
updateConstraintWidgetStatus( mValidConstraint );
164-
emit constraintStatusChanged( expression, description, errStr, mValidConstraint );
212+
emit constraintStatusChanged( expressionDesc, description, errStr, mValidConstraint );
165213
}
166214
}
167215

@@ -170,6 +218,11 @@ bool QgsEditorWidgetWrapper::isValidConstraint() const
170218
return mValidConstraint;
171219
}
172220

221+
QString QgsEditorWidgetWrapper::constraintFailureReason() const
222+
{
223+
return mConstraintFailureReason;
224+
}
225+
173226
bool QgsEditorWidgetWrapper::isInTable( const QWidget* parent )
174227
{
175228
if ( !parent ) return false;

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,21 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
124124

125125
/**
126126
* Get the current constraint status.
127-
* @return true if the constraint is valid or if there's not constraint,
127+
* @return true if the constraint is valid or if there's no constraint,
128128
* false otherwise
129129
* @note added in QGIS 2.16
130+
* @see constraintFailureReason()
130131
*/
131132
bool isValidConstraint() const;
132133

134+
/**
135+
* Returns the reason why a constraint check has failed (or an empty string
136+
* if constraint check was successful).
137+
* @see isValidConstraint()
138+
* @note added in QGIS 3.0
139+
*/
140+
QString constraintFailureReason() const;
141+
133142
signals:
134143
/**
135144
* Emit this signal, whenever the value changed.
@@ -239,6 +248,9 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
239248
*/
240249
bool mValidConstraint;
241250

251+
//! Contains the string explanation of why a constraint check failed
252+
QString mConstraintFailureReason;
253+
242254
int mFieldIdx;
243255
QgsFeature mFeature;
244256
mutable QVariant mDefaultValue; // Cache default value, we don't want to retrieve different serial numbers if called repeatedly

‎src/gui/qgsattributeform.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -824,8 +824,7 @@ bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields,
824824
{
825825
invalidFields.append( eww->field().name() );
826826

827-
QString desc = eww->layer()->editFormConfig().constraintDescription( eww->fieldIdx() );
828-
descriptions.append( desc );
827+
descriptions.append( eww->constraintFailureReason() );
829828

830829
valid = false; // continue to get all invalif fields
831830
}

0 commit comments

Comments
 (0)
Please sign in to comment.