Skip to content

Commit b6ccf03

Browse files
authoredOct 29, 2024··
Merge branch '14-lomakkeen-tuottaminen-templaatin-konfiguraatiosta' into 45-lisää-kaavamääräysryhmä-napin-implementaatio-templaattiformiin

7 files changed

+360
-5
lines changed
 
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class UnexpectedNoneError(Exception):
2+
"""Internal QGIS errors that should not be happened"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
from importlib import resources
2+
3+
from qgis.core import QgsProviderRegistry
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, QDialogButtonBox, QLineEdit, QMessageBox, QPushButton, QTableView
8+
9+
from arho_feature_template.core.exceptions import UnexpectedNoneError
10+
11+
ui_path = resources.files(__package__) / "load_plan_dialog.ui"
12+
13+
LoadPlanDialogBase, _ = uic.loadUiType(ui_path)
14+
15+
16+
class PlanFilterProxyModel(QSortFilterProxyModel):
17+
def filterAcceptsRow(self, source_row, source_parent): # noqa: N802
18+
model = self.sourceModel()
19+
if not model:
20+
return False
21+
22+
filter_text = self.filterRegularExpression().pattern()
23+
if not filter_text:
24+
return True
25+
26+
for column in range(5):
27+
index = model.index(source_row, column, source_parent)
28+
data = model.data(index)
29+
if data and filter_text.lower() in data.lower():
30+
return True
31+
32+
return False
33+
34+
35+
class LoadPlanDialog(QDialog, LoadPlanDialogBase): # type: ignore
36+
connectionComboBox: QComboBox # noqa: N815
37+
push_button_load: QPushButton
38+
planTableView: QTableView # noqa: N815
39+
searchLineEdit: QLineEdit # noqa: N815
40+
buttonBox: QDialogButtonBox # noqa: N815
41+
42+
def __init__(self, parent, connections):
43+
super().__init__(parent)
44+
self.setupUi(self)
45+
46+
self._selected_plan_id = None
47+
48+
self.buttonBox.rejected.connect(self.reject)
49+
self.buttonBox.accepted.connect(self.accept)
50+
self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False)
51+
52+
self.push_button_load.clicked.connect(self.load_plans)
53+
self.searchLineEdit.textChanged.connect(self.filter_plans)
54+
55+
self.connectionComboBox.addItems(connections)
56+
57+
self.planTableView.setSelectionMode(QTableView.SingleSelection)
58+
self.planTableView.setSelectionBehavior(QTableView.SelectRows)
59+
self.planTableView.selectionModel().selectionChanged.connect(self.on_selection_changed)
60+
61+
self.model = QStandardItemModel()
62+
self.model.setColumnCount(5)
63+
self.model.setHorizontalHeaderLabels(
64+
[
65+
"ID",
66+
"Tuottajan kaavatunnus",
67+
"Nimi",
68+
"Kaavan elinkaaren tila",
69+
"Kaavalaji",
70+
]
71+
)
72+
73+
self.filterProxyModel = PlanFilterProxyModel()
74+
self.filterProxyModel.setSourceModel(self.model)
75+
self.filterProxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
76+
77+
self.planTableView.setModel(self.filterProxyModel)
78+
79+
def load_plans(self):
80+
self.model.removeRows(0, self.model.rowCount())
81+
82+
selected_connection = self.connectionComboBox.currentText()
83+
if not selected_connection:
84+
self.planTableView.setModel(QStandardItemModel())
85+
return
86+
87+
provider_registry = QgsProviderRegistry.instance()
88+
if provider_registry is None:
89+
raise UnexpectedNoneError
90+
postgres_provider_metadata = provider_registry.providerMetadata("postgres")
91+
if postgres_provider_metadata is None:
92+
raise UnexpectedNoneError
93+
94+
try:
95+
connection = postgres_provider_metadata.createConnection(selected_connection)
96+
plans = connection.executeSql("""
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+
for plan in plans:
115+
self.model.appendRow([QStandardItem(column) for column in plan])
116+
117+
except Exception as e: # noqa: BLE001
118+
QMessageBox.critical(self, "Error", f"Failed to load plans: {e}")
119+
self.model.removeRows(0, self.model.rowCount())
120+
121+
def filter_plans(self):
122+
search_text = self.searchLineEdit.text()
123+
if search_text:
124+
search_regex = QRegularExpression(search_text)
125+
self.filterProxyModel.setFilterRegularExpression(search_regex)
126+
else:
127+
self.filterProxyModel.setFilterRegularExpression("")
128+
129+
def on_selection_changed(self):
130+
# Enable the OK button only if a row is selected
131+
selection = self.planTableView.selectionModel().selectedRows()
132+
ok_button = self.buttonBox.button(QDialogButtonBox.Ok)
133+
if selection:
134+
selected_row = selection[0].row()
135+
self._selected_plan_id = self.planTableView.model().index(selected_row, 0).data()
136+
ok_button.setEnabled(True)
137+
else:
138+
self._selected_plan_id = None
139+
ok_button.setEnabled(False)
140+
141+
def get_selected_connection(self):
142+
return self.connectionComboBox.currentText()
143+
144+
def get_selected_plan_id(self):
145+
return self._selected_plan_id
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ui version="4.0">
3+
<class>LoadPlanDialog</class>
4+
<widget class="QDialog" name="LoadPlanDialog">
5+
<property name="geometry">
6+
<rect>
7+
<x>0</x>
8+
<y>0</y>
9+
<width>800</width>
10+
<height>600</height>
11+
</rect>
12+
</property>
13+
<property name="minimumSize">
14+
<size>
15+
<width>800</width>
16+
<height>600</height>
17+
</size>
18+
</property>
19+
<property name="windowTitle">
20+
<string>Avaa kaava</string>
21+
</property>
22+
<layout class="QVBoxLayout" name="verticalLayout">
23+
<item>
24+
<widget class="QLabel" name="label">
25+
<property name="text">
26+
<string>Valitse tietokantayhteys:</string>
27+
</property>
28+
</widget>
29+
</item>
30+
<item>
31+
<layout class="QHBoxLayout" name="horizontalLayout">
32+
<item>
33+
<widget class="QComboBox" name="connectionComboBox">
34+
<property name="sizePolicy">
35+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
36+
<horstretch>0</horstretch>
37+
<verstretch>0</verstretch>
38+
</sizepolicy>
39+
</property>
40+
</widget>
41+
</item>
42+
<item>
43+
<widget class="QPushButton" name="push_button_load">
44+
<property name="text">
45+
<string>Lataa kaavat</string>
46+
</property>
47+
</widget>
48+
</item>
49+
</layout>
50+
</item>
51+
<item>
52+
<spacer name="spacer1">
53+
<property name="orientation">
54+
<enum>Qt::Vertical</enum>
55+
</property>
56+
<property name="sizeType">
57+
<enum>QSizePolicy::Expanding</enum>
58+
</property>
59+
<property name="sizeHint" stdset="0">
60+
<size>
61+
<width>0</width>
62+
<height>0</height>
63+
</size>
64+
</property>
65+
</spacer>
66+
</item>
67+
<item>
68+
<widget class="QLabel" name="label_search">
69+
<property name="text">
70+
<string>Etsi kaavoja:</string>
71+
</property>
72+
</widget>
73+
</item>
74+
<item>
75+
<widget class="QLineEdit" name="searchLineEdit">
76+
<property name="sizePolicy">
77+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
78+
<horstretch>0</horstretch>
79+
<verstretch>0</verstretch>
80+
</sizepolicy>
81+
</property>
82+
<property name="placeholderText">
83+
<string>Etsi kaavoja...</string>
84+
</property>
85+
</widget>
86+
</item>
87+
<item>
88+
<spacer name="spacer2">
89+
<property name="orientation">
90+
<enum>Qt::Vertical</enum>
91+
</property>
92+
<property name="sizeType">
93+
<enum>QSizePolicy::Expanding</enum>
94+
</property>
95+
<property name="sizeHint" stdset="0">
96+
<size>
97+
<width>0</width>
98+
<height>0</height>
99+
</size>
100+
</property>
101+
</spacer>
102+
</item>
103+
<item>
104+
<widget class="QLabel" name="label_plan_table">
105+
<property name="text">
106+
<string>Kaavat:</string>
107+
</property>
108+
</widget>
109+
</item>
110+
<item>
111+
<widget class="QTableView" name="planTableView">
112+
<property name="sizePolicy">
113+
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
114+
<horstretch>1</horstretch>
115+
<verstretch>1</verstretch>
116+
</sizepolicy>
117+
</property>
118+
</widget>
119+
</item>
120+
<item>
121+
<spacer name="spacer3">
122+
<property name="orientation">
123+
<enum>Qt::Vertical</enum>
124+
</property>
125+
<property name="sizeType">
126+
<enum>QSizePolicy::Expanding</enum>
127+
</property>
128+
<property name="sizeHint" stdset="0">
129+
<size>
130+
<width>0</width>
131+
<height>0</height>
132+
</size>
133+
</property>
134+
</spacer>
135+
</item>
136+
<item>
137+
<widget class="QDialogButtonBox" name="buttonBox">
138+
<property name="standardButtons">
139+
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
140+
</property>
141+
</widget>
142+
</item>
143+
</layout>
144+
</widget>
145+
<resources/>
146+
<connections/>
147+
</ui>

‎arho_feature_template/gui/plan_regulation_group_widget.py

+7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from qgis.core import QgsApplication
88
from qgis.PyQt import uic
9+
from qgis.PyQt.QtCore import pyqtSignal
910
from qgis.PyQt.QtGui import QFont, QIcon
1011
from qgis.PyQt.QtWidgets import QLabel, QLineEdit, QWidget
1112

@@ -24,6 +25,8 @@
2425
class PlanRegulationGroupWidget(QWidget, FormClass): # type: ignore
2526
"""A widget representation of a plan regulation group."""
2627

28+
delete_signal = pyqtSignal(QWidget)
29+
2730
def __init__(self, feature: Feature):
2831
super().__init__()
2932
self.setupUi(self)
@@ -62,9 +65,13 @@ def __init__(self, feature: Feature):
6265
if child.layer == "plan_requlation":
6366
self.create_widgets_for_plan_regulation(child)
6467

68+
def request_delete(self):
69+
self.delete_signal.emit(self)
70+
6571
def init_buttons(self):
6672
self.conf_btn.setIcon(QIcon(plugin_path("resources", "icons", "settings.svg")))
6773
self.del_btn.setIcon(QgsApplication.getThemeIcon("mActionDeleteSelected.svg"))
74+
self.del_btn.clicked.connect(self.request_delete)
6875

6976
def create_widgets_for_plan_regulation(self, plan_regulation_feature: Feature):
7077
row = self.plan_regulation_grid_layout.rowCount() + 1

‎arho_feature_template/gui/template_attribute_form.py

+5
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,15 @@ def remove_spacer(self):
6565

6666
def add_plan_regulation_group(self, feature_config: Feature):
6767
new_plan_regulation_group = PlanRegulationGroupWidget(feature_config)
68+
new_plan_regulation_group.delete_signal.connect(self.remove_plan_regulation_group)
6869
self.remove_spacer()
6970
self.plan_regulation_group_scrollarea_contents.layout().addWidget(new_plan_regulation_group)
7071
self.add_spacer()
7172

73+
def remove_plan_regulation_group(self, plan_regulation_group_widget: PlanRegulationGroupWidget):
74+
self.plan_regulation_group_scrollarea_contents.layout().removeWidget(plan_regulation_group_widget)
75+
plan_regulation_group_widget.deleteLater()
76+
7277
def init_add_plan_regulation_group_btn(self):
7378
menu = QMenu()
7479
for config in self.available_plan_regulation_group_configs:

‎arho_feature_template/plugin.py

+28-5
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@
55

66
from qgis.PyQt.QtCore import QCoreApplication, Qt, QTranslator
77
from qgis.PyQt.QtGui import QIcon
8-
from qgis.PyQt.QtWidgets import QAction, QWidget
8+
from qgis.PyQt.QtWidgets import QAction, QDialog, QMessageBox, QWidget
99
from qgis.utils import iface
1010

1111
from arho_feature_template.core.feature_template_library import FeatureTemplater, TemplateGeometryDigitizeMapTool
1212
from arho_feature_template.core.new_plan import NewPlan
13+
from arho_feature_template.core.update_plan import LandUsePlan, update_selected_plan
14+
from arho_feature_template.gui.load_plan_dialog import LoadPlanDialog
1315
from arho_feature_template.qgis_plugin_tools.tools.custom_logging import setup_logger, teardown_logger
1416
from arho_feature_template.qgis_plugin_tools.tools.i18n import setup_translation
1517
from arho_feature_template.qgis_plugin_tools.tools.resources import plugin_name
18+
from arho_feature_template.utils.db_utils import get_existing_database_connection_names
1619
from arho_feature_template.utils.misc_utils import PLUGIN_PATH
1720

1821
if TYPE_CHECKING:
@@ -134,6 +137,7 @@ def initGui(self) -> None: # noqa N802
134137

135138
iface.mapCanvas().mapToolSet.connect(self.templater.digitize_map_tool.deactivate)
136139

140+
# Add main plugin action to the toolbar
137141
self.template_dock_action = self.add_action(
138142
"",
139143
"Feature Templates",
@@ -146,11 +150,11 @@ def initGui(self) -> None: # noqa N802
146150

147151
self.new_land_use_plan_action = self.add_action(
148152
plan_icon_path,
149-
"Create New Plan",
150-
self.digitize_new_plan,
153+
"Create New Land Use Plan",
154+
self.add_new_plan,
151155
add_to_menu=True,
152156
add_to_toolbar=True,
153-
status_tip="Create a new plan",
157+
status_tip="Create a new land use plan",
154158
)
155159

156160
self.load_land_use_plan_action = self.add_action(
@@ -165,12 +169,31 @@ def on_map_tool_changed(self, new_tool: QgsMapTool, old_tool: QgsMapTool) -> Non
165169
if not isinstance(new_tool, TemplateGeometryDigitizeMapTool):
166170
self.template_dock_action.setChecked(False)
167171

168-
def digitize_new_plan(self):
172+
def add_new_plan(self):
169173
self.new_plan.add_new_plan()
170174

171175
def load_existing_land_use_plan(self) -> None:
172176
"""Open existing land use plan."""
173177

178+
connections = get_existing_database_connection_names()
179+
180+
if not connections:
181+
QMessageBox.critical(None, "Error", "No database connections found.")
182+
return
183+
184+
dialog = LoadPlanDialog(None, connections)
185+
186+
if dialog.exec_() == QDialog.Accepted:
187+
selected_plan_id = dialog.get_selected_plan_id()
188+
189+
if not selected_plan_id:
190+
QMessageBox.critical(None, "Error", "No plan was selected.")
191+
return
192+
193+
plan = LandUsePlan(selected_plan_id)
194+
195+
update_selected_plan(plan)
196+
174197
def unload(self) -> None:
175198
"""Removes the plugin menu item and icon from QGIS GUI."""
176199
for action in self.actions:
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from __future__ import annotations
2+
3+
import logging
4+
5+
from qgis.core import QgsProviderRegistry
6+
7+
from arho_feature_template.core.exceptions import UnexpectedNoneError
8+
9+
LOGGER = logging.getLogger("LandUsePlugin")
10+
11+
12+
def get_existing_database_connection_names() -> list[str]:
13+
"""
14+
Retrieve the list of existing database connections from QGIS settings.
15+
16+
:return: A set of available PostgreSQL connection names.
17+
"""
18+
19+
provider_registry = QgsProviderRegistry.instance()
20+
if provider_registry is None:
21+
raise UnexpectedNoneError
22+
postgres_provider_metadata = provider_registry.providerMetadata("postgres")
23+
if postgres_provider_metadata is None:
24+
raise UnexpectedNoneError
25+
26+
return list(postgres_provider_metadata.dbConnections(False))

0 commit comments

Comments
 (0)
Please sign in to comment.