Add a model for schemas from a database connection

This commit is contained in:
Nyall Dawson 2020-03-07 10:11:41 +10:00
parent 2eb4eaff86
commit a0a57d5bf7
7 changed files with 387 additions and 1 deletions

View File

@ -0,0 +1,69 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsdatabaseschemamodel.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsDatabaseSchemaModel : QAbstractItemModel
{
%Docstring
A model containing schemas from a database connection.
This class does not automatically subscribe to database updates. Schemas are queried
from the database initially upon model construction. In order
to update the listed schemas, QgsDatabaseSchemaModel.refresh() must be manually
called.
.. versionadded:: 3.14
%End
%TypeHeaderCode
#include "qgsdatabaseschemamodel.h"
%End
public:
explicit QgsDatabaseSchemaModel( const QString &provider, const QString &connection, QObject *parent /TransferThis/ = 0 );
%Docstring
Constructor for QgsDatabaseSchemaModel, for the specified ``provider`` and ``connection`` name.
.. warning::
The ``provider`` must support the connection API methods in its QgsProviderMetadata implementation
in order for the model to work correctly.
%End
explicit QgsDatabaseSchemaModel( QgsAbstractDatabaseProviderConnection *connection /Transfer/, QObject *parent /TransferThis/ = 0 );
virtual QModelIndex parent( const QModelIndex &child ) const;
virtual int rowCount( const QModelIndex &parent = QModelIndex() ) const;
virtual int columnCount( const QModelIndex &parent = QModelIndex() ) const;
virtual QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const;
virtual QModelIndex index( int row, int column, const QModelIndex &parent ) const;
public slots:
void refresh();
%Docstring
Refreshes the schema list by querying the underlying connection.
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsdatabaseschemamodel.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -36,6 +36,7 @@
%Include auto_generated/qgscoordinatetransformcontext.sip
%Include auto_generated/qgscredentials.sip
%Include auto_generated/qgsdartmeasurement.sip
%Include auto_generated/qgsdatabaseschemamodel.sip
%Include auto_generated/qgsdatadefinedsizelegend.sip
%Include auto_generated/qgsdataitem.sip
%Include auto_generated/qgsdataitemprovider.sip

View File

@ -227,6 +227,7 @@ SET(QGIS_CORE_SRCS
qgscoordinateutils.cpp
qgscredentials.cpp
qgsdartmeasurement.cpp
qgsdatabaseschemamodel.cpp
qgsdatadefinedsizelegend.cpp
qgsdataitem.cpp
qgsdataitemprovider.cpp
@ -727,6 +728,7 @@ SET(QGIS_CORE_HDRS
qgscoordinateutils.h
qgscredentials.h
qgsdartmeasurement.h
qgsdatabaseschemamodel.h
qgsdatadefinedsizelegend.h
qgsdataitem.h
qgsdataitemprovider.h

View File

@ -0,0 +1,120 @@
/***************************************************************************
qgsdatabaseschemamodel.cpp
--------------------------------------
Date : March 2020
Copyright : (C) 2020 Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsdatabaseschemamodel.h"
#include "qgsproviderregistry.h"
#include "qgsprovidermetadata.h"
#include "qgsabstractdatabaseproviderconnection.h"
QgsDatabaseSchemaModel::QgsDatabaseSchemaModel( const QString &provider, const QString &connection, QObject *parent )
: QAbstractItemModel( parent )
{
QgsProviderMetadata *metadata = QgsProviderRegistry::instance()->providerMetadata( provider );
Q_ASSERT( metadata );
mConnection.reset( dynamic_cast<QgsAbstractDatabaseProviderConnection *>( metadata->createConnection( connection ) ) );
Q_ASSERT( mConnection );
init();
}
QgsDatabaseSchemaModel::QgsDatabaseSchemaModel( QgsAbstractDatabaseProviderConnection *connection, QObject *parent )
: QAbstractItemModel( parent )
, mConnection( connection )
{
Q_ASSERT( mConnection );
init();
}
void QgsDatabaseSchemaModel::init()
{
Q_ASSERT( mConnection->capabilities() & QgsAbstractDatabaseProviderConnection::Capability::Schemas );
mSchemas = mConnection->schemas();
}
QModelIndex QgsDatabaseSchemaModel::parent( const QModelIndex &child ) const
{
Q_UNUSED( child )
return QModelIndex();
}
int QgsDatabaseSchemaModel::rowCount( const QModelIndex &parent ) const
{
if ( parent.isValid() )
return 0;
return mSchemas.count();
}
int QgsDatabaseSchemaModel::columnCount( const QModelIndex &parent ) const
{
Q_UNUSED( parent )
return 1;
}
QVariant QgsDatabaseSchemaModel::data( const QModelIndex &index, int role ) const
{
if ( !index.isValid() )
return QVariant();
const QString schemaName = mSchemas.value( index.row() );
switch ( role )
{
case Qt::DisplayRole:
case Qt::ToolTipRole:
{
return schemaName;
}
}
return QVariant();
}
QModelIndex QgsDatabaseSchemaModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( hasIndex( row, column, parent ) )
{
return createIndex( row, column, row );
}
return QModelIndex();
}
void QgsDatabaseSchemaModel::refresh()
{
const QStringList newSchemas = mConnection->schemas();
const QStringList oldSchemas = mSchemas;
for ( const QString &oldSchema : oldSchemas )
{
if ( !newSchemas.contains( oldSchema ) )
{
int r = mSchemas.indexOf( oldSchema );
beginRemoveRows( QModelIndex(), r, r );
mSchemas.removeAt( r );
endRemoveRows();
}
}
for ( const QString &newSchema : newSchemas )
{
if ( !mSchemas.contains( newSchema ) )
{
beginInsertRows( QModelIndex(), mSchemas.count(), mSchemas.count() );
mSchemas.append( newSchema );
endInsertRows();
}
}
}

View File

@ -0,0 +1,78 @@
/***************************************************************************
qgsdatabaseschemamodel.h
--------------------------------------
Date : March 2020
Copyright : (C) 2020 Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSDATABASESCHEMAMODEL_H
#define QGSDATABASESCHEMAMODEL_H
#include <QAbstractItemModel>
#include <QSortFilterProxyModel>
#include <QStringList>
#include <memory>
#include "qgis_core.h"
#include "qgis_sip.h"
class QgsProviderMetadata;
class QgsAbstractDatabaseProviderConnection;
/**
* \ingroup core
* \class QgsDatabaseSchemaModel
* \brief A model containing schemas from a database connection.
*
* This class does not automatically subscribe to database updates. Schemas are queried
* from the database initially upon model construction. In order
* to update the listed schemas, QgsDatabaseSchemaModel::refresh() must be manually
* called.
*
* \since QGIS 3.14
*/
class CORE_EXPORT QgsDatabaseSchemaModel : public QAbstractItemModel
{
Q_OBJECT
public:
/**
* Constructor for QgsDatabaseSchemaModel, for the specified \a provider and \a connection name.
*
* \warning The \a provider must support the connection API methods in its QgsProviderMetadata implementation
* in order for the model to work correctly.
*/
explicit QgsDatabaseSchemaModel( const QString &provider, const QString &connection, QObject *parent SIP_TRANSFERTHIS = nullptr );
explicit QgsDatabaseSchemaModel( QgsAbstractDatabaseProviderConnection *connection SIP_TRANSFER, QObject *parent SIP_TRANSFERTHIS = nullptr );
// QAbstractItemModel interface
QModelIndex parent( const QModelIndex &child ) const override;
int rowCount( const QModelIndex &parent = QModelIndex() ) const override;
int columnCount( const QModelIndex &parent = QModelIndex() ) const override;
QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override;
QModelIndex index( int row, int column, const QModelIndex &parent ) const override;
public slots:
/**
* Refreshes the schema list by querying the underlying connection.
*/
void refresh();
private:
void init();
std::unique_ptr< QgsAbstractDatabaseProviderConnection > mConnection;
QStringList mSchemas;
};
#endif // QGSDATABASESCHEMAMODEL_H

View File

@ -186,7 +186,6 @@ ADD_PYTHON_TEST(PyQgsProjectionSelectionWidgets test_qgsprojectionselectionwidge
ADD_PYTHON_TEST(PyQgsProjectMetadata test_qgsprojectmetadata.py)
ADD_PYTHON_TEST(PyQgsPropertyOverrideButton test_qgspropertyoverridebutton.py)
ADD_PYTHON_TEST(PyQgsProviderConnectionModel test_qgsproviderconnectionmodel.py)
ADD_PYTHON_TEST(PyQgsProviderConnectionPostgres test_qgsproviderconnection_postgres.py)
ADD_PYTHON_TEST(PyQgsProviderConnectionGpkg test_qgsproviderconnection_ogr_gpkg.py)
ADD_PYTHON_TEST(TestQgsRandomMarkerSymbolLayer test_qgsrandommarkersymbollayer.py)
ADD_PYTHON_TEST(PyQgsRange test_qgsrange.py)
@ -304,6 +303,8 @@ IF (ENABLE_PGTEST)
ADD_PYTHON_TEST(PyQgsAuthManagerPasswordPostgresTest test_authmanager_password_postgres.py)
ADD_PYTHON_TEST(PyQgsAuthManagerOgrPostgresTest test_authmanager_ogr_postgres.py)
ADD_PYTHON_TEST(PyQgsDbManagerPostgis test_db_manager_postgis.py)
ADD_PYTHON_TEST(PyQgsDatabaseSchemaModel test_qgsdatabaseschemamodel.py)
ADD_PYTHON_TEST(PyQgsProviderConnectionPostgres test_qgsproviderconnection_postgres.py)
ENDIF (ENABLE_PGTEST)
IF (ENABLE_MSSQLTEST)

View File

@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsDatabaseSchemaModel
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
"""
__author__ = 'Nyall Dawson'
__date__ = '07/03/2020'
__copyright__ = 'Copyright 2020, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import os
from qgis.core import (
QgsDatabaseSchemaModel,
QgsProviderRegistry,
)
from qgis.PyQt.QtCore import (
QCoreApplication,
QModelIndex,
Qt
)
from qgis.testing import unittest, start_app
class TestPyQgsDatabaseSchemaModel(unittest.TestCase):
# Provider test cases must define the string URI for the test
uri = ''
# Provider test cases must define the provider name (e.g. "postgres" or "ogr")
providerKey = 'postgres'
@classmethod
def setUpClass(cls):
"""Run before all tests"""
QCoreApplication.setOrganizationName("QGIS_Test")
QCoreApplication.setOrganizationDomain(cls.__name__)
QCoreApplication.setApplicationName(cls.__name__)
start_app()
cls.postgres_conn = "service='qgis_test'"
if 'QGIS_PGTEST_DB' in os.environ:
cls.postgres_conn = os.environ['QGIS_PGTEST_DB']
cls.uri = cls.postgres_conn + ' sslmode=disable'
def testModel(self):
conn = QgsProviderRegistry.instance().providerMetadata('postgres').createConnection(self.uri, {})
self.assertTrue(conn)
model = QgsDatabaseSchemaModel(conn)
self.assertGreaterEqual(model.rowCount(), 3)
old_count = model.rowCount()
self.assertEqual(model.columnCount(), 1)
schemas = [model.data(model.index(r, 0, QModelIndex()), Qt.DisplayRole) for r in range(model.rowCount())]
self.assertIn('public', schemas)
self.assertIn('CamelCaseSchema', schemas)
self.assertIn('qgis_test', schemas)
self.assertEqual(model.data(model.index(schemas.index('qgis_test'), 0, QModelIndex()), Qt.ToolTipRole), 'qgis_test')
self.assertIsNone(model.data(model.index(model.rowCount(), 0, QModelIndex()), Qt.DisplayRole))
model.refresh()
self.assertEqual(model.rowCount(), old_count)
conn.createSchema('myNewSchema')
self.assertEqual(model.rowCount(), old_count)
model.refresh()
self.assertEqual(model.rowCount(), old_count + 1)
schemas = [model.data(model.index(r, 0, QModelIndex()), Qt.DisplayRole) for r in range(model.rowCount())]
self.assertIn('public', schemas)
self.assertIn('CamelCaseSchema', schemas)
self.assertIn('qgis_test', schemas)
self.assertIn('myNewSchema', schemas)
conn.createSchema('myNewSchema2')
conn.createSchema('myNewSchema3')
model.refresh()
self.assertEqual(model.rowCount(), old_count + 3)
schemas = [model.data(model.index(r, 0, QModelIndex()), Qt.DisplayRole) for r in range(model.rowCount())]
self.assertIn('public', schemas)
self.assertIn('CamelCaseSchema', schemas)
self.assertIn('qgis_test', schemas)
self.assertIn('myNewSchema', schemas)
self.assertIn('myNewSchema2', schemas)
self.assertIn('myNewSchema3', schemas)
conn.createSchema('myNewSchema4')
conn.dropSchema('myNewSchema2')
conn.dropSchema('myNewSchema')
model.refresh()
self.assertEqual(model.rowCount(), old_count + 2)
schemas = [model.data(model.index(r, 0, QModelIndex()), Qt.DisplayRole) for r in range(model.rowCount())]
self.assertIn('public', schemas)
self.assertIn('CamelCaseSchema', schemas)
self.assertIn('qgis_test', schemas)
self.assertNotIn('myNewSchema', schemas)
self.assertNotIn('myNewSchema2', schemas)
self.assertIn('myNewSchema3', schemas)
self.assertIn('myNewSchema4', schemas)
conn.dropSchema('myNewSchema3')
conn.dropSchema('myNewSchema4')
model.refresh()
self.assertEqual(model.rowCount(), old_count)
schemas = [model.data(model.index(r, 0, QModelIndex()), Qt.DisplayRole) for r in range(model.rowCount())]
self.assertIn('public', schemas)
self.assertIn('CamelCaseSchema', schemas)
self.assertIn('qgis_test', schemas)
self.assertNotIn('myNewSchema3', schemas)
self.assertNotIn('myNewSchema4', schemas)
if __name__ == '__main__':
unittest.main()