From 797ef4411830543f15801a20de8261bb078e88ea Mon Sep 17 00:00:00 2001 From: jmkerloch Date: Thu, 28 Nov 2024 11:06:13 +0100 Subject: [PATCH 1/2] fix(project read): must check if layer is available in qgs project before use - check if layer is available in qgs - if no layer available, don't add layer to project - add some checks on returned object to avoid failure --- menu_from_project/logic/layer_load.py | 6 +- menu_from_project/logic/project_read.py | 161 +++++++++++++----------- menu_from_project/logic/qgs_manager.py | 2 +- menu_from_project/logic/xml_utils.py | 5 +- menu_from_project/menu_from_project.py | 15 ++- 5 files changed, 105 insertions(+), 84 deletions(-) diff --git a/menu_from_project/logic/layer_load.py b/menu_from_project/logic/layer_load.py index 89d62aa..bd0eac1 100644 --- a/menu_from_project/logic/layer_load.py +++ b/menu_from_project/logic/layer_load.py @@ -98,8 +98,8 @@ def addLayer( node = getFirstChildByTagNameValue( doc.documentElement(), "maplayer", "id", layerId ) - node = node.cloneNode() if node: + node = node.cloneNode() idNode = node.namedItem("id") layerType = node.toElement().attribute("type", "vector") # give it a new id (for multiple import) @@ -173,7 +173,7 @@ def addLayer( return newLayer, relationsToBuild else: - self.log("{} not found".format(layerId), indent=loop) + self.log("Layer {} not found. Can't add layer to QGIS.".format(layerId), indent=loop) return None, None @@ -285,6 +285,8 @@ def fixForm( layerNode = getFirstChildByTagNameValue( doc.documentElement(), "maplayer", "id", oldLayerId ) + if not layerNode: + self.log("{} not found for form relation fix".format(oldLayerId)) nodes = layerNode.toElement().elementsByTagName("attributeEditorForm") if nodes.count() == 0: diff --git a/menu_from_project/logic/project_read.py b/menu_from_project/logic/project_read.py index 5d36d0d..4534c63 100644 --- a/menu_from_project/logic/project_read.py +++ b/menu_from_project/logic/project_read.py @@ -2,9 +2,9 @@ from pathlib import Path from typing import Dict, Optional, Tuple -from qgis.core import QgsMapLayerType, QgsMessageLog, QgsWkbTypes # PyQGIS +from qgis.core import QgsMapLayerType, QgsMessageLog, QgsWkbTypes from qgis.PyQt import QtXml from qgis.PyQt.QtCore import QFileInfo @@ -148,7 +148,7 @@ def get_layer_menu_config( qgs_dom_manager: QgsDomManager, init_filename: str, absolute_project: bool, -) -> MenuLayerConfig: +) -> Optional[MenuLayerConfig]: """Get layer menu configuration from a xml node :param node: xml node @@ -163,7 +163,7 @@ def get_layer_menu_config( :param absolute_project: True if project is absolute, False otherwise :type absolute_project: bool :return: layer menu configuration - :rtype: MenuLayerConfig + :rtype: Optional[MenuLayerConfig] """ embedded, filename = read_embedded_properties( @@ -175,40 +175,43 @@ def get_layer_menu_config( if embedded: ml = qgs_dom_manager.getMapLayerDomFromQgs(filename, layer_id) - else: + elif layer_id in maplayer_dict: ml = maplayer_dict[layer_id] + else: + ml = None - if ml: - # Metadata infos - md = ml.namedItem("resourceMetadata") - metadata_title = md.namedItem("title").firstChild().toText().data() - metadata_abstract = md.namedItem("abstract").firstChild().toText().data() - - # Layer info - title = ml.namedItem("title").firstChild().toText().data() - abstract = ml.namedItem("abstract").firstChild().toText().data() - - # Layer notes - layer_notes = "" - elt_note = ml.namedItem("userNotes") - if elt_note.toElement().hasAttribute("value"): - layer_notes = elt_note.toElement().attribute("value") - - # Geometry and layer type - ml_elem = ml.toElement() - geometry_type_str = ml_elem.attribute("geometry") - if geometry_type_str == "": - # A TMS has not a geometry attribute. - # Let's read the "type" - geometry_type_str = ml_elem.attribute("type") - layer_type, geometry_type, is_spatial = get_layer_type_from_geometry_str( - geometry_type_str + if not ml: + QgsMessageLog.logMessage( + f"Menu from layer: Can't find layer {layer_id} in qgs project. Layer won't be added to project.", + __title__, + notifyUser=True, ) - else: - metadata_abstract, metadata_title, title, abstract, layer_notes = "" - layer_type = None - geometry_type = None - is_spatial = False + return None + # Metadata infos + md = ml.namedItem("resourceMetadata") + metadata_title = md.namedItem("title").firstChild().toText().data() + metadata_abstract = md.namedItem("abstract").firstChild().toText().data() + + # Layer info + title = ml.namedItem("title").firstChild().toText().data() + abstract = ml.namedItem("abstract").firstChild().toText().data() + + # Layer notes + layer_notes = "" + elt_note = ml.namedItem("userNotes") + if elt_note.toElement().hasAttribute("value"): + layer_notes = elt_note.toElement().attribute("value") + + # Geometry and layer type + ml_elem = ml.toElement() + geometry_type_str = ml_elem.attribute("geometry") + if geometry_type_str == "": + # A TMS has not a geometry attribute. + # Let's read the "type" + geometry_type_str = ml_elem.attribute("type") + layer_type, geometry_type, is_spatial = get_layer_type_from_geometry_str( + geometry_type_str + ) return MenuLayerConfig( name=element.attribute("name"), @@ -324,15 +327,15 @@ def get_group_menu_config( ) ) elif child.nodeName() == "layer-tree-layer": - childs.append( - get_layer_menu_config( - node=child, - maplayer_dict=maplayer_dict, - qgs_dom_manager=qgs_dom_manager, - init_filename=init_filename, - absolute_project=absolute_project, - ) + layer_config = get_layer_menu_config( + node=child, + maplayer_dict=maplayer_dict, + qgs_dom_manager=qgs_dom_manager, + init_filename=init_filename, + absolute_project=absolute_project, ) + if layer_config: + childs.append(layer_config) return MenuGroupConfig( name=name, embedded=embedded, filename=filename, childs=childs @@ -352,39 +355,45 @@ def get_project_menu_config( :return: Optional menu project configuration :rtype: Optional[MenuProjectConfig] """ + try: + # Get path to QgsProject file, local / downloaded / from postgres database + uri = project.file + qgs_dom_manager.set_project(project) + doc, filename = qgs_dom_manager.getQgsDoc(uri) + + # Define project name + name = project.name + if name == "": + name = get_project_title(doc) + if name == "": + name = Path(filename).stem + + # Get layer tree root + layer_tree_roots = doc.elementsByTagName("layer-tree-group") + if layer_tree_roots.length() > 0: + if node := layer_tree_roots.item(0): + # Create dict of maplayer nodes + maplayer_dict = create_map_layer_dict(doc) + # Parse node for group and layers + menu_project_config = MenuProjectConfig( + project_name=name, + filename=filename, + uri=uri, + root_group=get_group_menu_config( + node=node, + maplayer_dict=maplayer_dict, + qgs_dom_manager=qgs_dom_manager, + init_filename=filename, + absolute_project=is_absolute(doc), + ), + ) - # Get path to QgsProject file, local / downloaded / from postgres database - uri = project.file - qgs_dom_manager.set_project(project) - doc, filename = qgs_dom_manager.getQgsDoc(uri) - - # Define project name - name = project.name - if name == "": - name = get_project_title(doc) - if name == "": - name = Path(filename).stem - - # Get layer tree root - layer_tree_roots = doc.elementsByTagName("layer-tree-group") - if layer_tree_roots.length() > 0: - if node := layer_tree_roots.item(0): - # Create dict of maplayer nodes - maplayer_dict = create_map_layer_dict(doc) - # Parse node for group and layers - menu_project_config = MenuProjectConfig( - project_name=name, - filename=filename, - uri=uri, - root_group=get_group_menu_config( - node=node, - maplayer_dict=maplayer_dict, - qgs_dom_manager=qgs_dom_manager, - init_filename=filename, - absolute_project=is_absolute(doc), - ), - ) - - qgs_dom_manager.set_project(None) - return menu_project_config + qgs_dom_manager.set_project(None) + return menu_project_config + except Exception as e: + QgsMessageLog.logMessage( + f"Menu from layer: Can't parse qgs project for {project.name} : {e}", + __title__, + notifyUser=True, + ) return None diff --git a/menu_from_project/logic/qgs_manager.py b/menu_from_project/logic/qgs_manager.py index de3669b..62bece8 100644 --- a/menu_from_project/logic/qgs_manager.py +++ b/menu_from_project/logic/qgs_manager.py @@ -298,7 +298,7 @@ def getQgsDoc(self, uri: str) -> Tuple[QtXml.QDomDocument, str]: return doc, project_path - def getMapLayerDomFromQgs(self, fileName: str, layerId: str) -> QtXml.QDomNode: + def getMapLayerDomFromQgs(self, fileName: str, layerId: str) -> Optional[QtXml.QDomNode]: """Return the maplayer node in a project filepath given a maplayer ID. :param fileName: The project filepath on the filesystem. diff --git a/menu_from_project/logic/xml_utils.py b/menu_from_project/logic/xml_utils.py index 3886629..47bd466 100644 --- a/menu_from_project/logic/xml_utils.py +++ b/menu_from_project/logic/xml_utils.py @@ -1,3 +1,6 @@ + +from typing import Optional + # PyQGIS from qgis.PyQt.QtXml import QDomNode @@ -17,7 +20,7 @@ def getFirstChildByAttrValue(elt, tagName, key, value): return None -def getFirstChildByTagNameValue(elt, tagName, key, value): +def getFirstChildByTagNameValue(elt, tagName, key, value) -> Optional[QDomNode]: nodes = elt.elementsByTagName(tagName) for node in (nodes.at(i) for i in range(nodes.size())): nd = node.namedItem(key) diff --git a/menu_from_project/menu_from_project.py b/menu_from_project/menu_from_project.py index 999a8ac..7a16b76 100644 --- a/menu_from_project/menu_from_project.py +++ b/menu_from_project/menu_from_project.py @@ -153,10 +153,17 @@ def load_all_project_config( if not project_config: # Create project menu configuration from QgsProject project_config = get_project_menu_config(project, self.qgs_dom_manager) - # Save in cache - cache_manager.save_project_menu_config(project, project_config) - - result.append((project, project_config)) + if project_config: + # Save in cache + cache_manager.save_project_menu_config(project, project_config) + if project_config: + result.append((project, project_config)) + else: + self.log( + self.tr( + f"Can't define project configuration for project {project.name}" + ) + ) return result def project_config_loaded( From f43a12dac5448fcb2d4b5278949da4ec62103ed9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 28 Nov 2024 10:07:02 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- menu_from_project/logic/layer_load.py | 5 ++++- menu_from_project/logic/project_read.py | 1 - menu_from_project/logic/qgs_manager.py | 4 +++- menu_from_project/logic/xml_utils.py | 1 - 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/menu_from_project/logic/layer_load.py b/menu_from_project/logic/layer_load.py index bd0eac1..39e2e1b 100644 --- a/menu_from_project/logic/layer_load.py +++ b/menu_from_project/logic/layer_load.py @@ -173,7 +173,10 @@ def addLayer( return newLayer, relationsToBuild else: - self.log("Layer {} not found. Can't add layer to QGIS.".format(layerId), indent=loop) + self.log( + "Layer {} not found. Can't add layer to QGIS.".format(layerId), + indent=loop, + ) return None, None diff --git a/menu_from_project/logic/project_read.py b/menu_from_project/logic/project_read.py index 4534c63..16fa451 100644 --- a/menu_from_project/logic/project_read.py +++ b/menu_from_project/logic/project_read.py @@ -2,7 +2,6 @@ from pathlib import Path from typing import Dict, Optional, Tuple - # PyQGIS from qgis.core import QgsMapLayerType, QgsMessageLog, QgsWkbTypes from qgis.PyQt import QtXml diff --git a/menu_from_project/logic/qgs_manager.py b/menu_from_project/logic/qgs_manager.py index 62bece8..77fa51b 100644 --- a/menu_from_project/logic/qgs_manager.py +++ b/menu_from_project/logic/qgs_manager.py @@ -298,7 +298,9 @@ def getQgsDoc(self, uri: str) -> Tuple[QtXml.QDomDocument, str]: return doc, project_path - def getMapLayerDomFromQgs(self, fileName: str, layerId: str) -> Optional[QtXml.QDomNode]: + def getMapLayerDomFromQgs( + self, fileName: str, layerId: str + ) -> Optional[QtXml.QDomNode]: """Return the maplayer node in a project filepath given a maplayer ID. :param fileName: The project filepath on the filesystem. diff --git a/menu_from_project/logic/xml_utils.py b/menu_from_project/logic/xml_utils.py index 47bd466..7b540d2 100644 --- a/menu_from_project/logic/xml_utils.py +++ b/menu_from_project/logic/xml_utils.py @@ -1,4 +1,3 @@ - from typing import Optional # PyQGIS