Skip to content

Commit f2d0846

Browse files
Mtk112LKajan
authored andcommitted
Added functionality to load existing plan.
Added dialog for loading plan. Added database connection selection, searchable listing of all plans in the database. Filtering of layers based on id of selected plan.
1 parent 13db67c commit f2d0846

File tree

7 files changed

+554
-5
lines changed

7 files changed

+554
-5
lines changed
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from arho_feature_template.qgis_plugin_tools.tools.exceptions import QgsPluginException
2+
3+
4+
class AuthConfigException(QgsPluginException):
5+
pass
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from __future__ import annotations
2+
3+
from importlib import resources
4+
5+
from qgis.PyQt import uic
6+
from qgis.PyQt.QtWidgets import QDialog, QDialogButtonBox, QLineEdit
7+
8+
# Load the .ui file path using importlib resources
9+
ui_path = resources.files(__package__) / "ask_credentials.ui"
10+
11+
# Use uic.loadUiType to load the UI definition from the .ui file
12+
DbAskCredentialsDialogBase, _ = uic.loadUiType(ui_path)
13+
14+
15+
class DbAskCredentialsDialog(QDialog, DbAskCredentialsDialogBase): # type: ignore
16+
def __init__(self, parent: QDialog = None):
17+
super().__init__(parent)
18+
19+
# Set up the UI from the loaded .ui file
20+
self.setupUi(self)
21+
22+
# The UI elements defined in the .ui file
23+
self.userLineEdit: QLineEdit = self.findChild(QLineEdit, "userLineEdit")
24+
self.pwdLineEdit: QLineEdit = self.findChild(QLineEdit, "pwdLineEdit")
25+
self.buttonBox: QDialogButtonBox = self.findChild(QDialogButtonBox, "buttonBox")
26+
27+
# Connect the OK and Cancel buttons to their respective functions
28+
self.buttonBox.accepted.connect(self.accept)
29+
self.buttonBox.rejected.connect(self.reject)
30+
31+
def get_credentials(self) -> tuple[str, str]:
32+
"""
33+
Returns the entered username and password.
34+
:return: Tuple (username, password)
35+
"""
36+
return self.userLineEdit.text(), self.pwdLineEdit.text()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ui version="4.0">
3+
<class>DbAskCredentialsDialogBase</class>
4+
<widget class="QDialog" name="DbAskCredentialsDialogBase">
5+
<property name="geometry">
6+
<rect>
7+
<x>0</x>
8+
<y>0</y>
9+
<width>400</width>
10+
<height>200</height>
11+
</rect>
12+
</property>
13+
<property name="windowTitle">
14+
<string>Käyttäjän autentikaatio</string>
15+
</property>
16+
<layout class="QVBoxLayout" name="verticalLayout">
17+
<item>
18+
<layout class="QFormLayout" name="formLayout">
19+
<item row="0" column="0">
20+
<widget class="QLabel" name="label_username">
21+
<property name="text">
22+
<string>Käyttäjä:</string>
23+
</property>
24+
</widget>
25+
</item>
26+
<item row="0" column="1">
27+
<widget class="QLineEdit" name="userLineEdit">
28+
<property name="placeholderText">
29+
<string>Käyttäjä...</string>
30+
</property>
31+
</widget>
32+
</item>
33+
<item row="1" column="0">
34+
<widget class="QLabel" name="label_password">
35+
<property name="text">
36+
<string>Salasana:</string>
37+
</property>
38+
</widget>
39+
</item>
40+
<item row="1" column="1">
41+
<widget class="QLineEdit" name="pwdLineEdit">
42+
<property name="placeholderText">
43+
<string>Salasana...</string>
44+
</property>
45+
<property name="echoMode">
46+
<enum>QLineEdit::Password</enum>
47+
</property>
48+
</widget>
49+
</item>
50+
</layout>
51+
</item>
52+
<item>
53+
<widget class="QDialogButtonBox" name="buttonBox">
54+
<property name="orientation">
55+
<enum>Qt::Horizontal</enum>
56+
</property>
57+
<property name="standardButtons">
58+
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
59+
</property>
60+
</widget>
61+
</item>
62+
</layout>
63+
</widget>
64+
<resources/>
65+
<connections/>
66+
</ui>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
from importlib import resources
2+
3+
import psycopg2
4+
from qgis.PyQt import uic
5+
from qgis.PyQt.QtCore import QRegularExpression, QSortFilterProxyModel, Qt
6+
from qgis.PyQt.QtGui import QStandardItem, QStandardItemModel
7+
from qgis.PyQt.QtWidgets import QComboBox, QDialog, QLineEdit, QMessageBox, QPushButton, QTableView
8+
9+
from arho_feature_template.gui.ask_credentials import DbAskCredentialsDialog
10+
from arho_feature_template.utils.db_utils import get_db_connection_params
11+
12+
ui_path = resources.files(__package__) / "load_plan_dialog.ui"
13+
14+
LoadPlanDialogBase, _ = uic.loadUiType(ui_path)
15+
16+
17+
class PlanFilterProxyModel(QSortFilterProxyModel):
18+
def filterAcceptsRow(self, source_row, source_parent): # noqa: N802
19+
model = self.sourceModel()
20+
if not model:
21+
return False
22+
23+
filter_text = self.filterRegularExpression().pattern()
24+
if not filter_text:
25+
return True
26+
27+
for column in range(5):
28+
index = model.index(source_row, column, source_parent)
29+
data = model.data(index)
30+
if data and filter_text.lower() in data.lower():
31+
return True
32+
33+
return False
34+
35+
36+
class LoadPlanDialog(QDialog, LoadPlanDialogBase): # type: ignore
37+
def __init__(self, parent, connections):
38+
super().__init__(parent)
39+
40+
uic.loadUi(ui_path, self)
41+
42+
self.connectionComboBox: QComboBox = self.findChild(QComboBox, "connectionComboBox")
43+
self.planTableView: QTableView = self.findChild(QTableView, "planTableView")
44+
self.okButton: QPushButton = self.findChild(QPushButton, "okButton")
45+
46+
self.searchLineEdit: QLineEdit = self.findChild(QLineEdit, "searchLineEdit")
47+
self.searchLineEdit.setPlaceholderText("Etsi kaavoja...")
48+
49+
self.connectionComboBox.addItems(connections)
50+
51+
self.connectionComboBox.currentIndexChanged.connect(self.load_plans)
52+
self.okButton.clicked.connect(self.accept)
53+
54+
self.okButton.setEnabled(False)
55+
56+
self.filterProxyModel = PlanFilterProxyModel(self)
57+
self.filterProxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
58+
59+
self.planTableView.setModel(self.filterProxyModel)
60+
self.searchLineEdit.textChanged.connect(self.filter_plans)
61+
62+
self.selected_plan_id = None
63+
64+
def load_plans(self):
65+
selected_connection = self.connectionComboBox.currentText()
66+
if not selected_connection:
67+
self.planTableView.setModel(QStandardItemModel())
68+
return
69+
70+
cursor = None
71+
conn = None
72+
73+
try:
74+
conn_params = get_db_connection_params(selected_connection)
75+
76+
if not conn_params.get("user") or not conn_params.get("password"):
77+
# Trigger dialog to ask for missing credentials
78+
dialog = DbAskCredentialsDialog(self)
79+
dialog.rejected.connect(self.reject)
80+
if dialog.exec() == QDialog.Accepted:
81+
user, password = dialog.get_credentials()
82+
conn_params["user"] = user
83+
conn_params["password"] = password
84+
85+
conn = psycopg2.connect(
86+
host=conn_params["host"],
87+
port=conn_params["port"],
88+
dbname=conn_params["dbname"],
89+
user=conn_params["user"],
90+
password=conn_params["password"],
91+
sslmode=conn_params["sslmode"],
92+
)
93+
94+
cursor = conn.cursor()
95+
96+
cursor.execute("""
97+
SELECT
98+
p.id,
99+
p.producers_plan_identifier,
100+
p.name ->> 'fin' AS name_fin,
101+
l.name ->> 'fin' AS lifecycle_status_fin,
102+
pt.name ->> 'fin' AS plan_type_fin
103+
FROM
104+
hame.plan p
105+
LEFT JOIN
106+
codes.lifecycle_status l
107+
ON
108+
p.lifecycle_status_id = l.id
109+
LEFT JOIN
110+
codes.plan_type pt
111+
ON
112+
p.plan_type_id = pt.id;
113+
""")
114+
plans = cursor.fetchall()
115+
116+
model = QStandardItemModel(len(plans), 5)
117+
model.setHorizontalHeaderLabels(
118+
[
119+
"ID",
120+
"Tuottajan kaavatunnus",
121+
"Nimi",
122+
"Kaavan elinkaaren tila",
123+
"Kaavalaji",
124+
]
125+
)
126+
127+
for row_idx, plan in enumerate(plans):
128+
model.setItem(row_idx, 0, QStandardItem(str(plan[0]))) # id
129+
model.setItem(row_idx, 1, QStandardItem(str(plan[1]))) # producer_plan_identifier
130+
model.setItem(row_idx, 2, QStandardItem(str(plan[2]))) # name_fin
131+
model.setItem(row_idx, 3, QStandardItem(str(plan[3]))) # lifecycle_status_fin
132+
model.setItem(row_idx, 4, QStandardItem(str(plan[4]))) # plan_type_fin
133+
134+
self.filterProxyModel.setSourceModel(model)
135+
136+
self.planTableView.setSelectionMode(QTableView.SingleSelection)
137+
self.planTableView.setSelectionBehavior(QTableView.SelectRows)
138+
139+
self.planTableView.selectionModel().selectionChanged.connect(self.on_selection_changed)
140+
141+
except ValueError as ve:
142+
QMessageBox.critical(self, "Connection Error", str(ve))
143+
self.planTableView.setModel(QStandardItemModel())
144+
145+
except Exception as e: # noqa: BLE001
146+
QMessageBox.critical(self, "Error", f"Failed to load plans: {e}")
147+
self.planTableView.setModel(QStandardItemModel())
148+
149+
finally:
150+
if cursor:
151+
cursor.close()
152+
if conn:
153+
conn.close()
154+
155+
def filter_plans(self):
156+
search_text = self.searchLineEdit.text()
157+
if search_text:
158+
search_regex = QRegularExpression(search_text)
159+
self.filterProxyModel.setFilterRegularExpression(search_regex)
160+
else:
161+
self.filterProxyModel.setFilterRegularExpression("")
162+
163+
def on_selection_changed(self):
164+
# Enable the OK button only if a row is selected
165+
selection = self.planTableView.selectionModel().selectedRows()
166+
if selection:
167+
selected_row = selection[0].row()
168+
self.selected_plan_id = self.planTableView.model().index(selected_row, 0).data()
169+
self.okButton.setEnabled(True)
170+
else:
171+
self.selected_plan_id = None
172+
self.okButton.setEnabled(False)
173+
174+
def get_selected_connection(self):
175+
return self.connectionComboBox.currentText()
176+
177+
def get_selected_plan_id(self):
178+
return self.selected_plan_id

0 commit comments

Comments
 (0)