Skip to content
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c08af1e
utils functions for project versioning
JanCaha Dec 18, 2025
9a60b60
project version model and dialog
JanCaha Dec 18, 2025
03a06b4
handle is version and datesaved
JanCaha Dec 18, 2025
aa46ef5
update schema to store if project versioning is enabled
JanCaha Dec 18, 2025
7f597eb
add menu items for project versioning
JanCaha Dec 18, 2025
2f69f5d
update ui
JanCaha Dec 18, 2025
ab53a74
fix variable name
JanCaha Dec 18, 2025
141304d
make check more robust, check for both existence of table and trigger
JanCaha Dec 18, 2025
8fdc646
single function to handle project versioning
JanCaha Dec 18, 2025
659ee2b
pass reference
JanCaha Dec 19, 2025
a6cd5a5
docstring fixes
JanCaha Dec 19, 2025
387d9bf
remove leftover
JanCaha Dec 19, 2025
c7f3550
unref connection after use
JanCaha Dec 19, 2025
2b2725c
rename function
JanCaha Dec 19, 2025
14788c4
fix docstring
JanCaha Dec 19, 2025
7f6047b
make sure project table has comment column
JanCaha Dec 19, 2025
c5f1b24
tabůe qgis_projects must exist for enabling of versioning
JanCaha Dec 19, 2025
e0c84ae
mProjectVersioningEnabled need to be set on creation to avoid issues,…
JanCaha Dec 19, 2025
6bacd87
Merge branch 'master' into postgresql-qgis-project-versioning-v2
JanCaha Jan 4, 2026
a38e454
fix strings
JanCaha Jan 4, 2026
24e96bb
use only one connection to the signal
JanCaha Jan 5, 2026
8225329
clear model if project is empty
JanCaha Jan 5, 2026
75abe55
add const
JanCaha Jan 5, 2026
6618b8f
simplify
JanCaha Jan 5, 2026
18a0469
drop check
JanCaha Jan 5, 2026
a4af499
const variables
JanCaha Jan 5, 2026
4ae2a36
base model on QAbstractItemModel
JanCaha Jan 5, 2026
14ef7de
update button text
JanCaha Jan 5, 2026
e628967
use QTreeView instead of QTableView
JanCaha Jan 5, 2026
a208bb1
allow obtaining of project uri from dialog
JanCaha Jan 5, 2026
993d059
update to qtreeview
JanCaha Jan 5, 2026
0eb169e
get project uri from dialog and handle loading here with cursor over…
JanCaha Jan 5, 2026
da243f3
add better TODO
JanCaha Jan 9, 2026
5eff1fe
fix string
JanCaha Jan 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/providers/postgres/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ if (WITH_GUI)
raster/qgspostgresrastertemporalsettingswidget.cpp
qgspostgresutils.cpp
qgspostgresimportprojectdialog.cpp
qgspostgresprojectversionsdialog.cpp
qgspostgresprojectversionsmodel.cpp
)

set(PG_UIS ${CMAKE_SOURCE_DIR}/src/ui/qgspostgresrastertemporalsettingswidgetbase.ui)
Expand Down
68 changes: 68 additions & 0 deletions src/providers/postgres/qgspostgresdataitemguiprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
#include "qgspostgresconn.h"
#include "qgspostgresdataitems.h"
#include "qgspostgresimportprojectdialog.h"
#include "qgspostgresprojectstoragedialog.h"
#include "qgspostgresprojectversionsdialog.h"
#include "qgspostgresutils.h"
#include "qgsproject.h"
#include "qgsprovidermetadata.h"
Expand Down Expand Up @@ -138,6 +140,14 @@ void QgsPostgresDataItemGuiProvider::populateContextMenu( QgsDataItem *item, QMe
QAction *actionImportProject = new QAction( tr( "Import Projects…" ), projectMenu );
projectMenu->addAction( actionImportProject );
connect( actionImportProject, &QAction::triggered, this, [schemaItem, context] { saveProjects( schemaItem, context ); } );

QAction *enableAllowProjectVersioning = new QAction( tr( "Enable Projects Versioning…" ), projectMenu );
projectMenu->addAction( enableAllowProjectVersioning );
enableAllowProjectVersioning->setEnabled( !schemaItem->projectVersioningEnabled() );
connect( enableAllowProjectVersioning, &QAction::triggered, this, [schemaItem, context] {
bool enabled = enableProjectsVersioning( schemaItem->connectionName(), schemaItem->name(), context );
schemaItem->setProjectVersioningEnabled( enabled );
} );
}
}

Expand Down Expand Up @@ -196,6 +206,31 @@ void QgsPostgresDataItemGuiProvider::populateContextMenu( QgsDataItem *item, QMe
QAction *setProjectCommentAction = new QAction( tr( "Set Comment…" ), menu );
connect( setProjectCommentAction, &QAction::triggered, this, [projectItem, context] { setProjectComment( projectItem, context ); } );
menu->addAction( setProjectCommentAction );

// Project versioning
QgsPGSchemaItem *parentSchemaItem = qobject_cast<QgsPGSchemaItem *>( item->parent() );

if ( parentSchemaItem && parentSchemaItem->projectVersioningEnabled() )
{
QAction *showProjectVersions = new QAction( tr( "Show Project Versions…" ), menu );
menu->addAction( showProjectVersions );
connect( showProjectVersions, &QAction::triggered, this, [projectItem] {
QgsPostgresProjectVersionsDialog dlg = QgsPostgresProjectVersionsDialog( projectItem->connectionName(), projectItem->schemaName(), projectItem->name(), nullptr );
dlg.exec();
} );
}
else
{
QAction *enableAllowProjectVersioning = new QAction( tr( "Enable Projects Versioning…" ), menu );
menu->addAction( enableAllowProjectVersioning );
connect( enableAllowProjectVersioning, &QAction::triggered, this, [projectItem, parentSchemaItem, context] {
bool enabled = enableProjectsVersioning( projectItem->connectionName(), projectItem->schemaName(), context );
if ( parentSchemaItem )
{
parentSchemaItem->setProjectVersioningEnabled( enabled );
}
} );
}
}
else
{
Expand Down Expand Up @@ -1262,3 +1297,36 @@ void QgsPostgresDataItemGuiProvider::saveProjects( QgsPGSchemaItem *schemaItem,

conn->unref();
}


bool QgsPostgresDataItemGuiProvider::enableProjectsVersioning( const QString connectionName, const QString &schemaName, QgsDataItemGuiContext context )
{
const QgsDataSourceUri uri = QgsPostgresConn::connUri( connectionName );
QgsPostgresConn *conn = QgsPostgresConn::connectDb( uri, false );

if ( QgsPostgresUtils::qgisProjectVersioningEnabled( conn, schemaName ) )
{
notify( tr( "QGIS Project Versioning" ), tr( "Versioning of QGIS projects already active in schema “%1”." ).arg( schemaName ), context, Qgis::MessageLevel::Info );
conn->unref();
return false;
}

QMessageBox::StandardButton result = QgsPostgresProjectStorageDialog::questionAllowProjectVersioning( nullptr, schemaName );

if ( result == QMessageBox::StandardButton::Yes )
{
if ( !QgsPostgresUtils::enableQgisProjectVersioning( conn, schemaName ) )
{
notify( tr( "QGIS Project Versioning" ), tr( "Cannot setup versioning of QGIS projects in schema “%1”." ).arg( schemaName ), context, Qgis::MessageLevel::Critical );
conn->unref();
return false;
}

notify( tr( "QGIS Project Versioning" ), tr( "Versioning of QGIS projects setup in schema “%1”." ).arg( schemaName ), context, Qgis::MessageLevel::Success );
conn->unref();
return true;
}

conn->unref();
return false;
}
1 change: 1 addition & 0 deletions src/providers/postgres/qgspostgresdataitemguiprovider.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class QgsPostgresDataItemGuiProvider : public QObject, public QgsDataItemGuiProv
static void saveCurrentProject( QgsPGSchemaItem *schemaItem, QgsDataItemGuiContext context );
static void saveProjects( QgsPGSchemaItem *schemaItem, QgsDataItemGuiContext context );
static void setProjectComment( QgsPGProjectItem *projectItem, QgsDataItemGuiContext context );
static bool enableProjectsVersioning( const QString connectionName, const QString &schemaName, QgsDataItemGuiContext context );
};

#endif // QGSPOSTGRESDATAITEMGUIPROVIDER_H
8 changes: 8 additions & 0 deletions src/providers/postgres/qgspostgresdataitems.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,14 @@ QgsPGSchemaItem::QgsPGSchemaItem( QgsDataItem *parent, const QString &connection
, mConnectionName( connectionName )
{
mIconName = u"mIconDbSchema.svg"_s;

const QgsDataSourceUri uri = QgsPostgresConn::connUri( mConnectionName );
QgsPostgresConn *conn = QgsPostgresConn::connectDb( uri, false );
if ( conn )
{
mProjectVersioningEnabled = QgsPostgresUtils::qgisProjectVersioningEnabled( conn, mName );
}
conn->unref();
}

QVector<QgsDataItem *> QgsPGSchemaItem::createChildren()
Expand Down
15 changes: 15 additions & 0 deletions src/providers/postgres/qgspostgresdataitems.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,25 @@ class QgsPGSchemaItem : public QgsDatabaseSchemaItem

QString connectionName() const { return mConnectionName; }

/**
* Set if versioning of QGIS projects is enabled for this schema.
*
* \since QGIS 4.0
*/
void setProjectVersioningEnabled( const bool enabled ) { mProjectVersioningEnabled = enabled; }

/**
* Returns if versioning of QGIS projects is enabled for this schema.
*
* \since QGIS 4.0
*/
bool projectVersioningEnabled() const { return mProjectVersioningEnabled; }

private:
QgsPGLayerItem *createLayer( QgsPostgresLayerProperty layerProperty );

QString mConnectionName;
bool mProjectVersioningEnabled = false;

// QgsDataItem interface
public:
Expand Down
25 changes: 24 additions & 1 deletion src/providers/postgres/qgspostgresprojectstorage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,17 @@ bool QgsPostgresProjectStorage::readProject( const QString &uri, QIODevice *devi
}

bool ok = false;
QString sql( u"SELECT content FROM %1.qgis_projects WHERE name = %2"_s.arg( QgsPostgresConn::quotedIdentifier( projectUri.schemaName ), QgsPostgresConn::quotedValue( projectUri.projectName ) ) );
QString sql;

if ( projectUri.isVersion )
{
sql = u"SELECT content FROM %1.qgis_projects_versions WHERE name = %2 AND date_saved = %3"_s.arg( QgsPostgresConn::quotedIdentifier( projectUri.schemaName ), QgsPostgresConn::quotedValue( projectUri.projectName ), QgsPostgresConn::quotedValue( projectUri.dateSaved ) );
}
else
{
sql = u"SELECT content FROM %1.qgis_projects WHERE name = %2"_s.arg( QgsPostgresConn::quotedIdentifier( projectUri.schemaName ), QgsPostgresConn::quotedValue( projectUri.projectName ) );
}

QgsPostgresResult result( conn->PQexec( sql ) );
if ( result.PQresultStatus() == PGRES_TUPLES_OK )
{
Expand Down Expand Up @@ -261,6 +271,12 @@ QString QgsPostgresProjectStorage::encodeUri( const QgsPostgresProjectUri &postU
if ( !postUri.projectName.isEmpty() )
urlQuery.addQueryItem( "project", postUri.projectName );

if ( postUri.isVersion )
{
urlQuery.addQueryItem( "isVersion", "true" );
urlQuery.addQueryItem( "dateSaved", QVariant( postUri.dateSaved ).toString() );
}

u.setQuery( urlQuery );

return QString::fromUtf8( u.toEncoded() );
Expand Down Expand Up @@ -290,5 +306,12 @@ QgsPostgresProjectUri QgsPostgresProjectStorage::decodeUri( const QString &uri )

postUri.schemaName = urlQuery.queryItemValue( "schema" );
postUri.projectName = urlQuery.queryItemValue( "project" );

if ( urlQuery.hasQueryItem( "isVersion" ) )
postUri.isVersion = QVariant( urlQuery.queryItemValue( "isVersion" ) ).toBool();

if ( urlQuery.hasQueryItem( "dateSaved" ) )
postUri.dateSaved = urlQuery.queryItemValue( "dateSaved" );

return postUri;
}
7 changes: 4 additions & 3 deletions src/providers/postgres/qgspostgresprojectstorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,17 @@
#include "qgsprojectstorage.h"

//! Stores information parsed from postgres project URI
typedef struct
struct QgsPostgresProjectUri
{
bool valid;

QgsDataSourceUri connInfo; // using only the bits about connection info (server, port, username, password, service, ssl mode)

QString schemaName;
QString projectName;

} QgsPostgresProjectUri;
bool isVersion = false;
QString dateSaved;
};


//! Implements storage of QGIS projects inside a PostgreSQL table
Expand Down
123 changes: 118 additions & 5 deletions src/providers/postgres/qgspostgresprojectstoragedialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@
#include "qgspostgresprojectstoragedialog.h"

#include "qgsapplication.h"
#include "qgsguiutils.h"
#include "qgspostgresconn.h"
#include "qgspostgresconnpool.h"
#include "qgspostgresprojectstorage.h"
#include "qgspostgresprojectversionsdialog.h"
#include "qgspostgresutils.h"
#include "qgsprojectstorage.h"
#include "qgsprojectstorageregistry.h"

#include <QMenu>
#include <QMessageBox>
#include <QPushButton>

#include "moc_qgspostgresprojectstoragedialog.cpp"
Expand All @@ -42,18 +44,38 @@ QgsPostgresProjectStorageDialog::QgsPostgresProjectStorageDialog( bool saving, Q
btnManageProjects->setMenu( menuManageProjects );
buttonBox->addButton( btnManageProjects, QDialogButtonBox::ActionRole );

mVersionsTableView->setSelectionBehavior( QAbstractItemView::SelectRows );
mVersionsTableView->setSelectionMode( QAbstractItemView::SingleSelection );

mVersionsModel = new QgsPostgresProjectVersionsModel( QString(), this );
mVersionsTableView->setModel( mVersionsModel );

connect( mVersionsModel, &QAbstractTableModel::modelReset, this, [this] {
mVersionsTableView->resizeColumnsToContents();
mVersionsTableView->selectRow( 0 );
} );

if ( saving )
{
setWindowTitle( tr( "Save project to PostgreSQL" ) );
mCboProject->setEditable( true );
mGroupBoxVersions->setVisible( false );
}
else
{
setWindowTitle( tr( "Load project from PostgreSQL" ) );
mLabelProjectVersions->setVisible( false );
mEnableProjectVersions->setVisible( false );

mGroupBoxVersions->setCollapsed( true );
}

connect( mCboConnection, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsPostgresProjectStorageDialog::populateSchemas );

connect( mCboSchema, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsPostgresProjectStorageDialog::onSchemaChanged );

connect( mEnableProjectVersions, &QCheckBox::clicked, this, &QgsPostgresProjectStorageDialog::setupQgisProjectVersioning );

mLblProjectsNotAllowed->setVisible( false );

// populate connections
Expand Down Expand Up @@ -90,6 +112,8 @@ void QgsPostgresProjectStorageDialog::populateSchemas()
mCboSchema->clear();
mCboProject->clear();

mVersionsModel->setConnection( mCboConnection->currentText() );

QString name = mCboConnection->currentText();
QgsDataSourceUri uri = QgsPostgresConn::connUri( name );

Expand Down Expand Up @@ -163,6 +187,13 @@ void QgsPostgresProjectStorageDialog::onOK()
void QgsPostgresProjectStorageDialog::projectChanged()
{
mActionRemoveProject->setEnabled( mCboProject->count() != 0 && mExistingProjects.contains( mCboProject->currentText() ) );

if ( !mCboProject->currentText().isEmpty() )
{
QgsTemporaryCursorOverride override( Qt::WaitCursor );

mVersionsModel->populateVersions( mCboSchema->currentText(), mCboProject->currentText() );
}
}

void QgsPostgresProjectStorageDialog::removeProject()
Expand All @@ -180,9 +211,91 @@ void QgsPostgresProjectStorageDialog::removeProject()
QString QgsPostgresProjectStorageDialog::currentProjectUri( bool schemaOnly )
{
QgsPostgresProjectUri postUri;
postUri.connInfo = QgsPostgresConn::connUri( mCboConnection->currentText() );
postUri.schemaName = mCboSchema->currentText();
if ( !schemaOnly )
postUri.projectName = mCboProject->currentText();

// either project is empty (schema uri is requested) or nothig from versions is selected - return simple uri
if ( mCboProject->currentText().isEmpty() || mVersionsModel->rowCount() == 0 )
{
postUri.connInfo = QgsPostgresConn::connUri( mCboConnection->currentText() );
postUri.schemaName = mCboSchema->currentText();
if ( !schemaOnly )
postUri.projectName = mCboProject->currentText();
}
else
{
postUri = mVersionsModel->projectUriForRow( mVersionsTableView->currentIndex().row() );
}

return QgsPostgresProjectStorage::encodeUri( postUri );
}

void QgsPostgresProjectStorageDialog::onSchemaChanged()
{
QgsTemporaryCursorOverride override( Qt::WaitCursor );

QString name = mCboConnection->currentText();
QgsDataSourceUri uri = QgsPostgresConn::connUri( name );

QgsPostgresConn *conn = QgsPostgresConn::connectDb( QgsPostgresConn::connectionInfo( uri, false ), false );
if ( !conn )
{
QMessageBox::critical( this, tr( "Error" ), tr( "Connection failed" ) + "\n" + QgsPostgresConn::connectionInfo( uri, false ) );
return;
}

bool versioningEnabled = QgsPostgresUtils::qgisProjectVersioningEnabled( conn, mCboSchema->currentText() );

conn->unref();

QgsSignalBlocker( mEnableProjectVersions )->setChecked( versioningEnabled );
mEnableProjectVersions->setEnabled( !versioningEnabled );

mGroupBoxVersions->setEnabled( versioningEnabled );
}

void QgsPostgresProjectStorageDialog::setupQgisProjectVersioning()
{
if ( mEnableProjectVersions->isChecked() )
{
QMessageBox::StandardButton result = QgsPostgresProjectStorageDialog::questionAllowProjectVersioning( this, mCboSchema->currentText() );

if ( result == QMessageBox::StandardButton::Yes )
{
QgsTemporaryCursorOverride override( Qt::WaitCursor );

QString name = mCboConnection->currentText();
QgsDataSourceUri uri = QgsPostgresConn::connUri( name );

QgsPostgresConn *conn = QgsPostgresConn::connectDb( QgsPostgresConn::connectionInfo( uri, false ), false );
if ( !conn )
{
QMessageBox::critical( this, tr( "Error" ), tr( "Connection failed" ) + "\n" + QgsPostgresConn::connectionInfo( uri, false ) );
return;
}

if ( !QgsPostgresUtils::createProjectsTable( conn, mCboSchema->currentText() ) )
{
QMessageBox::critical( this, tr( "Error" ), tr( "Could not create qgis_projects table." ) );
return;
}

bool success = QgsPostgresUtils::enableQgisProjectVersioning( conn, mCboSchema->currentText() );

if ( !success )
{
QMessageBox::critical( this, tr( "Error" ), tr( "Could not setup QGIS project versioning." ) );
return;
}

mEnableProjectVersions->setEnabled( false );
}
else
{
QgsSignalBlocker( mEnableProjectVersions )->setChecked( false );
}
}
}

QMessageBox::StandardButton QgsPostgresProjectStorageDialog::questionAllowProjectVersioning( QWidget *parent, const QString &schemaName )
{
return QMessageBox::question( parent, tr( "Enable versioning of QGIS projects" ), tr( "Do you want to enable versioning of QGIS projects in the schema `%1`?\nThis will create new table in the schema and store older versions of QGIS projects there." ).arg( schemaName ) );
}
Loading