Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Edit service settings via tableView #4

Merged
merged 2 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,3 @@ repos:
ci:
autofix_prs: true
autoupdate_schedule: quarterly

1 change: 0 additions & 1 deletion pg_service_parser/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import os.path


DEFAULT_PG_SERVICE_PATH = os.path.expanduser("~/.pg_service.conf")
Empty file.
72 changes: 72 additions & 0 deletions pg_service_parser/core/item_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from qgis.PyQt.QtCore import QAbstractTableModel, Qt
from qgis.PyQt.QtGui import QFont


class ServiceConfigModel(QAbstractTableModel):
KEY_COL = 0
VALUE_COL = 1

def __init__(self, service_name, service_config):
super().__init__()
self.__service_name = service_name
self.__model_data = service_config
self.__dirty = False

def rowCount(self, parent):
return len(self.__model_data)

def columnCount(self, parent):
return 2

def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return None

key = list(self.__model_data.keys())[index.row()]
if role == Qt.DisplayRole:
if index.column() == self.KEY_COL:
return key
elif index.column() == self.VALUE_COL:
return self.__model_data[key]
elif role == Qt.EditRole and index.column() == self.VALUE_COL:
return self.__model_data[key]
elif role == Qt.FontRole and index.column() == self.KEY_COL:
font = QFont()
font.setBold(True)
return font

return None

def setData(self, index, value, role=Qt.EditRole) -> bool:
if not index.isValid():
return False

key = list(self.__model_data.keys())[index.row()]
if value != self.__model_data[key]:
self.__model_data[key] = value
self.__dirty = True
return True

return False

def flags(self, idx):
if not idx.isValid():
return ~Qt.ItemIsSelectable & ~Qt.ItemIsEnabled

_flags = Qt.ItemIsSelectable | Qt.ItemIsEnabled
if idx.column() == self.KEY_COL:
return _flags
elif idx.column() == self.VALUE_COL:
return _flags | Qt.ItemIsEditable

def is_dirty(self):
return self.__dirty

def service_config(self):
return self.__model_data.copy()

def service_name(self):
return self.__service_name

def set_not_dirty(self):
self.__dirty = False
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import os.path
from typing import List, Optional

import pgserviceparser
from typing import (List,
Optional)


def conf_path() -> str:
path = pgserviceparser.conf_path()
return path if os.path.exists(path) else None


def service_names(conf_file_path: Optional[str] = None) -> List[str]:
return pgserviceparser.service_names(conf_file_path)

Expand All @@ -18,9 +18,9 @@ def service_config(service_name: str, conf_file_path: Optional[str] = None) -> d


def write_service_settings(
service_name: str,
settings: dict,
conf_file_path: Optional[str] = None,
service_name: str,
settings: dict,
conf_file_path: Optional[str] = None,
) -> bool:
"""Returns true if it could write the settings to the file.

Expand All @@ -39,13 +39,15 @@ def write_service_settings(
return False


def create_service(service_name: str, settings: dict, conf_file_path: Optional[str] = None) -> bool:
def create_service(
service_name: str, settings: dict, conf_file_path: Optional[str] = None
) -> bool:
config = pgserviceparser.full_config(conf_file_path)
if service_name in config:
return False

config.add_section(service_name)
with open(conf_file_path or pgserviceparser.conf_path(), 'w') as f:
with open(conf_file_path or pgserviceparser.conf_path(), "w") as f:
config.write(f)

if service_name in config:
Expand All @@ -54,7 +56,9 @@ def create_service(service_name: str, settings: dict, conf_file_path: Optional[s
return False


def copy_service_settings(source_service_name: str, target_service_name: str, conf_file_path: Optional[str] = None) -> bool:
def copy_service_settings(
source_service_name: str, target_service_name: str, conf_file_path: Optional[str] = None
) -> bool:
settings = pgserviceparser.service_config(source_service_name, conf_file_path)

config = pgserviceparser.full_config(conf_file_path)
Expand All @@ -67,12 +71,17 @@ def copy_service_settings(source_service_name: str, target_service_name: str, co
return res


if __name__ == '__main__':
if __name__ == "__main__":
assert service_names() == []

# Add new service
_settings = {'host': 'localhost', 'port': '5432', 'user': 'postgres', 'password': 'secret',
'dbname': 'qgis_test_db'}
_settings = {
"host": "localhost",
"port": "5432",
"user": "postgres",
"password": "secret",
"dbname": "qgis_test_db",
}
assert create_service("qgis-test", _settings)
assert service_names() == ["qgis-test"]

Expand All @@ -82,8 +91,13 @@ def copy_service_settings(source_service_name: str, target_service_name: str, co
assert service_config("qgis-demo") == _settings

# Add new service
_settings = {'host': 'localhost', 'port': '5433', 'user': 'admin', 'password': 'secret',
'dbname': 'qgis_test_db2'}
_settings = {
"host": "localhost",
"port": "5433",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"port": "5433",
"port": "5432",

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those are/were just quick tests. There I was changing parameters so that the overwrite result was even more evident.

BTW, those quick tests could eventually become unit tests in this repo.

"user": "admin",
"password": "secret",
"dbname": "qgis_test_db2",
}
assert create_service("qgis-new-test", _settings)
assert service_names() == ["qgis-test", "qgis-demo", "qgis-new-test"]
assert service_config("qgis-new-test") == _settings
Expand Down
97 changes: 84 additions & 13 deletions pg_service_parser/gui/dlg_pg_service.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
from qgis.PyQt.QtCore import (Qt,
pyqtSlot)
from qgis.PyQt.QtWidgets import (QDialog,
QSizePolicy)
from qgis.gui import QgsMessageBar

from pg_service_parser.pg_service_parser_wrapper import (conf_path,
copy_service_settings,
service_names)
from qgis.PyQt.QtCore import Qt, pyqtSlot
from qgis.PyQt.QtWidgets import QDialog, QMessageBox, QSizePolicy

from pg_service_parser.core.item_models import ServiceConfigModel
from pg_service_parser.core.pg_service_parser_wrapper import (
conf_path,
copy_service_settings,
service_config,
service_names,
write_service_settings,
)
from pg_service_parser.utils import get_ui_class

DIALOG_UI = get_ui_class('pg_service_dialog.ui')
DIALOG_UI = get_ui_class("pg_service_dialog.ui")
COPY_TAB_INDEX = 0
EDIT_TAB_INDEX = 1

Expand All @@ -23,18 +26,24 @@ def __init__(self, parent):
conf_file_path = conf_path()
if not conf_file_path:
self.lblConfFile.setText("Config file not found!")
self.lblConfFile.setToolTip("Set your PGSERVICEFILE environment variable and reopen the dialog.")
self.lblConfFile.setToolTip(
"Set your PGSERVICEFILE environment variable and reopen the dialog."
)
self.txtConfFile.setVisible(False)
self.tabWidget.setEnabled(False)
return

self.__edit_model = None

self.txtConfFile.setText(conf_file_path)
self.lblWarning.setVisible(False)
self.tabWidget.setTabEnabled(EDIT_TAB_INDEX, False) # Not yet implemented

self.radOverwrite.toggled.connect(self.__update_target_controls)
self.btnCopyService.clicked.connect(self.__copy_service)
self.cboSourceService.currentIndexChanged.connect(self.__source_service_changed)
self.tabWidget.currentChanged.connect(self.__current_tab_changed)
self.cboEditService.currentIndexChanged.connect(self.__edit_service_changed)
self.btnUpdateService.clicked.connect(self.__update_service_clicked)

self.__initialize_copy_services()
self.__update_target_controls(True)
Expand Down Expand Up @@ -72,25 +81,87 @@ def __initialize_copy_services(self):
self.cboSourceService.addItems(service_names())
self.cboSourceService.setCurrentText(current_text)

def __initialize_edit_services(self):
self.__edit_model = None
current_text = self.cboEditService.currentText() # Remember latest currentText
self.cboEditService.blockSignals(True) # Avoid triggering custom slot while clearing
self.cboEditService.clear()
self.cboEditService.blockSignals(False)
self.cboEditService.addItems(service_names())
self.cboEditService.setCurrentText(current_text)

@pyqtSlot()
def __copy_service(self):
# Validations
if self.radCreate.isChecked():
if not self.txtNewService.text().strip():
self.bar.pushInfo("PG service", "Enter a service name and try again.")
return
elif self.txtNewService.text().strip() in service_names():
self.bar.pushWarning("PG service", "Service name already exists! Change it and try again.")
self.bar.pushWarning(
"PG service", "Service name already exists! Change it and try again."
)
return
elif self.radOverwrite.isChecked():
if not self.cboTargetService.currentText():
self.bar.pushInfo("PG service", "Select a valid target service and try again.")
return

target_service = self.cboTargetService.currentText() if self.radOverwrite.isChecked() else self.txtNewService.text().strip()
target_service = (
self.cboTargetService.currentText()
if self.radOverwrite.isChecked()
else self.txtNewService.text().strip()
)

if copy_service_settings(self.cboSourceService.currentText(), target_service):
self.bar.pushSuccess("PG service", f"PG service copied to '{target_service}'!")
if self.radCreate.isChecked():
self.__initialize_copy_services() # Reflect the newly added service
else:
self.bar.pushWarning("PG service", "There was a problem copying the service!")

@pyqtSlot(int)
def __current_tab_changed(self, index):
if index == COPY_TAB_INDEX:
# self.__initialize_copy_services()
pass # For now, services to be copied won't be altered in other tabs
elif index == EDIT_TAB_INDEX:
self.__initialize_edit_services()

@pyqtSlot(int)
def __edit_service_changed(self, index):
target_service = self.cboEditService.currentText()
if self.__edit_model and self.__edit_model.is_dirty():
if (
not QMessageBox.question(
self,
"Pending edits",
"There are pending edits for service '{}'. Are you sure you want to discard them?".format(
self.__edit_model.service_name()
),
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No,
)
== QMessageBox.Yes
):

self.cboEditService.blockSignals(True)
self.cboEditService.setCurrentText(self.__edit_model.service_name())
self.cboEditService.blockSignals(False)
return

self.__edit_model = ServiceConfigModel(target_service, service_config(target_service))
self.tblServiceConfig.setModel(self.__edit_model)

@pyqtSlot()
def __update_service_clicked(self):
if self.__edit_model and self.__edit_model.is_dirty():
target_service = self.cboEditService.currentText()
res = write_service_settings(target_service, self.__edit_model.service_config())
if res:
self.bar.pushSuccess("PG service", f"PG service '{target_service}' updated!")
self.__edit_model.set_not_dirty()
else:
self.bar.pushWarning("PG service", "There was a problem updating the service!")
else:
self.bar.pushInfo("PG service", "Edit the service configuration and try again.")
2 changes: 1 addition & 1 deletion pg_service_parser/imgs/warning.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion pg_service_parser/metadata.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ tracker=https://github.com/opengisch/qgis-pg-service-parser-plugin/issues
repository=https://github.com/opengisch/qgis-pg-service-parser-plugin
icon=images/icon.png
experimental=False
deprecated=False
deprecated=False
4 changes: 2 additions & 2 deletions pg_service_parser/pg_service_parser_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
from pg_service_parser.gui.dlg_pg_service import PgServiceDialog


class PgServiceParserPlugin():
class PgServiceParserPlugin:
def __init__(self, iface):
self.iface = iface
self.action = None

def initGui(self):
self.action = QAction('Go!', self.iface.mainWindow())
self.action = QAction("Go!", self.iface.mainWindow())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's create an icon for the plugin (in another PR it's fine)

self.action.triggered.connect(self.run)
self.iface.addToolBarIcon(self.action)

Expand Down
26 changes: 22 additions & 4 deletions pg_service_parser/ui/pg_service_dialog.ui
Original file line number Diff line number Diff line change
Expand Up @@ -205,13 +205,31 @@
</layout>
</item>
<item>
<widget class="QListWidget" name="lstServiceSettings">
<widget class="QTableView" name="tblServiceConfig">
<property name="editTriggers">
<set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed</set>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="modelColumn">
<number>0</number>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="gridStyle">
<enum>Qt::DotLine</enum>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
Expand All @@ -230,7 +248,7 @@
</spacer>
</item>
<item>
<widget class="QPushButton" name="btnCopyService_2">
<widget class="QPushButton" name="btnUpdateService">
<property name="text">
<string>Update service</string>
</property>
Expand Down
Loading