diff --git a/QgisModelBaker/gui/dataset_manager.py b/QgisModelBaker/gui/dataset_manager.py index 31c5cc4d5..74c9ee7bb 100644 --- a/QgisModelBaker/gui/dataset_manager.py +++ b/QgisModelBaker/gui/dataset_manager.py @@ -105,6 +105,7 @@ def _close_editing(self): if layer.isEditable(): editable_layers.append(layer) if editable_layers: + # in case it could not close it automatically warning_box = QMessageBox(self) warning_box.setIcon(QMessageBox.Warning) warning_title = self.tr("Layers still editable") diff --git a/QgisModelBaker/gui/panel/layer_tids_panel.py b/QgisModelBaker/gui/panel/layer_tids_panel.py new file mode 100644 index 000000000..cd21e026c --- /dev/null +++ b/QgisModelBaker/gui/panel/layer_tids_panel.py @@ -0,0 +1,263 @@ +""" +/*************************************************************************** + ------------------- + begin : 17.11.2023 + git sha : :%H$ + copyright : (C) 2023 by Dave Signer + email : david at opengis ch + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from enum import Enum, IntEnum + +from qgis.core import QgsProject +from qgis.gui import QgsFieldExpressionWidget +from qgis.PyQt.QtCore import QAbstractTableModel, QModelIndex, Qt +from qgis.PyQt.QtGui import QPixmap +from qgis.PyQt.QtWidgets import ( + QAbstractItemView, + QHeaderView, + QStyledItemDelegate, + QWidget, +) + +import QgisModelBaker.utils.gui_utils as gui_utils +from QgisModelBaker.libs.modelbaker.utils.qgis_utils import QgisProjectUtils +from QgisModelBaker.utils.gui_utils import CheckDelegate + +WIDGET_UI = gui_utils.get_ui_class("layer_tids_panel.ui") + + +class TIDModel(QAbstractTableModel): + """ + ItemModel providing all TIDs and default values of the passed project + + oid_settings is a dictionary like: + { + "Strasse": + { + "oid_domain": "STANDARDOID", + "interlis_topic" : "OIDMadness_V1", + "default_value_expression": "uuid()", + "in_form": True + } + [...] + } + """ + + class Roles(Enum): + LAYER = Qt.UserRole + 1 + + def __int__(self): + return self.value + + class Columns(IntEnum): + NAME = 0 + OID_DOMAIN = 1 + DEFAULT_VALUE = 2 + IN_FORM = 3 + + def __init__(self): + super().__init__() + self.oid_settings = {} + + def columnCount(self, parent): + return len(TIDModel.Columns) + + def rowCount(self, parent): + return len(self.oid_settings.keys()) + + def flags(self, index): + if index.column() == TIDModel.Columns.IN_FORM: + return Qt.ItemIsEnabled + if index.column() == TIDModel.Columns.DEFAULT_VALUE: + return Qt.ItemIsEditable | Qt.ItemIsEnabled + return Qt.ItemIsSelectable | Qt.ItemIsEnabled + + def index(self, row: int, column: int, parent: QModelIndex = ...) -> QModelIndex: + """ + default override + """ + return super().createIndex(row, column, parent) + + def parent(self, index): + """ + default override + """ + return index + + def headerData(self, section, orientation, role): + if orientation == Qt.Horizontal and role == Qt.DisplayRole: + if section == TIDModel.Columns.NAME: + return self.tr("Layer") + if section == TIDModel.Columns.OID_DOMAIN: + return self.tr("TID (OID Type)") + if section == TIDModel.Columns.DEFAULT_VALUE: + return self.tr("Default Value Expression") + if section == TIDModel.Columns.IN_FORM: + return self.tr("Show") + + def data(self, index, role): + if role == int(Qt.DisplayRole) or role == int(Qt.EditRole): + key = list(self.oid_settings.keys())[index.row()] + if index.column() == TIDModel.Columns.NAME: + return f"{key} ({self.oid_settings[key]['interlis_topic']})" + if index.column() == TIDModel.Columns.OID_DOMAIN: + return self.oid_settings[key]["oid_domain"] + if index.column() == TIDModel.Columns.DEFAULT_VALUE: + return self.oid_settings[key]["default_value_expression"] + if index.column() == TIDModel.Columns.IN_FORM: + return self.oid_settings[key]["in_form"] + elif role == int(Qt.ToolTipRole): + key = list(self.oid_settings.keys())[index.row()] + if index.column() == TIDModel.Columns.NAME: + return f"{key} ({self.oid_settings[key]['interlis_topic']})" + if index.column() == TIDModel.Columns.OID_DOMAIN: + message = self.tr( + "

The OID format is not defined, you can use whatever you want, but it should always start with an underscore _ or an alphanumeric value.

" + ) + oid_domain = self.oid_settings[key].get("oid_domain", "") + if oid_domain[-7:] == "UUIDOID": + message = self.tr( + "

The OID should be an Universally Unique Identifier (OID TEXT*36).

" + ) + elif oid_domain[-11:] == "STANDARDOID": + message = self.tr( + """ + +

+ The OID format requireds an 8 char prefix and 8 char postfix. +

+

Prefix (2 + 6 chars): Country identifier + a 'global' identification part assigned once by the official authority.

+

Postfix (8 chars): Sequence (numeric or alphanumeric) of your system as 'local' identification part.

+ + + """ + ) + elif oid_domain[-6:] == "I32OID": + message = self.tr( + "

The OID must be an integer value (OID 0 .. 2147483647).

" + ) + elif oid_domain[-6:] == "ANYOID": + message = self.tr( + "

The OID format could vary depending in what basket the object (entry) is located.

These objects could be in the following topics: {topics}".format( + topics=self.oid_settings[key]["interlis_topic"] + ) + ) + return message + if index.column() == TIDModel.Columns.DEFAULT_VALUE: + return self.oid_settings[key]["default_value_expression"] + if index.column() == TIDModel.Columns.IN_FORM: + return self.tr("Show t_ili_tid field (OID) in attribute form.") + elif role == int(TIDModel.Roles.LAYER): + key = list(self.oid_settings.keys())[index.row()] + return self.oid_settings[key]["layer"] + return None + + def setData(self, index, data, role): + if role == int(Qt.EditRole): + if index.column() == TIDModel.Columns.DEFAULT_VALUE: + key = list(self.oid_settings.keys())[index.row()] + self.oid_settings[key]["default_value_expression"] = data + self.dataChanged.emit(index, index) + if index.column() == TIDModel.Columns.IN_FORM: + key = list(self.oid_settings.keys())[index.row()] + self.oid_settings[key]["in_form"] = data + self.dataChanged.emit(index, index) + return True + + def load_tid_config(self, qgis_project=None): + self.beginResetModel() + self.oid_settings = QgisProjectUtils(qgis_project).get_oid_settings() + self.endResetModel() + + def save_tid_config(self, qgis_project=None): + if qgis_project: + QgisProjectUtils(qgis_project).set_oid_settings(self.oid_settings) + + +class FieldExpressionDelegate(QStyledItemDelegate): + def __init__(self, parent): + super().__init__(parent) + self.parent = parent + self.editor = None + + def createEditor(self, parent, option, index): + self.editor = QgsFieldExpressionWidget(parent) + layer = index.data(int(TIDModel.Roles.LAYER)) + self.editor.setLayer(layer) + return self.editor + + def setEditorData(self, editor, index): + value = index.data(int(Qt.DisplayRole)) + self.editor.setExpression(value) + + def setModelData(self, editor, model, index): + value = editor.expression() + print(f"new exp{value}") + model.setData(index, value, int(Qt.EditRole)) + + def updateEditorGeometry(self, editor, option, index): + self.editor.setGeometry(option.rect) + + def paint(self, painter, option, index): + opt = self.createEditor(self.parent, option, index) + opt.editable = False + value = index.data(int(Qt.DisplayRole)) + opt.setExpression(value) + opt.resize(option.rect.width(), option.rect.height()) + pixmap = QPixmap(opt.width(), opt.height()) + opt.render(pixmap) + painter.drawPixmap(option.rect, pixmap) + painter.restore() + + +class LayerTIDsPanel(QWidget, WIDGET_UI): + def __init__(self, parent=None): + QWidget.__init__(self, parent) + self.setupUi(self) + self.parent = parent + self.tid_model = TIDModel() + self.layer_tids_view.setModel(self.tid_model) + + self.layer_tids_view.horizontalHeader().setSectionResizeMode( + TIDModel.Columns.NAME, QHeaderView.Stretch + ) + self.layer_tids_view.horizontalHeader().setSectionResizeMode( + TIDModel.Columns.OID_DOMAIN, QHeaderView.ResizeToContents + ) + self.layer_tids_view.horizontalHeader().setSectionResizeMode( + TIDModel.Columns.DEFAULT_VALUE, QHeaderView.ResizeToContents + ) + self.layer_tids_view.horizontalHeader().setSectionResizeMode( + TIDModel.Columns.IN_FORM, QHeaderView.ResizeToContents + ) + + self.layer_tids_view.setItemDelegateForColumn( + TIDModel.Columns.IN_FORM, + CheckDelegate(self, Qt.EditRole), + ) + self.layer_tids_view.setItemDelegateForColumn( + TIDModel.Columns.DEFAULT_VALUE, + FieldExpressionDelegate(self), + ) + self.layer_tids_view.setEditTriggers(QAbstractItemView.AllEditTriggers) + + def load_tid_config(self, qgis_project=QgsProject.instance()): + self.tid_model.load_tid_config(qgis_project) + + def save_tid_config(self, qgis_project=QgsProject.instance()): + # if a cell is still edited, we need to store it in model by force + index = self.layer_tids_view.currentIndex() + self.layer_tids_view.currentChanged(index, index) + self.tid_model.save_tid_config(qgis_project) diff --git a/QgisModelBaker/gui/panel/set_sequence_panel.py b/QgisModelBaker/gui/panel/set_sequence_panel.py new file mode 100644 index 000000000..344891822 --- /dev/null +++ b/QgisModelBaker/gui/panel/set_sequence_panel.py @@ -0,0 +1,46 @@ +""" +/*************************************************************************** + ------------------- + begin : 17.11.2023 + git sha : :%H$ + copyright : (C) 2023 by Dave Signer + email : david at opengis ch + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtWidgets import QWidget + +import QgisModelBaker.utils.gui_utils as gui_utils + +WIDGET_UI = gui_utils.get_ui_class("set_sequence_panel.ui") + + +class SetSequencePanel(QWidget, WIDGET_UI): + def __init__(self, parent=None): + QWidget.__init__(self, parent) + self.setupUi(self) + + def load_sequence(self, db_connector=None): + if db_connector: + sequence_value = db_connector.get_ili2db_sequence_value() + self.sequence_value_edit.setValue(sequence_value) + + def save_sequence(self, db_connector=None): + if self.sequence_group.isChecked(): + if db_connector: + return db_connector.set_ili2db_sequence_value( + self.sequence_value_edit.value() + ) + return False, self.tr("Could not reset T_Id - no db_connector...") + else: + return True, self.tr("T_Id not set.") diff --git a/QgisModelBaker/gui/panel/tid_configurator_panel.py b/QgisModelBaker/gui/panel/tid_configurator_panel.py new file mode 100644 index 000000000..9ea585b3d --- /dev/null +++ b/QgisModelBaker/gui/panel/tid_configurator_panel.py @@ -0,0 +1,83 @@ +""" +/*************************************************************************** + ------------------- + begin : 17.11.2023 + git sha : :%H$ + copyright : (C) 2023 by Dave Signer + email : david at opengis ch + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtWidgets import QWidget + +import QgisModelBaker.libs.modelbaker.utils.db_utils as db_utils +from QgisModelBaker.gui.panel.layer_tids_panel import LayerTIDsPanel +from QgisModelBaker.gui.panel.set_sequence_panel import SetSequencePanel +from QgisModelBaker.libs.modelbaker.iliwrapper.ili2dbconfig import ( + Ili2DbCommandConfiguration, +) +from QgisModelBaker.utils import gui_utils + +WIDGET_UI = gui_utils.get_ui_class("tid_configurator_panel.ui") + + +class TIDConfiguratorPanel(QWidget, WIDGET_UI): + def __init__(self, parent=None): + QWidget.__init__(self, parent) + self.setupUi(self) + self.parent = parent + + self.layer_tids_panel = LayerTIDsPanel(self.parent) + self.layer_tids_layout.addWidget(self.layer_tids_panel) + + self.set_sequence_panel = SetSequencePanel(self.parent) + self.set_sequence_layout.addWidget(self.set_sequence_panel) + + self.reset_layer_tids_button.clicked.connect(self._reset_tid_configuration) + + self.qgis_project = None + self.db_connector = None + + def setup_dialog(self, qgis_project, db_connector=None): + self.qgis_project = qgis_project + + if self.qgis_project: + if db_connector: + self.db_connector = db_connector + else: + # getting the data source of the first layer in the layer tree + layers = qgis_project.layerTreeRoot().findLayers() + if layers: + first_tree_layer = layers[0] + configuration = Ili2DbCommandConfiguration() + source_provider = first_tree_layer.layer().dataProvider() + valid, mode = db_utils.get_configuration_from_sourceprovider( + source_provider, configuration + ) + if valid: + configuration.tool = mode + self.db_connector = db_utils.get_db_connector(configuration) + + self._reset_tid_configuration() + + def _reset_tid_configuration(self): + self.layer_tids_panel.load_tid_config(self.qgis_project) + self.set_sequence_panel.load_sequence(self.db_connector) + + def set_tid_configuration(self): + result, message = self.set_sequence_panel.save_sequence(self.db_connector) + # only set the project settings when the sequence part succeeds (or is not performed) + if result: + self.layer_tids_panel.save_tid_config(self.qgis_project) + return True, message + return False, message diff --git a/QgisModelBaker/gui/tid_manager.py b/QgisModelBaker/gui/tid_manager.py new file mode 100644 index 000000000..5987c5f84 --- /dev/null +++ b/QgisModelBaker/gui/tid_manager.py @@ -0,0 +1,81 @@ +""" +/*************************************************************************** + begin : 10.08.2021 + git sha : :%H$ + copyright : (C) 2021 by Dave Signer + email : david at opengis ch + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + +from qgis.core import QgsMapLayer, QgsProject +from qgis.PyQt.QtWidgets import QDialog, QMessageBox + +from QgisModelBaker.gui.panel.tid_configurator_panel import TIDConfiguratorPanel +from QgisModelBaker.utils import gui_utils + +DIALOG_UI = gui_utils.get_ui_class("tid_manager.ui") + + +class TIDManagerDialog(QDialog, DIALOG_UI): + def __init__(self, iface, parent=None): + + QDialog.__init__(self, parent) + self.iface = iface + self._close_editing() + self.parent = parent + + self.setupUi(self) + + self.tid_configurator_panel = TIDConfiguratorPanel(self.parent) + self.tid_configurator_layout.addWidget(self.tid_configurator_panel) + + self.buttonBox.accepted.connect(self._accepted) + self.buttonBox.rejected.connect(self._rejected) + + self.setStyleSheet(gui_utils.DEFAULT_STYLE) + + self.tid_configurator_panel.setup_dialog(QgsProject.instance()) + + def _close_editing(self): + editable_layers = [] + for layer in QgsProject.instance().mapLayers().values(): + if layer.type() == QgsMapLayer.VectorLayer: + self.iface.vectorLayerTools().stopEditing(layer) + if layer.isEditable(): + editable_layers.append(layer) + if editable_layers: + # in case it could not close it automatically + warning_box = QMessageBox(self) + warning_box.setIcon(QMessageBox.Warning) + warning_title = self.tr("Layers still editable") + warning_box.setWindowTitle(warning_title) + warning_box.setText( + self.tr( + "You still have layers in edit mode.\nIn case you modify the sequence in the database of those layers, it could lead to database locks.\nEditable layers are:\n - {}" + ).format("\n - ".join([layer.name() for layer in editable_layers])) + ) + warning_box.exec_() + + def _accepted(self): + result, message = self.tid_configurator_panel.set_tid_configuration() + if not result: + error_box = QMessageBox(self) + error_box.setIcon(QMessageBox.Error) + warning_title = self.tr("Problems on setting T_Id sequence.") + error_box.setWindowTitle(warning_title) + error_box.setText(message) + error_box.exec_() + else: + self.close() + + def _rejected(self): + self.close() diff --git a/QgisModelBaker/gui/topping_wizard/ili2dbsettings_page.py b/QgisModelBaker/gui/topping_wizard/ili2dbsettings_page.py index 2e2656e82..b4c485aa5 100644 --- a/QgisModelBaker/gui/topping_wizard/ili2dbsettings_page.py +++ b/QgisModelBaker/gui/topping_wizard/ili2dbsettings_page.py @@ -86,6 +86,7 @@ def data(self, index, role): return self.parameters.get(key, "") return None + # this is unusual that it's not first data and then role (could be changed) def setData(self, index, role, data): if role == int(Qt.EditRole): if index.column() == ParametersModel.Columns.NAME: diff --git a/QgisModelBaker/gui/topping_wizard/layers_page.py b/QgisModelBaker/gui/topping_wizard/layers_page.py index ee075182a..4b57f51a5 100644 --- a/QgisModelBaker/gui/topping_wizard/layers_page.py +++ b/QgisModelBaker/gui/topping_wizard/layers_page.py @@ -207,6 +207,7 @@ def data(self, index, role): return QgsLayerTreeModel.data(self, index, role) + # this is unusual that it's not first data and then role (could be changed) def setData(self, index, role, data): if role == Qt.CheckStateRole: node = self.index2node(index) diff --git a/QgisModelBaker/gui/workflow_wizard/import_data_configuration_page.py b/QgisModelBaker/gui/workflow_wizard/import_data_configuration_page.py index 9a4b2f7a1..79876c277 100644 --- a/QgisModelBaker/gui/workflow_wizard/import_data_configuration_page.py +++ b/QgisModelBaker/gui/workflow_wizard/import_data_configuration_page.py @@ -19,18 +19,15 @@ import os -from PyQt5.QtWidgets import QApplication from qgis.core import QgsApplication -from qgis.PyQt.QtCore import QEvent, Qt -from qgis.PyQt.QtGui import QIcon +from qgis.PyQt.QtCore import Qt +from qgis.PyQt.QtGui import QIcon, QPixmap from qgis.PyQt.QtWidgets import ( + QAbstractItemView, QComboBox, QCompleter, QHeaderView, - QStyle, QStyledItemDelegate, - QStyleOptionButton, - QStyleOptionComboBox, QWizardPage, ) @@ -42,7 +39,7 @@ MetaConfigCompleterDelegate, ) from QgisModelBaker.utils.globals import CATALOGUE_DATASETNAME, DEFAULT_DATASETNAME -from QgisModelBaker.utils.gui_utils import LogColor +from QgisModelBaker.utils.gui_utils import CheckDelegate, LogColor PAGE_UI = gui_utils.get_ui_class("workflow_wizard/import_data_configuration.ui") @@ -50,6 +47,7 @@ class DatasetComboDelegate(QStyledItemDelegate): def __init__(self, parent, db_connector): super().__init__(parent) + self.parent = parent self.refresh_datasets(db_connector) def refresh_datasets(self, db_connector): @@ -79,36 +77,16 @@ def updateEditorGeometry(self, editor, option, index): editor.setGeometry(option.rect) def paint(self, painter, option, index): - """ - Here it paints only the lable without a StyleItem for the ComboBox, because to edit it needs multiple clicks and the behavior gets confusing. - """ - opt = QStyleOptionComboBox() + opt = self.createEditor(self.parent, option, index) opt.editable = False - opt.rect = option.rect - value = index.data(int(Qt.DisplayRole)) - opt.currentText = value - QApplication.style().drawControl(QStyle.CE_ComboBoxLabel, opt, painter) - - -class CatalogueCheckDelegate(QStyledItemDelegate): - def __init__(self, parent): - super().__init__(parent) - - def editorEvent(self, event, model, option, index): - if event.type() == QEvent.MouseButtonRelease: - value = index.data(int(gui_utils.SourceModel.Roles.IS_CATALOGUE)) or False - model.setData( - index, not value, int(gui_utils.SourceModel.Roles.IS_CATALOGUE) - ) - return True - return super().editorEvent(event, model, option, index) - - def paint(self, painter, option, index): - opt = QStyleOptionButton() - opt.rect = option.rect - value = index.data(int(gui_utils.SourceModel.Roles.IS_CATALOGUE)) or False - opt.state |= QStyle.State_On if value else QStyle.State_Off - QApplication.style().drawControl(QStyle.CE_CheckBox, opt, painter) + value = index.data(int(gui_utils.SourceModel.Roles.DATASET_NAME)) + num = self.items.index(value) if value in self.items else 0 + opt.setCurrentIndex(num) + opt.resize(option.rect.width(), option.rect.height()) + pixmap = QPixmap(opt.width(), opt.height()) + opt.render(pixmap) + painter.drawPixmap(option.rect, pixmap) + painter.restore() class ImportDataConfigurationPage(QWizardPage, PAGE_UI): @@ -173,6 +151,7 @@ def __init__(self, parent, title): gui_utils.SourceModel.Columns.DATASET, QHeaderView.ResizeToContents ) + self.file_table_view.setEditTriggers(QAbstractItemView.AllEditTriggers) self.file_table_view.verticalHeader().setSectionsMovable(True) self.file_table_view.verticalHeader().setDragEnabled(True) self.file_table_view.verticalHeader().setDragDropMode(QHeaderView.InternalMove) @@ -269,7 +248,7 @@ def _set_basket_defaults(self): self.file_table_view.setItemDelegateForColumn( gui_utils.SourceModel.Columns.IS_CATALOGUE, - CatalogueCheckDelegate(self), + CheckDelegate(self, gui_utils.SourceModel.Roles.IS_CATALOGUE), ) self.file_table_view.setItemDelegateForColumn( gui_utils.SourceModel.Columns.DATASET, diff --git a/QgisModelBaker/gui/workflow_wizard/project_creation_page.py b/QgisModelBaker/gui/workflow_wizard/project_creation_page.py index 2e94ae094..abd006991 100644 --- a/QgisModelBaker/gui/workflow_wizard/project_creation_page.py +++ b/QgisModelBaker/gui/workflow_wizard/project_creation_page.py @@ -45,7 +45,6 @@ def __init__(self, parent, title): self.workflow_wizard = parent self.setupUi(self) - self.setFinalPage(True) self.setTitle(title) self.setStyleSheet(gui_utils.DEFAULT_STYLE) @@ -63,6 +62,16 @@ def __init__(self, parent, title): self.create_project_button.clicked.connect(self._create_project) + self.is_complete = False + + def isComplete(self): + return self.is_complete + + def setComplete(self, complete): + self.is_complete = complete + self.create_project_button.setDisabled(complete) + self.completeChanged.emit() + def set_configuration(self, configuration): self.configuration = configuration @@ -349,6 +358,7 @@ def _create_project(self): self.progress_bar.setValue(100) self.setStyleSheet(gui_utils.SUCCESS_STYLE) self.workflow_wizard.log_panel.print_info(self.tr("It's served!")) + self.setComplete(True) def ilidata_path_resolver(self, base_path, path): if "ilidata:" in path or "file:" in path: diff --git a/QgisModelBaker/gui/workflow_wizard/tid_configuration_page.py b/QgisModelBaker/gui/workflow_wizard/tid_configuration_page.py new file mode 100644 index 000000000..c1635addc --- /dev/null +++ b/QgisModelBaker/gui/workflow_wizard/tid_configuration_page.py @@ -0,0 +1,72 @@ +""" +/*************************************************************************** + ------------------- + begin : 17.11.2023 + git sha : :%H$ + copyright : (C) 2023 by Dave Signer + email : david at opengis ch + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.core import QgsProject +from qgis.PyQt.QtWidgets import QWizardPage + +import QgisModelBaker.libs.modelbaker.utils.db_utils as db_utils +from QgisModelBaker.gui.panel.tid_configurator_panel import TIDConfiguratorPanel +from QgisModelBaker.utils import gui_utils + +PAGE_UI = gui_utils.get_ui_class("workflow_wizard/tid_configuration.ui") + + +class TIDConfigurationPage(QWizardPage, PAGE_UI): + def __init__(self, parent, title): + QWizardPage.__init__(self, parent) + + self.workflow_wizard = parent + + self.setupUi(self) + self.setFinalPage(True) + self.setTitle(title) + self.setStyleSheet(gui_utils.DEFAULT_STYLE) + + self.tid_configurator_panel = TIDConfiguratorPanel(self.workflow_wizard) + self.tid_configurator_layout.addWidget(self.tid_configurator_panel) + + self.set_layer_tids_and_sequence_button.clicked.connect( + self._set_tid_configuration + ) + + self.configuration = None + + def set_configuration(self, configuration): + self.configuration = configuration + db_connector = db_utils.get_db_connector(self.configuration) + self.tid_configurator_panel.setup_dialog(QgsProject.instance(), db_connector) + + def _set_tid_configuration(self): + self.progress_bar.setValue(0) + # we store the settings to project and db + result, message = self.tid_configurator_panel.set_tid_configuration() + if result: + self.workflow_wizard.log_panel.print_info( + self.tr("Stored TID configurations to current project") + ) + self.workflow_wizard.log_panel.print_info( + self.tr("Stored the sequence value to current database") + ) + self.progress_bar.setValue(100) + self.setStyleSheet(gui_utils.SUCCESS_STYLE) + else: + self.workflow_wizard.log_panel.print_info(message) + self.progress_bar.setValue(0) + self.setStyleSheet(gui_utils.ERROR_STYLE) diff --git a/QgisModelBaker/gui/workflow_wizard/workflow_wizard.py b/QgisModelBaker/gui/workflow_wizard/workflow_wizard.py index 3db32c7d8..b90da69ed 100644 --- a/QgisModelBaker/gui/workflow_wizard/workflow_wizard.py +++ b/QgisModelBaker/gui/workflow_wizard/workflow_wizard.py @@ -44,6 +44,9 @@ ) from QgisModelBaker.gui.workflow_wizard.intro_page import IntroPage from QgisModelBaker.gui.workflow_wizard.project_creation_page import ProjectCreationPage +from QgisModelBaker.gui.workflow_wizard.tid_configuration_page import ( + TIDConfigurationPage, +) from QgisModelBaker.libs.modelbaker.iliwrapper.ili2dbconfig import ( ExportConfiguration, ImportDataConfiguration, @@ -161,6 +164,9 @@ def __init__(self, iface, base_config, parent): self.project_creation_page = ProjectCreationPage( self, self._current_page_title(PageIds.ProjectCreation) ) + self.tid_configuration_page = TIDConfigurationPage( + self, self._current_page_title(PageIds.TIDConfiguration) + ) self.generate_database_selection_page = DatabaseSelectionPage( self, self._current_page_title(PageIds.GenerateDatabaseSelection), @@ -189,6 +195,7 @@ def __init__(self, iface, base_config, parent): self.setPage(PageIds.ImportDataConfiguration, self.data_configuration_page) self.setPage(PageIds.ImportDataExecution, self.import_data_execution_page) self.setPage(PageIds.ProjectCreation, self.project_creation_page) + self.setPage(PageIds.TIDConfiguration, self.tid_configuration_page) self.setPage( PageIds.GenerateDatabaseSelection, self.generate_database_selection_page ) @@ -316,6 +323,9 @@ def next_id(self): if self.current_id == PageIds.ExportDataConfiguration: return PageIds.ExportDataExecution + if self.current_id == PageIds.ProjectCreation: + return PageIds.TIDConfiguration + return self.current_id def id_changed(self, new_id): @@ -369,6 +379,11 @@ def id_changed(self, new_id): self.import_schema_configuration ) + if self.current_id == PageIds.TIDConfiguration: + self.tid_configuration_page.set_configuration( + self.import_schema_configuration + ) + if self.current_id == PageIds.ImportDataConfiguration: self.data_configuration_page.setup_dialog( self._basket_handling(self.import_data_configuration) @@ -453,6 +468,8 @@ def _current_page_title(self, id): return self.tr("Data Export Sessions") elif id == PageIds.ProjectCreation: return self.tr("Generate a QGIS Project") + elif id == PageIds.TIDConfiguration: + return self.tr("Configure OID Generation") else: return self.tr("Model Baker - Workflow Wizard") diff --git a/QgisModelBaker/images/QgisModelBaker-tidmanager-icon.svg b/QgisModelBaker/images/QgisModelBaker-tidmanager-icon.svg new file mode 100644 index 000000000..f98a61e85 --- /dev/null +++ b/QgisModelBaker/images/QgisModelBaker-tidmanager-icon.svg @@ -0,0 +1,2 @@ + +image/svg+xml123ab diff --git a/QgisModelBaker/qgismodelbaker.py b/QgisModelBaker/qgismodelbaker.py index cd74c2c06..976b74d7b 100644 --- a/QgisModelBaker/qgismodelbaker.py +++ b/QgisModelBaker/qgismodelbaker.py @@ -48,6 +48,7 @@ from QgisModelBaker.gui.drop_message import DropMessageDialog from QgisModelBaker.gui.options import OptionsDialog from QgisModelBaker.gui.panel.dataset_selector import DatasetSelector +from QgisModelBaker.gui.tid_manager import TIDManagerDialog from QgisModelBaker.gui.topping_wizard.topping_wizard import ToppingWizardDialog from QgisModelBaker.gui.validate import ValidateDock from QgisModelBaker.gui.workflow_wizard.workflow_wizard import WorkflowWizardDialog @@ -65,10 +66,12 @@ def __init__(self, iface): self.workflow_wizard_dlg = None self.datasetmanager_dlg = None + self.tidmanager_dlg = None self.topping_wizard_dlg = None self.__workflow_wizard_action = None self.__datasetmanager_action = None + self.__tidmanager_action = None self.__validate_action = None self.__topping_wizard_action = None self.__configure_action = None @@ -131,6 +134,16 @@ def initGui(self): self.tr("Dataset Manager"), None, ) + self.__tidmanager_action = QAction( + QIcon( + os.path.join( + os.path.dirname(__file__), + "images/QgisModelBaker-tidmanager-icon.svg", + ) + ), + self.tr("TID (OID) Manager"), + None, + ) self.__validate_action = QAction( QIcon( os.path.join( @@ -172,11 +185,13 @@ def initGui(self): # set these actions checkable to visualize that the dialog is open self.__workflow_wizard_action.setCheckable(True) self.__datasetmanager_action.setCheckable(True) + self.__tidmanager_action.setCheckable(True) self.__validate_action.setCheckable(True) self.__topping_wizard_action.setCheckable(True) self.__configure_action.triggered.connect(self.show_options_dialog) self.__datasetmanager_action.triggered.connect(self.show_datasetmanager_dialog) + self.__tidmanager_action.triggered.connect(self.show_tidmanager_dialog) self.__validate_action.triggered.connect(self.show_validate_dock) self.__workflow_wizard_action.triggered.connect( self.show_workflow_wizard_dialog @@ -198,6 +213,9 @@ def initGui(self): self.iface.addPluginToDatabaseMenu( self.tr("Model Baker"), self.__datasetmanager_action ) + self.iface.addPluginToDatabaseMenu( + self.tr("Model Baker"), self.__tidmanager_action + ) self.iface.addPluginToDatabaseMenu( self.tr("Model Baker"), self.__topping_wizard_action ) @@ -239,6 +257,9 @@ def unload(self): self.iface.removePluginDatabaseMenu( self.tr("Model Baker"), self.__datasetmanager_action ) + self.iface.removePluginDatabaseMenu( + self.tr("Model Baker"), self.__tidmanager_action + ) self.iface.removePluginDatabaseMenu( self.tr("Model Baker"), self.__validate_action ) @@ -257,6 +278,7 @@ def unload(self): ) del self.__workflow_wizard_action del self.__datasetmanager_action + del self.__tidmanager_action del self.__validate_action del self.__configure_action del self.__help_action @@ -334,6 +356,23 @@ def datasetmanager_dialog_finished(self): self.__datasetmanager_action.setChecked(False) self.datasetmanager_dlg = None + def show_tidmanager_dialog(self): + if self.tidmanager_dlg: + self.tidmanager_dlg.reject() + else: + self.tidmanager_dlg = TIDManagerDialog(self.iface, self.iface.mainWindow()) + self.tidmanager_dlg.setAttribute(Qt.WA_DeleteOnClose) + self.tidmanager_dlg.setWindowFlags( + self.tidmanager_dlg.windowFlags() | Qt.Tool + ) + self.tidmanager_dlg.show() + self.tidmanager_dlg.finished.connect(self.tidmanager_dialog_finished) + self.__tidmanager_action.setChecked(True) + + def tidmanager_dialog_finished(self): + self.__tidmanager_action.setChecked(False) + self.tidmanager_dlg = None + def show_validate_dock(self): self.__validate_dock.setVisible(not self.__validate_dock.isVisible()) diff --git a/QgisModelBaker/ui/layer_tids_panel.ui b/QgisModelBaker/ui/layer_tids_panel.ui new file mode 100644 index 000000000..20e792118 --- /dev/null +++ b/QgisModelBaker/ui/layer_tids_panel.ui @@ -0,0 +1,40 @@ + + + layer_tids_layout + + + + 0 + 0 + 800 + 600 + + + + Select Files + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + + + + + diff --git a/QgisModelBaker/ui/set_sequence_panel.ui b/QgisModelBaker/ui/set_sequence_panel.ui new file mode 100644 index 000000000..d3f3dff18 --- /dev/null +++ b/QgisModelBaker/ui/set_sequence_panel.ui @@ -0,0 +1,65 @@ + + + set_sequence_layout + + + + 0 + 0 + 744 + 88 + + + + Select Files + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <html><head/><body><p>With this option you reset the technical sequence id on the database. </p><p>Do it only when you know what you are about to do.</p></body></html> + + + Reset the technical sequence used as t_id value + + + true + + + false + + + + + + T_Id (Sequence) Value + + + + + + + 2147483647 + + + + + + + + + + + diff --git a/QgisModelBaker/ui/tid_configurator_panel.ui b/QgisModelBaker/ui/tid_configurator_panel.ui new file mode 100644 index 000000000..6bbe5c264 --- /dev/null +++ b/QgisModelBaker/ui/tid_configurator_panel.ui @@ -0,0 +1,67 @@ + + + tid_configuration + + + + 0 + 0 + 797 + 443 + + + + Select Files + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QLayout::SetMaximumSize + + + + + + + QLayout::SetMinimumSize + + + + + + + Reset Values + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + diff --git a/QgisModelBaker/ui/tid_manager.ui b/QgisModelBaker/ui/tid_manager.ui new file mode 100644 index 000000000..1cc68744d --- /dev/null +++ b/QgisModelBaker/ui/tid_manager.ui @@ -0,0 +1,45 @@ + + + tid_manager + + + + 0 + 0 + 800 + 604 + + + + TID (OID) Manager + + + + + + 6 + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Define the default expression values used for the t_ili_tid field in the attribute forms according to the OID definition from the INTERLIS model. + + + true + + + + + + + + diff --git a/QgisModelBaker/ui/workflow_wizard/tid_configuration.ui b/QgisModelBaker/ui/workflow_wizard/tid_configuration.ui new file mode 100644 index 000000000..a0ec7bef8 --- /dev/null +++ b/QgisModelBaker/ui/workflow_wizard/tid_configuration.ui @@ -0,0 +1,80 @@ + + + tid_configuration + + + + 0 + 0 + 800 + 600 + + + + Select Files + + + + + + + 0 + 0 + + + + + 75 + true + + + + <html><head/><body><p>Define the default expression values used for the t_ili_tid field in the attribute forms according to the OID definition from the INTERLIS model.</p></body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + 0 + + + + + + + QLayout::SetMaximumSize + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Set configuration + + + + + + + + diff --git a/QgisModelBaker/utils/gui_utils.py b/QgisModelBaker/utils/gui_utils.py index 6ac5b1d04..ad818edcc 100644 --- a/QgisModelBaker/utils/gui_utils.py +++ b/QgisModelBaker/utils/gui_utils.py @@ -6,15 +6,25 @@ import xml.etree.ElementTree as CET from enum import Enum, IntEnum +from PyQt5.QtWidgets import QApplication from qgis.PyQt.QtCore import ( + QEvent, QModelIndex, + QRect, QSortFilterProxyModel, QStringListModel, Qt, pyqtSignal, ) from qgis.PyQt.QtGui import QIcon, QStandardItem, QStandardItemModel -from qgis.PyQt.QtWidgets import QCheckBox, QLineEdit, QListView +from qgis.PyQt.QtWidgets import ( + QCheckBox, + QLineEdit, + QListView, + QStyle, + QStyledItemDelegate, + QStyleOptionButton, +) from qgis.PyQt.uic import loadUiType from QgisModelBaker.libs.modelbaker.iliwrapper.ilicache import IliCache @@ -174,6 +184,7 @@ class PageIds: ExportDataConfiguration = 10 ExportDataExecution = 11 ProjectCreation = 12 + TIDConfiguration = 13 class ToppingWizardPageIds: @@ -682,6 +693,7 @@ def data(self, index, role): ] return SourceModel.data(self, index, role) + # this is unusual that it's not first data and then role (could be changed) def setData(self, index, role, data): if role == Qt.CheckStateRole: self.beginResetModel() @@ -827,11 +839,12 @@ def data(self, index, role): else: return QStringListModel.data(self, index, role) + # this is unusual that it's not first data and then role (could be changed) def setData(self, index, role, data): if role == Qt.CheckStateRole: self._checked_entries[self.data(index, Qt.DisplayRole)] = data else: - QStringListModel.setData(self, index, role, data) + QStringListModel.setData(self, index, data, role) def check(self, index): if self.data(index, Qt.CheckStateRole) == Qt.Checked: @@ -898,6 +911,7 @@ def data(self, index, role): else: return CheckEntriesModel.data(self, index, role) + # this is unusual that it's not first data and then role (could be changed) def setData(self, index, role, data): if role == int(SchemaModelsModel.Roles.PARENT_MODELS): self._parent_models[self.data(index, Qt.DisplayRole)] = data @@ -1107,3 +1121,36 @@ def model_topics(self, schema_identificator): for basket in self.schema_baskets[schema_identificator]: model_topics.add(basket.get("topic", "")) return list(model_topics) + + +class CheckDelegate(QStyledItemDelegate): + def __init__(self, parent, role): + super().__init__(parent) + self.role = role + + def editorEvent(self, event, model, option, index): + if event.type() == QEvent.MouseButtonPress: + value = index.data(int(self.role)) or False + model.setData(index, not value, int(self.role)) + return True + return super().editorEvent(event, model, option, index) + + def paint(self, painter, option, index): + opt = QStyleOptionButton() + opt.rect = option.rect + center_x = opt.rect.x() + opt.rect.width() / 2 + center_y = opt.rect.y() + opt.rect.height() / 2 + + checkbox_width = QApplication.style().pixelMetric(QStyle.PM_IndicatorWidth) + checkbox_height = QApplication.style().pixelMetric(QStyle.PM_IndicatorHeight) + checkbox_rect = QRect( + int(center_x - checkbox_width / 2), + int(center_y - checkbox_height / 2), + checkbox_width, + checkbox_height, + ) + opt.rect = checkbox_rect + + value = index.data(int(self.role)) or False + opt.state |= QStyle.State_On if value else QStyle.State_Off + QApplication.style().drawControl(QStyle.CE_CheckBox, opt, painter) diff --git a/docs/assets/oid_physical_data.png b/docs/assets/oid_physical_data.png new file mode 100644 index 000000000..9c0a80b15 Binary files /dev/null and b/docs/assets/oid_physical_data.png differ diff --git a/docs/assets/oid_tid_manager.png b/docs/assets/oid_tid_manager.png new file mode 100644 index 000000000..fb077bcd9 Binary files /dev/null and b/docs/assets/oid_tid_manager.png differ diff --git a/docs/assets/workflow_wizard_tid_generator_page.png b/docs/assets/workflow_wizard_tid_generator_page.png new file mode 100644 index 000000000..f79a9e418 Binary files /dev/null and b/docs/assets/workflow_wizard_tid_generator_page.png differ diff --git a/docs/background_info/oid_tid_generator.md b/docs/background_info/oid_tid_generator.md new file mode 100644 index 000000000..4cd118567 --- /dev/null +++ b/docs/background_info/oid_tid_generator.md @@ -0,0 +1,145 @@ +## OID, TID, BID, t_ili_tid, tid... Sorry, what? + +Often the models definition requires a cross-system unique identificator. The so called **OID**. + +``` +[...] + TOPIC Constructions = + BASKET OID AS INTERLIS.UUIDOID; + OID AS INTERLIS.STANDARDOID; +[...] +``` + +### In the data transferfile (`xtf`-file)... + +... the OID of the *basket* is named `BID` + +... the OID of the *objects* is named `TID` + +```xml + + Rue des Fleures1 + Rue des Fleures2 +``` + +### In the physical schema... + +... those OIDs are written to the column `t_ili_tid`. + +... while the `t_id` column contains just a ***schema-internal*** sequence used for foreign keys etc. Those `t_id`s are ***not*** the cross-system unique identificator. + +![oid physical data](../assets/oid_physical_data.png) + +!!! Note + There are situations when that you can find the `t_id` as `TID` in your data. It's when there is no `t_ili_tid` available ili2db could use. But be aware those `TID`s are not stable then. + +### OID Domains + +There are different types of OID domains. In Model Baker we try to set default values that fit into the definition. + +- UUIDOID +- I32OID +- STANDARDOID +- ANYOID +- User defined OID + +#### `UUIDOID` + +It's defined as `OID TEXT*36` and needs to be an [Universally Unique Identifier (UUID)](https://datatracker.ietf.org/doc/html/rfc4122). While the probability that a UUID will be duplicated is not zero, it is generally considered close enough to zero to be negligible. + +In the QGIS Project the default value expression for `t_ili_tid` is therefore this: +``` +uuid('WithoutBraces') +``` + +#### `I32OID` + +It's defined as `OID 0 .. 2147483647` what means it needs to be a positive 4 byte integer value. + +As a counter we take the one provided by the `t_id` sequence. + +In the QGIS Project the default value expression for `t_ili_tid` is therefore this: + +``` +t_id +``` + +#### `STANDARDOID` + +It's defined as `OID TEXT*16` and follows some specific requirements. + +It requires an 8 char prefix and 8 char postfix: + +- **Prefix (2 + 6 chars):** Country identifier + a *global identification part*. The global identification part can be ordered from the [official authority](https://www.interlis.ch/dienste/oid-bestellen) + +- **Postfix (8 chars):** Sequence (numeric or alphanumeric) of your system as *local identification part* + +Model Baker does not know what your *global identification part* is and uses a placeholder-prefix `%change%`. It's important that replace this part with your own prefix. + +As *local identification part* Model Baker suggests the counter provided by the `t_id` sequence. + +In the QGIS Project the default value expression for `t_ili_tid` is therefore this: + +``` +'%change%' || lpad( T_Id, 8, 0 ) +``` + +#### `ANYOID` + +The `ANYOID` does not define a format of the OID but just that an OID needs to be defined in all the extended models. This domain is only used on topics that need to be extended. This solution has some [limitations](#limitations). + +#### User defined OIDs and not defined OIDs + +For user defined OIDs or when OIDs are not defined, Model Baker tries to suggest something reasonable. + +If there is no definition with OID AS, ili2db assumes TEXT and therefore they need to fulfill the rules of the XML-ID-type. This means the **first character** must be a **letter or underscore**, followed by letters, numbers, dots, minus signs, underscores; no colons (!), see [www.w3.org/TR/REC-xml](http://www.w3.org/TR/REC-xml). + +In the QGIS Project the default value expression for `t_ili_tid` is therefore this: + +``` +'_' || uuid('WithoutBraces') +``` + +#### Summary +| OID domain | Suggested default value expression | +|---|---| +| INTERLIS.UUIDOID | `uuid('WithoutBraces')` | +| INTERLIS.I32OID | `t_id` | +| INTERLIS.STANDARDOID | `'%change%' \|\| lpad( t_id, 8, 0 )` | +| INTERLIS.ANYOID | `'_' \|\| uuid('WithoutBraces')` | +| City_V1.TypeID | `'_' \|\| uuid('WithoutBraces')` | +| not defined | `'_' \|\| uuid('WithoutBraces')` | + +### TID (OID) Manager + +Since the user has to be able to edit those values, they are provided in the GUI. + +Additionally to the [page implemented in the wizard](../../user_guide/import_workflow/#tid_(oid)_values) the configuration of the TIDs can be made on existing QGIS Projects. + +Find the **TID (OID) Manager** via the *Database > Model Baker* menu. + +![tid manager](../assets/oid_tid_manager.png) + +Here you can use the QGIS Expression Dialog to edit the default value expression for the `t_ili_tid` field of each layer. + +If you want to have the `t_ili_tid` exposed to the form, you can select ***Show***. + +If you need a counter in the expressions, you can use the `t_id` field, that has a schema-wide sequence counting up. This sequence can be reset as well by the user, but be careful not to set it lower than already existing `t_id`s in your project. See below the [limitations](#limitations). + +### Limitations + +This solution covers a lot, but not everything. + +#### ANYOID + +If a class is designed in a topic with an OID definition `ANYOID` and it's extended in multiple other topics with other OID definitions, we cannot say what should be the default value, since it could divert depending in what basket you work. + +#### OIDs on another system + +When the OIDs are not UUIDs but e.g. `STANDARDOID`s instead, it's not possible to know in a system, wheter in other systems the same OIDs are generated. This means it's in the responsibility of the user to set the expressions (and the counters) in a way, that they don't conflict with OIDS of object generated some where else. + +#### T_Id conflicts + +When resetting the T_Id (Sequence) value one needs to be careful, because it's used for the technical sequence id (used for foreign keys etc.) as well. This means it should not conflict with the `t_id`s of objects already existing. That's why it should only be decreased if you really know what you are doing. + +The conflicts are handled differently depending on the database system. On PostgreSQL you would encounter a duplicate key violation while on GeoPackage it would take the next available autogenerated t_id, but since it's not known on creating the TID (OID) it would break those values (e.g. write to it `chMBakerAutogene` instead of e.g. `chMBaker00000042`.) diff --git a/docs/user_guide/import_workflow.md b/docs/user_guide/import_workflow.md index 586ee4957..d0f3dbcf3 100644 --- a/docs/user_guide/import_workflow.md +++ b/docs/user_guide/import_workflow.md @@ -138,7 +138,19 @@ Then, with a simple click you generate your project. All the toppings received f ![baked project](../assets/generated_project.png) -Bon appetit! +### TID (OID) Values + +Often the models definition requires cross-system unique identificators. So called OIDs or TIDs, what are represented in the physical database as the `t_ili_tid` column. Find a clear definition and more details about them in the [corresponding chapter](../../background_info/oid_tid_generator). + +On creating a QGIS Project with Model Baker, there are preset default value expression generated for the `t_ili_tid` field on the attribute form. But often those default value expressions need to be edited by the user (like e.g. the prefix in the `STANDARDOID`). + +![tid generator](../assets/workflow_wizard_tid_generator_page.png) + +Here you can use the QGIS Expression Dialog to edit the default value expression for the `t_ili_tid` field of each layer. + +If you need a counter in the expressions, you can use the `t_id` field, that has a schema-wide sequence counting up. This sequence can be reset as well by the user, but be careful not to set it lower than already existing `t_id`s in your project. + +This settings can be made on an existing QGIS Project as well. Find the [TID (OID) Manager](../../background_info/oid_tid_generator/#tid_(oid)_manager) via the *Database > Model Baker* menu. ### Optimize QGIS Project if extended @@ -147,16 +159,15 @@ In case you gereate your project for an extended model structure that was create ![project generation](../assets/workflow_wizard_project_generation.png) Choose your optimization strategy in the checkbox: -- ***Hide unused base class layers*** - Base class layers with same named extensions will be *hidden* and and base class layers with multiple extensions *as well*. Except if the extension is in the *same model*, then it's will *not* be *hidden* but *renamed*. +- ***Hide unused base class layers*** + Base class layers with same named extensions will be *hidden* and and base class layers with multiple extensions *as well*. Except if the extension is in the *same model*, then it's will *not* be *hidden* but *renamed*. - Relations of hidden layers will *not* be *created* and with them *no* widgets + Relations of hidden layers will *not* be *created* and with them *no* widgets - ***Group unused base class layers*** + Base class layers with same named extensions will be *collected in a group* and base class layers with multiple extensions *as well*. Except if the extension is in the *same model*, then it's will *not* be *grouped* but *renamed*. - Base class layers with same named extensions will be *collected in a group* and base class layers with multiple extensions *as well*. Except if the extension is in the *same model*, then it's will *not* be *grouped* but *renamed*. - - Relations of grouped layers will be *created* but the widgets are *not applied* to the form. + Relations of grouped layers will be *created* but the widgets are *not applied* to the form. For more information about the optimization of extended models, see the [corresponding chapter](../../background_info/extended_models_optimization). diff --git a/mkdocs.yml b/mkdocs.yml index cbd7810bb..dd35b19f1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -44,6 +44,7 @@ nav: - Tipps & Tricks: - Repositories: background_info/repositories.md - Basket and Dataset Handling: background_info/basket_handling.md + - OIDs and TID Generator: background_info/oid_tid_generator.md - UsabILIty Hub: - Model Baker Integration: background_info/usabilityhub/modelbaker_integration.md - Technical Concept: background_info/usabilityhub/technical_concept.md @@ -78,6 +79,8 @@ plugins: Plugin Configuration: Plugin Konfiguration Repositories: Repositories Basket and Dataset Handling: Dataset und Basket Handling + OIDs and TID Generator: OID und TID Generator + Optimized Projects for Extended Models : Optimierte Projekte für erweiterte Modelle Model Baker Integration: Model Baker Integration Technical Concept: Technisches Konzept Catalogues and their special cases: Kataloge und ihre Spezialfälle