From 053c0449a3d4ea948799afb18cd99d93026b724d Mon Sep 17 00:00:00 2001 From: Damiano Lombardi Date: Wed, 8 Nov 2023 17:37:16 +0100 Subject: [PATCH 1/6] Postgres schemas combobox --- QgisModelBaker/gui/panel/pg_config_panel.py | 72 +++++++++++++++++---- QgisModelBaker/ui/pg_settings_panel.ui | 12 ++-- 2 files changed, 65 insertions(+), 19 deletions(-) diff --git a/QgisModelBaker/gui/panel/pg_config_panel.py b/QgisModelBaker/gui/panel/pg_config_panel.py index b229257f1..535f408d3 100644 --- a/QgisModelBaker/gui/panel/pg_config_panel.py +++ b/QgisModelBaker/gui/panel/pg_config_panel.py @@ -19,6 +19,9 @@ import logging from enum import IntEnum +import psycopg2 +import psycopg2.extras +from psycopg2 import OperationalError from qgis.PyQt.QtCore import Qt, pyqtSignal import QgisModelBaker.libs.modelbaker.libs.pgserviceparser as pgserviceparser @@ -81,7 +84,9 @@ def __init__(self, parent, db_action_type): self.pg_host_line_edit.setValidator(nonEmptyValidator) self.pg_database_line_edit.setValidator(nonEmptyValidator) - self.pg_schema_line_edit.setValidator(nonEmptyValidator) + self.pg_schema_combo_box.setValidator(nonEmptyValidator) + + self.pg_schema_combo_box.setEditable(True) self.pg_host_line_edit.textChanged.connect(self.validators.validate_line_edits) self.pg_host_line_edit.textChanged.emit(self.pg_host_line_edit.text()) @@ -89,15 +94,14 @@ def __init__(self, parent, db_action_type): self.validators.validate_line_edits ) self.pg_database_line_edit.textChanged.emit(self.pg_database_line_edit.text()) - self.pg_schema_line_edit.textChanged.connect( + self.pg_schema_combo_box.lineEdit().textChanged.connect( self.validators.validate_line_edits ) - self.pg_schema_line_edit.textChanged.emit(self.pg_host_line_edit.text()) self.pg_host_line_edit.textChanged.connect(self.notify_fields_modified) self.pg_port_line_edit.textChanged.connect(self.notify_fields_modified) self.pg_database_line_edit.textChanged.connect(self.notify_fields_modified) - self.pg_schema_line_edit.textChanged.connect(self.notify_fields_modified) + self.pg_schema_combo_box.currentTextChanged.connect(self.notify_fields_modified) # Fill pg_services combo box self.pg_service_combo_box.addItem(self.tr("None"), None) @@ -162,13 +166,16 @@ def __init__(self, parent, db_action_type): self._show_panel() def _show_panel(self): + + self._fill_schema_combo_box() + if ( self._db_action_type == DbActionType.GENERATE or self._db_action_type == DbActionType.IMPORT_DATA ): - self.pg_schema_line_edit.setPlaceholderText(self.tr("Schema Name")) + self.pg_schema_combo_box.setPlaceholderText(self.tr("Schema Name")) elif self._db_action_type == DbActionType.EXPORT: - self.pg_schema_line_edit.setPlaceholderText( + self.pg_schema_combo_box.setPlaceholderText( self.tr("[Enter a valid schema]") ) else: @@ -181,7 +188,7 @@ def get_fields(self, configuration): configuration.dbport = self.pg_port_line_edit.text().strip() configuration.dbusr = self.pg_auth_settings.username() configuration.database = self.pg_database_line_edit.text().strip() - configuration.dbschema = self.pg_schema_line_edit.text().strip().lower() + configuration.dbschema = self.pg_schema_combo_box.currentText().strip().lower() configuration.dbpwd = self.pg_auth_settings.password() configuration.dbauthid = self.pg_auth_settings.configId() @@ -241,7 +248,7 @@ def set_fields(self, configuration): self.pg_port_line_edit.setText(configuration.dbport) self.pg_auth_settings.setUsername(configuration.dbusr) self.pg_database_line_edit.setText(configuration.database) - self.pg_schema_line_edit.setText(configuration.dbschema) + self.pg_schema_combo_box.setCurrentText(configuration.dbschema) self.pg_auth_settings.setPassword(configuration.dbpwd) self.pg_auth_settings.setConfigId(configuration.dbauthid) @@ -300,7 +307,7 @@ def _pg_service_combo_box_changed(self): self.pg_port_line_edit.setText(service_config.get("port", "")) self.pg_auth_settings.setUsername(service_config.get("user", "")) self.pg_database_line_edit.setText(service_config.get("dbname", "")) - self.pg_schema_line_edit.setText("") + self.pg_schema_combo_box.setText("") self.pg_auth_settings.setPassword(service_config.get("password", "")) self.pg_auth_settings.setConfigId("") @@ -333,7 +340,7 @@ def _pg_service_combo_box_changed(self): index, PgConfigPanel._SERVICE_COMBOBOX_ROLE.DATABASE ) ) - self.pg_schema_line_edit.setText( + self.pg_schema_combo_box.setText( self.pg_service_combo_box.itemData( index, PgConfigPanel._SERVICE_COMBOBOX_ROLE.DBSCHEMA ) @@ -422,7 +429,7 @@ def _keep_custom_settings(self): ) self.pg_service_combo_box.setItemData( index, - self.pg_schema_line_edit.text().strip().lower(), + self.pg_schema_combo_box.text().strip().lower(), PgConfigPanel._SERVICE_COMBOBOX_ROLE.DBSCHEMA, ) self.pg_service_combo_box.setItemData( @@ -440,3 +447,46 @@ def _keep_custom_settings(self): self.pg_ssl_mode_combo_box.currentData(), PgConfigPanel._SERVICE_COMBOBOX_ROLE.SSLMODE, ) + + def _fill_schema_combo_box(self): + connection = None + try: + connection = psycopg2.connect( + dbname=self.pg_database_line_edit.text(), + user=self.pg_auth_settings.username(), + password=self.pg_auth_settings.password(), + host=self.pg_host_line_edit.text(), + port=self.pg_port_line_edit.text(), + ) + + except OperationalError as exception: + logging.warning(f"Pg connection error: {exception}") + return + + sql = """SELECT schema_name + FROM information_schema.schemata; """ + + cursor = connection.cursor() + cursor.execute(sql) + + schemas = cursor.fetchall() + print(schemas) + + AUTO_ADDED_SCHEMA = "auto_added_schema" + + currentText = self.pg_schema_combo_box.currentText() + + # Remove all items that were not added by the user + index_to_remove = self.pg_schema_combo_box.findData(AUTO_ADDED_SCHEMA) + while index_to_remove > -1: + self.pg_schema_combo_box.removeItem(index_to_remove) + index_to_remove = self.pg_schema_combo_box.findData(AUTO_ADDED_SCHEMA) + + for schema in schemas: + self.pg_schema_combo_box.addItem(schema[0], AUTO_ADDED_SCHEMA) + + currentTextIndex = self.pg_schema_combo_box.findText(currentText) + if currentTextIndex > -1: + self.pg_schema_combo_box.setCurrentIndex(currentTextIndex) + + connection.close() diff --git a/QgisModelBaker/ui/pg_settings_panel.ui b/QgisModelBaker/ui/pg_settings_panel.ui index d0f2291ed..0ec660531 100644 --- a/QgisModelBaker/ui/pg_settings_panel.ui +++ b/QgisModelBaker/ui/pg_settings_panel.ui @@ -69,13 +69,6 @@ - - - - - - - @@ -110,6 +103,9 @@ + + + @@ -124,7 +120,7 @@ pg_host_line_edit pg_port_line_edit pg_database_line_edit - pg_schema_line_edit + pg_schema_combo_box pg_use_super_login From 9fa4a9d12b375058a72cc60931d0049421a184ca Mon Sep 17 00:00:00 2001 From: Damiano Lombardi Date: Thu, 9 Nov 2023 14:38:58 +0100 Subject: [PATCH 2/6] Update QgisModelBaker/gui/panel/pg_config_panel.py --- QgisModelBaker/gui/panel/pg_config_panel.py | 1 - 1 file changed, 1 deletion(-) diff --git a/QgisModelBaker/gui/panel/pg_config_panel.py b/QgisModelBaker/gui/panel/pg_config_panel.py index 535f408d3..3deda41e8 100644 --- a/QgisModelBaker/gui/panel/pg_config_panel.py +++ b/QgisModelBaker/gui/panel/pg_config_panel.py @@ -470,7 +470,6 @@ def _fill_schema_combo_box(self): cursor.execute(sql) schemas = cursor.fetchall() - print(schemas) AUTO_ADDED_SCHEMA = "auto_added_schema" From 3dc7e3cfd5d1ef0ae550d2288c4fe1d10bb8b7b6 Mon Sep 17 00:00:00 2001 From: Damiano Lombardi Date: Fri, 10 Nov 2023 17:07:15 +0100 Subject: [PATCH 3/6] Move quering of schemas to library --- QgisModelBaker/gui/panel/db_config_panel.py | 2 +- QgisModelBaker/gui/panel/gpkg_config_panel.py | 3 - .../gui/panel/mssql_config_panel.py | 5 -- QgisModelBaker/gui/panel/pg_config_panel.py | 67 ++++++++++--------- 4 files changed, 38 insertions(+), 39 deletions(-) diff --git a/QgisModelBaker/gui/panel/db_config_panel.py b/QgisModelBaker/gui/panel/db_config_panel.py index 2a55a01f8..31b570dde 100644 --- a/QgisModelBaker/gui/panel/db_config_panel.py +++ b/QgisModelBaker/gui/panel/db_config_panel.py @@ -38,7 +38,7 @@ class DbConfigPanel(QWidget, metaclass=AbstractQWidgetMeta): :type notify_field_modified: pyqtSignal(str) """ - notify_fields_modified = pyqtSignal(str) + notify_fields_modified = pyqtSignal() def __init__(self, parent: QWidget, db_action_type: DbActionType): """ diff --git a/QgisModelBaker/gui/panel/gpkg_config_panel.py b/QgisModelBaker/gui/panel/gpkg_config_panel.py index ee26a799e..cc7d2eea4 100644 --- a/QgisModelBaker/gui/panel/gpkg_config_panel.py +++ b/QgisModelBaker/gui/panel/gpkg_config_panel.py @@ -17,7 +17,6 @@ """ import logging -from qgis.PyQt.QtCore import pyqtSignal from qgis.PyQt.QtGui import QValidator from QgisModelBaker.libs.modelbaker.utils.globals import DbActionType @@ -41,8 +40,6 @@ class GpkgConfigPanel(DbConfigPanel, WIDGET_UI): :type notify_field_modified: pyqtSignal(str) """ - notify_fields_modified = pyqtSignal(str) - ValidExtensions = ["gpkg", "GPKG"] def __init__(self, parent, db_action_type): diff --git a/QgisModelBaker/gui/panel/mssql_config_panel.py b/QgisModelBaker/gui/panel/mssql_config_panel.py index 8b3f75a61..d5fa152a8 100644 --- a/QgisModelBaker/gui/panel/mssql_config_panel.py +++ b/QgisModelBaker/gui/panel/mssql_config_panel.py @@ -17,8 +17,6 @@ """ import logging -from qgis.PyQt.QtCore import pyqtSignal - from QgisModelBaker.libs.modelbaker.utils.globals import DbActionType from QgisModelBaker.libs.modelbaker.utils.qt_utils import ( NonEmptyStringValidator, @@ -33,9 +31,6 @@ class MssqlConfigPanel(DbConfigPanel, WIDGET_UI): - - notify_fields_modified = pyqtSignal(str) - def __init__(self, parent, db_action_type): DbConfigPanel.__init__(self, parent, db_action_type) self.setupUi(self) diff --git a/QgisModelBaker/gui/panel/pg_config_panel.py b/QgisModelBaker/gui/panel/pg_config_panel.py index 3deda41e8..caae0f377 100644 --- a/QgisModelBaker/gui/panel/pg_config_panel.py +++ b/QgisModelBaker/gui/panel/pg_config_panel.py @@ -19,12 +19,14 @@ import logging from enum import IntEnum -import psycopg2 -import psycopg2.extras -from psycopg2 import OperationalError -from qgis.PyQt.QtCore import Qt, pyqtSignal +from qgis.PyQt.QtCore import Qt, QTimer import QgisModelBaker.libs.modelbaker.libs.pgserviceparser as pgserviceparser +import QgisModelBaker.libs.modelbaker.utils.db_utils as db_utils +from QgisModelBaker.libs.modelbaker.iliwrapper.globals import DbIliMode +from QgisModelBaker.libs.modelbaker.iliwrapper.ili2dbconfig import ( + Ili2DbCommandConfiguration, +) from QgisModelBaker.libs.modelbaker.utils.globals import DbActionType from QgisModelBaker.libs.modelbaker.utils.qt_utils import ( NonEmptyStringValidator, @@ -55,7 +57,7 @@ class _SERVICE_COMBOBOX_ROLE(IntEnum): DBAUTHID = Qt.UserRole + 7 SSLMODE = Qt.UserRole + 8 - notify_fields_modified = pyqtSignal(str) + REFRESH_SCHEMAS_TIMEOUT_MS = 500 def __init__(self, parent, db_action_type): DbConfigPanel.__init__(self, parent, db_action_type) @@ -63,6 +65,10 @@ def __init__(self, parent, db_action_type): self._current_service = None + self._fill_schema_combo_box_timer = QTimer() + self._fill_schema_combo_box_timer.setSingleShot(True) + self._fill_schema_combo_box_timer.timeout.connect(self._fill_schema_combo_box) + from QgisModelBaker.libs.modelbaker.iliwrapper.ili2dbconfig import ( BaseConfiguration, ) @@ -98,9 +104,9 @@ def __init__(self, parent, db_action_type): self.validators.validate_line_edits ) - self.pg_host_line_edit.textChanged.connect(self.notify_fields_modified) - self.pg_port_line_edit.textChanged.connect(self.notify_fields_modified) - self.pg_database_line_edit.textChanged.connect(self.notify_fields_modified) + self.pg_host_line_edit.textChanged.connect(self._fields_modified) + self.pg_port_line_edit.textChanged.connect(self._fields_modified) + self.pg_database_line_edit.textChanged.connect(self._fields_modified) self.pg_schema_combo_box.currentTextChanged.connect(self.notify_fields_modified) # Fill pg_services combo box @@ -307,7 +313,7 @@ def _pg_service_combo_box_changed(self): self.pg_port_line_edit.setText(service_config.get("port", "")) self.pg_auth_settings.setUsername(service_config.get("user", "")) self.pg_database_line_edit.setText(service_config.get("dbname", "")) - self.pg_schema_combo_box.setText("") + self.pg_schema_combo_box.setCurrentText("") self.pg_auth_settings.setPassword(service_config.get("password", "")) self.pg_auth_settings.setConfigId("") @@ -402,6 +408,9 @@ def _pg_service_combo_box_changed(self): == self.pg_ssl_mode_combo_box.findData(None) ) + logging.info("_pg_service_combo_box_changed") + self._fields_modified() + def _keep_custom_settings(self): index = self.pg_service_combo_box.findData( @@ -429,7 +438,7 @@ def _keep_custom_settings(self): ) self.pg_service_combo_box.setItemData( index, - self.pg_schema_combo_box.text().strip().lower(), + self.pg_schema_combo_box.currentText().strip().lower(), PgConfigPanel._SERVICE_COMBOBOX_ROLE.DBSCHEMA, ) self.pg_service_combo_box.setItemData( @@ -448,28 +457,28 @@ def _keep_custom_settings(self): PgConfigPanel._SERVICE_COMBOBOX_ROLE.SSLMODE, ) + def _fields_modified(self): + + self._fill_schema_combo_box_timer.start(self.REFRESH_SCHEMAS_TIMEOUT_MS) + + self.notify_fields_modified.emit() + def _fill_schema_combo_box(self): - connection = None - try: - connection = psycopg2.connect( - dbname=self.pg_database_line_edit.text(), - user=self.pg_auth_settings.username(), - password=self.pg_auth_settings.password(), - host=self.pg_host_line_edit.text(), - port=self.pg_port_line_edit.text(), - ) - except OperationalError as exception: - logging.warning(f"Pg connection error: {exception}") - return + configuration = Ili2DbCommandConfiguration() - sql = """SELECT schema_name - FROM information_schema.schemata; """ + mode = DbIliMode.pg + self.get_fields(configuration) - cursor = connection.cursor() - cursor.execute(sql) + configuration.tool = mode + configuration.db_ili_version = db_utils.db_ili_version(configuration) - schemas = cursor.fetchall() + db_connector = db_utils.get_db_connector(configuration) + if not db_connector: + logging.warning("Refresh schema list connection error") + return + + schemas = db_connector.get_schemas() AUTO_ADDED_SCHEMA = "auto_added_schema" @@ -482,10 +491,8 @@ def _fill_schema_combo_box(self): index_to_remove = self.pg_schema_combo_box.findData(AUTO_ADDED_SCHEMA) for schema in schemas: - self.pg_schema_combo_box.addItem(schema[0], AUTO_ADDED_SCHEMA) + self.pg_schema_combo_box.addItem(schema, AUTO_ADDED_SCHEMA) currentTextIndex = self.pg_schema_combo_box.findText(currentText) if currentTextIndex > -1: self.pg_schema_combo_box.setCurrentIndex(currentTextIndex) - - connection.close() From d8c3194e52c380b166c4cfeb979656aed0f0ac3d Mon Sep 17 00:00:00 2001 From: Damiano Lombardi Date: Fri, 10 Nov 2023 21:37:02 +0100 Subject: [PATCH 4/6] QComboBox don't have setPlaceholderText before Qt 5.15 --- QgisModelBaker/gui/panel/pg_config_panel.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/QgisModelBaker/gui/panel/pg_config_panel.py b/QgisModelBaker/gui/panel/pg_config_panel.py index caae0f377..603611c78 100644 --- a/QgisModelBaker/gui/panel/pg_config_panel.py +++ b/QgisModelBaker/gui/panel/pg_config_panel.py @@ -179,9 +179,11 @@ def _show_panel(self): self._db_action_type == DbActionType.GENERATE or self._db_action_type == DbActionType.IMPORT_DATA ): - self.pg_schema_combo_box.setPlaceholderText(self.tr("Schema Name")) + self.pg_schema_combo_box.lineEdit().setPlaceholderText( + self.tr("Schema Name") + ) elif self._db_action_type == DbActionType.EXPORT: - self.pg_schema_combo_box.setPlaceholderText( + self.pg_schema_combo_box.lineEdit().setPlaceholderText( self.tr("[Enter a valid schema]") ) else: From 1e800eb9b3f87033ef6849d482fbfd5c3193527e Mon Sep 17 00:00:00 2001 From: Damiano Lombardi Date: Sun, 12 Nov 2023 07:00:58 +0100 Subject: [PATCH 5/6] Update QgisModelBaker/gui/panel/pg_config_panel.py Remove debug print --- QgisModelBaker/gui/panel/pg_config_panel.py | 1 - 1 file changed, 1 deletion(-) diff --git a/QgisModelBaker/gui/panel/pg_config_panel.py b/QgisModelBaker/gui/panel/pg_config_panel.py index 603611c78..6f8a63590 100644 --- a/QgisModelBaker/gui/panel/pg_config_panel.py +++ b/QgisModelBaker/gui/panel/pg_config_panel.py @@ -410,7 +410,6 @@ def _pg_service_combo_box_changed(self): == self.pg_ssl_mode_combo_box.findData(None) ) - logging.info("_pg_service_combo_box_changed") self._fields_modified() def _keep_custom_settings(self): From 411088c985b758bd6d4df81f277a5b90e5fd30a6 Mon Sep 17 00:00:00 2001 From: Damiano Lombardi Date: Fri, 17 Nov 2023 20:12:06 +0100 Subject: [PATCH 6/6] Align tested qgis versions with the library --- .github/workflows/continuous_integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index fd9a46d21..45521375b 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -18,7 +18,7 @@ jobs: strategy: fail-fast: false matrix: - qgis_version: [focal-3.16, 3.22-jammy, latest] + qgis_version: [3.22-jammy, 3.28-jammy, latest] env: QGIS_TEST_VERSION: ${{ matrix.qgis_version }} steps: