diff --git a/python/PyQt6/core/auto_generated/qgsrelation.sip.in b/python/PyQt6/core/auto_generated/qgsrelation.sip.in index 7e0bf177a83..cd1581cce94 100644 --- a/python/PyQt6/core/auto_generated/qgsrelation.sip.in +++ b/python/PyQt6/core/auto_generated/qgsrelation.sip.in @@ -262,6 +262,13 @@ Returns a list of attributes used to form the referencing fields (foreign key) on the referencing (child) layer. :return: A list of attributes +%End + + bool referencingFieldsAllowNull() const; +%Docstring +Returns ``True`` if none of the referencing fields has a NOT NULL constraint. + +.. versionadded:: 3.28 %End bool isValid() const; diff --git a/python/core/auto_generated/qgsrelation.sip.in b/python/core/auto_generated/qgsrelation.sip.in index 7e0bf177a83..cd1581cce94 100644 --- a/python/core/auto_generated/qgsrelation.sip.in +++ b/python/core/auto_generated/qgsrelation.sip.in @@ -262,6 +262,13 @@ Returns a list of attributes used to form the referencing fields (foreign key) on the referencing (child) layer. :return: A list of attributes +%End + + bool referencingFieldsAllowNull() const; +%Docstring +Returns ``True`` if none of the referencing fields has a NOT NULL constraint. + +.. versionadded:: 3.28 %End bool isValid() const; diff --git a/src/core/qgsrelation.cpp b/src/core/qgsrelation.cpp index bc566e7aecc..0e4742b223b 100644 --- a/src/core/qgsrelation.cpp +++ b/src/core/qgsrelation.cpp @@ -356,6 +356,26 @@ QgsAttributeList QgsRelation::referencingFields() const } +bool QgsRelation::referencingFieldsAllowNull() const +{ + if ( ! referencingLayer() ) + { + return false; + } + + const auto fields = referencingFields(); + + return std::find_if( fields.constBegin(), fields.constEnd(), [&]( const auto & fieldIdx ) + { + if ( !referencingLayer()->fields().exists( fieldIdx ) ) + { + return false; + } + const QgsField field = referencingLayer()->fields().field( fieldIdx ); + return field.constraints().constraints().testFlag( QgsFieldConstraints::Constraint::ConstraintNotNull ); + } ) == fields.constEnd(); +} + bool QgsRelation::isValid() const { return d->mValid && !d->mReferencingLayer.isNull() && !d->mReferencedLayer.isNull() && d->mReferencingLayer.data()->isValid() && d->mReferencedLayer.data()->isValid(); diff --git a/src/core/qgsrelation.h b/src/core/qgsrelation.h index 0bb29051ad1..f7d9ed03a3c 100644 --- a/src/core/qgsrelation.h +++ b/src/core/qgsrelation.h @@ -332,6 +332,12 @@ class CORE_EXPORT QgsRelation */ QgsAttributeList referencingFields() const; + /** + * Returns TRUE if none of the referencing fields has a NOT NULL constraint. + * \since QGIS 3.28 + */ + bool referencingFieldsAllowNull() const; + /** * Returns the validity of this relation. Don't use the information if it's not valid. * A relation is considered valid if both referenced and referencig layers are valid. diff --git a/src/gui/editorwidgets/qgsrelationreferenceconfigdlg.cpp b/src/gui/editorwidgets/qgsrelationreferenceconfigdlg.cpp index 771d3c1f9a6..75e3ac856bf 100644 --- a/src/gui/editorwidgets/qgsrelationreferenceconfigdlg.cpp +++ b/src/gui/editorwidgets/qgsrelationreferenceconfigdlg.cpp @@ -151,20 +151,11 @@ void QgsRelationReferenceConfigDlg::relationChanged( int idx ) mCbxMapIdentification->setEnabled( mReferencedLayer->isSpatial() ); } - // Provide a default for AllowNull if it was not set by the config + // If AllowNULL is not set in the config, provide a default value based on the + // constraints of the referencing fields if ( ! mAllowNullWasSetByConfig ) { - const QgsAttributeList referencingFields = rel.referencingFields(); - const bool allowNull { std::find_if( referencingFields.constBegin(), referencingFields.constEnd(), [&]( const auto & fieldIdx ) - { - if ( !rel.referencingLayer()->fields().exists( fieldIdx ) ) - { - return false; - } - const QgsField field = rel.referencingLayer()->fields().field( fieldIdx ); - return field.constraints().constraints().testFlag( QgsFieldConstraints::Constraint::ConstraintNotNull ); - } ) == referencingFields.constEnd()}; - mCbxAllowNull->setChecked( allowNull ); + mCbxAllowNull->setChecked( rel.referencingFieldsAllowNull() ); } loadFields(); diff --git a/src/gui/editorwidgets/qgsrelationreferencewidgetwrapper.cpp b/src/gui/editorwidgets/qgsrelationreferencewidgetwrapper.cpp index a1e748423c7..2d58b5e21e7 100644 --- a/src/gui/editorwidgets/qgsrelationreferencewidgetwrapper.cpp +++ b/src/gui/editorwidgets/qgsrelationreferencewidgetwrapper.cpp @@ -105,7 +105,16 @@ void QgsRelationReferenceWidgetWrapper::initWidget( QWidget *editor ) } while ( ctx ); - mWidget->setRelation( relation, config( QStringLiteral( "AllowNULL" ) ).toBool() ); + // If AllowNULL is not set in the config, provide a default value based on the + // constraints of the referencing fields + if ( !config( QStringLiteral( "AllowNULL" ) ).isValid() ) + { + mWidget->setRelation( relation, relation.referencingFieldsAllowNull() ); + } + else + { + mWidget->setRelation( relation, config( QStringLiteral( "AllowNULL" ) ).toBool() ); + } connect( mWidget, &QgsRelationReferenceWidget::foreignKeysChanged, this, &QgsRelationReferenceWidgetWrapper::foreignKeysChanged ); } diff --git a/tests/src/python/test_qgsrelation.py b/tests/src/python/test_qgsrelation.py index 927e0001531..bb5bbc45b97 100644 --- a/tests/src/python/test_qgsrelation.py +++ b/tests/src/python/test_qgsrelation.py @@ -19,6 +19,7 @@ from qgis.core import ( QgsProject, QgsRelation, QgsVectorLayer, + QgsFieldConstraints, ) import unittest from qgis.testing import start_app, QgisTestCase @@ -250,6 +251,24 @@ class TestQgsRelation(QgisTestCase): # Check that it does not crash rel.generateId() + def test_referencingFieldsAllowNull(self): + rel = QgsRelation() + + rel.setId('rel1') + rel.setName('Relation Number One') + rel.setReferencingLayer(self.referencingLayer.id()) + rel.setReferencedLayer(self.referencedLayer.id()) + rel.addFieldPair('foreignkey', 'y') + + self.assertTrue(rel.referencingFieldsAllowNull()) + + referencingLayer = rel.referencingLayer() + + # Set Not Null constraint on the field + referencingLayer.setFieldConstraint(referencingLayer.fields().indexFromName('foreignkey'), QgsFieldConstraints.ConstraintNotNull) + + self.assertFalse(rel.referencingFieldsAllowNull()) + if __name__ == '__main__': unittest.main()