From 06720bcfb0ae0275cb37ec870785e044276a84e5 Mon Sep 17 00:00:00 2001 From: Niko Aarnio Date: Mon, 3 Feb 2025 15:20:49 +0200 Subject: [PATCH 1/6] add messages to actions in plan manager --- arho_feature_template/core/models.py | 3 + arho_feature_template/core/plan_manager.py | 57 ++++++++++++------- .../gui/dialogs/load_plan_dialog.py | 6 ++ 3 files changed, 44 insertions(+), 22 deletions(-) diff --git a/arho_feature_template/core/models.py b/arho_feature_template/core/models.py index 7acb172..d891c6f 100644 --- a/arho_feature_template/core/models.py +++ b/arho_feature_template/core/models.py @@ -562,6 +562,9 @@ def from_config_data(cls, data: dict) -> PlanFeature: # TODO: Implement return cls(**data) + def describe(self) -> str: + return f"{self.name} " if self.name else "" + @dataclass class Plan: diff --git a/arho_feature_template/core/plan_manager.py b/arho_feature_template/core/plan_manager.py index 59f31a2..9b3a9f7 100644 --- a/arho_feature_template/core/plan_manager.py +++ b/arho_feature_template/core/plan_manager.py @@ -7,7 +7,7 @@ from qgis.core import QgsProject, QgsVectorLayer, QgsWkbTypes from qgis.gui import QgsMapToolDigitizeFeature -from qgis.PyQt.QtWidgets import QDialog, QMessageBox +from qgis.PyQt.QtWidgets import QDialog from arho_feature_template.core.lambda_service import LambdaService from arho_feature_template.core.models import ( @@ -160,23 +160,34 @@ def update_active_plan_regulation_group_library(self): self.regulation_groups_dock.update_regulation_groups(self.active_plan_regulation_group_library) def create_new_regulation_group(self): - self._open_regulation_group_form(RegulationGroup()) + new_group = self._open_regulation_group_form(RegulationGroup()) + if new_group: + iface.messageBar().pushSuccess(None, f"Kaavamääräysryhmä {new_group.describe()}luotiin onnistuneesti.") def edit_regulation_group(self, regulation_group: RegulationGroup): - self._open_regulation_group_form(regulation_group) + edited_group = self._open_regulation_group_form(regulation_group) + if edited_group: + iface.messageBar().pushSuccess( + None, f"Kaavamääräysryhmää {edited_group.describe()}muokattiin onnistuneesti." + ) def _open_regulation_group_form(self, regulation_group: RegulationGroup): regulation_group_form = PlanRegulationGroupForm(regulation_group, self.active_plan_regulation_group_library) if regulation_group_form.exec_(): + model = regulation_group_form.model if regulation_group_form.save_as_config: - save_regulation_group_as_config(regulation_group_form.model) + save_regulation_group_as_config(model) else: - save_regulation_group(regulation_group_form.model) + save_regulation_group(model) self.update_active_plan_regulation_group_library() + return model + + return None def delete_regulation_group(self, group: RegulationGroup): delete_regulation_group(group) + iface.messageBar().pushSuccess(None, f"Kaavamääräysryhmä {group.describe()}poistettiin onnistuneesti.") self.update_active_plan_regulation_group_library() def toggle_identify_plan_features(self, activate: bool): # noqa: FBT001 @@ -238,12 +249,14 @@ def edit_plan(self): feature = PlanLayer.get_feature_by_id(get_active_plan_id(), no_geometries=False) if feature is None: + iface.messageBar().pushWarning("", "Mikään kaava ei ole avattuna.") return plan_model = PlanLayer.model_from_feature(feature) attribute_form = PlanAttributeForm(plan_model, self.regulation_group_libraries) if attribute_form.exec_(): feature = save_plan(attribute_form.model) + iface.messageBar().pushSuccess("", f"Kaavan {attribute_form.model.name} tietoja muokattiin onnistuneesti.") self.update_active_plan_regulation_group_library() def edit_lifecycles(self): @@ -291,6 +304,7 @@ def _plan_geom_digitized(self, feature: QgsFeature): attribute_form = PlanAttributeForm(plan_model, self.regulation_group_libraries) if attribute_form.exec_(): feature = save_plan(attribute_form.model) + iface.messageBar().pushSuccess("", f"Kaava {attribute_form.model.name} luotiin onnistuneesti.") plan_to_be_activated = feature["id"] else: plan_to_be_activated = self.previous_active_plan_id @@ -319,6 +333,7 @@ def _plan_feature_geom_digitized(self, feature: QgsFeature): ) if attribute_form.exec_(): save_plan_feature(attribute_form.model) + iface.messageBar().pushSuccess("", f"Kaavakohde {attribute_form.model.describe()}luotiin onnistuneesti.") self.update_active_plan_regulation_group_library() def edit_plan_feature(self, feature: QgsFeature, layer_name: str): @@ -331,6 +346,9 @@ def edit_plan_feature(self, feature: QgsFeature, layer_name: str): ) if attribute_form.exec_(): save_plan_feature(attribute_form.model) + iface.messageBar().pushSuccess( + "", f"Kaavakohdetta {attribute_form.model.describe()}muokattiin onnistuneesti." + ) self.update_active_plan_regulation_group_library() def set_active_plan(self, plan_id: str | None): @@ -363,7 +381,7 @@ def load_land_use_plan(self): connection_names = get_existing_database_connection_names() if not connection_names: - QMessageBox.critical(None, "Error", "No database connections found.") + iface.messageBar().pushCritical("", "Tietokantayhteyksiä ei löytynyt.") return if not handle_unsaved_changes(): @@ -373,14 +391,13 @@ def load_land_use_plan(self): if dialog.exec_() == QDialog.Accepted: selected_plan_id = dialog.get_selected_plan_id() + selected_plan_name = dialog.get_selected_plan_name() self.commit_all_editable_layers() - if not selected_plan_id: - QMessageBox.critical(None, "Error", "No plan was selected.") - return - self.set_active_plan(selected_plan_id) + iface.messageBar().pushSuccess("", f"Kaava {selected_plan_name} avattiin onnistuneesti.") + def commit_all_editable_layers(self): """Commit all changes in any editable layers.""" for layer in QgsProject.instance().mapLayers().values(): @@ -389,27 +406,27 @@ def commit_all_editable_layers(self): def get_plan_json(self): """Serializes plan and plan outline to JSON""" + plan_id = get_active_plan_id() + if not plan_id: + iface.messageBar().pushWarning("", "Mikään kaava ei ole avattuna.") + return + dialog = SerializePlan() if dialog.exec_() == QDialog.Accepted: self.json_plan_path = str(dialog.plan_file.filePath()) self.json_plan_outline_path = str(dialog.plan_outline_file.filePath()) - plan_id = get_active_plan_id() - if not plan_id: - QMessageBox.critical(None, "Virhe", "Ei aktiivista kaavaa.") - return - self.lambda_service.serialize_plan(plan_id) def save_plan_jsons(self, plan_json, outline_json): """This slot saves the plan and outline JSONs to files.""" if plan_json is None or outline_json is None: - QMessageBox.critical(None, "Virhe", "Kaava tai sen ulkoraja ei löytynyt.") + iface.messageBar().pushCritical("", "Kaavaa tai sen ulkorajaa ei löytynyt.") return # Retrieve paths if self.json_plan_path is None or self.json_plan_outline_path is None: - QMessageBox.critical(None, "Virhe", "Tiedostopolut eivät ole saatavilla.") + iface.messageBar().pushCritical("", "Tiedostopolut eivät ole saatavilla.") return # Save the JSONs @@ -419,11 +436,7 @@ def save_plan_jsons(self, plan_json, outline_json): with open(self.json_plan_outline_path, "w", encoding="utf-8") as outline_file: json.dump(outline_json, outline_file, ensure_ascii=False, indent=2) - QMessageBox.information( - None, - "Tallennus onnistui", - "Kaava ja sen ulkoraja tallennettu onnistuneesti.", - ) + iface.messageBar().pushSuccess("", "Kaava ja sen ulkoraja tallennettu onnistuneesti.") def unload(self): # Set pan map tool as active (to deactivate our custom tools to avoid errors) diff --git a/arho_feature_template/gui/dialogs/load_plan_dialog.py b/arho_feature_template/gui/dialogs/load_plan_dialog.py index cccb279..e5aec46 100644 --- a/arho_feature_template/gui/dialogs/load_plan_dialog.py +++ b/arho_feature_template/gui/dialogs/load_plan_dialog.py @@ -64,6 +64,7 @@ def __init__(self, parent, connection_names: list[str]): self.setupUi(self) self._selected_plan_id = None + self._selected_plan_name = None self.button_box.rejected.connect(self.reject) self.button_box.accepted.connect(self.accept) @@ -193,9 +194,11 @@ def on_selection_changed(self): if selection: selected_row = selection[0].row() self._selected_plan_id = self.plan_table_view.model().index(selected_row, 0).data(Qt.UserRole) + self._selected_plan_name = self.plan_table_view.model().index(selected_row, 0) self.button_box.button(QDialogButtonBox.Ok).setEnabled(True) else: self._selected_plan_id = None + self._selected_plan_name = None self.button_box.button(QDialogButtonBox.Ok).setEnabled(False) def get_selected_connection(self): @@ -203,3 +206,6 @@ def get_selected_connection(self): def get_selected_plan_id(self): return self._selected_plan_id + + def get_selected_plan_name(self): + return self._selected_plan_name From fa203e3a0322fc22a19262e40839b965760daf77 Mon Sep 17 00:00:00 2001 From: Niko Aarnio Date: Tue, 11 Feb 2025 10:54:47 +0200 Subject: [PATCH 2/6] add error messages when saving/deleting features fails Saving and deleting of features is now stopped when any failure occurs. Functions return QgsFeature or None, or bool indicating if operation was succesfull. Database throws still an error message also, but should throw only one. --- arho_feature_template/core/models.py | 4 +- arho_feature_template/core/plan_manager.py | 185 +++++++++++++-------- 2 files changed, 121 insertions(+), 68 deletions(-) diff --git a/arho_feature_template/core/models.py b/arho_feature_template/core/models.py index d891c6f..e8b24c9 100644 --- a/arho_feature_template/core/models.py +++ b/arho_feature_template/core/models.py @@ -562,8 +562,8 @@ def from_config_data(cls, data: dict) -> PlanFeature: # TODO: Implement return cls(**data) - def describe(self) -> str: - return f"{self.name} " if self.name else "" + def __str__(self): + return self.name if self.name else "" @dataclass diff --git a/arho_feature_template/core/plan_manager.py b/arho_feature_template/core/plan_manager.py index 9b3a9f7..174ce31 100644 --- a/arho_feature_template/core/plan_manager.py +++ b/arho_feature_template/core/plan_manager.py @@ -162,13 +162,13 @@ def update_active_plan_regulation_group_library(self): def create_new_regulation_group(self): new_group = self._open_regulation_group_form(RegulationGroup()) if new_group: - iface.messageBar().pushSuccess(None, f"Kaavamääräysryhmä {new_group.describe()}luotiin onnistuneesti.") + iface.messageBar().pushSuccess(None, f"Kaavamääräysryhmä {str(new_group)} luotiin onnistuneesti.") def edit_regulation_group(self, regulation_group: RegulationGroup): edited_group = self._open_regulation_group_form(regulation_group) if edited_group: iface.messageBar().pushSuccess( - None, f"Kaavamääräysryhmää {edited_group.describe()}muokattiin onnistuneesti." + None, f"Kaavamääräysryhmää {str(edited_group)} muokattiin onnistuneesti." ) def _open_regulation_group_form(self, regulation_group: RegulationGroup): @@ -178,17 +178,18 @@ def _open_regulation_group_form(self, regulation_group: RegulationGroup): model = regulation_group_form.model if regulation_group_form.save_as_config: save_regulation_group_as_config(model) - else: - save_regulation_group(model) + elif save_regulation_group(model) is None: + return None + # NOTE: Should we reinitialize regulation group dock even if saving failed? self.update_active_plan_regulation_group_library() return model return None def delete_regulation_group(self, group: RegulationGroup): - delete_regulation_group(group) - iface.messageBar().pushSuccess(None, f"Kaavamääräysryhmä {group.describe()}poistettiin onnistuneesti.") - self.update_active_plan_regulation_group_library() + if delete_regulation_group(group): + iface.messageBar().pushSuccess(None, f"Kaavamääräysryhmä {str(group)} poistettiin onnistuneesti.") + self.update_active_plan_regulation_group_library() def toggle_identify_plan_features(self, activate: bool): # noqa: FBT001 if activate: @@ -256,8 +257,11 @@ def edit_plan(self): attribute_form = PlanAttributeForm(plan_model, self.regulation_group_libraries) if attribute_form.exec_(): feature = save_plan(attribute_form.model) - iface.messageBar().pushSuccess("", f"Kaavan {attribute_form.model.name} tietoja muokattiin onnistuneesti.") - self.update_active_plan_regulation_group_library() + if feature: + iface.messageBar().pushSuccess( + "", f"Kaavan {attribute_form.model.name} tietoja muokattiin onnistuneesti." + ) + self.update_active_plan_regulation_group_library() def edit_lifecycles(self): plan_layer = PlanLayer.get_from_project() @@ -303,9 +307,12 @@ def _plan_geom_digitized(self, feature: QgsFeature): plan_model = Plan(geom=feature.geometry()) attribute_form = PlanAttributeForm(plan_model, self.regulation_group_libraries) if attribute_form.exec_(): - feature = save_plan(attribute_form.model) - iface.messageBar().pushSuccess("", f"Kaava {attribute_form.model.name} luotiin onnistuneesti.") - plan_to_be_activated = feature["id"] + feat = save_plan(attribute_form.model) + if feat: + iface.messageBar().pushSuccess("", f"Kaava {attribute_form.model.name} luotiin onnistuneesti.") + plan_to_be_activated = feat["id"] + else: + plan_to_be_activated = self.previous_active_plan_id else: plan_to_be_activated = self.previous_active_plan_id @@ -331,9 +338,8 @@ def _plan_feature_geom_digitized(self, feature: QgsFeature): attribute_form = PlanFeatureForm( plan_feature, title, self.regulation_group_libraries, self.active_plan_regulation_group_library ) - if attribute_form.exec_(): - save_plan_feature(attribute_form.model) - iface.messageBar().pushSuccess("", f"Kaavakohde {attribute_form.model.describe()}luotiin onnistuneesti.") + if attribute_form.exec_() and save_plan_feature(attribute_form.model): + iface.messageBar().pushSuccess("", f"Kaavakohde {str(attribute_form.model)} luotiin onnistuneesti.") self.update_active_plan_regulation_group_library() def edit_plan_feature(self, feature: QgsFeature, layer_name: str): @@ -344,10 +350,9 @@ def edit_plan_feature(self, feature: QgsFeature, layer_name: str): attribute_form = PlanFeatureForm( plan_feature, title, self.regulation_group_libraries, self.active_plan_regulation_group_library ) - if attribute_form.exec_(): - save_plan_feature(attribute_form.model) + if attribute_form.exec_() and save_plan_feature(attribute_form.model): iface.messageBar().pushSuccess( - "", f"Kaavakohdetta {attribute_form.model.describe()}muokattiin onnistuneesti." + "", f"Kaavakohdetta {str(attribute_form.model)} muokattiin onnistuneesti." ) self.update_active_plan_regulation_group_library() @@ -507,7 +512,7 @@ def regulation_group_library_from_active_plan() -> RegulationGroupLibrary: ) -def _save_feature(feature: QgsFeature, layer: QgsVectorLayer, id_: str | None, edit_text: str = ""): +def _save_feature(feature: QgsFeature, layer: QgsVectorLayer, id_: str | None, edit_text: str = "") -> bool: if not layer.isEditable(): layer.startEditing() layer.beginEditCommand(edit_text) @@ -518,10 +523,10 @@ def _save_feature(feature: QgsFeature, layer: QgsVectorLayer, id_: str | None, e layer.updateFeature(feature) layer.endEditCommand() - layer.commitChanges(stopEditing=False) + return layer.commitChanges(stopEditing=False) -def _delete_feature(feature: QgsFeature, layer: QgsVectorLayer, delete_text: str = ""): +def _delete_feature(feature: QgsFeature, layer: QgsVectorLayer, delete_text: str = "") -> bool: if not layer.isEditable(): layer.startEditing() layer.beginEditCommand(delete_text) @@ -529,19 +534,21 @@ def _delete_feature(feature: QgsFeature, layer: QgsVectorLayer, delete_text: str layer.deleteFeature(feature.id()) layer.endEditCommand() - layer.commitChanges(stopEditing=False) + return layer.commitChanges(stopEditing=False) -def save_plan(plan: Plan) -> QgsFeature: +def save_plan(plan: Plan) -> QgsFeature | None: feature = PlanLayer.feature_from_model(plan) layer = PlanLayer.get_from_project() - _save_feature( + if not _save_feature( feature=feature, layer=layer, id_=plan.id_, edit_text="Kaavan muokkaus" if plan.id_ is not None else "Kaavan luominen", - ) + ): + iface.messageBar().pushCritical("", "Kaavan tallentaminen epäonnistui") + return None plan_id = feature["id"] if plan.id_ is not None: @@ -549,27 +556,35 @@ def save_plan(plan: Plan) -> QgsFeature: for association in RegulationGroupAssociationLayer.get_dangling_associations( plan.general_regulations, plan.id_, PlanLayer.name ): - _delete_feature( + if not _delete_feature( association, RegulationGroupAssociationLayer.get_from_project(), "Kaavamääräysryhmän assosiaation poisto", - ) + ): + iface.messageBar().pushCritical("", "Kaavamääräysryhmän assosiaation poistaminen epäonnistui.") + return None # Check for documents to be deleted doc_layer = DocumentLayer.get_from_project() for doc_feature in DocumentLayer.get_documents_to_delete(plan.documents, plan.id_): - _delete_feature(doc_feature, doc_layer, "Asiakirjan poisto") + if not _delete_feature(doc_feature, doc_layer, "Asiakirjan poisto"): + iface.messageBar().pushCritical("", "Asiakirjan poistaminen epäonnistui.") + return None # Save general regulations if plan.general_regulations: for regulation_group in plan.general_regulations: regulation_group_feature = save_regulation_group(regulation_group, plan_id) - save_regulation_group_association(regulation_group_feature["id"], PlanLayer.name, plan_id) + if regulation_group_feature is None: + return None + if not save_regulation_group_association(regulation_group_feature["id"], PlanLayer.name, plan_id): + return None # Save documents for document in plan.documents: document.plan_id = plan_id - save_document(document) + if save_document(document) is None: + return None # Save lifecycles for lifecycle in plan.lifecycles: @@ -579,7 +594,7 @@ def save_plan(plan: Plan) -> QgsFeature: return feature -def save_plan_feature(plan_model: PlanFeature, plan_id: str | None = None) -> QgsFeature: +def save_plan_feature(plan_model: PlanFeature, plan_id: str | None = None) -> QgsFeature | None: layer_name = plan_model.layer_name if not layer_name: msg = "Cannot save plan feature without a target layer" @@ -593,42 +608,51 @@ def save_plan_feature(plan_model: PlanFeature, plan_id: str | None = None) -> Qg layer = layer_class.get_from_project() editing = plan_model.id_ is not None - _save_feature( + if not _save_feature( feature=plan_feature, layer=layer, id_=plan_model.id_, edit_text="Kaavakohteen muokkaus" if editing else "Kaavakohteen lisäys", - ) + ): + iface.messageBar().pushCritical("", "Kaavakohteen tallentaminen epäonnistui.") + return None # Check for deleted regulation groups if editing: for association in RegulationGroupAssociationLayer.get_dangling_associations( plan_model.regulation_groups, plan_feature["id"], layer_name ): - _delete_feature( + if not _delete_feature( association, RegulationGroupAssociationLayer.get_from_project(), "Kaavamääräysryhmän assosiaation poisto", - ) + ): + iface.messageBar().pushCritical("", "Kaavamääräysryhmän assosiaation poistaminen epäonnistui.") + return None # Save regulation groups for group in plan_model.regulation_groups: regulation_group_feature = save_regulation_group(group) - save_regulation_group_association(regulation_group_feature["id"], layer_name, plan_feature["id"]) + if regulation_group_feature is None: + return None + if not save_regulation_group_association(regulation_group_feature["id"], layer_name, plan_feature["id"]): + return None return plan_feature -def save_regulation_group(regulation_group: RegulationGroup, plan_id: str | None = None) -> QgsFeature: +def save_regulation_group(regulation_group: RegulationGroup, plan_id: str | None = None) -> QgsFeature | None: feature = RegulationGroupLayer.feature_from_model(regulation_group, plan_id) layer = RegulationGroupLayer.get_from_project() - _save_feature( + if not _save_feature( feature=feature, layer=layer, id_=regulation_group.id_, edit_text="Kaavamääräysryhmän muokkaus" if regulation_group.id_ is not None else "Kaavamääräysryhmän lisäys", - ) + ): + iface.messageBar().pushCritical("", "Kaavamääräysryhmän tallentaminen epäonnistui.") + return None if regulation_group.id_ is not None: # Check for regulations to be deleted @@ -636,33 +660,40 @@ def save_regulation_group(regulation_group: RegulationGroup, plan_id: str | None for reg_feature in PlanRegulationLayer.get_regulations_to_delete( regulation_group.regulations, regulation_group.id_ ): - _delete_feature(reg_feature, regulation_layer, "Kaavamääräyksen poisto") + if not _delete_feature(reg_feature, regulation_layer, "Kaavamääräyksen poisto"): + iface.messageBar().pushCritical("", "Kaavamääräyksen poistaminen epäonnistui.") + return None # Check for propositions to be deleted proposition_layer = PlanPropositionLayer.get_from_project() for prop_feature in PlanPropositionLayer.get_propositions_to_delete( regulation_group.propositions, regulation_group.id_ ): - _delete_feature(prop_feature, proposition_layer, "Kaavasuosituksen poisto") + if not _delete_feature(prop_feature, proposition_layer, "Kaavasuosituksen poisto"): + iface.messageBar().pushCritical("", "Kaavasuosituksen poistaminen epäonnistui.") + return None # Save regulations if regulation_group.regulations: for regulation in regulation_group.regulations: regulation.regulation_group_id = feature["id"] # Updating regulation group ID - save_regulation(regulation) + if save_regulation(regulation) is None: + return None # Save propositions if regulation_group.propositions: for proposition in regulation_group.propositions: proposition.regulation_group_id = feature["id"] # Updating regulation group ID - save_proposition(proposition) + if save_proposition(proposition) is None: + return None return feature -def delete_regulation_group(regulation_group: RegulationGroup, plan_id: str | None = None): +def delete_regulation_group(regulation_group: RegulationGroup, plan_id: str | None = None) -> bool: if regulation_group.id_ is None: - return + iface.messageBar().pushCritical("", "Kaavamääräysryhmän poistaminen epäonnistui (ei IDtä).") + return False feature = RegulationGroupLayer.feature_from_model(regulation_group, plan_id) layer = RegulationGroupLayer.get_from_project() @@ -674,91 +705,113 @@ def save_regulation_group_as_config(regulation_group: RegulationGroup): pass -def save_regulation_group_association(regulation_group_id: str, layer_name: str, feature_id: str): +def save_regulation_group_association(regulation_group_id: str, layer_name: str, feature_id: str) -> bool: if RegulationGroupAssociationLayer.association_exists(regulation_group_id, layer_name, feature_id): - return + return True feature = RegulationGroupAssociationLayer.feature_from(regulation_group_id, layer_name, feature_id) layer = RegulationGroupAssociationLayer.get_from_project() - _save_feature(feature=feature, layer=layer, id_=None, edit_text="Kaavamääräysryhmän assosiaation lisäys") + if not _save_feature(feature=feature, layer=layer, id_=None, edit_text="Kaavamääräysryhmän assosiaation lisäys"): + iface.messageBar().pushCritical("", "Kaavamääräysryhmän assosiaation tallentaminen epäonnistui.") + return False + + return True -def save_regulation(regulation: Regulation) -> QgsFeature: +def save_regulation(regulation: Regulation) -> QgsFeature | None: regulation_feature = PlanRegulationLayer.feature_from_model(regulation) layer = PlanRegulationLayer.get_from_project() - _save_feature( + if not _save_feature( feature=regulation_feature, layer=layer, id_=regulation.id_, edit_text="Kaavamääräyksen lisäys" if regulation.id_ is None else "Kaavamääräyksen muokkaus", - ) + ): + iface.messageBar().pushCritical("", "Kaavamääräyksen tallentaminen epäonnistui.") + return None for additional_information in regulation.additional_information: additional_information.plan_regulation_id = regulation_feature["id"] - save_additional_information(additional_information) + if save_additional_information(additional_information) is None: + return None return regulation_feature -def save_additional_information(additional_information: AdditionalInformation) -> QgsFeature: +def save_additional_information(additional_information: AdditionalInformation) -> QgsFeature | None: feature = AdditionalInformationLayer.feature_from_model(additional_information) layer = AdditionalInformationLayer.get_from_project() - _save_feature( + if not _save_feature( feature=feature, layer=layer, id_=additional_information.id_, edit_text="Lisätiedon lisäys" if additional_information.id_ is None else "Lisätiedon muokkaus", - ) + ): + iface.messageBar().pushCritical("", "Lisätiedon tallentaminen epäonnistui.") + return None + return feature -def delete_additional_information(additional_information: AdditionalInformation): +def delete_additional_information(additional_information: AdditionalInformation) -> bool: feature = AdditionalInformationLayer.feature_from_model(additional_information) layer = AdditionalInformationLayer.get_from_project() - _delete_feature(feature, layer, "Lisätiedon poisto") + if not _delete_feature(feature, layer, "Lisätiedon poisto"): + iface.messageBar().pushCritical("", "Lisätiedon poistaminen epäonnistui.") + return False + + return True -def delete_regulation(regulation: Regulation): +def delete_regulation(regulation: Regulation) -> bool: feature = PlanRegulationLayer.feature_from_model(regulation) layer = PlanRegulationLayer.get_from_project() _delete_feature(feature, layer, "Kaavamääräyksen poisto") -def save_proposition(proposition: Proposition) -> QgsFeature: +def save_proposition(proposition: Proposition) -> QgsFeature | None: feature = PlanPropositionLayer.feature_from_model(proposition) layer = PlanPropositionLayer.get_from_project() - _save_feature( + if not _save_feature( feature=feature, layer=layer, id_=proposition.id_, edit_text="Kaavasuosituksen lisäys" if proposition.id_ is None else "Kaavasuosituksen muokkaus", - ) + ): + iface.messageBar().pushCritical("", "Kaavasuosituksen tallentaminen epäonnistui.") + return None return feature -def delete_proposition(proposition: Proposition): +def delete_proposition(proposition: Proposition) -> bool: feature = PlanPropositionLayer.feature_from_model(proposition) layer = PlanPropositionLayer.get_from_project() - _delete_feature(feature, layer, "Kaavasuosituksen poisto") + if not _delete_feature(feature, layer, "Kaavasuosituksen poisto"): + iface.messageBar().pushCritical("", "Kaavasuosituksen poistaminen epäonnistui.") + return False + return True -def save_document(document: Document) -> QgsFeature: + +def save_document(document: Document) -> QgsFeature | None: feature = DocumentLayer.feature_from_model(document) layer = DocumentLayer.get_from_project() - _save_feature( + if not _save_feature( feature=feature, layer=layer, id_=document.id_, edit_text="Asiakirjan lisäys" if document.id_ is None else "Asiakirjan muokkaus", - ) + ): + iface.messageBar().pushCritical("", "Asiakirjan tallentaminen epäonnistui.") + return None return feature From 734d6ec56e2fcca1ac76e7a4d97dc3edc7b1036c Mon Sep 17 00:00:00 2001 From: Niko Aarnio Date: Mon, 24 Feb 2025 11:49:09 +0200 Subject: [PATCH 3/6] stop saving/deleting features only if needed --- arho_feature_template/core/plan_manager.py | 51 ++++++++-------------- 1 file changed, 19 insertions(+), 32 deletions(-) diff --git a/arho_feature_template/core/plan_manager.py b/arho_feature_template/core/plan_manager.py index 174ce31..0cb31b4 100644 --- a/arho_feature_template/core/plan_manager.py +++ b/arho_feature_template/core/plan_manager.py @@ -162,14 +162,12 @@ def update_active_plan_regulation_group_library(self): def create_new_regulation_group(self): new_group = self._open_regulation_group_form(RegulationGroup()) if new_group: - iface.messageBar().pushSuccess(None, f"Kaavamääräysryhmä {str(new_group)} luotiin onnistuneesti.") + iface.messageBar().pushSuccess(None, f"Kaavamääräysryhmä {new_group!s} luotiin onnistuneesti.") def edit_regulation_group(self, regulation_group: RegulationGroup): edited_group = self._open_regulation_group_form(regulation_group) if edited_group: - iface.messageBar().pushSuccess( - None, f"Kaavamääräysryhmää {str(edited_group)} muokattiin onnistuneesti." - ) + iface.messageBar().pushSuccess(None, f"Kaavamääräysryhmää {edited_group!s} muokattiin onnistuneesti.") def _open_regulation_group_form(self, regulation_group: RegulationGroup): regulation_group_form = PlanRegulationGroupForm(regulation_group, self.active_plan_regulation_group_library) @@ -188,7 +186,7 @@ def _open_regulation_group_form(self, regulation_group: RegulationGroup): def delete_regulation_group(self, group: RegulationGroup): if delete_regulation_group(group): - iface.messageBar().pushSuccess(None, f"Kaavamääräysryhmä {str(group)} poistettiin onnistuneesti.") + iface.messageBar().pushSuccess(None, f"Kaavamääräysryhmä {group!s} poistettiin onnistuneesti.") self.update_active_plan_regulation_group_library() def toggle_identify_plan_features(self, activate: bool): # noqa: FBT001 @@ -339,7 +337,7 @@ def _plan_feature_geom_digitized(self, feature: QgsFeature): plan_feature, title, self.regulation_group_libraries, self.active_plan_regulation_group_library ) if attribute_form.exec_() and save_plan_feature(attribute_form.model): - iface.messageBar().pushSuccess("", f"Kaavakohde {str(attribute_form.model)} luotiin onnistuneesti.") + iface.messageBar().pushSuccess("", f"Kaavakohde {attribute_form.model!s} luotiin onnistuneesti.") self.update_active_plan_regulation_group_library() def edit_plan_feature(self, feature: QgsFeature, layer_name: str): @@ -351,9 +349,7 @@ def edit_plan_feature(self, feature: QgsFeature, layer_name: str): plan_feature, title, self.regulation_group_libraries, self.active_plan_regulation_group_library ) if attribute_form.exec_() and save_plan_feature(attribute_form.model): - iface.messageBar().pushSuccess( - "", f"Kaavakohdetta {str(attribute_form.model)} muokattiin onnistuneesti." - ) + iface.messageBar().pushSuccess("", f"Kaavakohdetta {attribute_form.model!s} muokattiin onnistuneesti.") self.update_active_plan_regulation_group_library() def set_active_plan(self, plan_id: str | None): @@ -562,29 +558,25 @@ def save_plan(plan: Plan) -> QgsFeature | None: "Kaavamääräysryhmän assosiaation poisto", ): iface.messageBar().pushCritical("", "Kaavamääräysryhmän assosiaation poistaminen epäonnistui.") - return None # Check for documents to be deleted doc_layer = DocumentLayer.get_from_project() for doc_feature in DocumentLayer.get_documents_to_delete(plan.documents, plan.id_): if not _delete_feature(doc_feature, doc_layer, "Asiakirjan poisto"): iface.messageBar().pushCritical("", "Asiakirjan poistaminen epäonnistui.") - return None # Save general regulations if plan.general_regulations: for regulation_group in plan.general_regulations: regulation_group_feature = save_regulation_group(regulation_group, plan_id) if regulation_group_feature is None: - return None - if not save_regulation_group_association(regulation_group_feature["id"], PlanLayer.name, plan_id): - return None + continue # Skip association saving if saving regulation group failed + save_regulation_group_association(regulation_group_feature["id"], PlanLayer.name, plan_id) # Save documents for document in plan.documents: document.plan_id = plan_id - if save_document(document) is None: - return None + save_document(document) # Save lifecycles for lifecycle in plan.lifecycles: @@ -628,15 +620,13 @@ def save_plan_feature(plan_model: PlanFeature, plan_id: str | None = None) -> Qg "Kaavamääräysryhmän assosiaation poisto", ): iface.messageBar().pushCritical("", "Kaavamääräysryhmän assosiaation poistaminen epäonnistui.") - return None # Save regulation groups for group in plan_model.regulation_groups: regulation_group_feature = save_regulation_group(group) if regulation_group_feature is None: - return None - if not save_regulation_group_association(regulation_group_feature["id"], layer_name, plan_feature["id"]): - return None + continue # Skip association saving if saving regulation group failed + save_regulation_group_association(regulation_group_feature["id"], layer_name, plan_feature["id"]) return plan_feature @@ -662,7 +652,6 @@ def save_regulation_group(regulation_group: RegulationGroup, plan_id: str | None ): if not _delete_feature(reg_feature, regulation_layer, "Kaavamääräyksen poisto"): iface.messageBar().pushCritical("", "Kaavamääräyksen poistaminen epäonnistui.") - return None # Check for propositions to be deleted proposition_layer = PlanPropositionLayer.get_from_project() @@ -671,21 +660,18 @@ def save_regulation_group(regulation_group: RegulationGroup, plan_id: str | None ): if not _delete_feature(prop_feature, proposition_layer, "Kaavasuosituksen poisto"): iface.messageBar().pushCritical("", "Kaavasuosituksen poistaminen epäonnistui.") - return None # Save regulations if regulation_group.regulations: for regulation in regulation_group.regulations: regulation.regulation_group_id = feature["id"] # Updating regulation group ID - if save_regulation(regulation) is None: - return None + save_regulation(regulation) # Save propositions if regulation_group.propositions: for proposition in regulation_group.propositions: proposition.regulation_group_id = feature["id"] # Updating regulation group ID - if save_proposition(proposition) is None: - return None + save_proposition(proposition) return feature @@ -733,8 +719,7 @@ def save_regulation(regulation: Regulation) -> QgsFeature | None: for additional_information in regulation.additional_information: additional_information.plan_regulation_id = regulation_feature["id"] - if save_additional_information(additional_information) is None: - return None + save_additional_information(additional_information) return regulation_feature @@ -816,15 +801,17 @@ def save_document(document: Document) -> QgsFeature | None: return feature -def save_lifecycle(lifecycle: LifeCycle) -> QgsFeature: - """Save a LifeCycle object to the layer.""" +def save_lifecycle(lifecycle: LifeCycle) -> QgsFeature | None: feature = LifeCycleLayer.feature_from_model(lifecycle) layer = LifeCycleLayer.get_from_project() - _save_feature( + if not _save_feature( feature=feature, layer=layer, id_=lifecycle.id_, edit_text="Elinkaaren lisäys" if lifecycle.id_ is None else "Elinkaaren muokkaus", - ) + ): + iface.messageBar().pushCritical("", "Elinkaaren tallentaminen epäonnistui.") + return None + return feature From 65679abca9c5fed876b04bacc5b7e1644a15c27c Mon Sep 17 00:00:00 2001 From: Niko Aarnio Date: Mon, 24 Feb 2025 12:11:07 +0200 Subject: [PATCH 4/6] add wait cursor override for primary save/delete functions --- arho_feature_template/core/plan_manager.py | 5 +++++ arho_feature_template/utils/misc_utils.py | 12 ++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/arho_feature_template/core/plan_manager.py b/arho_feature_template/core/plan_manager.py index 0cb31b4..3632dec 100644 --- a/arho_feature_template/core/plan_manager.py +++ b/arho_feature_template/core/plan_manager.py @@ -60,6 +60,7 @@ handle_unsaved_changes, iface, set_active_plan_id, + use_wait_cursor, ) if TYPE_CHECKING: @@ -533,6 +534,7 @@ def _delete_feature(feature: QgsFeature, layer: QgsVectorLayer, delete_text: str return layer.commitChanges(stopEditing=False) +@use_wait_cursor def save_plan(plan: Plan) -> QgsFeature | None: feature = PlanLayer.feature_from_model(plan) layer = PlanLayer.get_from_project() @@ -586,6 +588,7 @@ def save_plan(plan: Plan) -> QgsFeature | None: return feature +@use_wait_cursor def save_plan_feature(plan_model: PlanFeature, plan_id: str | None = None) -> QgsFeature | None: layer_name = plan_model.layer_name if not layer_name: @@ -631,6 +634,7 @@ def save_plan_feature(plan_model: PlanFeature, plan_id: str | None = None) -> Qg return plan_feature +@use_wait_cursor def save_regulation_group(regulation_group: RegulationGroup, plan_id: str | None = None) -> QgsFeature | None: feature = RegulationGroupLayer.feature_from_model(regulation_group, plan_id) layer = RegulationGroupLayer.get_from_project() @@ -676,6 +680,7 @@ def save_regulation_group(regulation_group: RegulationGroup, plan_id: str | None return feature +@use_wait_cursor def delete_regulation_group(regulation_group: RegulationGroup, plan_id: str | None = None) -> bool: if regulation_group.id_ is None: iface.messageBar().pushCritical("", "Kaavamääräysryhmän poistaminen epäonnistui (ei IDtä).") diff --git a/arho_feature_template/utils/misc_utils.py b/arho_feature_template/utils/misc_utils.py index bb4f577..39b8605 100644 --- a/arho_feature_template/utils/misc_utils.py +++ b/arho_feature_template/utils/misc_utils.py @@ -5,9 +5,9 @@ from typing import TYPE_CHECKING, Any, cast from qgis.core import QgsExpressionContextUtils, QgsProject, QgsVectorLayer -from qgis.PyQt.QtCore import QSettings, pyqtBoundSignal +from qgis.PyQt.QtCore import QSettings, Qt, pyqtBoundSignal from qgis.PyQt.QtWidgets import QMessageBox -from qgis.utils import iface +from qgis.utils import OverrideCursor, iface if TYPE_CHECKING: from qgis.core import QgsMapLayer @@ -128,3 +128,11 @@ def deserialize_localized_text(text_value: dict[str, str] | None | Any) -> str | if isinstance(text_value, dict): text = text_value.get(LANGUAGE) return text + + +def use_wait_cursor(func): + def wrapper(*args, **kwargs): + with OverrideCursor(Qt.WaitCursor): + return func(*args, **kwargs) + + return wrapper From 2760f62e4230c1b25ed8f8bf00e2b62c8c467bac Mon Sep 17 00:00:00 2001 From: Niko Aarnio Date: Tue, 25 Feb 2025 10:06:02 +0200 Subject: [PATCH 5/6] remove most success messages, fix use_wait_cursor decorator --- arho_feature_template/core/plan_manager.py | 25 ++++------------------ arho_feature_template/utils/misc_utils.py | 2 ++ 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/arho_feature_template/core/plan_manager.py b/arho_feature_template/core/plan_manager.py index 3632dec..5f3ac8d 100644 --- a/arho_feature_template/core/plan_manager.py +++ b/arho_feature_template/core/plan_manager.py @@ -161,14 +161,10 @@ def update_active_plan_regulation_group_library(self): self.regulation_groups_dock.update_regulation_groups(self.active_plan_regulation_group_library) def create_new_regulation_group(self): - new_group = self._open_regulation_group_form(RegulationGroup()) - if new_group: - iface.messageBar().pushSuccess(None, f"Kaavamääräysryhmä {new_group!s} luotiin onnistuneesti.") + self._open_regulation_group_form(RegulationGroup()) def edit_regulation_group(self, regulation_group: RegulationGroup): - edited_group = self._open_regulation_group_form(regulation_group) - if edited_group: - iface.messageBar().pushSuccess(None, f"Kaavamääräysryhmää {edited_group!s} muokattiin onnistuneesti.") + self._open_regulation_group_form(regulation_group) def _open_regulation_group_form(self, regulation_group: RegulationGroup): regulation_group_form = PlanRegulationGroupForm(regulation_group, self.active_plan_regulation_group_library) @@ -187,7 +183,6 @@ def _open_regulation_group_form(self, regulation_group: RegulationGroup): def delete_regulation_group(self, group: RegulationGroup): if delete_regulation_group(group): - iface.messageBar().pushSuccess(None, f"Kaavamääräysryhmä {group!s} poistettiin onnistuneesti.") self.update_active_plan_regulation_group_library() def toggle_identify_plan_features(self, activate: bool): # noqa: FBT001 @@ -257,9 +252,6 @@ def edit_plan(self): if attribute_form.exec_(): feature = save_plan(attribute_form.model) if feature: - iface.messageBar().pushSuccess( - "", f"Kaavan {attribute_form.model.name} tietoja muokattiin onnistuneesti." - ) self.update_active_plan_regulation_group_library() def edit_lifecycles(self): @@ -307,11 +299,7 @@ def _plan_geom_digitized(self, feature: QgsFeature): attribute_form = PlanAttributeForm(plan_model, self.regulation_group_libraries) if attribute_form.exec_(): feat = save_plan(attribute_form.model) - if feat: - iface.messageBar().pushSuccess("", f"Kaava {attribute_form.model.name} luotiin onnistuneesti.") - plan_to_be_activated = feat["id"] - else: - plan_to_be_activated = self.previous_active_plan_id + plan_to_be_activated = feat["id"] if feat else self.previous_active_plan_id else: plan_to_be_activated = self.previous_active_plan_id @@ -338,7 +326,6 @@ def _plan_feature_geom_digitized(self, feature: QgsFeature): plan_feature, title, self.regulation_group_libraries, self.active_plan_regulation_group_library ) if attribute_form.exec_() and save_plan_feature(attribute_form.model): - iface.messageBar().pushSuccess("", f"Kaavakohde {attribute_form.model!s} luotiin onnistuneesti.") self.update_active_plan_regulation_group_library() def edit_plan_feature(self, feature: QgsFeature, layer_name: str): @@ -350,7 +337,6 @@ def edit_plan_feature(self, feature: QgsFeature, layer_name: str): plan_feature, title, self.regulation_group_libraries, self.active_plan_regulation_group_library ) if attribute_form.exec_() and save_plan_feature(attribute_form.model): - iface.messageBar().pushSuccess("", f"Kaavakohdetta {attribute_form.model!s} muokattiin onnistuneesti.") self.update_active_plan_regulation_group_library() def set_active_plan(self, plan_id: str | None): @@ -393,13 +379,10 @@ def load_land_use_plan(self): if dialog.exec_() == QDialog.Accepted: selected_plan_id = dialog.get_selected_plan_id() - selected_plan_name = dialog.get_selected_plan_name() self.commit_all_editable_layers() self.set_active_plan(selected_plan_id) - iface.messageBar().pushSuccess("", f"Kaava {selected_plan_name} avattiin onnistuneesti.") - def commit_all_editable_layers(self): """Commit all changes in any editable layers.""" for layer in QgsProject.instance().mapLayers().values(): @@ -438,7 +421,7 @@ def save_plan_jsons(self, plan_json, outline_json): with open(self.json_plan_outline_path, "w", encoding="utf-8") as outline_file: json.dump(outline_json, outline_file, ensure_ascii=False, indent=2) - iface.messageBar().pushSuccess("", "Kaava ja sen ulkoraja tallennettu onnistuneesti.") + iface.messageBar().pushSuccess("", "Kaava ja kaavan ulkoraja tallennettu.") def unload(self): # Set pan map tool as active (to deactivate our custom tools to avoid errors) diff --git a/arho_feature_template/utils/misc_utils.py b/arho_feature_template/utils/misc_utils.py index 39b8605..6f82e1f 100644 --- a/arho_feature_template/utils/misc_utils.py +++ b/arho_feature_template/utils/misc_utils.py @@ -2,6 +2,7 @@ import os from contextlib import suppress +from functools import wraps from typing import TYPE_CHECKING, Any, cast from qgis.core import QgsExpressionContextUtils, QgsProject, QgsVectorLayer @@ -131,6 +132,7 @@ def deserialize_localized_text(text_value: dict[str, str] | None | Any) -> str | def use_wait_cursor(func): + @wraps(func) def wrapper(*args, **kwargs): with OverrideCursor(Qt.WaitCursor): return func(*args, **kwargs) From b8d74a310802bdcdd68f510a7a0ff5a78fda5659 Mon Sep 17 00:00:00 2001 From: Niko Aarnio Date: Tue, 25 Feb 2025 10:20:42 +0200 Subject: [PATCH 6/6] fix missing return values and error messages for some delete feature functions --- arho_feature_template/core/plan_manager.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/arho_feature_template/core/plan_manager.py b/arho_feature_template/core/plan_manager.py index 5f3ac8d..4b5a5cb 100644 --- a/arho_feature_template/core/plan_manager.py +++ b/arho_feature_template/core/plan_manager.py @@ -672,7 +672,11 @@ def delete_regulation_group(regulation_group: RegulationGroup, plan_id: str | No feature = RegulationGroupLayer.feature_from_model(regulation_group, plan_id) layer = RegulationGroupLayer.get_from_project() - _delete_feature(feature, layer, "Kaavamääräysryhmän poisto") + if not _delete_feature(feature, layer, "Kaavamääräysryhmän poisto"): + iface.messageBar().pushCritical("", "Kaavamääräysryhmän poistaminen epäonnistui.") + return False + + return True def save_regulation_group_as_config(regulation_group: RegulationGroup): @@ -743,7 +747,11 @@ def delete_regulation(regulation: Regulation) -> bool: feature = PlanRegulationLayer.feature_from_model(regulation) layer = PlanRegulationLayer.get_from_project() - _delete_feature(feature, layer, "Kaavamääräyksen poisto") + if not _delete_feature(feature, layer, "Kaavamääräyksen poisto"): + iface.messageBar().pushCritical("", "Lisätiedon poistaminen epäonnistui.") + return False + + return True def save_proposition(proposition: Proposition) -> QgsFeature | None: