diff --git a/arho_feature_template/core/models.py b/arho_feature_template/core/models.py index 21fc449..a341fbd 100644 --- a/arho_feature_template/core/models.py +++ b/arho_feature_template/core/models.py @@ -12,7 +12,7 @@ from arho_feature_template.exceptions import ConfigSyntaxError from arho_feature_template.project.layers.code_layers import AdditionalInformationTypeLayer, UndergroundTypeLayer from arho_feature_template.qgis_plugin_tools.tools.resources import resources_path -from arho_feature_template.utils.misc_utils import LANGUAGE, get_layer_by_name, iface +from arho_feature_template.utils.misc_utils import deserialize_localized_text, get_layer_by_name, iface if TYPE_CHECKING: from datetime import datetime @@ -246,8 +246,8 @@ class RegulationConfig: id: str regulation_code: str - name: str - description: str + name: str | None + description: str | None status: str level: int parent_id: str | None @@ -268,8 +268,8 @@ def from_feature(cls, feature: QgsFeature) -> RegulationConfig: return cls( id=feature["id"], regulation_code=feature["value"], - name=feature["name"].get(LANGUAGE) if feature["name"] else None, - description=feature["description"].get(LANGUAGE) if feature["description"] else None, + name=deserialize_localized_text(feature["name"]), + description=deserialize_localized_text(feature["description"]), status=feature["status"], level=feature["level"], parent_id=feature["parent_id"], @@ -287,8 +287,8 @@ class AdditionalInformationConfig: # From layer id: str additional_information_type: str - name: str - description: str + name: str | None + description: str | None status: str level: int parent_id: str | None @@ -344,8 +344,8 @@ def initialize(cls, config_fp: Path = ADDITIONAL_INFORMATION_CONFIG_PATH) -> Add ai_config = AdditionalInformationConfig( id=feature["id"], additional_information_type=ai_code, - name=feature["name"].get(LANGUAGE) if feature["name"] else None, - description=feature["description"].get(LANGUAGE) if feature["description"] else None, + name=deserialize_localized_text(feature["name"]), + description=deserialize_localized_text(feature["description"]), status=feature["status"], level=feature["level"], parent_id=feature["parent_id"], diff --git a/arho_feature_template/gui/components/plan_regulation_widget.py b/arho_feature_template/gui/components/plan_regulation_widget.py index 27f77f8..b92bc16 100644 --- a/arho_feature_template/gui/components/plan_regulation_widget.py +++ b/arho_feature_template/gui/components/plan_regulation_widget.py @@ -118,6 +118,8 @@ def _add_action(parent_id: str, info_type: str, display_name: str): for child_code in top_level_config.children: config = ai_config_library.get_config_by_code(child_code) + if not config.name: + continue _add_action(top_level_code, config.additional_information_type, config.name) # Create main menu for btn and add submenus diff --git a/arho_feature_template/gui/docks/new_feature_dock.py b/arho_feature_template/gui/docks/new_feature_dock.py index 933cfd7..8fdc21d 100644 --- a/arho_feature_template/gui/docks/new_feature_dock.py +++ b/arho_feature_template/gui/docks/new_feature_dock.py @@ -21,19 +21,18 @@ class NewFeatureDock(QgsDockWidget, DockClass): # type: ignore + library_selection: QComboBox + search_box: QgsFilterLineEdit + template_list: QListWidget + txt_tip: QLabel + dockWidgetContents: QWidget # noqa: N815 + tool_activated = pyqtSignal() - def __init__(self): + def __init__(self) -> None: super().__init__() self.setupUi(self) - # TYPES - self.library_selection: QComboBox - self.search_box: QgsFilterLineEdit - self.template_list: QListWidget - self.txt_tip: QLabel - self.dockWidgetContents: QWidget - # INIT # 1. New feature grid self.new_feature_grid = NewFeatureGridWidget() @@ -66,7 +65,7 @@ def on_active_feature_type_changed(self, feature_name: str, layer_name: str): if self.active_feature_type: self.tool_activated.emit() - def filter_plan_feature_templates(self): + def filter_plan_feature_templates(self) -> None: # Consider both search text and active plan feature type search_text = self.search_box.value().lower() diff --git a/arho_feature_template/gui/docks/regulation_groups_dock.py b/arho_feature_template/gui/docks/regulation_groups_dock.py index e4b5869..6e525a6 100644 --- a/arho_feature_template/gui/docks/regulation_groups_dock.py +++ b/arho_feature_template/gui/docks/regulation_groups_dock.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from importlib import resources from typing import TYPE_CHECKING @@ -20,6 +22,14 @@ class RegulationGroupsDock(QgsDockWidget, DockClass): # type: ignore + search_box: QgsFilterLineEdit + regulation_group_list: QListWidget + dockWidgetContents: QWidget # noqa: N815 + + new_btn: QPushButton + delete_btn: QPushButton + edit_btn: QPushButton + new_regulation_group_requested = pyqtSignal() edit_regulation_group_requested = pyqtSignal(RegulationGroup) delete_regulation_group_requested = pyqtSignal(RegulationGroup) @@ -28,15 +38,6 @@ def __init__(self): super().__init__() self.setupUi(self) - # TYPES - self.search_box: QgsFilterLineEdit - self.regulation_group_list: QListWidget - self.dockWidgetContents: QWidget - - self.new_btn: QPushButton - self.delete_btn: QPushButton - self.edit_btn: QPushButton - self.new_btn.setIcon(QgsApplication.getThemeIcon("mActionAdd.svg")) self.delete_btn.setIcon(QgsApplication.getThemeIcon("mActionDeleteSelected.svg")) self.edit_btn.setIcon(QgsApplication.getThemeIcon("mActionEditTable.svg")) @@ -115,7 +116,7 @@ def on_delete_btn_clicked(self): if response == QMessageBox.Yes: self.delete_regulation_group_requested.emit(self.selected_group) - def filter_regulation_groups(self): + def filter_regulation_groups(self) -> None: search_text = self.search_box.value().lower() for index in range(self.regulation_group_list.count()): diff --git a/arho_feature_template/project/layers/plan_layers.py b/arho_feature_template/project/layers/plan_layers.py index 564c43d..06a5228 100644 --- a/arho_feature_template/project/layers/plan_layers.py +++ b/arho_feature_template/project/layers/plan_layers.py @@ -23,7 +23,12 @@ from arho_feature_template.exceptions import FeatureNotFoundError, LayerEditableError, LayerNotFoundError from arho_feature_template.project.layers import AbstractLayer from arho_feature_template.project.layers.code_layers import PlanRegulationTypeLayer -from arho_feature_template.utils.misc_utils import LANGUAGE, get_active_plan_id, iface +from arho_feature_template.utils.misc_utils import ( + deserialize_localized_text, + get_active_plan_id, + iface, + serialize_localized_text, +) logger = logging.getLogger(__name__) @@ -78,8 +83,8 @@ def feature_from_model(cls, model: Plan) -> QgsFeature: feature = cls.initialize_feature_from_model(model) feature.setGeometry(model.geom) - feature["name"] = {LANGUAGE: model.name if model.name else None} - feature["description"] = {LANGUAGE: model.description if model.description else None} + feature["name"] = serialize_localized_text(model.name) + feature["description"] = serialize_localized_text(model.description) feature["permanent_plan_identifier"] = model.permanent_plan_identifier feature["record_number"] = model.record_number feature["producers_plan_identifier"] = model.producers_plan_identifier @@ -98,8 +103,8 @@ def model_from_feature(cls, feature: QgsFeature) -> Plan: ] return Plan( geom=feature.geometry(), - name=feature["name"].get(LANGUAGE) if feature["name"] else None, - description=feature["description"].get(LANGUAGE) if feature["description"] else None, + name=deserialize_localized_text(feature["name"]), + description=deserialize_localized_text(feature["description"]), permanent_plan_identifier=feature["permanent_plan_identifier"], record_number=feature["record_number"], producers_plan_identifier=feature["producers_plan_identifier"], @@ -122,7 +127,9 @@ def model_from_feature(cls, feature: QgsFeature) -> Plan: @classmethod def get_plan_name(cls, plan_id: str) -> str: attribute_value = cls.get_attribute_value_by_another_attribute_value("name", "id", plan_id) - return attribute_value.get(LANGUAGE, "Nimetön") if attribute_value else "Nimetön" + name = deserialize_localized_text(attribute_value) + + return name or "Nimetön" class PlanFeatureLayer(AbstractPlanLayer): @@ -134,9 +141,9 @@ def feature_from_model(cls, model: PlanFeature, plan_id: str | None = None) -> Q feature = cls.initialize_feature_from_model(model) feature.setGeometry(model.geom) - feature["name"] = {LANGUAGE: model.name if model.name else None} + feature["name"] = serialize_localized_text(model.name) feature["type_of_underground_id"] = model.type_of_underground_id - feature["description"] = {LANGUAGE: model.description if model.description else None} + feature["description"] = serialize_localized_text(model.description) feature["plan_id"] = plan_id if plan_id else get_active_plan_id() return feature @@ -151,8 +158,8 @@ def model_from_feature(cls, feature: QgsFeature) -> PlanFeature: geom=feature.geometry(), type_of_underground_id=feature["type_of_underground_id"], layer_name=cls.get_from_project().name(), - name=feature["name"].get(LANGUAGE) if feature["name"] else None, - description=feature["description"].get(LANGUAGE), + name=deserialize_localized_text(feature["name"]), + description=deserialize_localized_text(feature["description"]), regulation_groups=[ RegulationGroupLayer.model_from_feature(feat) for feat in regulation_group_features if feat is not None ], @@ -195,7 +202,7 @@ def feature_from_model(cls, model: RegulationGroup, plan_id: str | None = None) feature = cls.initialize_feature_from_model(model) feature["short_name"] = model.short_name if model.short_name else None - feature["name"] = {LANGUAGE: model.name if model.name else None} + feature["name"] = serialize_localized_text(model.name) feature["type_of_plan_regulation_group_id"] = model.type_code_id feature["plan_id"] = plan_id if plan_id else get_active_plan_id() feature["id"] = model.id_ if model.id_ else feature["id"] @@ -205,7 +212,7 @@ def feature_from_model(cls, model: RegulationGroup, plan_id: str | None = None) def model_from_feature(cls, feature: QgsFeature) -> RegulationGroup: return RegulationGroup( type_code_id=feature["type_of_plan_regulation_group_id"], - name=feature["name"].get(LANGUAGE) if feature["name"] else None, + name=deserialize_localized_text(feature["name"]), short_name=feature["short_name"], color_code=None, group_number=None, @@ -313,11 +320,11 @@ def attribute_value_model_from_feature(feature: QgsFeature) -> AttributeValue: numeric_range_min=feature["numeric_range_min"], numeric_range_max=feature["numeric_range_max"], unit=feature["unit"], - text_value=feature["text_value"].get(LANGUAGE) if feature["text_value"] else None, + text_value=deserialize_localized_text(feature["text_value"]), text_syntax=feature["text_syntax"], code_list=feature["code_list"], code_value=feature["code_value"], - code_title=feature["code_title"].get(LANGUAGE) if feature["code_title"] else None, + code_title=deserialize_localized_text(feature["code_title"]), height_reference_point=feature["height_reference_point"], ) @@ -330,11 +337,11 @@ def update_feature_from_attribute_value_model(value: AttributeValue | None, feat feature["numeric_range_min"] = value.numeric_range_min feature["numeric_range_max"] = value.numeric_range_max feature["unit"] = value.unit - feature["text_value"] = {LANGUAGE: value.text_value if value.text_value else None} + feature["text_value"] = serialize_localized_text(value.text_value) feature["text_syntax"] = value.text_syntax feature["code_list"] = value.code_list feature["code_value"] = value.code_value - feature["code_title"] = {LANGUAGE: value.code_title if value.code_title else None} + feature["code_title"] = serialize_localized_text(value.code_title) feature["height_reference_point"] = value.height_reference_point @@ -428,7 +435,7 @@ class PlanPropositionLayer(AbstractPlanLayer): def feature_from_model(cls, model: Proposition) -> QgsFeature: feature = cls.initialize_feature_from_model(model) - feature["text_value"] = {LANGUAGE: model.value if model.value else None} + feature["text_value"] = serialize_localized_text(model.value) feature["plan_regulation_group_id"] = model.regulation_group_id feature["ordering"] = model.proposition_number feature["plan_theme_id"] = model.theme_id @@ -438,8 +445,12 @@ def feature_from_model(cls, model: Proposition) -> QgsFeature: @classmethod def model_from_feature(cls, feature: QgsFeature) -> Proposition: + proposition_value = deserialize_localized_text(feature["text_value"]) + if not proposition_value: + msg = "Proposition value cannot be empty." + raise ValueError(msg) return Proposition( - value=feature["text_value"].get(LANGUAGE) if feature["text_value"] else None, + value=proposition_value, regulation_group_id=feature["plan_regulation_group_id"], proposition_number=feature["ordering"], theme_id=feature["plan_theme_id"], @@ -468,7 +479,7 @@ class DocumentLayer(AbstractPlanLayer): def feature_from_model(cls, model: Document) -> QgsFeature: feature = cls.initialize_feature_from_model(model) - feature["name"] = {LANGUAGE: model.name if model.name else None} + feature["name"] = serialize_localized_text(model.name) feature["url"] = model.url feature["type_of_document_id"] = model.type_of_document_id feature["decision"] = model.decision @@ -487,7 +498,7 @@ def feature_from_model(cls, model: Document) -> QgsFeature: @classmethod def model_from_feature(cls, feature: QgsFeature) -> Document: return Document( - name=feature["name"].get(LANGUAGE) if feature["name"] else None, + name=deserialize_localized_text(feature["name"]), url=feature["url"], type_of_document_id=feature["type_of_document_id"], decision=feature["decision"], diff --git a/arho_feature_template/utils/misc_utils.py b/arho_feature_template/utils/misc_utils.py index 7d6d9bb..bb4f577 100644 --- a/arho_feature_template/utils/misc_utils.py +++ b/arho_feature_template/utils/misc_utils.py @@ -2,7 +2,7 @@ import os from contextlib import suppress -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING, Any, cast from qgis.core import QgsExpressionContextUtils, QgsProject, QgsVectorLayer from qgis.PyQt.QtCore import QSettings, pyqtBoundSignal @@ -113,3 +113,18 @@ def disconnect_signal(signal: pyqtBoundSignal) -> None: """ with suppress(TypeError): signal.disconnect() + + +def serialize_localized_text(text: str | None) -> dict[str, str] | None: + if isinstance(text, str): + text = text.strip() + if text: + return {LANGUAGE: text} + return None + + +def deserialize_localized_text(text_value: dict[str, str] | None | Any) -> str | None: + text = None + if isinstance(text_value, dict): + text = text_value.get(LANGUAGE) + return text