Skip to content
Permalink
Browse files

Merge pull request #30758 from stev-0/pg_json

Allows editing of postgres JSON fields from Text Edit Widget
  • Loading branch information
m-kuhn committed Jan 23, 2020
2 parents 76bb060 + b11b090 commit 5cd1c6d07cb8adfdb1253fa8eb5b60c08a972de6
@@ -347,7 +347,6 @@ attribute ``attributeName`` to ``attributeValue``
.. versionadded:: 3.2
%End


};


@@ -44,6 +44,17 @@ class QgsAttributeForm : QWidget

const QgsFeature &feature();

void displayWarning( const QString &message );
%Docstring
Displays a warning message in the form message bar

:param message: message string

.. seealso:: :py:func:`mode`

.. versionadded:: 3.12
%End


void hideButtonBox();
%Docstring
@@ -449,10 +449,14 @@ json QgsJsonUtils::jsonFromVariant( const QVariant &val )

QVariant QgsJsonUtils::parseJson( const std::string &jsonString )
{
// tracks whether entire json string is a primitive
bool isPrimitive = true;

std::function<QVariant( json )> _parser { [ & ]( json jObj ) -> QVariant {
QVariant result;
if ( jObj.is_array() )
{
isPrimitive = false;
QVariantList results;
for ( const auto &item : jObj )
{
@@ -462,6 +466,7 @@ QVariant QgsJsonUtils::parseJson( const std::string &jsonString )
}
else if ( jObj.is_object() )
{
isPrimitive = false;
QVariantMap results;
for ( const auto &item : jObj.items() )
{
@@ -492,7 +497,14 @@ QVariant QgsJsonUtils::parseJson( const std::string &jsonString )
}
else if ( jObj.is_string() )
{
result = QString::fromStdString( jObj.get<std::string>() );
if ( isPrimitive && jObj.get<std::string>().length() == 0 )
{
result = QString::fromStdString( jObj.get<std::string>() ).append( "\"" ).insert( 0, "\"" );
}
else
{
result = QString::fromStdString( jObj.get<std::string>() );
}
}
else if ( jObj.is_null() )
{
@@ -347,7 +347,6 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
*/
bool setFormFeatureAttribute( const QString &attributeName, const QVariant &attributeValue );


private:

/**
@@ -19,8 +19,12 @@
#include "qgsfieldvalidator.h"
#include "qgsfilterlineedit.h"
#include "qgsapplication.h"
#include "qgsjsonutils.cpp"
#include "qgsmessagebar.h"
#include "qgslogger.h"

#include <QSettings>
#include <nlohmann/json.hpp>

QgsTextEditWrapper::QgsTextEditWrapper( QgsVectorLayer *layer, int fieldIdx, QWidget *editor, QWidget *parent )
: QgsEditorWidgetWrapper( layer, fieldIdx, editor, parent )
@@ -64,7 +68,8 @@ QVariant QgsTextEditWrapper::value() const
}

QVariant res( v );
if ( field().convertCompatible( res ) )
// treat VariantMap fields including JSON differently
if ( field().type() != QVariant::Map && field().convertCompatible( res ) )
{
return res;
}
@@ -74,6 +79,35 @@ QVariant QgsTextEditWrapper::value() const
// input rather then discarding it entirely
return QVariant( v.left( field().length() ) );
}
else if ( field().type() == QVariant::Map )
{
// replace empty string (invalid) with quoted empty string
if ( v.isEmpty() )
{
QVariant qjson = QgsJsonUtils::parseJson( std::string( "\"\"" ) );
mInvalidJSON = false;
return qjson;
}
if ( json::accept( v.toUtf8() ) )
{
QVariant qjson = QgsJsonUtils::parseJson( v.toStdString() );
mInvalidJSON = false;
return qjson;
}
else
// return null value if json is invalid
{
if ( v.length() > 0 )
{
mInvalidJSON = true;
}
else
{
mInvalidJSON = false;
}
return QVariant();
}
}
else
{
return QVariant( field().type() );
@@ -82,6 +116,7 @@ QVariant QgsTextEditWrapper::value() const

QWidget *QgsTextEditWrapper::createWidget( QWidget *parent )
{
mForm = qobject_cast<QgsAttributeForm *>( parent );
if ( config( QStringLiteral( "IsMultiline" ) ).toBool() )
{
if ( config( QStringLiteral( "UseHtml" ) ).toBool() )
@@ -101,6 +136,7 @@ QWidget *QgsTextEditWrapper::createWidget( QWidget *parent )

void QgsTextEditWrapper::initWidget( QWidget *editor )
{
mInvalidJSON = false;
mTextBrowser = qobject_cast<QTextBrowser *>( editor );
mTextEdit = qobject_cast<QTextEdit *>( editor );
mPlainTextEdit = qobject_cast<QPlainTextEdit *>( editor );
@@ -162,7 +198,7 @@ void QgsTextEditWrapper::showIndeterminateState()
if ( mLineEdit )
{
mLineEdit->blockSignals( true );
// for interdeminate state we need to clear the placeholder text - we want an empty line edit, not
// for indeterminate state we need to clear the placeholder text - we want an empty line edit, not
// one showing the default value (e.g., "NULL")
mLineEdit->setPlaceholderText( QString() );
}
@@ -177,6 +213,18 @@ void QgsTextEditWrapper::showIndeterminateState()
mLineEdit->blockSignals( false );
}

void QgsTextEditWrapper::setFeature( const QgsFeature &feature )
{
// Do nothing if the value has not changed
if ( mInvalidJSON )
mForm->displayWarning( tr( "Your JSON was invalid and has been reverted back to the last valid edit or the original data" ) );
{
mInvalidJSON = false;
}
setFormFeature( feature );
setValue( feature.attribute( fieldIdx() ) );
}

void QgsTextEditWrapper::updateValues( const QVariant &val, const QVariantList & )
{
if ( mLineEdit )
@@ -211,6 +259,11 @@ void QgsTextEditWrapper::setEnabled( bool enabled )
}
}

bool QgsTextEditWrapper::isInvalidJSON()
{
return mInvalidJSON;
}

void QgsTextEditWrapper::textChanged( const QString & )
{
if ( mLineEdit )
@@ -228,11 +281,34 @@ void QgsTextEditWrapper::setWidgetValue( const QVariant &val )
if ( !( field().type() == QVariant::Int || field().type() == QVariant::Double || field().type() == QVariant::LongLong || field().type() == QVariant::Date ) )
v = QgsApplication::nullRepresentation();
}
else if ( field().type() == QVariant::Map )
{
// this has to be overridden for json which has only values (i.e. no objects or arrays), as qgsfield.cpp displayString()
// uses QJsonDocument which doesn't recognise this as valid JSON although it technically is
if ( field().displayString( val ).isEmpty() )
{
if ( val.type() == QVariant::String && val.toString() != QStringLiteral( "\"\"" ) )
{
v = val.toString().append( "\"" ).insert( 0, "\"" );
}
else
{
v = val.toString();
}
}
else
{
v = field().displayString( val );
}
}
else if ( val.type() == QVariant::Double && std::isnan( val.toDouble() ) )
{
v = QgsApplication::nullRepresentation();
}
else
{
v = field().displayString( val );

}
// For numbers, remove the group separator that might cause validation errors
// when the user is editing the field value.
// We are checking for editable layer because in the form field context we do not
@@ -21,6 +21,7 @@
#include <QLineEdit>
#include <QPlainTextEdit>
#include <QTextBrowser>
#include "qgsattributeform.h"
#include "qgis_gui.h"

SIP_NO_FILE
@@ -59,6 +60,13 @@ class GUI_EXPORT QgsTextEditWrapper : public QgsEditorWidgetWrapper
QVariant value() const override;
void showIndeterminateState() override;


/**
* Returns whether the text edit widget contains Invalid JSON
* \since QGIS 3.12
*/
bool isInvalidJSON();

/**
* Add a hint text on the widget
* \param hintText The hint text to display
@@ -73,11 +81,14 @@ class GUI_EXPORT QgsTextEditWrapper : public QgsEditorWidgetWrapper

public slots:
void setEnabled( bool enabled ) override;
void setFeature( const QgsFeature &feature ) override;

private slots:
void textChanged( const QString &text );

private:
bool mutable mInvalidJSON;
QgsAttributeForm *mForm;
void updateValues( const QVariant &val, const QVariantList & = QVariantList() ) override;

QTextBrowser *mTextBrowser = nullptr;
@@ -21,6 +21,7 @@
#include "qgshighlight.h"
#include "qgsapplication.h"
#include "qgssettings.h"
#include "qgsmessagebar.h"

QgsAttributeDialog::QgsAttributeDialog( QgsVectorLayer *vl, QgsFeature *thepFeature, bool featureOwner, QWidget *parent, bool showDialogButtons, const QgsAttributeEditorContext &context )
: QDialog( parent )
@@ -65,8 +66,18 @@ void QgsAttributeDialog::setHighlight( QgsHighlight *h )

void QgsAttributeDialog::accept()
{
mAttributeForm->save();
QDialog::accept();
bool didSave = mAttributeForm->save();
if ( didSave )
{
QDialog::accept();
}
else
{
mMessageBar->pushMessage( QString(),
tr( "Your JSON value is invalid and has not been saved" ),
Qgis::MessageLevel::Critical,
5 );
}
}

void QgsAttributeDialog::show()
@@ -91,6 +102,12 @@ void QgsAttributeDialog::init( QgsVectorLayer *layer, QgsFeature *feature, const
setWindowTitle( tr( "%1 - Feature Attributes" ).arg( layer->name() ) );
setLayout( new QGridLayout() );
layout()->setMargin( 0 );
mMessageBar = new QgsMessageBar( this );
mMessageBar->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
layout()->addWidget( mMessageBar );

setLayout( layout() );

mTrackedVectorLayerTools.setVectorLayerTools( trackedContext.vectorLayerTools() );
trackedContext.setVectorLayerTools( &mTrackedVectorLayerTools );
if ( showDialogButtons )
@@ -113,6 +113,7 @@ class GUI_EXPORT QgsAttributeDialog : public QDialog
QString mReturnvarname;
QgsAttributeForm *mAttributeForm = nullptr;
QgsFeature *mOwnedFeature = nullptr;
QgsMessageBar *mMessageBar = nullptr;

QgsTrackedVectorLayerTools mTrackedVectorLayerTools;

@@ -42,6 +42,7 @@
#include "qgsapplication.h"
#include "qgsexpressioncontextutils.h"
#include "qgsfeaturerequest.h"
#include "qgstexteditwrapper.h"

#include <QDir>
#include <QTextStream>
@@ -311,7 +312,6 @@ bool QgsAttributeForm::saveEdits()
bool changedLayer = false;

QgsFeature updatedFeature = QgsFeature( mFeature );

if ( mFeature.isValid() || mMode == QgsAttributeEditorContext::AddFeatureMode )
{
bool doUpdate = false;
@@ -329,6 +329,12 @@ bool QgsAttributeForm::saveEdits()
QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
if ( eww )
{
// check for invalid JSON values
QgsTextEditWrapper *text_edit = qobject_cast<QgsTextEditWrapper *>( eww );
if ( text_edit && text_edit->isInvalidJSON() )
{
return false;
}
QVariantList dstVars = QVariantList() << dst.at( eww->fieldIdx() );
QVariantList srcVars = QVariantList() << eww->value();
QList<int> fieldIndexes = QList<int>() << eww->fieldIdx();
@@ -594,6 +600,14 @@ void QgsAttributeForm::pushSelectedFeaturesMessage()
}
}

void QgsAttributeForm::displayWarning( const QString &message )
{
mMessageBar->pushMessage( QString(),
message,
Qgis::Warning,
messageTimeout() );
}

void QgsAttributeForm::runSearchSelect( QgsVectorLayer::SelectBehavior behavior )
{
QString filter = createFilterExpression();
@@ -75,6 +75,14 @@ class GUI_EXPORT QgsAttributeForm : public QWidget

const QgsFeature &feature() { return mFeature; }

/**
* Displays a warning message in the form message bar
* \param message message string
* \see mode()
* \since QGIS 3.12
*/
void displayWarning( const QString &message );

// TODO QGIS 4.0 - make private

/**
@@ -154,6 +154,10 @@ QValidator::State QgsFieldValidator::validate( QString &s, int &i ) const
{
return QDate::fromString( s, mDateFormat ).isValid() ? Acceptable : Intermediate;
}
else if ( mField.type() == QVariant::Map )
{
return Acceptable;
}
else
{
QgsDebugMsg( QStringLiteral( "unsupported type %1 for validation" ).arg( mField.type() ) );
@@ -1225,7 +1225,16 @@ QString QgsPostgresConn::quotedJsonValue( const QVariant &value )
{
if ( value.isNull() || !value.isValid() )
return QStringLiteral( "null" );
const auto j { QgsJsonUtils::jsonFromVariant( value ) };
// where json is a string literal just construct it from that rather than dump
if ( value.type() == QVariant::String )
{
QString valueStr = value.toString();
if ( valueStr.at( 0 ) == "\"" && valueStr.at( valueStr.size() - 1 ) == "\"" )
{
return quotedString( value.toString() );
}
}
const auto j = QgsJsonUtils::jsonFromVariant( value );
return quotedString( QString::fromStdString( j.dump() ) );
}

0 comments on commit 5cd1c6d

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