Skip to content

Commit 8b16179

Browse files
authored
46 poista kaavamääräysryhmä -napin implementaatio templaattilomakkeeseen (#48)
1 parent 7d52c7d commit 8b16179

15 files changed

+555
-15
lines changed

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,7 @@ the one with the path `.venv\Scripts\python.exe`.
6161
This plugin is distributed under the terms of the [GNU General Public License, version 2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) license.
6262

6363
See [LICENSE](LICENSE) for more information.
64+
65+
### Attributations
66+
<a href="https://www.flaticon.com/free-icons/open" title="open icons">Open icons created by Smashicons - Flaticon</a>
67+
<a href="https://www.flaticon.com/free-icons/land-use" title="land use icons">Land use icons created by Fusion5085 - Flaticon</a>
+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"""
+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from qgis.core import QgsProject, QgsVectorLayer
2+
from qgis.utils import iface
3+
4+
from arho_feature_template.core.update_plan import LandUsePlan, update_selected_plan
5+
6+
7+
class NewPlan:
8+
def add_new_plan(self):
9+
# Filtered layers are not editable, so clear filters first.
10+
self.clear_all_filters()
11+
12+
layers = QgsProject.instance().mapLayersByName("Kaava")
13+
if not layers:
14+
iface.messageBar().pushMessage("Error", "Layer 'Kaava' not found", level=3)
15+
return
16+
17+
kaava_layer = layers[0]
18+
19+
if not kaava_layer.isEditable():
20+
kaava_layer.startEditing()
21+
22+
iface.setActiveLayer(kaava_layer)
23+
24+
iface.actionAddFeature().trigger()
25+
26+
# Connect the featureAdded signal
27+
kaava_layer.featureAdded.connect(self.feature_added)
28+
29+
def feature_added(self):
30+
kaava_layer = iface.activeLayer()
31+
kaava_layer.featureAdded.disconnect()
32+
feature_ids_before_commit = kaava_layer.allFeatureIds()
33+
if kaava_layer.isEditable():
34+
if not kaava_layer.commitChanges():
35+
iface.messageBar().pushMessage("Error", "Failed to commit changes to the layer.", level=3)
36+
return
37+
else:
38+
iface.messageBar().pushMessage("Error", "Layer is not editable.", level=3)
39+
return
40+
41+
feature_ids_after_commit = kaava_layer.allFeatureIds()
42+
43+
# Find the new plan.id by comparing fids before and after commit.
44+
new_feature_id = next((fid for fid in feature_ids_after_commit if fid not in feature_ids_before_commit), None)
45+
46+
if new_feature_id is not None:
47+
new_feature = kaava_layer.getFeature(new_feature_id)
48+
49+
if new_feature.isValid():
50+
feature_id_value = new_feature["id"]
51+
update_selected_plan(LandUsePlan(feature_id_value))
52+
else:
53+
iface.messageBar().pushMessage("Error", "Invalid feature retrieved.", level=3)
54+
else:
55+
iface.messageBar().pushMessage("Error", "No new feature was added.", level=3)
56+
57+
def clear_all_filters(self):
58+
"""Clear filters for all vector layers in the project."""
59+
layers = QgsProject.instance().mapLayers().values()
60+
61+
for layer in layers:
62+
if isinstance(layer, QgsVectorLayer):
63+
layer.setSubsetString("")
+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from dataclasses import dataclass
2+
3+
from qgis.core import QgsMapLayer, QgsProject, QgsVectorLayer
4+
from qgis.utils import iface
5+
6+
7+
# To be extended and moved
8+
@dataclass
9+
class LandUsePlan:
10+
id: str
11+
12+
13+
# To be replaced later
14+
LAYER_PLAN_ID_MAP = {
15+
"Kaava": "id",
16+
"Maankäytön kohteet": "plan_id",
17+
"Muut pisteet": "plan_id",
18+
"Viivat": "plan_id",
19+
"Aluevaraus": "plan_id",
20+
"Osa-alue": "plan_id",
21+
}
22+
23+
24+
def update_selected_plan(new_plan: LandUsePlan):
25+
"""Update the project layers based on the selected land use plan."""
26+
plan_id = new_plan.id
27+
28+
for layer_name, field_name in LAYER_PLAN_ID_MAP.items():
29+
# Set the filter on each layer using the plan_id
30+
set_filter_for_vector_layer(layer_name, field_name, plan_id)
31+
32+
33+
def set_filter_for_vector_layer(layer_name: str, field_name: str, field_value: str):
34+
"""Set a filter for the given vector layer."""
35+
layers = QgsProject.instance().mapLayersByName(layer_name)
36+
37+
if not _check_layer_count(layers):
38+
return
39+
40+
layer = layers[0]
41+
42+
expression = f"\"{field_name}\" = '{field_value}'"
43+
44+
# Apply the filter to the layer
45+
if not layer.setSubsetString(expression):
46+
iface.messageBar().pushMessage("Error", f"Failed to filter layer {layer_name} with query {expression}", level=3)
47+
48+
49+
def _check_layer_count(layers: list) -> bool:
50+
"""Check if any layers are returned."""
51+
if not layers:
52+
iface.messageBar().pushMessage("Error", "ERROR: No layers found with the specified name.", level=3)
53+
return False
54+
return True
55+
56+
57+
def _check_vector_layer(layer: QgsMapLayer) -> bool:
58+
"""Check if the given layer is a vector layer."""
59+
if not isinstance(layer, QgsVectorLayer):
60+
iface.messageBar().pushMessage("Error", f"Layer {layer.name()} is not a vector layer: {type(layer)}", level=3)
61+
return False
62+
return True
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

0 commit comments

Comments
 (0)