Skip to content

Commit 01fa4f2

Browse files
committed
add editing for plan layer
- add toolbar action for editing plan attributes - modify plan model to allow Nones for all attributes - add set_value methods for CodeComboBoxes
1 parent 4bc98ae commit 01fa4f2

File tree

6 files changed

+159
-46
lines changed

6 files changed

+159
-46
lines changed

arho_feature_template/core/models.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -331,14 +331,14 @@ def from_config_data(cls, data: dict) -> PlanFeature:
331331

332332
@dataclass
333333
class Plan:
334-
name: str
335-
description: str | None
336-
plan_type_id: str
337-
lifecycle_status_id: str
338-
record_number: str | None
339-
matter_management_identifier: str | None
340-
permanent_plan_identifier: str | None
341-
producers_plan_identifier: str | None
334+
name: str | None = None
335+
description: str | None = None
336+
plan_type_id: str | None = None
337+
lifecycle_status_id: str | None = None
338+
record_number: str | None = None
339+
matter_management_identifier: str | None = None
340+
permanent_plan_identifier: str | None = None
341+
producers_plan_identifier: str | None = None
342342
organisation_id: str | None = None
343343
general_regulations: list[RegulationGroup] = field(default_factory=list)
344344
geom: QgsGeometry | None = None

arho_feature_template/core/plan_manager.py

+46-26
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from arho_feature_template.core.lambda_service import LambdaService
1212
from arho_feature_template.core.models import (
1313
FeatureTemplateLibrary,
14+
Plan,
1415
PlanFeature,
1516
RegulationGroupCategory,
1617
RegulationGroupLibrary,
@@ -50,7 +51,7 @@
5051
if TYPE_CHECKING:
5152
from qgis.core import QgsFeature
5253

53-
from arho_feature_template.core.models import Plan, Regulation, RegulationGroup
54+
from arho_feature_template.core.models import Regulation, RegulationGroup
5455

5556
logger = logging.getLogger(__name__)
5657

@@ -154,6 +155,22 @@ def add_new_plan(self):
154155
self.digitize_map_tool.setLayer(plan_layer)
155156
iface.mapCanvas().setMapTool(self.digitize_map_tool)
156157

158+
def edit_plan(self):
159+
plan_layer = PlanLayer.get_from_project()
160+
if not plan_layer:
161+
return
162+
163+
active_plan_id = QgsExpressionContextUtils.projectScope(QgsProject.instance()).variable("active_plan_id")
164+
feature = PlanLayer.get_feature_by_id(active_plan_id)
165+
if feature is None:
166+
iface.messageBar().pushWarning("", "No active/open plan found!")
167+
return
168+
plan_model = PlanLayer.model_from_feature(feature)
169+
170+
attribute_form = PlanAttributeForm(plan_model, self.regulation_group_libraries)
171+
if attribute_form.exec_():
172+
feature = save_plan(attribute_form.model)
173+
157174
def add_new_plan_feature(self):
158175
if not handle_unsaved_changes():
159176
return
@@ -180,11 +197,10 @@ def _plan_geom_digitized(self, feature: QgsFeature):
180197
if not plan_layer:
181198
return
182199

183-
attribute_form = PlanAttributeForm(self.regulation_group_libraries)
200+
plan_model = Plan(geom=feature.geometry())
201+
attribute_form = PlanAttributeForm(plan_model, self.regulation_group_libraries)
184202
if attribute_form.exec_():
185-
plan_attributes = attribute_form.get_plan_attributes()
186-
plan_attributes.geom = feature.geometry()
187-
feature = save_plan(plan_attributes)
203+
feature = save_plan(attribute_form.model)
188204
plan_to_be_activated = feature["id"]
189205
else:
190206
plan_to_be_activated = self.previous_active_plan_id
@@ -379,33 +395,37 @@ def _delete_feature(feature: QgsFeature, layer: QgsVectorLayer, delete_text: str
379395
layer.commitChanges(stopEditing=False)
380396

381397

382-
def save_plan(plan_data: Plan) -> QgsFeature:
383-
plan_layer = PlanLayer.get_from_project()
384-
in_edit_mode = plan_layer.isEditable()
385-
if not in_edit_mode:
386-
plan_layer.startEditing()
387-
388-
edit_message = "Kaavan lisäys" if plan_data.id_ is None else "Kaavan muokkaus"
389-
plan_layer.beginEditCommand(edit_message)
390-
391-
# plan_data.organisation_id = "99e20d66-9730-4110-815f-5947d3f8abd3"
392-
plan_feature = PlanLayer.feature_from_model(plan_data)
398+
def save_plan(plan: Plan) -> QgsFeature:
399+
feature = PlanLayer.feature_from_model(plan)
400+
layer = PlanLayer.get_from_project()
393401

394-
if plan_data.id_ is None:
395-
plan_layer.addFeature(plan_feature)
396-
else:
397-
plan_layer.updateFeature(plan_feature)
402+
editing = plan.id_ is not None
403+
_save_feature(
404+
feature=feature,
405+
layer=layer,
406+
id_=plan.id_,
407+
edit_text="Kaavan muokkaus" if editing else "Kaavan luominen",
408+
)
398409

399-
plan_layer.endEditCommand()
400-
plan_layer.commitChanges(stopEditing=False)
410+
# Check for deleted general regulations
411+
if editing:
412+
for association in RegulationGroupAssociationLayer.get_dangling_associations(
413+
plan.general_regulations, feature["id"], PlanLayer.name
414+
):
415+
_delete_feature(
416+
association,
417+
RegulationGroupAssociationLayer.get_from_project(),
418+
"Kaavamääräysryhmän assosiaation poisto",
419+
)
401420

402-
if plan_data.general_regulations:
403-
for regulation_group in plan_data.general_regulations:
404-
plan_id = plan_feature["id"]
421+
# Save general regulations
422+
if plan.general_regulations:
423+
for regulation_group in plan.general_regulations:
424+
plan_id = feature["id"]
405425
regulation_group_feature = save_regulation_group(regulation_group, plan_id)
406426
save_regulation_group_association(regulation_group_feature["id"], PlanLayer.name, plan_id)
407427

408-
return plan_feature
428+
return feature
409429

410430

411431
def save_plan_feature(plan_feature: PlanFeature, plan_id: str | None = None) -> QgsFeature:

arho_feature_template/gui/components/code_combobox.py

+40-2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,17 @@ def populate_from_code_layer(self, layer_type: type[AbstractCodeLayer]) -> None:
3939
def value(self) -> str:
4040
return self.currentData()
4141

42+
def set_value(self, value: str | None) -> None:
43+
if value is None:
44+
self.setCurrentIndex(0)
45+
return
46+
47+
index = self.findData(value)
48+
if index != -1:
49+
self.setCurrentIndex(index)
50+
else:
51+
self.setCurrentIndex(0) # Set NULL if not found
52+
4253

4354
class HierarchicalCodeComboBox(QComboBox):
4455
def __init__(self, parent=None):
@@ -56,8 +67,8 @@ def __init__(self, parent=None):
5667
null_item = QTreeWidgetItem(["NULL"])
5768
null_item.setData(0, Qt.UserRole, None)
5869
self.tree_widget.addTopLevelItem(null_item)
59-
null_index = self.tree_widget.indexFromItem(null_item)
60-
self.tree_widget.setCurrentIndex(null_index)
70+
self.null_index = self.tree_widget.indexFromItem(null_item)
71+
self.tree_widget.setCurrentIndex(self.null_index)
6172

6273
def populate_from_code_layer(self, layer_type: type[AbstractCodeLayer]) -> None:
6374
try:
@@ -90,3 +101,30 @@ def populate_from_code_layer(self, layer_type: type[AbstractCodeLayer]) -> None:
90101
def value(self) -> str:
91102
item = self.tree_widget.selectedItems()[0]
92103
return item.data(0, Qt.UserRole)
104+
105+
def set_value(self, value: str | None) -> None:
106+
# NOTE: Does not work fully currently
107+
108+
def find_item_recursive(item: QTreeWidgetItem, value: str) -> QTreeWidgetItem:
109+
if item.data(0, Qt.UserRole) == value:
110+
return item
111+
for i in range(item.childCount()):
112+
found_item = find_item_recursive(item.child(i), value)
113+
if found_item:
114+
return found_item
115+
return None
116+
117+
if value is None:
118+
self.setCurrentIndex(0)
119+
return
120+
121+
for i in range(self.count()):
122+
found_item = find_item_recursive(self.tree_widget.topLevelItem(i), value)
123+
if found_item:
124+
idx = self.tree_widget.indexFromItem(found_item)
125+
self.setRootModelIndex(idx.parent())
126+
self.setCurrentIndex(idx.row())
127+
self.setRootModelIndex(self.null_index.parent())
128+
return
129+
130+
self.setCurrentIndex(0) # Set combobox index to NULL

arho_feature_template/gui/dialogs/plan_attribute_form.py

+32-3
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,33 @@ class PlanAttributeForm(QDialog, FormClass): # type: ignore
5151

5252
button_box: QDialogButtonBox
5353

54-
def __init__(self, regulation_group_libraries: list[RegulationGroupLibrary], parent=None):
54+
def __init__(self, plan: Plan, regulation_group_libraries: list[RegulationGroupLibrary], parent=None):
5555
super().__init__(parent)
5656

5757
self.setupUi(self)
5858

59+
self.plan = plan
60+
5961
self.plan_type_combo_box.populate_from_code_layer(PlanTypeLayer)
6062
self.lifecycle_status_combo_box.populate_from_code_layer(LifeCycleStatusLayer)
6163
self.organisation_combo_box.populate_from_code_layer(OrganisationLayer)
6264

65+
self.plan_type_combo_box.set_value(plan.plan_type_id)
66+
self.lifecycle_status_combo_box.set_value(plan.lifecycle_status_id)
67+
self.organisation_combo_box.set_value(plan.organisation_id)
68+
self.name_line_edit.setText(plan.name if plan.name else "")
69+
self.description_text_edit.setText(plan.description if plan.description else "")
70+
self.permanent_identifier_line_edit.setText(
71+
plan.permanent_plan_identifier if plan.permanent_plan_identifier else ""
72+
)
73+
self.record_number_line_edit.setText(plan.record_number if plan.record_number else "")
74+
self.producers_plan_identifier_line_edit.setText(
75+
plan.producers_plan_identifier if plan.producers_plan_identifier else ""
76+
)
77+
self.matter_management_identifier_line_edit.setText(
78+
plan.matter_management_identifier if plan.matter_management_identifier else ""
79+
)
80+
6381
self.name_line_edit.textChanged.connect(self._check_required_fields)
6482
self.organisation_combo_box.currentIndexChanged.connect(self._check_required_fields)
6583
self.plan_type_combo_box.currentIndexChanged.connect(self._check_required_fields)
@@ -74,7 +92,13 @@ def __init__(self, regulation_group_libraries: list[RegulationGroupLibrary], par
7492

7593
self.regulation_groups_selection_widget.tree.itemDoubleClicked.connect(self.add_selected_plan_regulation_group)
7694

95+
for regulation_group in plan.general_regulations:
96+
self.add_plan_regulation_group(regulation_group)
97+
7798
self.button_box.button(QDialogButtonBox.Ok).setEnabled(False)
99+
self.button_box.accepted.connect(self._on_ok_clicked)
100+
101+
self._check_required_fields()
78102

79103
def _check_required_fields(self) -> None:
80104
ok_button = self.button_box.button(QDialogButtonBox.Ok)
@@ -129,9 +153,9 @@ def init_plan_regulation_group_library(self, library: RegulationGroupLibrary):
129153

130154
# ---
131155

132-
def get_plan_attributes(self) -> Plan:
156+
def into_model(self) -> Plan:
133157
return Plan(
134-
id_=None,
158+
id_=self.plan.id_,
135159
name=self.name_line_edit.text(),
136160
description=self.description_text_edit.toPlainText() or None,
137161
plan_type_id=self.plan_type_combo_box.value(),
@@ -142,4 +166,9 @@ def get_plan_attributes(self) -> Plan:
142166
matter_management_identifier=self.matter_management_identifier_line_edit.text() or None,
143167
lifecycle_status_id=self.lifecycle_status_combo_box.value(),
144168
general_regulations=[reg_group_widget.into_model() for reg_group_widget in self.regulation_group_widgets],
169+
geom=self.plan.geom,
145170
)
171+
172+
def _on_ok_clicked(self):
173+
self.model = self.into_model()
174+
self.accept()

arho_feature_template/plugin.py

+10
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,16 @@ def initGui(self) -> None: # noqa N802
167167
status_tip="Lataa/avaa kaava",
168168
)
169169

170+
self.edit_land_use_plan_action = self.add_action(
171+
text="Muokkaa kaavaa",
172+
# icon=QgsApplication.getThemeIcon("mActionFileOpen.svg"),
173+
triggered_callback=self.plan_manager.edit_plan,
174+
parent=iface.mainWindow(),
175+
add_to_menu=True,
176+
add_to_toolbar=True,
177+
status_tip="Muokkaa aktiivisen kaavan tietoja",
178+
)
179+
170180
self.new_feature_dock_action = self.add_action(
171181
text="Luo kaavakohde",
172182
icon=QgsApplication.getThemeIcon("mIconFieldGeometry.svg"),

arho_feature_template/project/layers/plan_layers.py

+23-7
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,18 @@
55
from numbers import Number
66
from string import Template
77
from textwrap import dedent
8-
from typing import TYPE_CHECKING, Any, ClassVar
8+
from typing import Any, ClassVar
99

1010
from qgis.core import NULL, QgsExpressionContextUtils, QgsFeature, QgsProject, QgsVectorLayerUtils
1111
from qgis.utils import iface
1212

13-
from arho_feature_template.core.models import PlanFeature, Regulation, RegulationGroup, RegulationLibrary
13+
from arho_feature_template.core.models import Plan, PlanFeature, Regulation, RegulationGroup, RegulationLibrary
1414
from arho_feature_template.exceptions import FeatureNotFoundError, LayerEditableError
1515
from arho_feature_template.project.layers import AbstractLayer
1616
from arho_feature_template.project.layers.code_layers import PlanRegulationTypeLayer
1717

1818
logger = logging.getLogger(__name__)
1919

20-
if TYPE_CHECKING:
21-
from arho_feature_template.core.models import Plan
22-
2320

2421
class AbstractPlanLayer(AbstractLayer):
2522
filter_template: ClassVar[Template]
@@ -65,13 +62,12 @@ class PlanLayer(AbstractPlanLayer):
6562

6663
@classmethod
6764
def feature_from_model(cls, model: Plan) -> QgsFeature:
68-
layer = cls.get_from_project()
65+
feature = cls.initialize_feature_from_model(model)
6966

7067
if not model.geom:
7168
message = "Plan must have a geometry to be added to the layer"
7269
raise ValueError(message)
7370

74-
feature = QgsVectorLayerUtils.createFeature(layer, model.geom)
7571
feature["name"] = {"fin": model.name}
7672
feature["description"] = {"fin": model.description}
7773
feature["permanent_plan_identifier"] = model.permanent_plan_identifier
@@ -84,6 +80,26 @@ def feature_from_model(cls, model: Plan) -> QgsFeature:
8480

8581
return feature
8682

83+
@classmethod
84+
def model_from_feature(cls, feature: QgsFeature) -> Plan:
85+
return Plan(
86+
geom=feature.geometry(),
87+
name=feature["name"]["fin"],
88+
description=feature["description"]["fin"],
89+
permanent_plan_identifier=feature["permanent_plan_identifier"],
90+
record_number=feature["record_number"],
91+
producers_plan_identifier=feature["producers_plan_identifier"],
92+
matter_management_identifier=feature["matter_management_identifier"],
93+
plan_type_id=feature["plan_type_id"],
94+
lifecycle_status_id=feature["lifecycle_status_id"],
95+
organisation_id=feature["organisation_id"],
96+
general_regulations=[
97+
RegulationGroupLayer.model_from_feature(RegulationGroupLayer.get_feature_by_id(group_id))
98+
for group_id in RegulationGroupAssociationLayer.get_group_ids_for_feature(feature["id"], cls.name)
99+
],
100+
id_=feature["id"],
101+
)
102+
87103

88104
class PlanFeatureLayer(AbstractPlanLayer):
89105
@classmethod

0 commit comments

Comments
 (0)