Display generated field as read-only is editors

This commit is contained in:
Julien Cabieces 2020-07-29 15:12:16 +02:00
parent ce466a4726
commit 87f8e1514e
8 changed files with 75 additions and 16 deletions

View File

@ -371,6 +371,22 @@ Defaults may be set by the provider and can be overridden
by manual field configuration.
:return: the value
%End
void setReadOnly( bool readOnly );
%Docstring
Make field read-only if ``readOnly`` is set to true. This is the case for
providers which support generated fields for instance.
.. versionadded:: 3.18
%End
bool isReadOnly() const;
%Docstring
Returns ``True`` if this field is a read-only field. This is the case for
providers which support generated fields for instance.
.. versionadded:: 3.18
%End
SIP_PYOBJECT __repr__();

View File

@ -561,6 +561,17 @@ QgsEditorWidgetSetup QgsField::editorWidgetSetup() const
return d->editorWidgetSetup;
}
void QgsField::setReadOnly( bool readOnly )
{
d->isReadOnly = readOnly;
}
bool QgsField::isReadOnly() const
{
return d->isReadOnly;
}
/***************************************************************************
* This class is considered CRITICAL and any change MUST be accompanied with
* full unit tests in testqgsfield.cpp.

View File

@ -433,6 +433,20 @@ class CORE_EXPORT QgsField
*/
QgsEditorWidgetSetup editorWidgetSetup() const;
/**
* Make field read-only if \a readOnly is set to true. This is the case for
* providers which support generated fields for instance.
* \since QGIS 3.18
*/
void setReadOnly( bool readOnly );
/**
* Returns TRUE if this field is a read-only field. This is the case for
* providers which support generated fields for instance.
* \since QGIS 3.18
*/
bool isReadOnly() const;
#ifdef SIP_RUN
SIP_PYOBJECT __repr__();
% MethodCode

View File

@ -78,6 +78,7 @@ class QgsFieldPrivate : public QSharedData
, flags( other.flags )
, defaultValueDefinition( other.defaultValueDefinition )
, constraints( other.constraints )
, isReadOnly( other.isReadOnly )
{
}
@ -88,7 +89,8 @@ class QgsFieldPrivate : public QSharedData
return ( ( name == other.name ) && ( type == other.type ) && ( subType == other.subType )
&& ( length == other.length ) && ( precision == other.precision )
&& ( alias == other.alias ) && ( defaultValueDefinition == other.defaultValueDefinition )
&& ( constraints == other.constraints ) && ( flags == other.flags ) );
&& ( constraints == other.constraints ) && ( flags == other.flags )
&& ( isReadOnly == other.isReadOnly ) );
}
//! Name
@ -126,6 +128,9 @@ class QgsFieldPrivate : public QSharedData
QgsEditorWidgetSetup editorWidgetSetup;
//! Read-only
bool isReadOnly = false;
private:
QgsFieldPrivate &operator=( const QgsFieldPrivate & ) = delete;
};

View File

@ -863,7 +863,9 @@ bool _fieldIsEditable( const QgsVectorLayer *layer, int fieldIndex, const QgsFea
{
return layer->isEditable() &&
!layer->editFormConfig().readOnly( fieldIndex ) &&
( ( layer->dataProvider() && layer->dataProvider()->capabilities() & QgsVectorDataProvider::ChangeAttributeValues ) || FID_IS_NEW( feature.id() ) );
layer->dataProvider() &&
( ( layer->dataProvider()->capabilities() & QgsVectorDataProvider::ChangeAttributeValues ) || FID_IS_NEW( feature.id() ) ) &&
!layer->fields().at( fieldIndex ).isReadOnly();
}
bool QgsVectorLayerUtils::fieldIsEditable( const QgsVectorLayer *layer, int fieldIndex, const QgsFeature &feature )

View File

@ -773,6 +773,7 @@ bool QgsOracleProvider::loadFields()
QVariant::Type type = field.type();
QgsField newField( field.name(), type, types.value( field.name() ), field.length(), field.precision(), comments.value( field.name() ) );
newField.setReadOnly( alwaysGenerated.value( field.name(), false ) );
QgsFieldConstraints constraints;
if ( mPrimaryKeyAttrs.contains( i ) )
@ -1221,7 +1222,6 @@ QString QgsOracleProvider::defaultValueClause( int fieldId ) const
return QString();
}
bool QgsOracleProvider::skipConstraintCheck( int fieldIndex, QgsFieldConstraints::Constraint constraint, const QVariant &value ) const
{
Q_UNUSED( constraint );

View File

@ -898,7 +898,7 @@ bool QgsPostgresProvider::loadFields()
notNullMap[attrelid][attnum] = attNotNull;
uniqueMap[attrelid][attnum] = uniqueConstraint;
identityMap[attrelid][attnum] = attIdentity.isEmpty() ? " " : attIdentity;
generatedMap[attrelid][attnum] = attGenerated.isEmpty() ? "" : defVal;
generatedMap[attrelid][attnum] = attGenerated.isEmpty() ? QString() : defVal;
// Also include atttype oid from pg_attribute, because PQnfields only returns basic type for for domains
attroids.insert( attType );
@ -1236,9 +1236,13 @@ bool QgsPostgresProvider::loadFields()
}
mDefaultValues.insert( mAttributeFields.size(), defValMap[tableoid][attnum] );
mGeneratedValues.insert( mAttributeFields.size(), generatedMap[tableoid][attnum] );
const QString generatedValue = generatedMap[tableoid][attnum];
if ( !generatedValue.isNull() )
mGeneratedValues.insert( mAttributeFields.size(), generatedValue );
QgsField newField = QgsField( fieldName, fieldType, fieldTypeName, fieldSize, fieldPrec, fieldComment, fieldSubType );
newField.setReadOnly( !generatedValue.isNull() );
QgsFieldConstraints constraints;
if ( notNullMap[tableoid][attnum] || ( mPrimaryKeyAttrs.size() == 1 && mPrimaryKeyAttrs[0] == i ) || identityMap[tableoid][attnum] != ' ' )
@ -2115,7 +2119,6 @@ bool QgsPostgresProvider::isValid() const
QString QgsPostgresProvider::defaultValueClause( int fieldId ) const
{
QString defVal = mDefaultValues.value( fieldId, QString() );
QString genVal = mGeneratedValues.value( fieldId, QString() );
// with generated columns (PostgreSQL 12+), the provider will ALWAYS evaluate the default values.
// The only acceptable value for such columns on INSERT or UPDATE clauses is the keyword "DEFAULT".
@ -2124,7 +2127,7 @@ QString QgsPostgresProvider::defaultValueClause( int fieldId ) const
// On inserting a new feature or updating a generated field, this is
// omitted from the generated queries.
// See https://www.postgresql.org/docs/12/ddl-generated-columns.html
if ( !genVal.isEmpty() )
if ( mGeneratedValues.contains( fieldId ) )
{
return defVal;
}
@ -2994,7 +2997,7 @@ bool QgsPostgresProvider::changeAttributeValues( const QgsChangedAttributesMap &
pkChanged = pkChanged || mPrimaryKeyAttrs.contains( siter.key() );
if ( mGeneratedValues.contains( siter.key() ) && !mGeneratedValues.value( siter.key(), QString() ).isEmpty() )
if ( mGeneratedValues.contains( siter.key() ) )
{
QgsLogger::warning( tr( "Changing the value of GENERATED field %1 is not allowed." ).arg( fld.name() ) );
continue;
@ -3358,7 +3361,7 @@ bool QgsPostgresProvider::changeFeatures( const QgsChangedAttributesMap &attr_ma
pkChanged = pkChanged || mPrimaryKeyAttrs.contains( siter.key() );
if ( mGeneratedValues.contains( siter.key() ) && !mGeneratedValues.value( siter.key(), QString() ).isEmpty() )
if ( mGeneratedValues.contains( siter.key() ) )
{
QgsLogger::warning( tr( "Changing the value of GENERATED field %1 is not allowed." ).arg( fld.name() ) );
continue;

View File

@ -30,6 +30,7 @@ from qgis.core import (
QgsTestUtils,
QgsFeatureSource,
QgsFieldConstraints,
QgsVectorLayerUtils,
NULL
)
from qgis.PyQt.QtCore import QDate, QTime, QDateTime, QVariant
@ -1171,38 +1172,45 @@ class ProviderTestCase(FeatureSourceTestCase):
vl.startEditing()
feature = next(vl.getFeatures())
# to be fixed
# self.assertEqual(QgsVectorLayerUtils.fieldIsEditable(vl, 1, feature), editable)
self.assertFalse(QgsVectorLayerUtils.fieldIsEditable(vl, 1, feature))
self.assertTrue(QgsVectorLayerUtils.fieldIsEditable(vl, 0, feature))
# same test on a new inserted feature
feature = QgsFeature(vl.fields())
feature.setAttribute(0, 2)
vl.addFeature(feature)
self.assertTrue(feature.id() < 0)
# to be fixed
# self.assertEqual(QgsVectorLayerUtils.fieldIsEditable(vl, 1, feature), editable)
self.assertFalse(QgsVectorLayerUtils.fieldIsEditable(vl, 1, feature))
self.assertTrue(QgsVectorLayerUtils.fieldIsEditable(vl, 0, feature))
vl.commitChanges()
feature = vl.getFeature(2)
self.assertTrue(feature.isValid())
self.assertEqual(feature.attribute(1), "test:2")
# to be fixed
# self.assertEqual(QgsVectorLayerUtils.fieldIsEditable(vl, 1, feature), editable)
self.assertFalse(QgsVectorLayerUtils.fieldIsEditable(vl, 1, feature))
self.assertFalse(QgsVectorLayerUtils.fieldIsEditable(vl, 0, feature))
# test update id and commit
vl.startEditing()
self.assertFalse(QgsVectorLayerUtils.fieldIsEditable(vl, 1, feature))
self.assertTrue(QgsVectorLayerUtils.fieldIsEditable(vl, 0, feature))
self.assertTrue(vl.changeAttributeValue(2, 0, 10))
self.assertTrue(vl.commitChanges())
feature = vl.getFeature(10)
self.assertTrue(feature.isValid())
self.assertEqual(feature.attribute(0), 10)
self.assertEqual(feature.attribute(1), "test:10")
self.assertFalse(QgsVectorLayerUtils.fieldIsEditable(vl, 1, feature))
self.assertFalse(QgsVectorLayerUtils.fieldIsEditable(vl, 0, feature))
# test update the_field and commit (the value is not changed because the field is generated)
vl.startEditing()
self.assertFalse(QgsVectorLayerUtils.fieldIsEditable(vl, 1, feature))
self.assertTrue(QgsVectorLayerUtils.fieldIsEditable(vl, 0, feature))
self.assertTrue(vl.changeAttributeValue(10, 1, "new value"))
self.assertTrue(vl.commitChanges())
feature = vl.getFeature(10)
self.assertTrue(feature.isValid())
self.assertEqual(feature.attribute(1), "test:10")
self.assertFalse(QgsVectorLayerUtils.fieldIsEditable(vl, 1, feature))
self.assertFalse(QgsVectorLayerUtils.fieldIsEditable(vl, 0, feature))