mirror of
				https://github.com/qgis/QGIS.git
				synced 2025-11-04 00:04:25 -05:00 
			
		
		
		
	Merge pull request #51309 from nirvn/validity_table
[attributes table] Add constraint-based conditional styling and failing constraints feature filter
This commit is contained in:
		
						commit
						8e12541dfd
					
				@ -369,6 +369,7 @@
 | 
			
		||||
        <file>themes/default/mActionOpenTable.svg</file>
 | 
			
		||||
        <file>themes/default/mActionOpenTableEdited.svg</file>
 | 
			
		||||
        <file>themes/default/mActionOpenTableSelected.svg</file>
 | 
			
		||||
        <file>themes/default/mActionOpenTableInvalid.svg</file>
 | 
			
		||||
        <file>themes/default/mActionOpenTableVisible.svg</file>
 | 
			
		||||
        <file>themes/default/mActionAddTable.svg</file>
 | 
			
		||||
        <file>themes/default/mActionOffsetPointSymbols.svg</file>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								images/themes/default/mActionOpenTableInvalid.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								images/themes/default/mActionOpenTableInvalid.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<svg width="24" height="24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><metadata><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/></cc:Work></rdf:RDF></metadata><g stroke="#6e97c4"><path d="m2.5 7.5h19v14h-19z" fill="#e6e6e6"/><path d="m2.5 2.5h19v5h-19z" fill="#bad9ec"/><path d="m5 5h4" fill="none" stroke-width="2"/><path d="m11 5h8" fill="none" stroke-width="2"/></g><g transform="translate(0,-8)" fill="none" stroke="#6e97c4"><path d="m5 18.7h4"/><path d="m11 18.7h8" stroke="#6e97c4"/><path d="m5 21.7h4"/><path d="m11 21.7h8"/><path d="m5 24.7h4" stroke="#6e97c4"/><path d="m11 24.7h8"/></g><text transform="scale(1 .99998)" x="7.3536935" y="22.897999" fill="#fe9800" font-family="'Wide Latin'" font-size="21.505px" stroke-width=".53763" style="line-height:1.25" xml:space="preserve"><tspan x="7.3536935" y="22.897999" stroke-width=".53763">✘</tspan></text></svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 1.1 KiB  | 
@ -8,8 +8,8 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
typedef QList<QgsConditionalStyle> QgsConditionalStyles;
 | 
			
		||||
 | 
			
		||||
typedef QList<QgsConditionalStyle> QgsConditionalStyles;
 | 
			
		||||
 | 
			
		||||
class QgsConditionalLayerStyles : QObject
 | 
			
		||||
{
 | 
			
		||||
@ -43,12 +43,24 @@ Each row will check be checked against each rule.
 | 
			
		||||
.. seealso:: :py:func:`rowStyles`
 | 
			
		||||
 | 
			
		||||
.. versionadded:: 2.12
 | 
			
		||||
%End
 | 
			
		||||
 | 
			
		||||
    QgsConditionalStyle constraintFailureStyles( QgsFieldConstraints::ConstraintStrength strength );
 | 
			
		||||
%Docstring
 | 
			
		||||
Returns a style associated to a constraint failure.
 | 
			
		||||
 | 
			
		||||
:param strength: the type of constraint
 | 
			
		||||
 | 
			
		||||
.. versionadded:: 3.30
 | 
			
		||||
%End
 | 
			
		||||
 | 
			
		||||
    void setFieldStyles( const QString &fieldName, const QList<QgsConditionalStyle> &styles );
 | 
			
		||||
%Docstring
 | 
			
		||||
Set the conditional ``styles`` for a field, with the specified ``fieldName``.
 | 
			
		||||
 | 
			
		||||
The field value is inserted into a 'value' variable to conduct
 | 
			
		||||
expression checks.
 | 
			
		||||
 | 
			
		||||
.. seealso:: :py:func:`fieldStyles`
 | 
			
		||||
%End
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -168,6 +168,16 @@ be unique within regard to ``existingValues``.
 | 
			
		||||
The optional seed value can be used as a basis for generated values.
 | 
			
		||||
 | 
			
		||||
.. versionadded:: 3.6
 | 
			
		||||
%End
 | 
			
		||||
 | 
			
		||||
    static bool attributeHasConstraints( const QgsVectorLayer *layer, int attributeIndex );
 | 
			
		||||
%Docstring
 | 
			
		||||
Returns ``True`` if a feature attribute has active constraints.
 | 
			
		||||
 | 
			
		||||
:param layer: the vector layer from which field constraints will be checked for
 | 
			
		||||
:param attributeIndex: the attribute index
 | 
			
		||||
 | 
			
		||||
.. versionadded:: 3.30
 | 
			
		||||
%End
 | 
			
		||||
 | 
			
		||||
    static bool validateAttribute( const QgsVectorLayer *layer, const QgsFeature &feature, int attributeIndex, QStringList &errors /Out/,
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,8 @@ class QgsAttributeTableFilterModel: QSortFilterProxyModel, QgsFeatureModel
 | 
			
		||||
      ShowSelected,
 | 
			
		||||
      ShowVisible,
 | 
			
		||||
      ShowFilteredList,
 | 
			
		||||
      ShowEdited
 | 
			
		||||
      ShowEdited,
 | 
			
		||||
      ShowInvalid,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    enum ColumnType
 | 
			
		||||
 | 
			
		||||
@ -247,6 +247,22 @@ Any extra columns need to be implemented by proxy models in front of this model.
 | 
			
		||||
%Docstring
 | 
			
		||||
Empty extra columns to announce from this model.
 | 
			
		||||
Any extra columns need to be implemented by proxy models in front of this model.
 | 
			
		||||
%End
 | 
			
		||||
 | 
			
		||||
    bool showValidityState() const;
 | 
			
		||||
%Docstring
 | 
			
		||||
Returns whether the attribute table will add a visual feedback to cells when an attribute
 | 
			
		||||
constraint is not met.
 | 
			
		||||
 | 
			
		||||
.. versionadded:: 3.30
 | 
			
		||||
%End
 | 
			
		||||
 | 
			
		||||
    void setShowValidityState( bool show );
 | 
			
		||||
%Docstring
 | 
			
		||||
Sets whether the attribute table will add a visual feedback to cells when an attribute constraint
 | 
			
		||||
is not met.
 | 
			
		||||
 | 
			
		||||
.. versionadded:: 3.30
 | 
			
		||||
%End
 | 
			
		||||
 | 
			
		||||
  public slots:
 | 
			
		||||
 | 
			
		||||
@ -23,7 +23,35 @@
 | 
			
		||||
 | 
			
		||||
QgsConditionalLayerStyles::QgsConditionalLayerStyles( QObject *parent )
 | 
			
		||||
  : QObject( parent )
 | 
			
		||||
{}
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QgsConditionalStyle QgsConditionalLayerStyles::constraintFailureStyles( QgsFieldConstraints::ConstraintStrength strength )
 | 
			
		||||
{
 | 
			
		||||
  switch ( strength )
 | 
			
		||||
  {
 | 
			
		||||
    case QgsFieldConstraints::ConstraintStrengthHard:
 | 
			
		||||
    {
 | 
			
		||||
      QgsConditionalStyle hardConstraintFailureStyle;
 | 
			
		||||
      hardConstraintFailureStyle.setBackgroundColor( QColor( 255, 152, 0 ) );
 | 
			
		||||
      hardConstraintFailureStyle.setTextColor( QColor( 0, 0, 0 ) );
 | 
			
		||||
      return hardConstraintFailureStyle;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case QgsFieldConstraints::ConstraintStrengthSoft:
 | 
			
		||||
    {
 | 
			
		||||
      QgsConditionalStyle softConstraintFailureStyle;
 | 
			
		||||
      softConstraintFailureStyle.setBackgroundColor( QColor( 255, 191, 12 ) );
 | 
			
		||||
      softConstraintFailureStyle.setTextColor( QColor( 0, 0, 0 ) );
 | 
			
		||||
      return softConstraintFailureStyle;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case QgsFieldConstraints::ConstraintStrengthNotSet:
 | 
			
		||||
      return QgsConditionalStyle();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return QgsConditionalStyle();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QgsConditionalStyles QgsConditionalLayerStyles::rowStyles() const
 | 
			
		||||
{
 | 
			
		||||
@ -56,13 +84,13 @@ QList<QgsConditionalStyle> QgsConditionalLayerStyles::fieldStyles( const QString
 | 
			
		||||
bool QgsConditionalLayerStyles::writeXml( QDomNode &node, QDomDocument &doc, const QgsReadWriteContext &context ) const
 | 
			
		||||
{
 | 
			
		||||
  QDomElement stylesel = doc.createElement( QStringLiteral( "conditionalstyles" ) );
 | 
			
		||||
 | 
			
		||||
  QDomElement rowel = doc.createElement( QStringLiteral( "rowstyles" ) );
 | 
			
		||||
  const auto constMRowStyles = mRowStyles;
 | 
			
		||||
  for ( const QgsConditionalStyle &style : constMRowStyles )
 | 
			
		||||
  {
 | 
			
		||||
    style.writeXml( rowel, doc, context );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  stylesel.appendChild( rowel );
 | 
			
		||||
 | 
			
		||||
  QDomElement fieldsel = doc.createElement( QStringLiteral( "fieldstyles" ) );
 | 
			
		||||
@ -79,7 +107,6 @@ bool QgsConditionalLayerStyles::writeXml( QDomNode &node, QDomDocument &doc, con
 | 
			
		||||
    }
 | 
			
		||||
    fieldsel.appendChild( fieldel );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  stylesel.appendChild( fieldsel );
 | 
			
		||||
 | 
			
		||||
  node.appendChild( stylesel );
 | 
			
		||||
@ -100,9 +127,11 @@ bool QgsConditionalLayerStyles::rulesNeedGeometry() const
 | 
			
		||||
 | 
			
		||||
bool QgsConditionalLayerStyles::readXml( const QDomNode &node, const QgsReadWriteContext &context )
 | 
			
		||||
{
 | 
			
		||||
  const QDomElement condel = node.firstChildElement( QStringLiteral( "conditionalstyles" ) );
 | 
			
		||||
  mRowStyles.clear();
 | 
			
		||||
  mFieldStyles.clear();
 | 
			
		||||
 | 
			
		||||
  const QDomElement condel = node.firstChildElement( QStringLiteral( "conditionalstyles" ) );
 | 
			
		||||
 | 
			
		||||
  const QDomElement rowstylesel = condel.firstChildElement( QStringLiteral( "rowstyles" ) );
 | 
			
		||||
  QDomNodeList nodelist = rowstylesel.toElement().elementsByTagName( QStringLiteral( "style" ) );
 | 
			
		||||
  for ( int i = 0; i < nodelist.count(); i++ )
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,8 @@
 | 
			
		||||
#define QGSCONDITIONALSTYLE_H
 | 
			
		||||
 | 
			
		||||
#include "qgis_core.h"
 | 
			
		||||
#include "qgsfield.h"
 | 
			
		||||
 | 
			
		||||
#include <QObject>
 | 
			
		||||
#include <QFont>
 | 
			
		||||
#include <QColor>
 | 
			
		||||
@ -32,7 +34,6 @@ class QgsSymbol;
 | 
			
		||||
 | 
			
		||||
typedef QList<QgsConditionalStyle> QgsConditionalStyles;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * \ingroup core
 | 
			
		||||
 * \brief The QgsConditionalLayerStyles class holds conditional style information
 | 
			
		||||
@ -65,9 +66,19 @@ class CORE_EXPORT QgsConditionalLayerStyles : public QObject
 | 
			
		||||
     */
 | 
			
		||||
    void setRowStyles( const QgsConditionalStyles &styles );
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a style associated to a constraint failure.
 | 
			
		||||
     * \param strength the type of constraint
 | 
			
		||||
     * \since QGIS 3.30
 | 
			
		||||
     */
 | 
			
		||||
    QgsConditionalStyle constraintFailureStyles( QgsFieldConstraints::ConstraintStrength strength );
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the conditional \a styles for a field, with the specified \a fieldName.
 | 
			
		||||
     *
 | 
			
		||||
     * The field value is inserted into a 'value' variable to conduct
 | 
			
		||||
     * expression checks.
 | 
			
		||||
     *
 | 
			
		||||
     * \see fieldStyles()
 | 
			
		||||
     */
 | 
			
		||||
    void setFieldStyles( const QString &fieldName, const QList<QgsConditionalStyle> &styles );
 | 
			
		||||
 | 
			
		||||
@ -367,6 +367,20 @@ QVariant QgsVectorLayerUtils::createUniqueValueFromCache( const QgsVectorLayer *
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool QgsVectorLayerUtils::attributeHasConstraints( const QgsVectorLayer *layer, int attributeIndex )
 | 
			
		||||
{
 | 
			
		||||
  if ( !layer )
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  if ( attributeIndex < 0 || attributeIndex >= layer->fields().count() )
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  const QgsFieldConstraints constraints = layer->fields().at( attributeIndex ).constraints();
 | 
			
		||||
  return ( constraints.constraints() & QgsFieldConstraints::ConstraintNotNull ||
 | 
			
		||||
           constraints.constraints() & QgsFieldConstraints::ConstraintUnique ||
 | 
			
		||||
           constraints.constraints() & QgsFieldConstraints::ConstraintExpression );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool QgsVectorLayerUtils::validateAttribute( const QgsVectorLayer *layer, const QgsFeature &feature, int attributeIndex, QStringList &errors,
 | 
			
		||||
    QgsFieldConstraints::ConstraintStrength strength, QgsFieldConstraints::ConstraintOrigin origin )
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
@ -183,6 +183,14 @@ class CORE_EXPORT QgsVectorLayerUtils
 | 
			
		||||
     */
 | 
			
		||||
    static QVariant createUniqueValueFromCache( const QgsVectorLayer *layer, int fieldIndex, const QSet<QVariant> &existingValues, const QVariant &seed = QVariant() );
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns TRUE if a feature attribute has active constraints.
 | 
			
		||||
     * \param layer the vector layer from which field constraints will be checked for
 | 
			
		||||
     * \param attributeIndex the attribute index
 | 
			
		||||
     * \since QGIS 3.30
 | 
			
		||||
     */
 | 
			
		||||
    static bool attributeHasConstraints( const QgsVectorLayer *layer, int attributeIndex );
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Tests a feature attribute value to check whether it passes all constraints which are present on the corresponding field.
 | 
			
		||||
     * Returns TRUE if the attribute value is valid for the field. Any constraint failures will be reported in the errors argument.
 | 
			
		||||
 | 
			
		||||
@ -313,7 +313,11 @@ bool QgsAttributeTableFilterModel::selectedOnTop()
 | 
			
		||||
void QgsAttributeTableFilterModel::setFilteredFeatures( const QgsFeatureIds &ids )
 | 
			
		||||
{
 | 
			
		||||
  mFilteredFeatures = ids;
 | 
			
		||||
  setFilterMode( ShowFilteredList );
 | 
			
		||||
  if ( mFilterMode != ShowFilteredList &&
 | 
			
		||||
       mFilterMode != ShowInvalid )
 | 
			
		||||
  {
 | 
			
		||||
    setFilterMode( ShowFilteredList );
 | 
			
		||||
  }
 | 
			
		||||
  invalidateFilter();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -337,6 +341,11 @@ void QgsAttributeTableFilterModel::setFilterMode( FilterMode filterMode )
 | 
			
		||||
    connectFilterModeConnections( filterMode );
 | 
			
		||||
    mFilterMode = filterMode;
 | 
			
		||||
    invalidate();
 | 
			
		||||
 | 
			
		||||
    if ( mFilterMode == QgsAttributeTableFilterModel::ShowInvalid )
 | 
			
		||||
    {
 | 
			
		||||
      filterFeatures();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -356,6 +365,7 @@ void QgsAttributeTableFilterModel::disconnectFilterModeConnections()
 | 
			
		||||
    case ShowSelected:
 | 
			
		||||
      break;
 | 
			
		||||
    case ShowFilteredList:
 | 
			
		||||
    case ShowInvalid:
 | 
			
		||||
      disconnect( layer(), &QgsVectorLayer::featureAdded, this, &QgsAttributeTableFilterModel::startTimedFilterFeatures );
 | 
			
		||||
      disconnect( layer(), &QgsVectorLayer::attributeValueChanged, this, &QgsAttributeTableFilterModel::onAttributeValueChanged );
 | 
			
		||||
      disconnect( layer(), &QgsVectorLayer::geometryChanged, this, &QgsAttributeTableFilterModel::onGeometryChanged );
 | 
			
		||||
@ -380,6 +390,7 @@ void QgsAttributeTableFilterModel::connectFilterModeConnections( QgsAttributeTab
 | 
			
		||||
    case ShowSelected:
 | 
			
		||||
      break;
 | 
			
		||||
    case ShowFilteredList:
 | 
			
		||||
    case ShowInvalid:
 | 
			
		||||
      connect( layer(), &QgsVectorLayer::featureAdded, this, &QgsAttributeTableFilterModel::startTimedFilterFeatures );
 | 
			
		||||
      connect( layer(), &QgsVectorLayer::attributeValueChanged, this, &QgsAttributeTableFilterModel::onAttributeValueChanged );
 | 
			
		||||
      connect( layer(), &QgsVectorLayer::geometryChanged, this, &QgsAttributeTableFilterModel::onGeometryChanged );
 | 
			
		||||
@ -396,6 +407,7 @@ bool QgsAttributeTableFilterModel::filterAcceptsRow( int sourceRow, const QModel
 | 
			
		||||
      return true;
 | 
			
		||||
 | 
			
		||||
    case ShowFilteredList:
 | 
			
		||||
    case ShowInvalid:
 | 
			
		||||
      return mFilteredFeatures.contains( masterModel()->rowToId( sourceRow ) );
 | 
			
		||||
 | 
			
		||||
    case ShowSelected:
 | 
			
		||||
@ -449,7 +461,7 @@ void QgsAttributeTableFilterModel::onAttributeValueChanged( QgsFeatureId fid, in
 | 
			
		||||
  Q_UNUSED( fid );
 | 
			
		||||
  Q_UNUSED( value );
 | 
			
		||||
 | 
			
		||||
  if ( mFilterExpression.referencedAttributeIndexes( layer()->fields() ).contains( idx ) )
 | 
			
		||||
  if ( mFilterMode == QgsAttributeTableFilterModel::ShowInvalid || mFilterExpression.referencedAttributeIndexes( layer()->fields() ).contains( idx ) )
 | 
			
		||||
  {
 | 
			
		||||
    startTimedFilterFeatures();
 | 
			
		||||
  }
 | 
			
		||||
@ -457,7 +469,7 @@ void QgsAttributeTableFilterModel::onAttributeValueChanged( QgsFeatureId fid, in
 | 
			
		||||
 | 
			
		||||
void QgsAttributeTableFilterModel::onGeometryChanged()
 | 
			
		||||
{
 | 
			
		||||
  if ( mFilterExpression.needsGeometry() )
 | 
			
		||||
  if ( mFilterMode == QgsAttributeTableFilterModel::ShowInvalid || mFilterExpression.needsGeometry() )
 | 
			
		||||
  {
 | 
			
		||||
    startTimedFilterFeatures();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -48,7 +48,8 @@ class GUI_EXPORT QgsAttributeTableFilterModel: public QSortFilterProxyModel, pub
 | 
			
		||||
      ShowSelected,     //!< Show only selected features
 | 
			
		||||
      ShowVisible,      //!< Show only visible features (depends on the map canvas)
 | 
			
		||||
      ShowFilteredList, //!< Show only features whose ids are on the filter list. {\see setFilteredFeatures}
 | 
			
		||||
      ShowEdited        //!< Show only features which have unsaved changes
 | 
			
		||||
      ShowEdited,       //!< Show only features which have unsaved changes
 | 
			
		||||
      ShowInvalid,      //!< Show only features not respecting constraints (since QGIS 3.30)
 | 
			
		||||
    };
 | 
			
		||||
    Q_ENUM( FilterMode )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -511,6 +511,7 @@ void QgsAttributeTableModel::fieldConditionalStyleChanged( const QString &fieldN
 | 
			
		||||
  if ( fieldName.isNull() )
 | 
			
		||||
  {
 | 
			
		||||
    mRowStylesMap.clear();
 | 
			
		||||
    mConstraintStylesMap.clear();
 | 
			
		||||
    emit dataChanged( index( 0, 0 ), index( rowCount() - 1, columnCount() - 1 ) );
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
@ -749,11 +750,38 @@ QVariant QgsAttributeTableModel::data( const QModelIndex &index, int role ) cons
 | 
			
		||||
        styles = QgsConditionalStyle::matchingConditionalStyles( mLayer->conditionalStyles()->rowStyles(), QVariant(),  mExpressionContext );
 | 
			
		||||
        mRowStylesMap.insert( mFeat.id(), styles );
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const QgsConditionalStyle rowstyle = QgsConditionalStyle::compressStyles( styles );
 | 
			
		||||
 | 
			
		||||
      QgsConditionalStyle constraintstyle;
 | 
			
		||||
      if ( mShowValidityState && QgsVectorLayerUtils::attributeHasConstraints( mLayer, fieldId ) )
 | 
			
		||||
      {
 | 
			
		||||
        if ( mConstraintStylesMap.contains( mFeat.id() ) &&
 | 
			
		||||
             mConstraintStylesMap[mFeat.id()].contains( fieldId ) )
 | 
			
		||||
        {
 | 
			
		||||
          constraintstyle = mConstraintStylesMap[mFeat.id()][fieldId];
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
          QStringList errors;
 | 
			
		||||
          if ( !QgsVectorLayerUtils::validateAttribute( mLayer, mFeat, fieldId, errors, QgsFieldConstraints::ConstraintStrengthHard ) )
 | 
			
		||||
          {
 | 
			
		||||
            constraintstyle = mLayer->conditionalStyles()->constraintFailureStyles( QgsFieldConstraints::ConstraintStrengthHard );
 | 
			
		||||
          }
 | 
			
		||||
          else
 | 
			
		||||
          {
 | 
			
		||||
            if ( !QgsVectorLayerUtils::validateAttribute( mLayer, mFeat, fieldId, errors, QgsFieldConstraints::ConstraintStrengthSoft ) )
 | 
			
		||||
            {
 | 
			
		||||
              constraintstyle = mLayer->conditionalStyles()->constraintFailureStyles( QgsFieldConstraints::ConstraintStrengthSoft );
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          mConstraintStylesMap[mFeat.id()].insert( fieldId, constraintstyle );
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      styles = mLayer->conditionalStyles()->fieldStyles( field.name() );
 | 
			
		||||
      styles = QgsConditionalStyle::matchingConditionalStyles( styles, val,  mExpressionContext );
 | 
			
		||||
      styles.insert( 0, rowstyle );
 | 
			
		||||
      styles.insert( 0, constraintstyle );
 | 
			
		||||
      const QgsConditionalStyle style = QgsConditionalStyle::compressStyles( styles );
 | 
			
		||||
 | 
			
		||||
      if ( style.isValid() )
 | 
			
		||||
@ -796,6 +824,7 @@ bool QgsAttributeTableModel::setData( const QModelIndex &index, const QVariant &
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  mRowStylesMap.remove( mFeat.id() );
 | 
			
		||||
  mConstraintStylesMap.remove( mFeat.id() );
 | 
			
		||||
 | 
			
		||||
  if ( !mLayer->isModified() )
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
@ -256,6 +256,20 @@ class GUI_EXPORT QgsAttributeTableModel: public QAbstractTableModel
 | 
			
		||||
     */
 | 
			
		||||
    void setExtraColumns( int extraColumns );
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns whether the attribute table will add a visual feedback to cells when an attribute
 | 
			
		||||
     * constraint is not met.
 | 
			
		||||
     * \since QGIS 3.30
 | 
			
		||||
     */
 | 
			
		||||
    bool showValidityState() const { return mShowValidityState; }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets whether the attribute table will add a visual feedback to cells when an attribute constraint
 | 
			
		||||
     * is not met.
 | 
			
		||||
     * \since QGIS 3.30
 | 
			
		||||
     */
 | 
			
		||||
    void setShowValidityState( bool show ) { mShowValidityState = show; }
 | 
			
		||||
 | 
			
		||||
  public slots:
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -350,6 +364,7 @@ class GUI_EXPORT QgsAttributeTableModel: public QAbstractTableModel
 | 
			
		||||
    QHash<QgsFeatureId, int> mIdRowMap;
 | 
			
		||||
    QHash<int, QgsFeatureId> mRowIdMap;
 | 
			
		||||
    mutable QHash<QgsFeatureId, QList<QgsConditionalStyle> > mRowStylesMap;
 | 
			
		||||
    mutable QHash<QgsFeatureId, QHash<int, QgsConditionalStyle> > mConstraintStylesMap;
 | 
			
		||||
 | 
			
		||||
    mutable QgsExpressionContext mExpressionContext;
 | 
			
		||||
 | 
			
		||||
@ -412,6 +427,8 @@ class GUI_EXPORT QgsAttributeTableModel: public QAbstractTableModel
 | 
			
		||||
    //! TRUE if triggered by afterRollback()
 | 
			
		||||
    bool mIsCleaningUpAfterRollback = false;
 | 
			
		||||
 | 
			
		||||
    bool mShowValidityState = false;
 | 
			
		||||
 | 
			
		||||
    friend class TestQgsAttributeTable;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -326,6 +326,10 @@ void QgsDualView::setFilterMode( QgsAttributeTableFilterModel::FilterMode filter
 | 
			
		||||
    case QgsAttributeTableFilterModel::ShowSelected:
 | 
			
		||||
      disconnect( masterModel()->layer(), &QgsVectorLayer::selectionChanged, this, &QgsDualView::updateSelectedFeatures );
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case QgsAttributeTableFilterModel::ShowInvalid:
 | 
			
		||||
      mMasterModel->setShowValidityState( false );
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  QgsFeatureRequest r = mMasterModel->request();
 | 
			
		||||
@ -363,6 +367,16 @@ void QgsDualView::setFilterMode( QgsAttributeTableFilterModel::FilterMode filter
 | 
			
		||||
      connect( masterModel()->layer(), &QgsVectorLayer::layerModified, this, &QgsDualView::updateEditedAddedFeatures );
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case QgsAttributeTableFilterModel::ShowInvalid:
 | 
			
		||||
    {
 | 
			
		||||
      mMasterModel->setShowValidityState( true );
 | 
			
		||||
      const QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) );
 | 
			
		||||
      filterFeatures( QStringLiteral( "is_feature_valid() = false" ), context );
 | 
			
		||||
      connect( mFilterModel, &QgsAttributeTableFilterModel::featuresFiltered, this, &QgsDualView::filterChanged );
 | 
			
		||||
      connect( mFilterModel, &QgsAttributeTableFilterModel::filterError, this, &QgsDualView::filterError );
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case QgsAttributeTableFilterModel::ShowAll:
 | 
			
		||||
    case QgsAttributeTableFilterModel::ShowFilteredList:
 | 
			
		||||
    {
 | 
			
		||||
@ -390,6 +404,7 @@ void QgsDualView::setFilterMode( QgsAttributeTableFilterModel::FilterMode filter
 | 
			
		||||
    case QgsAttributeTableFilterModel::ShowAll:
 | 
			
		||||
    case QgsAttributeTableFilterModel::ShowEdited:
 | 
			
		||||
    case QgsAttributeTableFilterModel::ShowFilteredList:
 | 
			
		||||
    case QgsAttributeTableFilterModel::ShowInvalid:
 | 
			
		||||
    case QgsAttributeTableFilterModel::ShowSelected:
 | 
			
		||||
      setBrowsingAutoPanScaleAllowed( true );
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
@ -55,6 +55,7 @@ QgsFeatureFilterWidget::QgsFeatureFilterWidget( QWidget *parent )
 | 
			
		||||
  mActionShowAllFilter->setIcon( QgsApplication::getThemeIcon( "/mActionOpenTable.svg" ) );
 | 
			
		||||
  mActionAdvancedFilter->setIcon( QgsApplication::getThemeIcon( "/mActionFilter2.svg" ) );
 | 
			
		||||
  mActionSelectedFilter->setIcon( QgsApplication::getThemeIcon( "/mActionOpenTableSelected.svg" ) );
 | 
			
		||||
  mActionInvalidFilter->setIcon( QgsApplication::getThemeIcon( "/mActionOpenTableInvalid.svg" ) );
 | 
			
		||||
  mActionVisibleFilter->setIcon( QgsApplication::getThemeIcon( "/mActionOpenTableVisible.svg" ) );
 | 
			
		||||
  mActionEditedFilter->setIcon( QgsApplication::getThemeIcon( "/mActionOpenTableEdited.svg" ) );
 | 
			
		||||
 | 
			
		||||
@ -70,6 +71,7 @@ QgsFeatureFilterWidget::QgsFeatureFilterWidget( QWidget *parent )
 | 
			
		||||
  connect( mActionAdvancedFilter, &QAction::triggered, this, &QgsFeatureFilterWidget::filterExpressionBuilder );
 | 
			
		||||
  connect( mActionShowAllFilter, &QAction::triggered, this, &QgsFeatureFilterWidget::filterShowAll );
 | 
			
		||||
  connect( mActionSelectedFilter, &QAction::triggered, this, &QgsFeatureFilterWidget::filterSelected );
 | 
			
		||||
  connect( mActionInvalidFilter, &QAction::triggered, this, &QgsFeatureFilterWidget::filterInvalid );
 | 
			
		||||
  connect( mActionVisibleFilter, &QAction::triggered, this, &QgsFeatureFilterWidget::filterVisible );
 | 
			
		||||
  connect( mActionEditedFilter, &QAction::triggered, this, &QgsFeatureFilterWidget::filterEdited );
 | 
			
		||||
  connect( mFilterQuery, &QLineEdit::returnPressed, this, &QgsFeatureFilterWidget::filterQueryAccepted );
 | 
			
		||||
@ -138,6 +140,18 @@ void QgsFeatureFilterWidget::filterVisible()
 | 
			
		||||
  mMainView->setFilterMode( QgsAttributeTableFilterModel::ShowVisible );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void QgsFeatureFilterWidget::filterInvalid()
 | 
			
		||||
{
 | 
			
		||||
  mFilterButton->setDefaultAction( mActionInvalidFilter );
 | 
			
		||||
  mFilterButton->setPopupMode( QToolButton::InstantPopup );
 | 
			
		||||
  mFilterQuery->setVisible( false );
 | 
			
		||||
  mApplyFilterButton->setVisible( false );
 | 
			
		||||
  mStoreFilterExpressionButton->setVisible( false );
 | 
			
		||||
  const QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) );
 | 
			
		||||
  mMainView->filterFeatures( QStringLiteral( "is_feature_valid() = false" ), context );
 | 
			
		||||
  mMainView->setFilterMode( QgsAttributeTableFilterModel::ShowInvalid );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void QgsFeatureFilterWidget::filterEdited()
 | 
			
		||||
{
 | 
			
		||||
  mFilterButton->setDefaultAction( mActionEditedFilter );
 | 
			
		||||
@ -192,6 +206,7 @@ void QgsFeatureFilterWidget::columnBoxInit()
 | 
			
		||||
  {
 | 
			
		||||
    mFilterButton->addAction( mActionVisibleFilter );
 | 
			
		||||
  }
 | 
			
		||||
  mFilterButton->addAction( mActionInvalidFilter );
 | 
			
		||||
  mFilterButton->addAction( mActionEditedFilter );
 | 
			
		||||
  mFilterButton->addAction( mActionFilterColumnsMenu );
 | 
			
		||||
  mFilterButton->addAction( mActionAdvancedFilter );
 | 
			
		||||
 | 
			
		||||
@ -69,6 +69,7 @@ class GUI_EXPORT QgsFeatureFilterWidget : public QWidget, private Ui::QgsFeature
 | 
			
		||||
  public slots:
 | 
			
		||||
    void filterShowAll();
 | 
			
		||||
    void filterSelected();
 | 
			
		||||
    void filterInvalid();
 | 
			
		||||
    void filterVisible();
 | 
			
		||||
    void filterEdited();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -34,8 +34,8 @@ QgsFieldConditionalFormatWidget::QgsFieldConditionalFormatWidget( QWidget *paren
 | 
			
		||||
  setupUi( this );
 | 
			
		||||
  setPanelTitle( tr( "Conditional Styles" ) );
 | 
			
		||||
  connect( mFieldCombo, &QgsFieldComboBox::fieldChanged, this, &QgsFieldConditionalFormatWidget::fieldChanged );
 | 
			
		||||
  connect( fieldRadio, &QAbstractButton::clicked, this, &QgsFieldConditionalFormatWidget::reloadStyles );
 | 
			
		||||
  connect( rowRadio, &QAbstractButton::clicked, this, &QgsFieldConditionalFormatWidget::reloadStyles );
 | 
			
		||||
  connect( fieldRadio, &QAbstractButton::clicked, this, &QgsFieldConditionalFormatWidget::typeChanged );
 | 
			
		||||
  connect( rowRadio, &QAbstractButton::clicked, this, &QgsFieldConditionalFormatWidget::typeChanged );
 | 
			
		||||
  connect( mNewButton, &QAbstractButton::clicked, this, &QgsFieldConditionalFormatWidget::addNewRule );
 | 
			
		||||
  connect( listView, &QAbstractItemView::clicked, this, &QgsFieldConditionalFormatWidget::ruleClicked );
 | 
			
		||||
  mModel = new QStandardItemModel( listView );
 | 
			
		||||
@ -103,10 +103,11 @@ void QgsFieldConditionalFormatWidget::editStyle( int editIndex, const QgsConditi
 | 
			
		||||
      fieldName = mFieldCombo->currentField();
 | 
			
		||||
      mLayer->conditionalStyles()->setFieldStyles( fieldName, styles );
 | 
			
		||||
    }
 | 
			
		||||
    if ( rowRadio->isChecked() )
 | 
			
		||||
    else if ( rowRadio->isChecked() )
 | 
			
		||||
    {
 | 
			
		||||
      mLayer->conditionalStyles()->setRowStyles( styles );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    reloadStyles();
 | 
			
		||||
    emit rulesUpdated( fieldName );
 | 
			
		||||
  } );
 | 
			
		||||
@ -142,10 +143,11 @@ QList<QgsConditionalStyle> QgsFieldConditionalFormatWidget::getStyles()
 | 
			
		||||
  {
 | 
			
		||||
    styles = mLayer->conditionalStyles()->fieldStyles( mFieldCombo->currentField() );
 | 
			
		||||
  }
 | 
			
		||||
  if ( rowRadio->isChecked() )
 | 
			
		||||
  else if ( rowRadio->isChecked() )
 | 
			
		||||
  {
 | 
			
		||||
    styles = mLayer->conditionalStyles()->rowStyles();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return styles;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -187,6 +189,11 @@ QList<QgsConditionalStyle> QgsFieldConditionalFormatWidget::defaultPresets()
 | 
			
		||||
  return styles;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void QgsFieldConditionalFormatWidget::typeChanged()
 | 
			
		||||
{
 | 
			
		||||
  reloadStyles();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void QgsFieldConditionalFormatWidget::reloadStyles()
 | 
			
		||||
{
 | 
			
		||||
  mModel->clear();
 | 
			
		||||
@ -224,7 +231,7 @@ void QgsFieldConditionalFormatWidget::deleteCurrentRule()
 | 
			
		||||
    fieldName = mFieldCombo->currentField();
 | 
			
		||||
    mLayer->conditionalStyles()->setFieldStyles( fieldName, styles );
 | 
			
		||||
  }
 | 
			
		||||
  if ( rowRadio->isChecked() )
 | 
			
		||||
  else if ( rowRadio->isChecked() )
 | 
			
		||||
  {
 | 
			
		||||
    mLayer->conditionalStyles()->setRowStyles( styles );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -110,6 +110,7 @@ class GUI_EXPORT QgsFieldConditionalFormatWidget : public QgsPanelWidget, privat
 | 
			
		||||
 | 
			
		||||
  private slots:
 | 
			
		||||
 | 
			
		||||
    void typeChanged();
 | 
			
		||||
    void ruleClicked( const QModelIndex &index );
 | 
			
		||||
    void reloadStyles();
 | 
			
		||||
    void addNewRule();
 | 
			
		||||
 | 
			
		||||
@ -198,7 +198,16 @@
 | 
			
		||||
     <normaloff>:/images/themes/default/mActionOpenTableVisible.svg</normaloff>:/images/themes/default/mActionOpenTableVisible.svg</iconset>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="text">
 | 
			
		||||
    <string>Show Features Visible On Map</string>
 | 
			
		||||
    <string>Show Features Visible on Map</string>
 | 
			
		||||
   </property>
 | 
			
		||||
  </action>
 | 
			
		||||
  <action name="mActionInvalidFilter">
 | 
			
		||||
   <property name="icon">
 | 
			
		||||
    <iconset resource="../../images/images.qrc">
 | 
			
		||||
     <normaloff>:/images/themes/default/mActionOpenTableInvalid.svg</normaloff>:/images/themes/default/mActionOpenTableInvalid.svg</iconset>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="text">
 | 
			
		||||
    <string>Show Features with Failing Constraints</string>
 | 
			
		||||
   </property>
 | 
			
		||||
  </action>
 | 
			
		||||
  <action name="mActionEditedFilter">
 | 
			
		||||
 | 
			
		||||
@ -21,18 +21,15 @@
 | 
			
		||||
  </property>
 | 
			
		||||
  <layout class="QVBoxLayout" name="verticalLayout">
 | 
			
		||||
   <item>
 | 
			
		||||
    <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0,2">
 | 
			
		||||
     <property name="sizeConstraint">
 | 
			
		||||
      <enum>QLayout::SetDefaultConstraint</enum>
 | 
			
		||||
     </property>
 | 
			
		||||
    <layout class="QGridLayout" name="gridLayout_4">
 | 
			
		||||
     <property name="topMargin">
 | 
			
		||||
      <number>0</number>
 | 
			
		||||
     </property>
 | 
			
		||||
     <item row="0" column="3" rowspan="2">
 | 
			
		||||
     <item row="2" column="0">
 | 
			
		||||
      <widget class="QPushButton" name="mNewButton">
 | 
			
		||||
       <property name="sizePolicy">
 | 
			
		||||
        <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
 | 
			
		||||
         <horstretch>0</horstretch>
 | 
			
		||||
        <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
 | 
			
		||||
         <horstretch>1</horstretch>
 | 
			
		||||
         <verstretch>0</verstretch>
 | 
			
		||||
        </sizepolicy>
 | 
			
		||||
       </property>
 | 
			
		||||
@ -45,32 +42,36 @@
 | 
			
		||||
       </property>
 | 
			
		||||
      </widget>
 | 
			
		||||
     </item>
 | 
			
		||||
     <item row="0" column="2">
 | 
			
		||||
     <item row="0" column="0">
 | 
			
		||||
      <layout class="QHBoxLayout" name="horizontalLayout">
 | 
			
		||||
       <property name="topMargin">
 | 
			
		||||
        <number>0</number>
 | 
			
		||||
       </property>
 | 
			
		||||
       <item>
 | 
			
		||||
        <widget class="QgsFieldComboBox" name="mFieldCombo"/>
 | 
			
		||||
        <widget class="QRadioButton" name="fieldRadio">
 | 
			
		||||
         <property name="sizePolicy">
 | 
			
		||||
          <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
 | 
			
		||||
           <horstretch>0</horstretch>
 | 
			
		||||
           <verstretch>0</verstretch>
 | 
			
		||||
          </sizepolicy>
 | 
			
		||||
         </property>
 | 
			
		||||
         <property name="text">
 | 
			
		||||
          <string>Field</string>
 | 
			
		||||
         </property>
 | 
			
		||||
         <property name="checked">
 | 
			
		||||
          <bool>true</bool>
 | 
			
		||||
         </property>
 | 
			
		||||
        </widget>
 | 
			
		||||
       </item>
 | 
			
		||||
       <item>
 | 
			
		||||
        <widget class="QgsFieldComboBox" name="mFieldCombo">
 | 
			
		||||
         <property name="sizePolicy">
 | 
			
		||||
          <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
 | 
			
		||||
            <horstretch>1</horstretch>
 | 
			
		||||
            <verstretch>0</verstretch>
 | 
			
		||||
          </sizepolicy>
 | 
			
		||||
         </property>
 | 
			
		||||
        </widget>
 | 
			
		||||
       </item>
 | 
			
		||||
      </layout>
 | 
			
		||||
     </item>
 | 
			
		||||
     <item row="0" column="0">
 | 
			
		||||
      <widget class="QRadioButton" name="fieldRadio">
 | 
			
		||||
       <property name="sizePolicy">
 | 
			
		||||
        <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
 | 
			
		||||
         <horstretch>0</horstretch>
 | 
			
		||||
         <verstretch>0</verstretch>
 | 
			
		||||
        </sizepolicy>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="text">
 | 
			
		||||
        <string>Field</string>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="checked">
 | 
			
		||||
        <bool>true</bool>
 | 
			
		||||
       </property>
 | 
			
		||||
      </widget>
 | 
			
		||||
     </item>
 | 
			
		||||
     <item row="1" column="0">
 | 
			
		||||
      <widget class="QRadioButton" name="rowRadio">
 | 
			
		||||
       <property name="sizePolicy">
 | 
			
		||||
 | 
			
		||||
@ -67,6 +67,7 @@ class TestQgsAttributeTable : public QObject
 | 
			
		||||
    void testSortNumbers();
 | 
			
		||||
    void testStartMultiEditNoChanges();
 | 
			
		||||
    void testMultiEditMakeUncommittedChanges();
 | 
			
		||||
    void testInvalidView();
 | 
			
		||||
 | 
			
		||||
  private:
 | 
			
		||||
    QgisApp *mQgisApp = nullptr;
 | 
			
		||||
@ -793,5 +794,43 @@ void TestQgsAttributeTable::testOpenWithFilterExpression()
 | 
			
		||||
  QCOMPARE( dlg->mMainView->filteredFeatures(), QgsFeatureIds() << 1 << 3 );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TestQgsAttributeTable::testInvalidView()
 | 
			
		||||
{
 | 
			
		||||
  std::unique_ptr< QgsVectorLayer> tempLayer( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:4326&field=pk:int&field=col1:date" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
 | 
			
		||||
  QVERIFY( tempLayer->isValid() );
 | 
			
		||||
 | 
			
		||||
  QgsPolylineXY line;
 | 
			
		||||
  line << QgsPointXY( 0, 0 ) << QgsPointXY( 1, 1 );
 | 
			
		||||
  QgsGeometry geometry = QgsGeometry::fromPolylineXY( line ) ;
 | 
			
		||||
  QgsFeature f1( tempLayer->dataProvider()->fields(), 1 );
 | 
			
		||||
  f1.setGeometry( geometry );
 | 
			
		||||
  f1.setAttributes( QgsAttributes() << 1 << QDate( 2020, 1, 1 ) );
 | 
			
		||||
  QgsFeature f2( tempLayer->dataProvider()->fields(), 2 );
 | 
			
		||||
  f2.setGeometry( geometry );
 | 
			
		||||
  f2.setAttributes( QgsAttributes() << 2 << QDate( 2020, 3, 1 ) );
 | 
			
		||||
  QgsFeature f3( tempLayer->dataProvider()->fields(), 3 );
 | 
			
		||||
  line.clear();
 | 
			
		||||
  line << QgsPointXY( -3, -3 ) << QgsPointXY( -2, -2 );
 | 
			
		||||
  geometry = QgsGeometry::fromPolylineXY( line );
 | 
			
		||||
  f3.setGeometry( geometry );
 | 
			
		||||
  f3.setAttributes( QgsAttributes() << 3 << QDate( 2020, 1, 1 ) );
 | 
			
		||||
  QVERIFY( tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 << f3 ) );
 | 
			
		||||
 | 
			
		||||
  const QString filterExpression = QStringLiteral( "col1 >= to_date('2020-02-03')" );
 | 
			
		||||
  tempLayer->setConstraintExpression( 1, QStringLiteral( "col1 >= to_date('2020-02-03')" ) );
 | 
			
		||||
  tempLayer->setFieldConstraint( 1, QgsFieldConstraints::ConstraintExpression, QgsFieldConstraints::ConstraintStrengthHard );
 | 
			
		||||
 | 
			
		||||
  std::unique_ptr< QgsAttributeTableDialog > dlg( new QgsAttributeTableDialog( tempLayer.get(),
 | 
			
		||||
      QgsAttributeTableFilterModel::ShowAll,
 | 
			
		||||
      nullptr,
 | 
			
		||||
      Qt::Window,
 | 
			
		||||
      nullptr,
 | 
			
		||||
      filterExpression ) );
 | 
			
		||||
  dlg->mFeatureFilterWidget->filterInvalid();
 | 
			
		||||
 | 
			
		||||
  // feature id 2 is filtered out due not matching the provided filter expression
 | 
			
		||||
  QCOMPARE( dlg->mMainView->filteredFeatures(), QgsFeatureIds() << 1 << 3 );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QGSTEST_MAIN( TestQgsAttributeTable )
 | 
			
		||||
#include "testqgsattributetable.moc"
 | 
			
		||||
 | 
			
		||||
@ -264,7 +264,9 @@ class TestQgsVectorLayerUtils(unittest.TestCase):
 | 
			
		||||
        layer = createLayerWithOnePoint()
 | 
			
		||||
 | 
			
		||||
        # field expression check
 | 
			
		||||
        self.assertFalse(QgsVectorLayerUtils.attributeHasConstraints(layer, 1))
 | 
			
		||||
        layer.setConstraintExpression(1, 'fldint>5')
 | 
			
		||||
        self.assertTrue(QgsVectorLayerUtils.attributeHasConstraints(layer, 1))
 | 
			
		||||
 | 
			
		||||
        f = QgsFeature(2)
 | 
			
		||||
        f.setAttributes(["test123", 6])
 | 
			
		||||
@ -297,7 +299,10 @@ class TestQgsVectorLayerUtils(unittest.TestCase):
 | 
			
		||||
        self.assertTrue(res)
 | 
			
		||||
        self.assertEqual(len(errors), 0)
 | 
			
		||||
 | 
			
		||||
        self.assertFalse(QgsVectorLayerUtils.attributeHasConstraints(layer, 1))
 | 
			
		||||
        layer.setFieldConstraint(1, QgsFieldConstraints.ConstraintNotNull)
 | 
			
		||||
        self.assertTrue(QgsVectorLayerUtils.attributeHasConstraints(layer, 1))
 | 
			
		||||
 | 
			
		||||
        res, errors = QgsVectorLayerUtils.validateAttribute(layer, f, 1)
 | 
			
		||||
        self.assertFalse(res)
 | 
			
		||||
        self.assertEqual(len(errors), 1)
 | 
			
		||||
@ -315,7 +320,11 @@ class TestQgsVectorLayerUtils(unittest.TestCase):
 | 
			
		||||
        res, errors = QgsVectorLayerUtils.validateAttribute(layer, f, 1)
 | 
			
		||||
        self.assertTrue(res)
 | 
			
		||||
        self.assertEqual(len(errors), 0)
 | 
			
		||||
 | 
			
		||||
        self.assertFalse(QgsVectorLayerUtils.attributeHasConstraints(layer, 1))
 | 
			
		||||
        layer.setFieldConstraint(1, QgsFieldConstraints.ConstraintUnique)
 | 
			
		||||
        self.assertTrue(QgsVectorLayerUtils.attributeHasConstraints(layer, 1))
 | 
			
		||||
 | 
			
		||||
        res, errors = QgsVectorLayerUtils.validateAttribute(layer, f, 1)
 | 
			
		||||
        self.assertFalse(res)
 | 
			
		||||
        self.assertEqual(len(errors), 1)
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user