Skip to content
Permalink
Browse files

Merge pull request #4839 from pblottiere/joinconstraints

[FEATURE] Constraints are resolved for joined fields
  • Loading branch information
Hugo Mercier
Hugo Mercier committed Jul 25, 2017
2 parents c5371b6 + 588fe49 commit 1b9c5be10d4733852cf2c5c843252bbf17306675
@@ -125,6 +125,18 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper
\param constraintOrigin optional origin for constraints to check. This can be used to limit the constraints tested
to only provider or layer based constraints.
.. versionadded:: 2.16
%End

void updateConstraint( const QgsVectorLayer *layer, int index, const QgsFeature &feature, QgsFieldConstraints::ConstraintOrigin constraintOrigin = QgsFieldConstraints::ConstraintOriginNotSet );
%Docstring
Update constraint on a feature coming from a specific layer.
\param layer The vector layer where the feature is defined
\param index The index of the field to check
\param feature The feature to use to evaluate the constraint
\param constraintOrigin Optional origin for constraints to check. This
can be used to limit the constraints tested to only provider or layer
based constraints.
.. versionadded:: 3.0
%End

bool isValidConstraint() const;
@@ -416,10 +416,11 @@ QgsFeature QgsVectorLayerJoinBuffer::joinedFeatureOf( const QgsVectorLayerJoinIn

if ( info->joinLayer() )
{
joinedFeature.setFields( info->joinLayer()->fields() );

QString joinFieldName = info->joinFieldName();
const QVariant targetValue = feature.attribute( info->targetFieldName() );
QString fieldRef = QgsExpression::quotedColumnRef( info->joinFieldName() );
QString quotedVal = QgsExpression::quotedValue( targetValue.toString() );
const QString filter = QStringLiteral( "%1 = %2" ).arg( fieldRef, quotedVal );
QString filter = QgsExpression::createFieldEqualityExpression( joinFieldName, targetValue );

QgsFeatureRequest request;
request.setFilterExpression( filter );
@@ -18,6 +18,8 @@
#include "qgsvectordataprovider.h"
#include "qgsfields.h"
#include "qgsvectorlayerutils.h"
#include "qgsvectorlayerjoinbuffer.h"
#include "qgsvectorlayerjoininfo.h"

#include <QTableView>

@@ -119,53 +121,76 @@ void QgsEditorWidgetWrapper::updateConstraintWidgetStatus( ConstraintResult cons

void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft, QgsFieldConstraints::ConstraintOrigin constraintOrigin )
{
updateConstraint( layer(), mFieldIdx, ft, constraintOrigin );
}

void QgsEditorWidgetWrapper::updateConstraint( const QgsVectorLayer *layer, int index, const QgsFeature &ft, QgsFieldConstraints::ConstraintOrigin constraintOrigin )
{
QStringList errors;
QStringList softErrors;
QStringList expressions;
QStringList descriptions;
bool toEmit( false );
QgsField field = layer()->fields().at( mFieldIdx );
bool hardConstraintsOk( true );
bool softConstraintsOk( true );

QgsField field = layer->fields().at( index );
QString expression = field.constraints().constraintExpression();
QStringList expressions, descriptions;

if ( ! expression.isEmpty() )
if ( ft.isValid() )
{
expressions << expression;
descriptions << field.constraints().constraintDescription();
toEmit = true;
}

if ( field.constraints().constraints() & QgsFieldConstraints::ConstraintNotNull )
{
descriptions << tr( "Not NULL" );
if ( !expression.isEmpty() )
if ( ! expression.isEmpty() )
{
expressions << field.name() + QStringLiteral( " IS NOT NULL" );
expressions << expression;
descriptions << field.constraints().constraintDescription();
toEmit = true;
}
else
{
expressions << QStringLiteral( "IS NOT NULL" );
}
toEmit = true;
}

if ( field.constraints().constraints() & QgsFieldConstraints::ConstraintUnique )
{
descriptions << tr( "Unique" );
if ( !expression.isEmpty() )
if ( field.constraints().constraints() & QgsFieldConstraints::ConstraintNotNull )
{
expressions << field.name() + QStringLiteral( " IS UNIQUE" );
descriptions << tr( "Not NULL" );
if ( !expression.isEmpty() )
{
expressions << field.name() + QStringLiteral( " IS NOT NULL" );
}
else
{
expressions << QStringLiteral( "IS NOT NULL" );
}
toEmit = true;
}
else

if ( field.constraints().constraints() & QgsFieldConstraints::ConstraintUnique )
{
expressions << QStringLiteral( "IS UNIQUE" );
descriptions << tr( "Unique" );
if ( !expression.isEmpty() )
{
expressions << field.name() + QStringLiteral( " IS UNIQUE" );
}
else
{
expressions << QStringLiteral( "IS UNIQUE" );
}
toEmit = true;
}
toEmit = true;

hardConstraintsOk = QgsVectorLayerUtils::validateAttribute( layer, ft, index, errors, QgsFieldConstraints::ConstraintStrengthHard, constraintOrigin );

softConstraintsOk = QgsVectorLayerUtils::validateAttribute( layer, ft, index, softErrors, QgsFieldConstraints::ConstraintStrengthSoft, constraintOrigin );
errors << softErrors;
}
else // invalid feature
{
if ( ! expression.isEmpty() )
{
hardConstraintsOk = true;
softConstraintsOk = false;

QStringList errors;
bool hardConstraintsOk = QgsVectorLayerUtils::validateAttribute( layer(), ft, mFieldIdx, errors, QgsFieldConstraints::ConstraintStrengthHard, constraintOrigin );
errors << "Invalid feature";

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

mValidConstraint = hardConstraintsOk && softConstraintsOk;
mIsBlockingCommit = !hardConstraintsOk;
@@ -138,6 +138,18 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
*/
void updateConstraint( const QgsFeature &featureContext, QgsFieldConstraints::ConstraintOrigin constraintOrigin = QgsFieldConstraints::ConstraintOriginNotSet );

/**
* Update constraint on a feature coming from a specific layer.
* \param layer The vector layer where the feature is defined
* \param index The index of the field to check
* \param feature The feature to use to evaluate the constraint
* \param constraintOrigin Optional origin for constraints to check. This
* can be used to limit the constraints tested to only provider or layer
* based constraints.
* \since QGIS 3.0
*/
void updateConstraint( const QgsVectorLayer *layer, int index, const QgsFeature &feature, QgsFieldConstraints::ConstraintOrigin constraintOrigin = QgsFieldConstraints::ConstraintOriginNotSet );

/**
* Get the current constraint status.
* \returns true if the constraint is valid or if there's no constraint,
@@ -720,17 +720,15 @@ void QgsAttributeForm::updateConstraints( QgsEditorWidgetWrapper *eww )
// to test, but they are unlikely to have any control over provider-side constraints
// 2. the provider has already accepted the value, so presumably it doesn't violate the constraint
// and there's no point rechecking!
QgsFieldConstraints::ConstraintOrigin constraintOrigin = mLayer->isEditable() ? QgsFieldConstraints::ConstraintOriginNotSet
: QgsFieldConstraints::ConstraintOriginLayer;

// update eww constraint
eww->updateConstraint( ft, constraintOrigin );
updateConstraint( ft, eww );

// update eww dependencies constraint
QList<QgsEditorWidgetWrapper *> deps = constraintDependencies( eww );

Q_FOREACH ( QgsEditorWidgetWrapper *depsEww, deps )
depsEww->updateConstraint( ft, constraintOrigin );
updateConstraint( ft, depsEww );

// sync OK button status
synchronizeEnabledState();
@@ -745,6 +743,34 @@ void QgsAttributeForm::updateConstraints( QgsEditorWidgetWrapper *eww )
}
}

void QgsAttributeForm::updateConstraint( const QgsFeature &ft, QgsEditorWidgetWrapper *eww )
{
QgsFieldConstraints::ConstraintOrigin constraintOrigin = mLayer->isEditable() ? QgsFieldConstraints::ConstraintOriginNotSet : QgsFieldConstraints::ConstraintOriginLayer;

if ( eww->layer()->fields().fieldOrigin( eww->fieldIdx() ) == QgsFields::OriginJoin )
{
int srcFieldIdx;
const QgsVectorLayerJoinInfo *info = eww->layer()->joinBuffer()->joinForFieldIndex( eww->fieldIdx(), eww->layer()->fields(), srcFieldIdx );

if ( info && info->joinLayer() && info->isDynamicFormEnabled() )
{
if ( mJoinedFeatures.contains( info ) )
{
eww->updateConstraint( info->joinLayer(), srcFieldIdx, mJoinedFeatures[info], constraintOrigin );
return;
}
else // if we are here, it means there's not joined field for this feature
{
eww->updateConstraint( QgsFeature() );
return;
}
}
}

// default constraint update
eww->updateConstraint( ft, constraintOrigin );
}

bool QgsAttributeForm::currentFormFeature( QgsFeature &feature )
{
bool rc = true;
@@ -765,7 +791,7 @@ bool QgsAttributeForm::currentFormFeature( QgsFeature &feature )
QVariant srcVar = eww->value();
// need to check dstVar.isNull() != srcVar.isNull()
// otherwise if dstVar=NULL and scrVar=0, then dstVar = srcVar
if ( ( dstVar != srcVar || dstVar.isNull() != srcVar.isNull() ) && srcVar.isValid() && !mLayer->editFormConfig().readOnly( eww->fieldIdx() ) )
if ( ( dstVar != srcVar || dstVar.isNull() != srcVar.isNull() ) && srcVar.isValid() )
dst[eww->fieldIdx()] = srcVar;
}
else
@@ -1954,6 +1980,8 @@ void QgsAttributeForm::updateJoinedFields( const QgsEditorWidgetWrapper &eww )

QgsFeature joinFeature = mLayer->joinBuffer()->joinedFeatureOf( info, formFeature );

mJoinedFeatures[info] = joinFeature;

QStringList *subsetFields = info->joinFieldNamesSubset();
if ( subsetFields )
{
@@ -318,6 +318,7 @@ class GUI_EXPORT QgsAttributeForm : public QWidget
//! constraints management
void updateAllConstraints();
void updateConstraints( QgsEditorWidgetWrapper *w );
void updateConstraint( const QgsFeature &ft, QgsEditorWidgetWrapper *eww );
bool currentFormFeature( QgsFeature &feature );
bool currentFormValidConstraints( QStringList &invalidFields, QStringList &descriptions );
QList<QgsEditorWidgetWrapper *> constraintDependencies( QgsEditorWidgetWrapper *w );
@@ -340,6 +341,7 @@ class GUI_EXPORT QgsAttributeForm : public QWidget
QList<QgsAttributeFormInterface *> mInterfaces;
QMap< int, QgsAttributeFormEditorWidget * > mFormEditorWidgets;
QgsExpressionContext mExpressionContext;
QMap<const QgsVectorLayerJoinInfo *, QgsFeature> mJoinedFeatures;

struct ContainerInformation
{
@@ -43,6 +43,7 @@ class TestQgsAttributeForm : public QObject
void testFieldMultiConstraints();
void testOKButtonStatus();
void testDynamicForm();
void testConstraintsOnJoinedFields();
};

void TestQgsAttributeForm::initTestCase()
@@ -440,5 +441,75 @@ void TestQgsAttributeForm::testDynamicForm()
delete layerC;
}

void TestQgsAttributeForm::testConstraintsOnJoinedFields()
{
QString validLabel = QStringLiteral( "col0<font color=\"green\">✔</font>" );
QString warningLabel = QStringLiteral( "col0<font color=\"orange\">✘</font>" );

// make temporary layers
QString defA = QStringLiteral( "Point?field=id_a:integer" );
QgsVectorLayer *layerA = new QgsVectorLayer( defA, QStringLiteral( "layerA" ), QStringLiteral( "memory" ) );

QString defB = QStringLiteral( "Point?field=id_b:integer&field=col0:integer" );
QgsVectorLayer *layerB = new QgsVectorLayer( defB, QStringLiteral( "layerB" ), QStringLiteral( "memory" ) );

// set constraints on joined layer
layerB->setConstraintExpression( 1, QStringLiteral( "col0 < 10" ) );
layerB->setFieldConstraint( 1, QgsFieldConstraints::ConstraintExpression, QgsFieldConstraints::ConstraintStrengthSoft );

// join configuration
QgsVectorLayerJoinInfo infoJoinAB;
infoJoinAB.setTargetFieldName( "id_a" );
infoJoinAB.setJoinLayer( layerB );
infoJoinAB.setJoinFieldName( "id_b" );
infoJoinAB.setDynamicFormEnabled( true );

layerA->addJoin( infoJoinAB );

// add features for main layer
QgsFeature ftA( layerA->fields() );
ftA.setAttribute( QStringLiteral( "id_a" ), 1 );
layerA->startEditing();
layerA->addFeature( ftA );
layerA->commitChanges();

// add features for joined layer
QgsFeature ft0B( layerB->fields() );
ft0B.setAttribute( QStringLiteral( "id_b" ), 30 );
ft0B.setAttribute( QStringLiteral( "col0" ), 9 );
layerB->startEditing();
layerB->addFeature( ft0B );
layerB->commitChanges();

QgsFeature ft1B( layerB->fields() );
ft1B.setAttribute( QStringLiteral( "id_b" ), 31 );
ft1B.setAttribute( QStringLiteral( "col0" ), 11 );
layerB->startEditing();
layerB->addFeature( ft1B );
layerB->commitChanges();

// build a form for this feature
QgsAttributeForm form( layerA );
form.setMode( QgsAttributeForm::AddFeatureMode );
form.setFeature( ftA );

// change layerA join id field
form.changeAttribute( "id_a", QVariant( 30 ) );

// compare
QgsEditorWidgetWrapper *ww = nullptr;
ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[1] );
QLabel *label = form.mBuddyMap.value( ww->widget() );
QCOMPARE( label->text(), "layerB_" + validLabel );

// change layerA join id field
form.changeAttribute( "id_a", QVariant( 31 ) );

// compare
ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[1] );
label = form.mBuddyMap.value( ww->widget() );
QCOMPARE( label->text(), "layerB_" + warningLabel );
}

QGSTEST_MAIN( TestQgsAttributeForm )
#include "testqgsattributeform.moc"

0 comments on commit 1b9c5be

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