Skip to content

Commit 09b3568

Browse files
committed
add multiple verbal regulation types for regulation widget
1 parent 6089fad commit 09b3568

File tree

5 files changed

+173
-22
lines changed

5 files changed

+173
-22
lines changed

arho_feature_template/core/models.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ class Regulation:
435435
files: list[str] = field(default_factory=list)
436436
theme: str | None = None
437437
topic_tag: str | None = None
438-
verbal_regulation_type_id: str | None = None
438+
verbal_regulation_type_ids: list[str] = field(default_factory=list)
439439
regulation_group_id: str | None = None
440440
id_: str | None = None
441441

arho_feature_template/core/plan_manager.py

+38-2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
PlanRegulationLayer,
4848
RegulationGroupAssociationLayer,
4949
RegulationGroupLayer,
50+
TypeOfVerbalRegulationAssociationLayer,
5051
plan_layers,
5152
)
5253
from arho_feature_template.resources.libraries.feature_templates import feature_template_library_config_files
@@ -700,22 +701,57 @@ def save_regulation(regulation: Regulation) -> QgsFeature | None:
700701
regulation_feature = PlanRegulationLayer.feature_from_model(regulation)
701702
layer = PlanRegulationLayer.get_from_project()
702703

704+
editing = regulation.id_ is not None
703705
if not _save_feature(
704706
feature=regulation_feature,
705707
layer=layer,
706708
id_=regulation.id_,
707-
edit_text="Kaavamääräyksen lisäys" if regulation.id_ is None else "Kaavamääräyksen muokkaus",
709+
edit_text="Kaavamääräyksen muokkaus" if editing else "Kaavamääräyksen lisäys",
708710
):
709711
iface.messageBar().pushCritical("", "Kaavamääräyksen tallentaminen epäonnistui.")
710712
return None
711713

714+
reg_id = regulation_feature["id"]
715+
716+
# Check for deleted verbal regulation types
717+
if editing:
718+
for association in TypeOfVerbalRegulationAssociationLayer.get_dangling_associations(
719+
reg_id, regulation.verbal_regulation_type_ids
720+
):
721+
if not _delete_feature(
722+
association,
723+
TypeOfVerbalRegulationAssociationLayer.get_from_project(),
724+
"Sanallisen kaavamääräyksen lajin assosiaation poisto",
725+
):
726+
iface.messageBar().pushCritical(
727+
"", "Sanallisen kaavamääräyksen lajin assosiaation poistaminen epäonnistui."
728+
)
729+
712730
for additional_information in regulation.additional_information:
713-
additional_information.plan_regulation_id = regulation_feature["id"]
731+
additional_information.plan_regulation_id = reg_id
714732
save_additional_information(additional_information)
715733

734+
for verbal_regulation_type_id in regulation.verbal_regulation_type_ids:
735+
save_type_of_verbal_regulation_association(reg_id, verbal_regulation_type_id)
736+
716737
return regulation_feature
717738

718739

740+
def save_type_of_verbal_regulation_association(regulation_id: str, verbal_regulation_type_id: str) -> bool:
741+
if TypeOfVerbalRegulationAssociationLayer.association_exists(regulation_id, verbal_regulation_type_id):
742+
return True
743+
feature = TypeOfVerbalRegulationAssociationLayer.feature_from(regulation_id, verbal_regulation_type_id)
744+
layer = TypeOfVerbalRegulationAssociationLayer.get_from_project()
745+
746+
if not _save_feature(
747+
feature=feature, layer=layer, id_=None, edit_text="Sanallisen kaavamääräyksen lajin assosiaation lisäys"
748+
):
749+
iface.messageBar().pushCritical("", "Sanallisen kaavamääräyksen lajin assosiaation tallentaminen epäonnistui.")
750+
return False
751+
752+
return True
753+
754+
719755
def save_additional_information(additional_information: AdditionalInformation) -> QgsFeature | None:
720756
feature = AdditionalInformationLayer.feature_from_model(additional_information)
721757
layer = AdditionalInformationLayer.get_from_project()

arho_feature_template/gui/components/plan_regulation_widget.py

+43-16
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,39 @@
11
from __future__ import annotations
22

33
from importlib import resources
4-
from typing import TYPE_CHECKING
4+
from typing import cast
55

66
from qgis.core import QgsApplication
77
from qgis.gui import QgsFileWidget
88
from qgis.PyQt import uic
99
from qgis.PyQt.QtCore import Qt, pyqtSignal
10-
from qgis.PyQt.QtWidgets import QFormLayout, QFrame, QLabel, QLineEdit, QMenu, QToolButton, QVBoxLayout, QWidget
10+
from qgis.PyQt.QtWidgets import (
11+
QFormLayout,
12+
QFrame,
13+
QLabel,
14+
QLineEdit,
15+
QMenu,
16+
QPushButton,
17+
QToolButton,
18+
QVBoxLayout,
19+
QWidget,
20+
)
1121

1222
from arho_feature_template.core.models import (
1323
AdditionalInformation,
1424
AdditionalInformationConfigLibrary,
1525
Regulation,
1626
)
1727
from arho_feature_template.gui.components.additional_information_widget import AdditionalInformationWidget
18-
from arho_feature_template.gui.components.code_combobox import HierarchicalCodeComboBox
1928
from arho_feature_template.gui.components.required_field_label import RequiredFieldLabel
2029
from arho_feature_template.gui.components.value_input_widgets import (
2130
IntegerInputWidget,
2231
SinglelineTextInputWidget,
32+
TypeOfVerbalRegulationWidget,
2333
ValueWidgetManager,
2434
)
25-
from arho_feature_template.project.layers.code_layers import VerbalRegulationType
2635
from arho_feature_template.utils.misc_utils import LANGUAGE, get_layer_by_name
2736

28-
if TYPE_CHECKING:
29-
from qgis.PyQt.QtWidgets import QPushButton
30-
3137
ui_path = resources.files(__package__) / "plan_regulation_widget.ui"
3238
FormClass, _ = uic.loadUiType(ui_path)
3339

@@ -69,7 +75,7 @@ def __init__(self, regulation: Regulation, parent=None):
6975
self.file_widgets: list[QgsFileWidget] = []
7076
self.theme_widget: SinglelineTextInputWidget | None = None
7177
self.topic_tag_widget: SinglelineTextInputWidget | None = None
72-
self.type_of_verbal_regulation_widget: HierarchicalCodeComboBox | None = None
78+
self.type_of_verbal_regulation_widgets: list[TypeOfVerbalRegulationWidget] = []
7379

7480
self.expanded = True
7581
self.additional_information_frame.hide()
@@ -88,11 +94,10 @@ def _init_widgets(self):
8894
self._add_widget(RequiredFieldLabel("Arvo"), self.value_widget_manager.value_widget)
8995

9096
if self.config.regulation_code == "sanallinenMaarays":
91-
self.type_of_verbal_regulation_widget = HierarchicalCodeComboBox()
92-
self.type_of_verbal_regulation_widget.populate_from_code_layer(VerbalRegulationType)
93-
self._add_widget(RequiredFieldLabel("Sanallisen määräyksen laji"), self.type_of_verbal_regulation_widget)
94-
if self.regulation.verbal_regulation_type_id is not None:
95-
self.type_of_verbal_regulation_widget.set_value(self.regulation.verbal_regulation_type_id)
97+
for type_id in self.regulation.verbal_regulation_type_ids:
98+
self._add_type_of_verbal_regulation(type_id)
99+
if len(self.type_of_verbal_regulation_widgets) == 0:
100+
self._add_type_of_verbal_regulation()
96101

97102
# Additional information
98103
for info in self.regulation.additional_information:
@@ -197,7 +202,31 @@ def _add_theme(self, theme_name: str):
197202
self.theme_widget = SinglelineTextInputWidget(theme_name, False)
198203
self._add_widget(QLabel("Kaavoitusteema"), self.theme_widget)
199204

205+
def _add_type_of_verbal_regulation(self, type_id: str | None = None):
206+
if len(self.type_of_verbal_regulation_widgets) == 0:
207+
widget = TypeOfVerbalRegulationWidget(with_add_btn=True)
208+
btn = cast(QPushButton, widget.add_btn)
209+
btn.clicked.connect(self._add_type_of_verbal_regulation)
210+
else:
211+
widget = TypeOfVerbalRegulationWidget(with_del_btn=True)
212+
btn = cast(QPushButton, widget.del_btn)
213+
btn.clicked.connect(lambda: self._delete_type_of_verbal_regulation(widget))
214+
215+
if type_id:
216+
widget.set_value(type_id)
217+
218+
self.type_of_verbal_regulation_widgets.append(widget)
219+
self._add_widget(RequiredFieldLabel("Sanallisen määräyksen laji"), widget)
220+
221+
def _delete_type_of_verbal_regulation(self, widget_to_delete: TypeOfVerbalRegulationWidget):
222+
self.type_of_verbal_regulation_widgets.remove(widget_to_delete)
223+
for label, widget in self.widgets:
224+
if widget is widget_to_delete:
225+
widget.deleteLater()
226+
label.deleteLater()
227+
200228
def into_model(self) -> Regulation:
229+
verbal_regulation_type_ids = [widget.get_value() for widget in self.type_of_verbal_regulation_widgets]
201230
return Regulation(
202231
config=self.config,
203232
value=self.value_widget_manager.into_model() if self.value_widget_manager else None,
@@ -206,8 +235,6 @@ def into_model(self) -> Regulation:
206235
files=[file.filePath() for file in self.file_widgets],
207236
theme=self.theme_widget.get_value() if self.theme_widget else None,
208237
topic_tag=self.topic_tag_widget.get_value() if self.topic_tag_widget else None,
209-
verbal_regulation_type_id=self.type_of_verbal_regulation_widget.value()
210-
if self.type_of_verbal_regulation_widget
211-
else None,
238+
verbal_regulation_type_ids=[value for value in verbal_regulation_type_ids if value is not None],
212239
id_=self.regulation.id_,
213240
)

arho_feature_template/gui/components/value_input_widgets.py

+51-1
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,22 @@
22

33
import logging
44

5+
from qgis.core import QgsApplication
56
from qgis.gui import QgsDoubleSpinBox, QgsSpinBox
6-
from qgis.PyQt.QtWidgets import QFormLayout, QHBoxLayout, QLabel, QLineEdit, QSizePolicy, QTextEdit, QWidget
7+
from qgis.PyQt.QtWidgets import (
8+
QFormLayout,
9+
QHBoxLayout,
10+
QLabel,
11+
QLineEdit,
12+
QPushButton,
13+
QSizePolicy,
14+
QTextEdit,
15+
QWidget,
16+
)
717

818
from arho_feature_template.core.models import AttributeValue, AttributeValueDataType
19+
from arho_feature_template.gui.components.code_combobox import HierarchicalCodeComboBox
20+
from arho_feature_template.project.layers.code_layers import VerbalRegulationType
921

1022
logger = logging.getLogger(__name__)
1123

@@ -144,6 +156,44 @@ def get_value(self) -> tuple[str | None, str | None, str | None]:
144156
return (title if title else None, code_list if code_list else None, code_value if code_value else None)
145157

146158

159+
class TypeOfVerbalRegulationWidget(QWidget):
160+
def __init__(
161+
self,
162+
with_add_btn: bool = False, # noqa: FBT001, FBT002
163+
with_del_btn: bool = False, # noqa: FBT001, FBT002
164+
parent=None,
165+
):
166+
super().__init__(parent)
167+
168+
layout = QHBoxLayout()
169+
layout.setContentsMargins(0, 0, 0, 0)
170+
self.input_widget = HierarchicalCodeComboBox()
171+
self.input_widget.populate_from_code_layer(VerbalRegulationType)
172+
layout.addWidget(self.input_widget)
173+
174+
self.add_btn: QPushButton | None = None
175+
if with_add_btn:
176+
self.add_btn = QPushButton()
177+
self.add_btn.setMaximumWidth(30)
178+
self.add_btn.setIcon(QgsApplication.getThemeIcon("mActionAdd.svg"))
179+
layout.addWidget(self.add_btn)
180+
181+
self.del_btn: QPushButton | None = None
182+
if with_del_btn:
183+
self.del_btn = QPushButton()
184+
self.del_btn.setMaximumWidth(30)
185+
self.del_btn.setIcon(QgsApplication.getThemeIcon("mActionDeleteSelected.svg"))
186+
layout.addWidget(self.del_btn)
187+
188+
self.setLayout(layout)
189+
190+
def set_value(self, value: str | None):
191+
self.input_widget.set_value(value)
192+
193+
def get_value(self) -> str | None:
194+
return self.input_widget.value()
195+
196+
147197
class ValueWidgetManager:
148198
def __init__(self, value: AttributeValue | None, default_value: AttributeValue):
149199
if value is None:

arho_feature_template/project/layers/plan_layers.py

+40-2
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,6 @@ def feature_from_model(cls, model: Regulation) -> QgsFeature:
377377
feature["type_of_plan_regulation_id"] = model.config.id
378378

379379
feature["subject_identifiers"] = model.topic_tag.split(",") if model.topic_tag else None
380-
feature["type_of_verbal_plan_regulation_id"] = model.verbal_regulation_type_id
381380

382381
update_feature_from_attribute_value_model(model.value, feature)
383382

@@ -393,6 +392,7 @@ def model_from_feature(cls, feature: QgsFeature) -> Regulation:
393392
if not config:
394393
msg = f"Regulation config not found for {regulation_code}"
395394
raise ValueError(msg)
395+
396396
return Regulation(
397397
config=config,
398398
value=attribute_value_model_from_feature(feature),
@@ -407,7 +407,9 @@ def model_from_feature(cls, feature: QgsFeature) -> Regulation:
407407
theme=None,
408408
topic_tag=None,
409409
regulation_group_id=feature["plan_regulation_group_id"],
410-
verbal_regulation_type_id=feature["type_of_verbal_plan_regulation_id"],
410+
verbal_regulation_type_ids=(
411+
list(TypeOfVerbalRegulationAssociationLayer.get_verbal_type_ids_for_regulation(feature["id"]))
412+
),
411413
id_=feature["id"],
412414
)
413415

@@ -425,6 +427,42 @@ def get_regulations_to_delete(cls, regulations: list[Regulation], group_id: str)
425427
]
426428

427429

430+
class TypeOfVerbalRegulationAssociationLayer(AbstractPlanLayer):
431+
name = "Sanallisten kaavamääräyksien lajien assosiaatiot"
432+
filter_template = None
433+
434+
@classmethod
435+
def feature_from(cls, regulation_id: str, type_of_verbal_regulation_id: str) -> QgsFeature | None:
436+
layer = cls.get_from_project()
437+
438+
feature = QgsVectorLayerUtils.createFeature(layer)
439+
feature["plan_regulation_id"] = regulation_id
440+
feature["type_of_verbal_plan_regulation_id"] = type_of_verbal_regulation_id
441+
return feature
442+
443+
@classmethod
444+
def association_exists(cls, regulation_id: str, type_of_verbal_regulation_id: str) -> bool:
445+
for feature in cls.get_features_by_attribute_value("plan_regulation_id", regulation_id):
446+
if feature["type_of_verbal_plan_regulation_id"] == type_of_verbal_regulation_id:
447+
return True
448+
return False
449+
450+
@classmethod
451+
def get_associations_for_regulation(cls, regulation_id: str) -> Generator[QgsFeature]:
452+
return cls.get_features_by_attribute_value("plan_regulation_id", regulation_id)
453+
454+
@classmethod
455+
def get_verbal_type_ids_for_regulation(cls, regulation_id: str) -> Generator[QgsFeature]:
456+
return cls.get_attribute_values_by_another_attribute_value(
457+
"type_of_verbal_plan_regulation_id", "plan_regulation_id", regulation_id
458+
)
459+
460+
@classmethod
461+
def get_dangling_associations(cls, regulation_id: str, updated_type_ids: list[str]) -> list[QgsFeature]:
462+
associations = cls.get_associations_for_regulation(regulation_id)
463+
return [assoc for assoc in associations if assoc["type_of_verbal_plan_regulation_id"] not in updated_type_ids]
464+
465+
428466
class PlanPropositionLayer(AbstractPlanLayer):
429467
name = "Kaavasuositus"
430468
filter_template = Template(

0 commit comments

Comments
 (0)