From dc03cedf992c46b1f1b996b1692a5c9013ab4b0f Mon Sep 17 00:00:00 2001 From: Etienne Trimaille Date: Mon, 6 Nov 2023 11:22:56 +0100 Subject: [PATCH] CFG - Keep config even if the layer is unavailable #527 --- CHANGELOG.md | 5 ++- lizmap/dialogs/dock_html_preview.py | 3 ++ lizmap/server_lwc.py | 11 +++--- lizmap/table_manager/base.py | 55 +++++++++++++++++++++++------ lizmap/test/test_table_manager.py | 27 ++++++++++---- 5 files changed, 80 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f854c932..ef3785fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ * Add a new tool for checking the project against some rules : * Possibility to set some safeguards, according to the user level and the server * Some rules might be blocking the CFG file -* Following the previes featue about rules, add new buttons to auto fix the project : +* Following the previous features about rules, add new buttons to auto fix the project : * Use estimated metadata * Use geometry simplification * Use project trust option @@ -16,6 +16,9 @@ * Avoid a Python error about missing primary key * Add a button for adding easily the French IGN orthophoto for French QGIS users * Add attributions on layers which are provided by the plugin +* Possibility to open the plugin even if some layers are temporary unavailable and to no loose some Lizmap configuration + if the layer was used in a tool +* Display some warnings icons if the layer or the field was not loaded correctly or not existing anymore ## 3.18.1 - 2023-10-27 diff --git a/lizmap/dialogs/dock_html_preview.py b/lizmap/dialogs/dock_html_preview.py index 56dc1035..db99a3f4 100644 --- a/lizmap/dialogs/dock_html_preview.py +++ b/lizmap/dialogs/dock_html_preview.py @@ -121,6 +121,9 @@ def __init__(self, parent, *__args): def set_server_url(self, url: str): """ Set the server URL according to the main dialog. """ + if not url: + return + if not url.endswith('/'): url += '/' self._server_url = url diff --git a/lizmap/server_lwc.py b/lizmap/server_lwc.py index 5bbc70d8..13fe9724 100755 --- a/lizmap/server_lwc.py +++ b/lizmap/server_lwc.py @@ -623,9 +623,6 @@ def request_finished(self, row: int): with open(self.cache_file_for_name(server_alias), 'w', encoding='utf8') as json_file: json_file.write(json_file_content) - # TODO, I think there something wrong here - # action_text_cell.setData(Qt.UserRole, content) - # Add the JSON metadata in the server combobox index = self.server_combo.findData(url, ServerComboData.ServerUrl.value) self.server_combo.setItemData(index, content, ServerComboData.JsonMetadata.value) @@ -643,6 +640,7 @@ def request_finished(self, row: int): markdown += '* Lizmap plugin : {}\n'.format(version()) markdown += '* QGIS Desktop : {}\n'.format(Qgis.QGIS_VERSION.split('-')[0]) qgis_cell.setData(Qt.UserRole, markdown) + qgis_cell.setData(Qt.UserRole + 1, content) # Only adding Lizmap saas if set to true if is_lizmap_cloud(content): @@ -1070,7 +1068,12 @@ def context_menu_requested(self, position: QPoint): slot = partial(QDesktopServices.openUrl, QUrl(ServerWizard.url_server_info(url))) open_server_info_url.triggered.connect(slot) - if any(item in version() for item in UNSTABLE_VERSION_PREFIX) or to_bool(os.getenv("LIZMAP_ADVANCED_USER")): + is_dev = ( + any( + item in version() for item in UNSTABLE_VERSION_PREFIX + ) or to_bool(os.getenv("LIZMAP_ADVANCED_USER")) + ) + if is_dev: open_url = menu.addAction(tr("Open raw JSON file URL") + "…") left_item = self.table.item(item.row(), TableCell.Url.value) url = left_item.data(Qt.UserRole) diff --git a/lizmap/table_manager/base.py b/lizmap/table_manager/base.py index 97044853..5e730577 100755 --- a/lizmap/table_manager/base.py +++ b/lizmap/table_manager/base.py @@ -223,6 +223,15 @@ def edit_existing_row(self): row = selection[0].row() + # Invalid layer + cell = self.table.item(row, 0) + value = cell.data(Qt.UserRole + 1) + if isinstance(value, bool) and not value: + # value is a boolean = False + # We can't edit a layer which is invalid, + # Maybe just the layer wasn't loaded in QGIS desktop because the file was missing + return + data = dict() for i, key in enumerate(self.keys): cell = self.table.item(row, i) @@ -258,13 +267,29 @@ def _edit_row(self, row, data): value = value(self._layer) if input_type == InputType.Layer: - layer = self.project.mapLayer(value) - self._layer = layer - cell.setText(layer.name()) - cell.setData(Qt.UserRole, layer.id()) - cell.setData(Qt.ToolTipRole, '{} ({})'.format(layer.name(), layer.crs().authid())) - # noinspection PyArgumentList - cell.setIcon(QgsMapLayerModel.iconForLayer(layer)) + self._layer = self.project.mapLayer(value) + cell.setData(Qt.UserRole, value) + if self._layer: + cell.setText(self._layer.name()) + cell.setData(Qt.ToolTipRole, '{} ({})'.format(self._layer.name(), self._layer.crs().authid())) + if self._layer.isValid(): + cell.setIcon(QgsMapLayerModel.iconForLayer(self._layer)) + cell.setData(Qt.UserRole + 1, True) + + if not self._layer or not self._layer.isValid(): + # Layer is not correctly loading in QGIS + cell.setData(Qt.UserRole + 1, False) + cell.setIcon(QIcon(":/images/themes/default/mIconWarning.svg")) + if self._layer: + tooltip_value = self._layer.name() + else: + tooltip_value = value + cell.setData( + Qt.ToolTipRole, + tr( + 'Layer {} is unavailable in QGIS, it\'s not possible to edit its configuration.' + ).format(tooltip_value) + ) elif input_type == InputType.Layers: names = [] @@ -294,6 +319,13 @@ def _edit_row(self, row, data): 'Field "{}" not found in the layer. You should check this configuration or fix your ' 'fields.' ).format(value)) + else: + # No layer + cell.setIcon(QIcon(":/images/themes/default/mIconWarning.svg")) + cell.setData( + Qt.ToolTipRole, + tr("Not possible to check the field type if the layer is not loaded in QGIS").format(value) + ) elif input_type == InputType.Fields: cell.setText(value) @@ -962,11 +994,14 @@ def from_json(self, data: dict): if definition['type'] == InputType.Layer: vector_layer = self.project.mapLayer(value) if not vector_layer or not vector_layer.isValid(): + # A layer temporary not available will be found in the project, but "not valid". + # Some metadata like CRS was still imported from the QGS file, but not fields LOGGER.warning( - 'In Lizmap configuration file, section "{}", the layer with ID "{}" is invalid or ' - 'does not exist. Skipping that layer.'.format( + 'In Lizmap configuration file, section "{}", the layer with ID "{}" is invalid or does ' + 'not exist. Trying to keep configuration.'.format( self.definitions.key(), value)) - valid_layer = False + # Let's try to keep the configuration + # valid_layer = False layer_data[key] = value elif definition['type'] == InputType.Layers: layer_data[key] = value diff --git a/lizmap/test/test_table_manager.py b/lizmap/test/test_table_manager.py index 37925fcd..dc65a0be 100755 --- a/lizmap/test/test_table_manager.py +++ b/lizmap/test/test_table_manager.py @@ -1185,17 +1185,16 @@ def test_locate_by_layer(self): } self.assertDictEqual(data, expected) - def test_fake_layer_id_table_manager(self): - """Test we can skip a wrong layer id.""" + def test_unavailable_layer_table_manager(self): + """ Test we can keep layer which is unavailable at the moment. """ table = QTableWidget() definitions = AtlasDefinitions() table_manager = TableManager( None, definitions, AtlasEditionDialog, table, None, None, None, None) - self.assertEqual(table.columnCount(), len(definitions.layer_config.keys())) + self.assertEqual(table_manager.table.rowCount(), 0) - # JSON from LWC 3.4 and above layer_1 = { "layer": "ID_WHICH_DOES_NOT_EXIST", "primaryKey": "id", @@ -1213,9 +1212,25 @@ def test_fake_layer_id_table_manager(self): layer_1 ] } - self.assertEqual(table_manager.table.rowCount(), 0) table_manager.from_json(json) - self.assertEqual(table_manager.table.rowCount(), 0) + self.assertEqual(table_manager.table.rowCount(), 1) + data = table_manager.to_json(version=LwcVersions.Lizmap_3_6) + + expected = { + 'atlasLayer': 'ID_WHICH_DOES_NOT_EXIST', + 'atlasPrimaryKey': 'id', + 'atlasDisplayLayerDescription': 'False', + 'atlasFeatureLabel': 'name', + 'atlasSortField': 'name', + 'atlasHighlightGeometry': 'True', + 'atlasZoom': 'center', + 'atlasDisplayPopup': 'True', + 'atlasTriggerFilter': 'True', + 'atlasDuration': 5, + 'atlasEnabled': 'True', + 'atlasMaxWidth': 25 + } + self.assertDictEqual(expected, data) def test_table_manager(self): """Test about the table manager.