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

Add remove service settings #31

Merged
merged 4 commits into from
May 17, 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
17 changes: 17 additions & 0 deletions pg_service_parser/conf/service_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Settings available for manual addition
# See https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS
SERVICE_SETTINGS = {
"host": "localhost",
"port": "5432",
"dbname": "test",
"user": "",
"password": "",
"passfile": "",
}

# Settings to initialize new files
SETTINGS_TEMPLATE = {
"host": "localhost",
"port": "5432",
"dbname": "test",
}
69 changes: 54 additions & 15 deletions pg_service_parser/core/item_models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from qgis.PyQt.QtCore import QAbstractTableModel, Qt, pyqtSignal
from qgis.PyQt.QtCore import QAbstractTableModel, QModelIndex, Qt, pyqtSignal
from qgis.PyQt.QtGui import QColorConstants, QFont


Expand All @@ -8,19 +8,43 @@ class ServiceConfigModel(QAbstractTableModel):

is_dirty_changed = pyqtSignal(bool) # Whether the model gets dirty or not

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

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

def columnCount(self, parent):
def columnCount(self, parent=QModelIndex()):
return 2

def index_to_setting_key(self, index):
return list(self.__model_data.keys())[index.row()]

def add_settings(self, settings: dict):
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount() + len(settings) - 1)
self.__model_data.update(settings)
self.__set_dirty_status(True)
self.endInsertRows()

if self.__model_data == self.__original_data:
self.__set_dirty_status(False)

def remove_setting(self, index: QModelIndex):
if not index.isValid():
return

self.beginRemoveRows(QModelIndex(), index.row(), index.row())
del self.__model_data[list(self.__model_data.keys())[index.row()]]
self.__set_dirty_status(True)
self.endRemoveRows()

if self.__model_data == self.__original_data:
self.__set_dirty_status(False)

def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return None
Expand All @@ -38,15 +62,18 @@ def data(self, index, role=Qt.DisplayRole):
font = QFont()
font.setBold(True)
return font
elif (
index.column() == self.VALUE_COL
and self.__model_data[key] != self.__original_data[key]
elif index.column() == self.VALUE_COL and (
key not in self.__original_data
or self.__model_data[key] != self.__original_data[key]
):
font = QFont()
font.setItalic(True)
return font
elif role == Qt.ForegroundRole and index.column() == self.VALUE_COL:
if self.__model_data[key] != self.__original_data[key]:
if (
key not in self.__original_data
or self.__model_data[key] != self.__original_data[key]
):
return QColorConstants.DarkGreen

return None
Expand All @@ -59,13 +86,11 @@ def setData(self, index, value, role=Qt.EditRole) -> bool:
if value != self.__model_data[key]:
self.__model_data[key] = value

if value != self.__original_data[key]:
self.__dirty = True
self.is_dirty_changed.emit(True)
if key not in self.__original_data or value != self.__original_data[key]:
self.__set_dirty_status(True)
else:
if self.__model_data == self.__original_data:
self.__dirty = False
self.is_dirty_changed.emit(False)
self.__set_dirty_status(False)

return True

Expand All @@ -84,6 +109,13 @@ def flags(self, idx):
def is_dirty(self):
return self.__dirty

def __set_dirty_status(self, status: bool):
self.__dirty = status
self.is_dirty_changed.emit(status)

def current_setting_keys(self) -> list[str]:
return list(self.__model_data.keys())

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

Expand All @@ -93,5 +125,12 @@ def service_name(self):
def set_not_dirty(self):
# Data saved in the provider
self.__original_data = self.__model_data.copy()
self.__dirty = False
self.is_dirty_changed.emit(False)
self.__set_dirty_status(False)

def invalid_settings(self):
"""
Validation for service entries.

:return: List of invalid settings.
"""
return [k for k, v in self.__model_data.items() if v.strip() == ""]
10 changes: 7 additions & 3 deletions pg_service_parser/core/pg_service_parser_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@


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


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


def add_new_service(service_name: str, conf_file_path: Optional[str] = None) -> bool:
return create_service(service_name, {}, conf_file_path)


def service_config(service_name: str, conf_file_path: Optional[str] = None) -> dict:
return pgserviceparser.service_config(service_name, conf_file_path)

Expand All @@ -37,7 +40,8 @@ def create_service(
config.write(f)

if service_name in config:
pgserviceparser.write_service(service_name, settings)
if settings:
pgserviceparser.write_service(service_name, settings)
return True

return False
Expand Down
87 changes: 84 additions & 3 deletions pg_service_parser/gui/dlg_pg_service.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
from pathlib import Path

from qgis.core import QgsApplication
from qgis.gui import QgsMessageBar
from qgis.PyQt.QtCore import Qt, pyqtSlot
from qgis.PyQt.QtWidgets import QDialog, QMessageBox, QSizePolicy

from pg_service_parser.conf.service_settings import SERVICE_SETTINGS, SETTINGS_TEMPLATE
from pg_service_parser.core.item_models import ServiceConfigModel
from pg_service_parser.core.pg_service_parser_wrapper import (
add_new_service,
conf_path,
copy_service_settings,
service_config,
service_names,
write_service,
)
from pg_service_parser.gui.dlg_service_name import ServiceNameDialog
from pg_service_parser.gui.dlg_service_settings import ServiceSettingsDialog
from pg_service_parser.utils import get_ui_class

DIALOG_UI = get_ui_class("pg_service_dialog.ui")
Expand All @@ -23,26 +30,46 @@ def __init__(self, parent):
QDialog.__init__(self, parent)
self.setupUi(self)

# Flag to handle initialization of new files
self.__new_empty_file = False

conf_file_path = conf_path()
if not conf_file_path:
self.__initialize_dialog(conf_file_path)

def __initialize_dialog(self, conf_file_path):
if not conf_file_path.exists():
self.btnCreateServiceFile.setIcon(QgsApplication.getThemeIcon("/mActionNewPage.svg"))
self.btnCreateServiceFile.clicked.connect(self.__create_file_clicked)
self.lblConfFile.setText("Config file not found!")
self.lblConfFile.setToolTip(
"Set your PGSERVICEFILE environment variable and reopen the dialog."
not_found_tooltip = (
"Create a config file at a default location or\n"
"set your PGSERVICEFILE environment variable and reopen the dialog."
)
self.lblConfFile.setToolTip(not_found_tooltip)
self.lblWarning.setToolTip(not_found_tooltip)
self.txtConfFile.setVisible(False)
self.tabWidget.setEnabled(False)
return

self.__edit_model = None

self.btnAddSettings.setIcon(QgsApplication.getThemeIcon("/symbologyAdd.svg"))
self.btnRemoveSetting.setIcon(QgsApplication.getThemeIcon("/symbologyRemove.svg"))
self.txtConfFile.setText(str(conf_file_path))
self.lblWarning.setVisible(False)
self.lblConfFile.setText("Config file path found at ")
self.lblConfFile.setToolTip("")
self.txtConfFile.setVisible(True)
self.tabWidget.setEnabled(True)
self.btnCreateServiceFile.setVisible(False)

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.btnAddSettings.clicked.connect(self.__add_settings_clicked)
self.btnRemoveSetting.clicked.connect(self.__remove_setting_clicked)
self.btnUpdateService.clicked.connect(self.__update_service_clicked)

self.__initialize_edit_services()
Expand All @@ -53,6 +80,19 @@ def __init__(self, parent):
self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
self.layout().insertWidget(0, self.bar)

@pyqtSlot()
def __create_file_clicked(self):
dlg = ServiceNameDialog(self)
dlg.exec_()
if dlg.result() == QDialog.Accepted:
path = conf_path()
Path.touch(path)
add_new_service(dlg.service_name)

# Set flag to get a template after some initialization
self.__new_empty_file = True
self.__initialize_dialog(path)

@pyqtSlot(bool)
def __update_target_controls(self, checked):
self.cboTargetService.setEnabled(self.radOverwrite.isChecked())
Expand Down Expand Up @@ -157,9 +197,50 @@ def __edit_service_changed(self, index):
self.__edit_model.is_dirty_changed.connect(self.btnUpdateService.setEnabled)
self.btnUpdateService.setDisabled(True)

if self.__new_empty_file:
# Add service template
self.__edit_model.add_settings(SETTINGS_TEMPLATE)
self.__new_empty_file = False

@pyqtSlot()
def __add_settings_clicked(self):
dlg = ServiceSettingsDialog(self, self.__edit_model.current_setting_keys())
dlg.exec_()

if dlg.settings_to_add:
settings = {k: v for k, v in SERVICE_SETTINGS.items() if k in dlg.settings_to_add}
self.__edit_model.add_settings(settings)

@pyqtSlot()
def __remove_setting_clicked(self):
selected_indexes = self.tblServiceConfig.selectedIndexes()
if selected_indexes:
setting_key = self.__edit_model.index_to_setting_key(selected_indexes[0])
if (
QMessageBox.question(
self,
"Remove service setting",
f"Are you sure you want to remove the '{setting_key}' setting?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No,
)
== QMessageBox.Yes
):
self.__edit_model.remove_setting(selected_indexes[0])

@pyqtSlot()
def __update_service_clicked(self):
if self.__edit_model and self.__edit_model.is_dirty():
invalid = self.__edit_model.invalid_settings()
if invalid:
self.bar.pushWarning(
"PG service",
"Settings '{}' have invalid values. Adjust them and try again.".format(
"', '".join(invalid)
),
)
return

target_service = self.cboEditService.currentText()
write_service(target_service, self.__edit_model.service_config())
self.bar.pushSuccess("PG service", f"PG service '{target_service}' updated!")
Expand Down
21 changes: 21 additions & 0 deletions pg_service_parser/gui/dlg_service_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from qgis.PyQt.QtCore import pyqtSlot
from qgis.PyQt.QtWidgets import QDialog

from pg_service_parser.utils import get_ui_class

DIALOG_UI = get_ui_class("service_name_dialog.ui")


class ServiceNameDialog(QDialog, DIALOG_UI):

def __init__(self, parent):
QDialog.__init__(self, parent)
self.setupUi(self)

self.buttonBox.accepted.connect(self.__accepted)
self.service_name = "my-service"

@pyqtSlot()
def __accepted(self):
if self.txtServiceName.text().strip():
self.service_name = self.txtServiceName.text().replace(" ", "-")
24 changes: 24 additions & 0 deletions pg_service_parser/gui/dlg_service_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from qgis.PyQt.QtCore import pyqtSlot
from qgis.PyQt.QtWidgets import QDialog

from pg_service_parser.conf.service_settings import SERVICE_SETTINGS
from pg_service_parser.utils import get_ui_class

DIALOG_UI = get_ui_class("service_settings_dialog.ui")


class ServiceSettingsDialog(QDialog, DIALOG_UI):

def __init__(self, parent, settings_to_hide: list[str]):
QDialog.__init__(self, parent)
self.setupUi(self)

self.buttonBox.accepted.connect(self.__accepted)

settings = set(SERVICE_SETTINGS.keys()) - set(settings_to_hide)
self.lstSettings.addItems(settings)
self.settings_to_add = []

@pyqtSlot()
def __accepted(self):
self.settings_to_add = [item.text() for item in self.lstSettings.selectedItems()]
Loading