diff --git a/lizmap/definitions/lizmap_cloud.py b/lizmap/definitions/lizmap_cloud.py
new file mode 100644
index 00000000..d6ea29a8
--- /dev/null
+++ b/lizmap/definitions/lizmap_cloud.py
@@ -0,0 +1,10 @@
+__copyright__ = 'Copyright 2023, 3Liz'
+__license__ = 'GPL version 3'
+__email__ = 'info@3liz.org'
+
+CLOUD_DOMAIN = 'lizmap.com'
+CLOUD_NAME = 'Lizmap Cloud'
+CLOUD_MAX_PARENT_FOLDER = 2 # TODO Check COG, is-it 3 ?
+
+CLOUD_ONLINE_URL = 'https://docs.lizmap.cloud'
+CLOUD_ONLINE_LANGUAGES = ('en', 'fr')
diff --git a/lizmap/definitions/online_help.py b/lizmap/definitions/online_help.py
index acf8aa7a..92a08d73 100644
--- a/lizmap/definitions/online_help.py
+++ b/lizmap/definitions/online_help.py
@@ -7,13 +7,15 @@
from qgis.core import QgsSettings
from qgis.PyQt.QtCore import QLocale, QUrl
+from lizmap.definitions.lizmap_cloud import (
+ CLOUD_ONLINE_LANGUAGES,
+ CLOUD_ONLINE_URL,
+)
+
DOMAIN = 'https://docs.lizmap.com'
VERSION = 'current'
ONLINE_HELP_LANGUAGES = ('en', 'es', 'it', 'ja', 'pt', 'fi', 'fr')
-CLOUD = 'https://docs.lizmap.cloud'
-CLOUD_HELP_LANGUAGES = ('en', 'fr')
-
def current_locale() -> str:
""" Get the main language, with 2 characters only. """
@@ -25,9 +27,9 @@ def current_locale() -> str:
def online_cloud_help(page: str = '') -> QUrl:
""" Online help URL according to locale and version. """
locale = current_locale()
- if locale not in CLOUD_HELP_LANGUAGES:
+ if locale not in CLOUD_ONLINE_LANGUAGES:
locale = 'en'
- return QUrl(f"{CLOUD}/{locale}/{page}")
+ return QUrl(f"{CLOUD_ONLINE_URL}/{locale}/{page}")
def online_lwc_help(page: str = '', version=VERSION) -> QUrl:
diff --git a/lizmap/definitions/qgis_settings.py b/lizmap/definitions/qgis_settings.py
index 33c48462..e64fdb3c 100644
--- a/lizmap/definitions/qgis_settings.py
+++ b/lizmap/definitions/qgis_settings.py
@@ -17,10 +17,10 @@ def key(cls, key):
return KEY + '/' + key
PreventEcw = 'prevent_ecw'
- PreventPgAuthId = 'prevent_pg_auth_id'
+ PreventPgAuthDb = 'prevent_pg_auth_db'
PreventPgService = 'prevent_pg_service'
ForcePgUserPass = 'force_pg_user_password'
- PreventNetworkDrive = 'prevent_network_drive'
+ PreventDrive = 'prevent_drive'
AllowParentFolder = 'allow_parent_folder'
NumberParentFolder = 'number_parent_folder'
BeginnerMode = 'beginner_mode'
diff --git a/lizmap/definitions/warnings.py b/lizmap/definitions/warnings.py
deleted file mode 100644
index b137d81e..00000000
--- a/lizmap/definitions/warnings.py
+++ /dev/null
@@ -1,15 +0,0 @@
-__copyright__ = 'Copyright 2022, 3Liz'
-__license__ = 'GPL version 3'
-__email__ = 'info@3liz.org'
-
-
-from enum import Enum, unique
-
-
-@unique
-class Warnings(Enum):
- OgcNotValid = 'ogc_not_valid'
- UseLayerIdAsName = 'use_layer_id_as_name'
- SaasLizmapCloud = 'saas_lizmap_cloud_invalid'
- InvalidFieldType = 'invalid_field_type'
- DuplicatedLayersWithFilters = 'duplicated_layers_with_filters'
diff --git a/lizmap/dialogs/main.py b/lizmap/dialogs/main.py
index c2341b6c..ca48a6f8 100755
--- a/lizmap/dialogs/main.py
+++ b/lizmap/dialogs/main.py
@@ -28,22 +28,23 @@
)
from qgis.utils import OverrideCursor, iface
+from lizmap.definitions.lizmap_cloud import CLOUD_MAX_PARENT_FOLDER, CLOUD_NAME
from lizmap.definitions.qgis_settings import Settings
from lizmap.log_panel import LogPanel
-from lizmap.models.check_project import TableCheck
from lizmap.project_checker_tools import (
ALLOW_PARENT_FOLDER,
FORCE_LOCAL_FOLDER,
FORCE_PG_USER_PASS,
PREVENT_AUTH_DB,
PREVENT_ECW,
- PREVENT_NETWORK_DRIVE,
+ PREVENT_OTHER_DRIVE,
PREVENT_SERVICE,
project_trust_layer_metadata,
simplify_provider_side,
use_estimated_metadata,
)
-from lizmap.saas import SAAS_MAX_PARENT_FOLDER, SAAS_NAME, fix_ssl
+from lizmap.saas import fix_ssl
+from lizmap.widgets.check_project import Checks, TableCheck
try:
from qgis.PyQt.QtWebKitWidgets import QWebView
@@ -177,7 +178,7 @@ def __init__(self, parent=None, is_dev_version=True):
self.button_use_estimated_md.setIcon(QIcon(":images/themes/default/mIconPostgis.svg"))
self.button_trust_project.clicked.connect(self.fix_project_trust)
- # self.button_trust_project.setIcon(QIcon(":images/themes/default/mIconPostgis.svg"))
+ self.button_trust_project.setIcon(QIcon(':/images/themes/default/mIconQgsProjectFile.svg'))
self.button_simplify_geom.clicked.connect(self.fix_simplify_geom_provider)
self.button_simplify_geom.setIcon(QIcon(":images/themes/default/mIconPostgis.svg"))
@@ -231,13 +232,13 @@ def __init__(self, parent=None, is_dev_version=True):
)
self.label_file_action.setOpenExternalLinks(True)
- self.radio_beginner.setToolTip(
+ self.radio_beginner.setToolTip(tr(
'If one safeguard is not OK, the Lizmap configuration file is not going to be generated.'
- )
- self.radio_normal.setToolTip(
+ ))
+ self.radio_normal.setToolTip(tr(
'If one safeguard is not OK, only a warning will be displayed, not blocking the saving of the Lizmap '
'configuration file.'
- )
+ ))
self.radio_force_local_folder.setText(FORCE_LOCAL_FOLDER)
self.radio_force_local_folder.setToolTip(tr(
@@ -247,7 +248,8 @@ def __init__(self, parent=None, is_dev_version=True):
self.radio_allow_parent_folder.setToolTip(tr(
'Files can be located in a parent folder from {}, up to the setting below.'
).format(self.project.absolutePath()))
- self.safe_network_drive.setText(PREVENT_NETWORK_DRIVE)
+
+ self.safe_other_drive.setText(PREVENT_OTHER_DRIVE)
self.safe_pg_service.setText(PREVENT_SERVICE)
self.safe_pg_auth_db.setText(PREVENT_AUTH_DB)
self.safe_pg_user_password.setText(FORCE_PG_USER_PASS)
@@ -275,27 +277,34 @@ def __init__(self, parent=None, is_dev_version=True):
self.safe_number_parent.setValue(QgsSettings().value(Settings.key(Settings.NumberParentFolder), type=int))
self.safe_number_parent.valueChanged.connect(self.save_settings)
- # Network drive
- self.safe_network_drive.setChecked(QgsSettings().value(Settings.key(Settings.PreventNetworkDrive), type=bool))
- self.safe_network_drive.toggled.connect(self.save_settings)
+ # Other drive
+ self.safe_other_drive.setChecked(QgsSettings().value(Settings.key(Settings.PreventDrive), type=bool))
+ self.safe_other_drive.toggled.connect(self.save_settings)
+ self.safe_other_drive.setToolTip(Checks.PreventDrive.description)
# PG Service
self.safe_pg_service.setChecked(QgsSettings().value(Settings.key(Settings.PreventPgService), type=bool))
self.safe_pg_service.toggled.connect(self.save_settings)
+ self.safe_pg_service.setToolTip(Checks.PgService.description)
# PG Auth DB
- self.safe_pg_auth_db.setChecked(QgsSettings().value(Settings.key(Settings.PreventPgAuthId), type=bool))
+ self.safe_pg_auth_db.setChecked(QgsSettings().value(Settings.key(Settings.PreventPgAuthDb), type=bool))
self.safe_pg_auth_db.toggled.connect(self.save_settings)
+ self.safe_pg_auth_db.setToolTip(Checks.AuthenticationDb.description)
# User password
self.safe_pg_user_password.setChecked(QgsSettings().value(Settings.key(Settings.ForcePgUserPass), type=bool))
self.safe_pg_user_password.toggled.connect(self.save_settings)
+ self.safe_pg_user_password.setToolTip(Checks.PgForceUserPass.description)
# ECW
self.safe_ecw.setChecked(QgsSettings().value(Settings.key(Settings.PreventEcw), type=bool))
self.safe_ecw.toggled.connect(self.save_settings)
+ self.safe_ecw.setToolTip(Checks.PreventEcw.description)
- self.label_safe_lizmap_cloud.setText(tr("Some safe guards are overridden by {}.").format(SAAS_NAME))
+ self.label_safe_lizmap_cloud.setText(tr(
+ "Some safeguards are overridden by {host}. Even in 'normal' mode, some safeguards are becoming 'blocking' "
+ "with a {host} instance.").format(host=CLOUD_NAME))
msg = (
'
'
'- {max_parent}
'
@@ -305,8 +314,8 @@ def __init__(self, parent=None, is_dev_version=True):
'- {ecw}
'
'
'.format(
max_parent=tr("Maximum of parent folder {} : {}").format(
- SAAS_MAX_PARENT_FOLDER, relative_path(SAAS_MAX_PARENT_FOLDER)),
- network=PREVENT_NETWORK_DRIVE,
+ CLOUD_MAX_PARENT_FOLDER, relative_path(CLOUD_MAX_PARENT_FOLDER)),
+ network=PREVENT_OTHER_DRIVE,
auth_db=PREVENT_AUTH_DB,
user_pass=FORCE_PG_USER_PASS,
ecw=PREVENT_ECW,
@@ -315,21 +324,11 @@ def __init__(self, parent=None, is_dev_version=True):
self.label_safe_lizmap_cloud.setToolTip(msg)
self.table_checks.setup()
+ css_path = resources_path('css', 'log.css')
+ with open(css_path, encoding='utf8') as f:
+ css = f.read()
+ self.html_help.document().setDefaultStyleSheet(css)
- # self.table_checks.setSelectionMode(QAbstractItemView.SingleSelection)
- # self.table_checks.setEditTriggers(QAbstractItemView.NoEditTriggers)
- # self.table_checks.setSelectionBehavior(QAbstractItemView.SelectRows)
- # self.table_checks.setAlternatingRowColors(True)
- # self.table_checks.horizontalHeader().setStretchLastSection(True)
- # self.table_checks.horizontalHeader().setVisible(True)
- # print("BOB")
- #
- # self.table_checks.setColumnCount(len(Headers))
- # for i, header in enumerate(Headers):
- # column = QTableWidgetItem(header.label)
- # column.setToolTip(header.tooltip)
- # self.table_checks.setHorizontalHeaderItem(i, column)
- # print(i)
@property
def check_results(self) -> TableCheck:
return self.table_checks
@@ -908,7 +907,7 @@ def radio_mode_normal_toggled(self):
widgets = (
self.group_file_layer,
self.safe_number_parent,
- self.safe_network_drive,
+ self.safe_other_drive,
self.safe_pg_service,
self.safe_pg_auth_db,
self.safe_pg_user_password,
@@ -924,9 +923,9 @@ def save_settings(self):
QgsSettings().setValue(Settings.key(Settings.BeginnerMode), not self.radio_normal.isChecked())
QgsSettings().setValue(Settings.key(Settings.AllowParentFolder), self.radio_allow_parent_folder.isChecked())
QgsSettings().setValue(Settings.key(Settings.NumberParentFolder), self.safe_number_parent.value())
- QgsSettings().setValue(Settings.key(Settings.PreventNetworkDrive), self.safe_network_drive.isChecked())
+ QgsSettings().setValue(Settings.key(Settings.PreventDrive), self.safe_other_drive.isChecked())
QgsSettings().setValue(Settings.key(Settings.PreventPgService), self.safe_pg_service.isChecked())
- QgsSettings().setValue(Settings.key(Settings.PreventPgAuthId), self.safe_pg_auth_db.isChecked())
+ QgsSettings().setValue(Settings.key(Settings.PreventPgAuthDb), self.safe_pg_auth_db.isChecked())
QgsSettings().setValue(Settings.key(Settings.ForcePgUserPass), self.safe_pg_user_password.isChecked())
QgsSettings().setValue(Settings.key(Settings.PreventEcw), self.safe_ecw.isChecked())
diff --git a/lizmap/models/__init__.py b/lizmap/models/__init__.py
deleted file mode 100644
index d4bbd6ea..00000000
--- a/lizmap/models/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-__copyright__ = 'Copyright 2023, 3Liz'
-__license__ = 'GPL version 3'
-__email__ = 'info@3liz.org'
diff --git a/lizmap/models/check_project.py b/lizmap/models/check_project.py
deleted file mode 100644
index 2ec4ad09..00000000
--- a/lizmap/models/check_project.py
+++ /dev/null
@@ -1,222 +0,0 @@
-__copyright__ = 'Copyright 2023, 3Liz'
-__license__ = 'GPL version 3'
-__email__ = 'info@3liz.org'
-
-from enum import Enum
-
-from qgis.core import QgsMarkerSymbol, QgsSymbolLayerUtils
-from qgis.PyQt.QtCore import QSize, Qt
-from qgis.PyQt.QtGui import QIcon
-from qgis.PyQt.QtWidgets import (
- QAbstractItemView,
- QTableWidget,
- QTableWidgetItem,
-)
-
-from lizmap.qgis_plugin_tools.tools.i18n import tr
-
-
-class Header:
- def __init__(self, label, tooltip):
- self.label = label
- self.tooltip = tooltip
-
-
-class Headers(Header, Enum):
- Severity = tr('Severity'), tr("Severity of the error")
- Level = tr('Level'), tr("Level of the error")
- Object = tr('Object name'), tr("Name of the object bringing the issue")
- Name = tr('Name'), tr('Name of the error')
- Error = tr('Error'), tr('Description of the error')
-
-
-class Severity:
- def __init__(self, data, label, tooltip, color):
- self.data = data
- self.label = label
- self.color = color
- self.tooltip = tooltip
-
- def marker(self) -> QIcon:
- pixmap = QgsSymbolLayerUtils.symbolPreviewPixmap(
- QgsMarkerSymbol.createSimple(
- {
- "name": "circle",
- "color": self.color,
- "size": "2",
- }
- ),
- QSize(16, 16)
- )
- return QIcon(pixmap)
-
-
-class Severities(Severity, Enum):
- Blocking = 'blocking', tr('Blocking'), tr('This is blocking the CFG file'), 'green'
- Important = 'important', tr('Important'), tr('This is important to fix, to improve performance'), 'red'
- Normal = 'normal', tr('Normal'), tr('This would be nice to have look'), 'blue'
- Low = 'low', tr('Low'), tr('Nice to do'), 'green'
-
-
-class Level:
- def __init__(self, data: str, label: str, tooltip: str, icon: QIcon):
- self.data = data
- self.label = label
- self.icon = icon
- self.tooltip = tooltip
-
-
-class Levels:
- GlobalConfig = Level(
- 'global',
- tr('Global'),
- tr('Issue in the global configuration'),
- QIcon(':/images/themes/default/console/iconSettingsConsole.svg'),
- )
- Project = Level(
- 'project',
- tr('Project'),
- tr('Issue at the project level, usually in Project properties dialog'),
- QIcon(':/images/themes/default/mIconQgsProjectFile.svg'),
- )
- Layer = Level(
- 'layer',
- tr('Layer'),
- tr('Issue at the layer level'),
- QIcon(':/images/themes/default/algorithms/mAlgorithmMergeLayers.svg'),
- )
-
-
-class Check:
- def __init__(self, title: str, description: str, tooltip: str, level: Level, severity: Severity):
- self.title = title
- self.description = description
- self.tooltip = tooltip
- self.level = level
- self.severity = severity
-
-
-class Checks(Check, Enum):
- EstimatedMetadata = (
- tr('Estimated metadata'),
- tr("Estimated metadata is missing on the layer"),
- '',
- Levels.Layer,
- Severities.Blocking,
- )
- DuplicatedLayerNameOrGroup = (
- tr('Duplicated layer name or group'),
- tr("It's not possible to store all the Lizmap configuration for these layer(s) or group(s)."),
- '- '
- 'You must change them to make them unique'
- '
- '
- 'Reconfigure their settings in the "Layers" tab of the plugin'
- '
',
- Levels.Project,
- Severities.Blocking,
- )
-
-
-class Error:
- def __init__(self, identifier: str, check: Check):
- self.identifier = identifier
- self.check = check
-
-
-class TableCheck(QTableWidget):
- def setup(self):
- self.setSelectionMode(QAbstractItemView.SingleSelection)
- self.setEditTriggers(QAbstractItemView.NoEditTriggers)
- self.setSelectionBehavior(QAbstractItemView.SelectRows)
- self.setAlternatingRowColors(True)
- self.horizontalHeader().setStretchLastSection(True)
- self.horizontalHeader().setVisible(True)
-
- self.setColumnCount(len(Headers))
- for i, header in enumerate(Headers):
- column = QTableWidgetItem(header.label)
- column.setToolTip(header.tooltip)
- self.setHorizontalHeaderItem(i, column)
-
- def add_error(self, error: Error):
- self.add_row(
- error.check.severity,
- error.check.level,
- error.identifier,
- error.check.title,
- error.check.description,
- error.check.tooltip
- )
-
- def add_row(self, severity: Severity, level: Level, identifier: str, error_name, error_description, error_help):
- row = self.rowCount()
- self.setRowCount(row + 1)
-
- item = QTableWidgetItem(severity.label)
- item.setData(Qt.UserRole, severity.data)
- item.setToolTip(severity.tooltip)
- item.setIcon(severity.marker())
- self.setItem(row, 0, item)
-
- item = QTableWidgetItem(level.label)
- item.setData(Qt.UserRole, level.data)
- item.setToolTip(level.tooltip)
- item.setIcon(level.icon)
- self.setItem(row, 1, item)
-
- item = QTableWidgetItem(identifier)
- item.setData(Qt.UserRole, identifier)
- self.setItem(row, 2, item)
-
- item = QTableWidgetItem(error_name)
- item.setData(Qt.UserRole, error_name)
- self.setItem(row, 3, item)
-
- item = QTableWidgetItem(error_description)
- item.setData(Qt.UserRole, error_description)
- item.setToolTip(error_help)
- self.setItem(row, 4, item)
-
-# class TableModel(QAbstractTableModel):
-#
-# def __init__(self, parent=None, *args, **kwargs):
-# super().__init__(parent, *args, **kwargs)
-# self.rows = []
-#
-# def add_row(self, name, date, interest):
-# self.rows.append((name, date, interest))
-#
-# index = self.createIndex(0,0)
-# self.dataChanged.emit(index, index, [Qt.DisplayRole])
-# # print(self.insertRow(0))
-# # print(self.rowCount())
-# # self.setData(self.createIndex(self.rowCount(), 0), name, Qt.EditRole)
-# # self.setData(self.createIndex(self.rowCount(), 1), date, Qt.EditRole)
-# # self.setData(self.createIndex(self.rowCount(), 2), interest, Qt.EditRole)
-#
-# def rowCount(self, parent=None):
-# return len(self.rows)
-#
-# def columnCount(self, parent):
-# return len(Headers)
-# def data(self, index, role):
-# if role != Qt.DisplayRole:
-# return QVariant()
-#
-# if index.column() >= len(self.rows[index.row()]):
-# return 'a'
-#
-# # What's the value of the cell at the given index?
-# return self.rows[index.row()][index.column()]
-#
-# def headerData(self, section, orientation, role):
-# if orientation != Qt.Horizontal:
-# return ''
-#
-# header: Header = list(Headers)[section]
-#
-# if role == Qt.DisplayRole:
-# return header.label
-#
-# if role == Qt.ToolTipRole:
-# return header.tooltip
diff --git a/lizmap/plugin.py b/lizmap/plugin.py
index eef29b4a..17407b3c 100755
--- a/lizmap/plugin.py
+++ b/lizmap/plugin.py
@@ -86,6 +86,7 @@
from lizmap.definitions.filter_by_login import FilterByLoginDefinitions
from lizmap.definitions.filter_by_polygon import FilterByPolygonDefinitions
from lizmap.definitions.layouts import LayoutsDefinitions
+from lizmap.definitions.lizmap_cloud import CLOUD_MAX_PARENT_FOLDER, CLOUD_NAME
from lizmap.definitions.locate_by_layer import LocateByLayerDefinitions
from lizmap.definitions.online_help import (
MAPPING_INDEX_DOC,
@@ -95,7 +96,6 @@
from lizmap.definitions.qgis_settings import Settings
from lizmap.definitions.time_manager import TimeManagerDefinitions
from lizmap.definitions.tooltip import ToolTipDefinitions
-from lizmap.definitions.warnings import Warnings
from lizmap.dialogs.html_editor import HtmlEditorDialog
from lizmap.dialogs.html_maptip import HtmlMapTipDialog
from lizmap.dialogs.lizmap_popup import LizmapPopupDialog
@@ -117,26 +117,21 @@
from lizmap.ogc_project_validity import OgcProjectValidity
from lizmap.project_checker_tools import (
ALLOW_PARENT_FOLDER,
+ FORCE_LOCAL_FOLDER,
FORCE_PG_USER_PASS,
PREVENT_AUTH_DB,
PREVENT_ECW,
- PREVENT_NETWORK_DRIVE,
+ PREVENT_OTHER_DRIVE,
PREVENT_SERVICE,
- auto_generated_primary_key_field,
duplicated_layer_name_or_group,
duplicated_layer_with_filter,
- invalid_int8_primary_key,
+ project_invalid_pk,
project_safeguards_checks,
project_trust_layer_metadata,
simplify_provider_side,
use_estimated_metadata,
)
-from lizmap.saas import (
- SAAS_MAX_PARENT_FOLDER,
- SAAS_NAME,
- check_project_ssl_postgis,
- is_lizmap_cloud,
-)
+from lizmap.saas import check_project_ssl_postgis, is_lizmap_cloud
from lizmap.table_manager.base import TableManager
from lizmap.table_manager.dataviz import TableManagerDataviz
from lizmap.table_manager.layouts import TableManagerLayouts
@@ -179,6 +174,7 @@
)
from lizmap.tooltip import Tooltip
from lizmap.version_checker import VersionChecker
+from lizmap.widgets.check_project import Checks, Error, Severities, SourceLayer
if qgis_version() >= 31400:
from qgis.core import QgsProjectServerValidator
@@ -215,9 +211,9 @@ def __init__(self, iface):
if prevent_ecw is None:
QgsSettings().setValue(Settings.key(Settings.PreventEcw), True)
- prevent_auth_id = QgsSettings().value(Settings.key(Settings.PreventPgAuthId), defaultValue=None)
+ prevent_auth_id = QgsSettings().value(Settings.key(Settings.PreventPgAuthDb), defaultValue=None)
if prevent_auth_id is None:
- QgsSettings().setValue(Settings.key(Settings.PreventPgAuthId), True)
+ QgsSettings().setValue(Settings.key(Settings.PreventPgAuthDb), True)
prevent_service = QgsSettings().value(Settings.key(Settings.PreventPgService), defaultValue=None)
if prevent_service is None:
@@ -227,9 +223,9 @@ def __init__(self, iface):
if force_pg_user_pass is None:
QgsSettings().setValue(Settings.key(Settings.ForcePgUserPass), True)
- prevent_network_drive = QgsSettings().value(Settings.key(Settings.PreventNetworkDrive), defaultValue=None)
- if prevent_network_drive is None:
- QgsSettings().setValue(Settings.key(Settings.PreventNetworkDrive), True)
+ prevent_other_drive = QgsSettings().value(Settings.key(Settings.PreventDrive), defaultValue=None)
+ if prevent_other_drive is None:
+ QgsSettings().setValue(Settings.key(Settings.PreventDrive), True)
allow_parent_folder = QgsSettings().value(Settings.key(Settings.AllowParentFolder), defaultValue=None)
if allow_parent_folder is None:
@@ -1005,7 +1001,7 @@ def initGui(self):
# noinspection PyUnresolvedReferences
self.help_action.triggered.connect(self.show_help)
- self.help_action_cloud = QAction(icon, SAAS_NAME, self.iface.mainWindow())
+ self.help_action_cloud = QAction(icon, CLOUD_NAME, self.iface.mainWindow())
self.iface.pluginHelpMenu().addAction(self.help_action_cloud)
# noinspection PyUnresolvedReferences
self.help_action.triggered.connect(self.show_help_cloud)
@@ -2873,39 +2869,17 @@ def project_config_file(
if next_version != 'next':
current_version = next_version
- warnings = []
- log_index_panel = self.dlg.mOptionsListWidget.count() - 2
- settings_panel_name = self.dlg.mOptionsListWidget.item(self.dlg.mOptionsListWidget.count() - 1).text()
-
- # If the log panel must be shown to the user
- # Either a warning or an error
- show_log_panel = False
- warning_suggest = tr('This issue not blocking the generation of the Lizmap configuration file.')
- # If the user must fix some issues, the CFG will not be generated
- # But with QGIS desktop < 3.22, it's not possible to do it automatically
- error_cfg_saving = False
- error_cfg_suggest = tr('Or use the automatic fixing button.')
- error_cfg_message = tr(
- "This issue must be fixed, the configuration is not going to be saved. You must visit the '{}' panel to "
- "click the auto-fix button for layers currently loaded in this project."
- ).format(settings_panel_name)
- # Temporary disabled when used in production
- qgis_min_required_not_prod_ready = 32200 if self.is_dev_version else 39900
+ duplicated_in_cfg = duplicated_layer_name_or_group(self.project)
+ for name, count in duplicated_in_cfg.items():
+ if count >= 2:
+ source = '"{}" → "'.format(name) + tr("count {} layers").format(count)
+ self.dlg.check_results.add_error(Error(source, Checks.DuplicatedLayerNameOrGroup))
# Layer ID as short name
if lwc_version >= LwcVersions.Lizmap_3_6:
use_layer_id, _ = self.project.readEntry('WMSUseLayerIDs', '/')
if to_bool(use_layer_id, False):
- show_log_panel = True
- self.dlg.log_panel.append(tr('Use layer IDs as name'), Html.H2)
- self.dlg.log_panel.append(warning_suggest, Html.P)
- self.dlg.log_panel.append(tr(
- "Since Lizmap Web Client 3.6, it's not possible anymore to use the option 'Use layer IDs "
- "as name' in the project properties dialog, QGIS server tab, then WMS capabilities."
- ), Html.P)
- self.dlg.log_panel.append(tr(
- "Please uncheck this checkbox and re-save the Lizmap configuration file."), Html.P)
- warnings.append(Warnings.UseLayerIdAsName.value)
+ self.dlg.check_results.add_error(Error(Path(self.project.fileName()).name, Checks.WmsUseLayerIds))
target_status = self.dlg.server_combo.currentData(ServerComboData.LwcBranchStatus.value)
if not target_status:
@@ -2917,10 +2891,10 @@ def project_config_file(
# Global checks config
prevent_ecw = QgsSettings().value(Settings.key(Settings.PreventEcw), True, bool)
- prevent_auth_id = QgsSettings().value(Settings.key(Settings.PreventPgAuthId), True, bool)
+ prevent_auth_id = QgsSettings().value(Settings.key(Settings.PreventPgAuthDb), True, bool)
prevent_service = QgsSettings().value(Settings.key(Settings.PreventPgService), True, bool)
force_pg_user_pass = QgsSettings().value(Settings.key(Settings.ForcePgUserPass), True, bool)
- prevent_network_drive = QgsSettings().value(Settings.key(Settings.PreventNetworkDrive), True, bool)
+ prevent_other_drive = QgsSettings().value(Settings.key(Settings.PreventDrive), True, bool)
allow_parent_folder = QgsSettings().value(Settings.key(Settings.AllowParentFolder), False, bool)
count_parent_folder = QgsSettings().value(Settings.key(Settings.NumberParentFolder), 2, int)
@@ -2932,9 +2906,9 @@ def project_config_file(
prevent_ecw = True
prevent_auth_id = True
force_pg_user_pass = True
- prevent_network_drive = True
- if count_parent_folder > SAAS_MAX_PARENT_FOLDER:
- count_parent_folder = SAAS_MAX_PARENT_FOLDER
+ prevent_other_drive = True
+ if count_parent_folder > CLOUD_MAX_PARENT_FOLDER:
+ count_parent_folder = CLOUD_MAX_PARENT_FOLDER
# prevent_service = False We encourage service
# allow_parent_folder = False Of course we can
@@ -2947,66 +2921,65 @@ def project_config_file(
summary.append(PREVENT_SERVICE)
if force_pg_user_pass:
summary.append(FORCE_PG_USER_PASS)
- if prevent_network_drive:
- summary.append(PREVENT_NETWORK_DRIVE)
+ if prevent_other_drive:
+ summary.append(PREVENT_OTHER_DRIVE)
if allow_parent_folder:
summary.append(ALLOW_PARENT_FOLDER + " : " + tr("{} folder(s)").format(count_parent_folder))
+ else:
+ summary.append(FORCE_LOCAL_FOLDER)
parent_folder = relative_path(count_parent_folder)
- results, more = project_safeguards_checks(
+ results = project_safeguards_checks(
self.project,
prevent_ecw=prevent_ecw,
prevent_auth_id=prevent_auth_id,
prevent_service=prevent_service,
force_pg_user_pass=force_pg_user_pass,
- prevent_network_drive=prevent_network_drive,
+ prevent_other_drive=prevent_other_drive,
allow_parent_folder=allow_parent_folder,
parent_folder=parent_folder,
lizmap_cloud=lizmap_cloud,
)
- if len(results):
- show_log_panel = True
- warnings.append(Warnings.SaasLizmapCloud.value)
- self.dlg.log_panel.append(tr('Some safeguards are not compatible'), Html.H2)
- # Let's show a summary
- if lizmap_cloud:
- self.dlg.log_panel.append(tr("According to global settings, overriden then by {} :").format(SAAS_NAME), Html.P)
- else:
- self.dlg.log_panel.append(tr("According to global settings"), Html.P)
-
- self.dlg.log_panel.start_table()
- for i, rule in enumerate(summary):
- self.dlg.log_panel.add_row(i)
- self.dlg.log_panel.append(rule, Html.Td)
- self.dlg.log_panel.end_row()
-
- self.dlg.log_panel.append("
")
+ # Let's show a summary
+ if lizmap_cloud:
+ self.dlg.log_panel.append(
+ tr("According to global settings, overriden then by {} :").format(CLOUD_NAME), Html.P)
+ else:
+ self.dlg.log_panel.append(tr("According to global settings"), Html.P)
- if beginner_mode:
- self.dlg.log_panel.append(tr(
- "These issues below are blocking saving the Lizmap configuration file in the 'Beginner' mode."
- ), Html.P, level=Qgis.Critical)
- else:
- self.dlg.log_panel.append(warning_suggest, Html.P)
+ self.dlg.log_panel.start_table()
+ for i, rule in enumerate(summary):
+ self.dlg.log_panel.add_row(i)
+ self.dlg.log_panel.append(rule, Html.Td)
+ self.dlg.log_panel.end_row()
+ self.dlg.log_panel.end_table()
- self.dlg.log_panel.append("
")
+ self.dlg.log_panel.append("
")
- self.dlg.log_panel.start_table()
- for i, error in enumerate(results.values()):
- self.dlg.log_panel.add_row(i)
- self.dlg.log_panel.append(error, Html.Td)
- self.dlg.log_panel.end_row()
+ for layer, error in results.items():
- self.dlg.log_panel.end_table()
- self.dlg.log_panel.append("
")
+ # Severity depends on beginner mode
+ severity = Severities.Blocking if beginner_mode else Severities.Important
+ # But override severities for Lizmap Cloud
+ # Because even with a 'normal' user, it won't work
+ override = (
+ Checks.PreventEcw, Checks.PgForceUserPass, Checks.AuthenticationDb, Checks.PreventDrive)
+ if error in override:
+ severity = Severities.Blocking
- if more:
- self.dlg.log_panel.append(more)
- self.dlg.log_panel.append("
")
+ self.dlg.check_results.add_error(
+ Error(
+ layer.layer_name,
+ error,
+ source_type=SourceLayer(layer.layer_name, layer.layer_id),
+ ),
+ lizmap_cloud=lizmap_cloud,
+ severity=severity,
+ )
+ if results:
if beginner_mode:
- error_cfg_saving = True
self.dlg.log_panel.append(tr(
"The process is stopping, the CFG file is not going to be generated because some safeguards "
"are not compatible and you are using the 'Beginner' mode. Either fix these issues or switch "
@@ -3021,168 +2994,83 @@ def project_config_file(
if check_server:
error, message = check_project_ssl_postgis(self.project)
- if error:
- self.dlg.log_panel.append(tr('SSL connections to a PostgreSQL database'), Html.H2)
- self.dlg.log_panel.append(tr(
- "Connections to a PostgreSQL database hosted on {} must use a SSL secured connection."
- ).format(SAAS_NAME), Html.P)
- self.dlg.log_panel.append(tr(
- "In the plugin, then in the '{}' panel, there is a helper to change the datasource of layers in "
- "the current project only. It works only with minimum QGIS 3.22."
- ).format(settings_panel_name), Html.P)
- self.dlg.log_panel.append(tr(
- "You must still edit your global PostgreSQL connection to allow SSL, it will take effect only "
- "on newly added layer into a project."
- ), Html.P)
- self.dlg.log_panel.append(message, Html.P)
- show_log_panel = True
-
- if Qgis.QGIS_VERSION_INT >= qgis_min_required_not_prod_ready:
- error_cfg_saving = True
- self.dlg.log_panel.append(error_cfg_suggest, Html.P)
- self.dlg.log_panel.append(error_cfg_message, Html.P, level=Qgis.Critical)
- self.dlg.enabled_ssl_button(True)
- else:
- self.dlg.log_panel.append(warning_suggest, Html.P)
-
- autogenerated_keys = {}
- int8 = []
- for layer in self.project.mapLayers().values():
-
- if not isinstance(layer, QgsVectorLayer):
- continue
-
- result, field = auto_generated_primary_key_field(layer)
- if result:
- if field not in autogenerated_keys.keys():
- autogenerated_keys[field] = []
-
- autogenerated_keys[field].append(layer.name())
-
- if invalid_int8_primary_key(layer):
- int8.append(layer.name())
-
- if autogenerated_keys or int8:
- show_log_panel = True
- warnings.append(Warnings.InvalidFieldType.value)
- self.dlg.log_panel.append(tr('Some fields are invalid for QGIS server'), Html.H2)
- self.dlg.log_panel.append(warning_suggest, Html.P)
-
- for field, layers in autogenerated_keys.items():
- # field can be "tid", "ctid" etc
- self.dlg.log_panel.append(tr(
- "These layers don't have a proper primary key in the database. So QGIS Desktop tried to set a "
- "temporary field called '{}' to be a unique identifier. On QGIS Server, this will bring issues."
- ).format(field), Html.P)
- self.dlg.log_panel.append("
")
- layers.sort()
- self.dlg.log_panel.start_table()
- for i, layer_name in enumerate(layers):
- self.dlg.log_panel.add_row(i)
- self.dlg.log_panel.append(layer_name, Html.Td)
- self.dlg.log_panel.end_row()
-
- self.dlg.log_panel.end_table()
-
- if int8:
- int8.sort()
- self.dlg.log_panel.append(tr(
- "The primary key has been detected as a bigint (integer8) for your layer :"), Html.P)
- self.dlg.log_panel.start_table()
- for i, layer_name in enumerate(int8):
- self.dlg.log_panel.add_row(i)
- self.dlg.log_panel.append(layer_name, Html.Td)
- self.dlg.log_panel.end_row()
- self.dlg.log_panel.end_table()
- self.dlg.log_panel.append("
")
-
- if autogenerated_keys or int8:
- self.dlg.log_panel.append(tr(
- "We highly recommend you to set a proper integer field as a primary key, but neither a bigint nor "
- "an integer8."), Html.P)
- self.dlg.log_panel.append(tr(
- "The process is continuing but expect these layers to have some issues with some tools in "
- "Lizmap Web Client: zoom to feature, filtering…"
- ), style=Html.P)
+ for layer in error:
+ self.dlg.check_results.add_error(
+ Error(
+ layer.layer_name,
+ Checks.SSLConnection,
+ source_type=SourceLayer(layer.layer_name, layer.layer_id),
+ )
+ )
+ self.dlg.enabled_ssl_button(True)
+
+ autogenerated_keys, int8 = project_invalid_pk(self.project)
+ for layer in autogenerated_keys:
+ self.dlg.check_results.add_error(
+ Error(
+ layer.layer_name,
+ Checks.MissingPk,
+ source_type=SourceLayer(layer.layer_name, layer.layer_id),
+ )
+ )
+ for layer in int8:
+ self.dlg.check_results.add_error(
+ Error(
+ layer.layer_name,
+ Checks.PkInt8,
+ source_type=SourceLayer(layer.layer_name, layer.layer_id),
+ )
+ )
if lwc_version >= LwcVersions.Lizmap_3_7:
text = duplicated_layer_with_filter(self.project)
if text:
self.dlg.log_panel.append(tr('Optimisation about the legend'), Html.H2)
- self.dlg.log_panel.append(warning_suggest, Html.P)
+ (self.dlg.log_panel.append
+ (tr('This issue not blocking the generation of the Lizmap configuration file.'), Html.P))
self.dlg.log_panel.append(text, style=Html.P)
- warnings.append(Warnings.DuplicatedLayersWithFilters.value)
results = simplify_provider_side(self.project)
- if len(results):
- self.dlg.log_panel.append(tr('Simplify geometry on the provider side'), Html.H2)
- self.dlg.log_panel.append(tr(
- 'These PostgreSQL vector layers can have the simplification on the provider side') + ':', Html.P)
- self.dlg.log_panel.start_table()
- for i, layer in enumerate(results):
- self.dlg.log_panel.add_row(i)
- self.dlg.log_panel.append(layer, Html.Td)
- self.dlg.log_panel.end_row()
- self.dlg.log_panel.end_table()
- self.dlg.log_panel.append(tr(
- 'Visit the layer properties, then in the "Rendering" tab to enable it.'), Html.P)
- if Qgis.QGIS_VERSION_INT >= qgis_min_required_not_prod_ready:
- self.dlg.log_panel.append(error_cfg_message, Html.P, level=Qgis.Critical)
- show_log_panel = True
- error_cfg_saving = True
+ for layer in results:
+ self.dlg.check_results.add_error(
+ Error(
+ layer.layer_name,
+ Checks.SimplifyGeometry,
+ source_type=SourceLayer(layer.layer_name, layer.layer_id),
+ )
+ )
self.dlg.enabled_simplify_geom(True)
results = use_estimated_metadata(self.project)
- if len(results):
- self.dlg.log_panel.append(tr('Estimated metadata'), Html.H2)
- self.dlg.log_panel.append(tr(
- 'These PostgreSQL layers can have the use estimated metadata option enabled') + ':', Html.P)
- self.dlg.log_panel.start_table()
- for i, layer in enumerate(results):
- self.dlg.log_panel.add_row(i)
- self.dlg.log_panel.append(layer, Html.Td)
- self.dlg.log_panel.end_row()
- self.dlg.log_panel.end_table()
- self.dlg.log_panel.append(tr(
- 'Edit your PostgreSQL connection to enable this option, then change the datasource by right clicking '
- 'on each layer above, then click "Change datasource" in the menu. Finally reselect your layer in the '
- 'new dialog.'), Html.P)
- if Qgis.QGIS_VERSION_INT >= qgis_min_required_not_prod_ready:
- self.dlg.log_panel.append(error_cfg_suggest, Html.P)
- self.dlg.log_panel.append(error_cfg_message, Html.P, level=Qgis.Critical)
- show_log_panel = True
- error_cfg_saving = True
- self.dlg.enabled_estimated_md_button(True)
- else:
- self.dlg.log_panel.append(warning_suggest, Html.P)
+ for layer in results:
+ self.dlg.check_results.add_error(
+ Error(
+ layer.layer_name,
+ Checks.EstimatedMetadata,
+ source_type=SourceLayer(layer.layer_name, layer.layer_id),
+ )
+ )
+ self.dlg.enabled_estimated_md_button(True)
- if not project_trust_layer_metadata(self.project) and Qgis.QGIS_VERSION_INT >= qgis_min_required_not_prod_ready:
- self.dlg.log_panel.append(tr('Trust project metadata'), Html.H2)
- self.dlg.log_panel.append(tr(
- 'The project does not have the "Trust project metadata" enabled at the project level'), Html.P)
- self.dlg.log_panel.append(tr(
- 'In the project properties → Data sources → at the bottom, there is a checkbox to trust the project '
- 'when the layer has no metadata.'), Html.P)
- self.dlg.log_panel.append(error_cfg_suggest, Html.P)
- self.dlg.log_panel.append(error_cfg_message, Html.P, level=Qgis.Critical)
- show_log_panel = True
- error_cfg_saving = True
+ if not project_trust_layer_metadata(self.project):
+ self.dlg.check_results.add_error(Error(Path(self.project.fileName()).name, Checks.TrustProject))
self.dlg.enabled_trust_project(True)
- if with_gui and show_log_panel:
- self.dlg.mOptionsListWidget.setCurrentRow(log_index_panel)
+ self.dlg.check_results.sort()
+
+ if with_gui and self.dlg.check_results.has_rows():
+ self.dlg.mOptionsListWidget.setCurrentRow(self.dlg.mOptionsListWidget.count() - 2)
+ self.dlg.tab_log.setCurrentIndex(0)
self.dlg.out_log.moveCursor(QTextCursor.Start)
self.dlg.out_log.ensureCursorVisible()
- if Qgis.QGIS_VERSION_INT >= qgis_min_required_not_prod_ready:
- if with_gui and error_cfg_saving and not ignore_error:
- self.dlg.log_panel.append(tr('Issues which can be fixed automatically'), Html.H2)
- self.dlg.log_panel.append(tr(
- 'You have issue(s) listed above, and there is a wizard to auto fix your project. Saving the '
- 'configuration file is stopping.'), Html.Strong, time=True)
- self.dlg.display_message_bar(
- "Error", tr('You must fix some issues about this project'), Qgis.Critical)
- return None
+ if self.dlg.check_results.has_blocking() and not ignore_error:
+ self.dlg.display_message_bar(
+ tr("Blocking issue"),
+ tr("The project has at least one blocking issue. The file is not saved."),
+ Qgis.Critical,
+ )
+ return None
metadata = {
'qgis_desktop_version': qgis_version(),
@@ -3198,12 +3086,11 @@ def project_config_file(
if valid is not None:
metadata['project_valid'] = valid
- if not valid:
- warnings.append(Warnings.OgcNotValid.value)
liz2json = dict()
liz2json['metadata'] = metadata
- liz2json['warnings'] = warnings
+ if self.dlg.include_in_cfg.isChecked():
+ liz2json['warnings'] = self.dlg.check_results.to_json()
liz2json["options"] = dict()
liz2json["layers"] = dict()
@@ -3542,26 +3429,14 @@ def check_project_validity(self):
validator = QgsProjectServerValidator()
valid, results = validator.validate(self.project)
- if not valid:
- self.dlg.log_panel.append(tr("OGC validation"), style=Html.H2)
- self.dlg.log_panel.append(
- tr("According to OGC standard : {}").format(tr('Valid') if valid else tr('Not valid')), Html.P)
- self.dlg.log_panel.append(
- tr(
- "Open the 'Project properties', then 'QGIS Server' tab, at the bottom, you can check your project "
- "according to OGC standard. If you need to fix a layer shortname, go to the 'Layer properties' "
- "for the given layer, then 'QGIS Server' tab, edit the shortname."
- ), Html.P)
-
LOGGER.info(f"Project has been detected : {'VALID' if valid else 'NOT valid'} according to OGC validation.")
-
if not valid:
- message = tr(
- 'The QGIS project is not valid according to OGC standards. You should check '
- 'messages in the "Project properties" → "QGIS Server" tab then "Test configuration" at the bottom. '
- '{} error(s) have been found').format(len(results))
- # noinspection PyUnresolvedReferences
- self.iface.messageBar().pushMessage('Lizmap', message, level=Qgis.Warning, duration=DURATION_WARNING_BAR)
+ self.dlg.check_results.add_error(
+ Error(
+ Path(self.project.fileName()).name,
+ Checks.OgcValid,
+ )
+ )
self.dlg.check_api_key_address()
@@ -3670,6 +3545,16 @@ def save_cfg_file(
Check the user defined data from GUI and save them to both global and project config files.
"""
+ server_metadata = self.dlg.server_combo.currentData(ServerComboData.JsonMetadata.value)
+ self.dlg.check_results.truncate()
+ beginner_mode = QgsSettings().value(Settings.key(Settings.BeginnerMode), True, bool)
+ self.dlg.html_help.setHtml(
+ Checks.html(
+ severity=Severities.Blocking if beginner_mode else Severities.Important,
+ lizmap_cloud=is_lizmap_cloud(server_metadata)
+ )
+ )
+
self.dlg.log_panel.clear()
self.dlg.log_panel.append(tr('Start saving the Lizmap configuration'), style=Html.P, time=True)
variables = self.project.customVariables()
@@ -3724,27 +3609,6 @@ def save_cfg_file(
))
return False
- duplicated_in_cfg = duplicated_layer_name_or_group(self.project)
-
- # message = tr('Some layer(s) or group(s) have a duplicated name in the legend.')
- # message += '\n\n'
- # message += tr(
- # "It's not possible to store all the Lizmap configuration for these layer(s) or group(s), you should "
- # "change them to make them unique and reconfigure their settings in the 'Layers' tab of the plugin.")
- # message += '\n\n'
- # display = False
- for name, count in duplicated_in_cfg.items():
- if count >= 2:
- from lizmap.models.check_project import Checks, Error
- identifier = '"{}" → "'.format(name) + tr("count {} layers").format(count) + '\n'
- self.dlg.check_results.add_error(Error(identifier, Checks.DuplicatedLayerNameOrGroup))
- # display = True
- # message += '\n\n'
- # message += stop_process
- # if display:
- # ScrollMessageBox(self.dlg, QMessageBox.Warning, tr('Configuration error'), message)
- # return False
-
if not self.is_dev_version:
if not self.server_manager.check_lwc_version(lwc_version.value):
QMessageBox.critical(
diff --git a/lizmap/project_checker_tools.py b/lizmap/project_checker_tools.py
index b00d58e0..18feb407 100644
--- a/lizmap/project_checker_tools.py
+++ b/lizmap/project_checker_tools.py
@@ -17,15 +17,10 @@
QgsWkbTypes,
)
+from lizmap.definitions.lizmap_cloud import CLOUD_DOMAIN
from lizmap.qgis_plugin_tools.tools.i18n import tr
-from lizmap.saas import (
- SAAS_DOMAIN,
- SAAS_NAME,
- edit_connection,
- edit_connection_title,
- right_click_step,
-)
from lizmap.tools import is_vector_pg, update_uri
+from lizmap.widgets.check_project import Checks, SourceLayer
""" Some checks which can be done on a layer. """
@@ -33,7 +28,7 @@
FORCE_LOCAL_FOLDER = tr('Prevent file based layers to be in a parent folder')
ALLOW_PARENT_FOLDER = tr('Allow file based layers to be in a parent folder')
-PREVENT_NETWORK_DRIVE = tr('Prevent file based layers to be stored on another network drive')
+PREVENT_OTHER_DRIVE = tr('Prevent file based layers to be stored on another drive (network or local)')
PREVENT_SERVICE = tr('Prevent PostgreSQL layers to use a service file')
PREVENT_AUTH_DB = tr('Prevent PostgreSQL layers to use the authentication database')
FORCE_PG_USER_PASS = tr(
@@ -47,75 +42,40 @@ def project_safeguards_checks(
prevent_auth_id: bool,
prevent_service: bool,
force_pg_user_pass: bool,
- prevent_network_drive: bool,
+ prevent_other_drive: bool,
allow_parent_folder: bool,
parent_folder: str,
lizmap_cloud: bool,
-) -> Tuple[Dict[str, str], str]:
+) -> Dict:
""" Check the project about safeguards. """
# Do not use homePath, it's not designed for this if the user has set a custom home path
project_home = Path(project.absolutePath())
- layer_error: Dict[str, str] = {}
+ results = {}
- connection_error = False
for layer in project.mapLayers().values():
if isinstance(layer, QgsRasterLayer):
if layer.source().lower().endswith('ecw') and prevent_ecw:
- if lizmap_cloud:
- layer_error[layer.name()] = tr(
- 'The layer "{}" is an ECW. Because of the ECW\'s licence, this format is not compatible with '
- 'QGIS server. You should switch to a COG format.'
- ).format(layer.name())
- else:
- layer_error[layer.name()] = tr(
- 'The layer "{}" is an ECW. You have activated a safeguard about preventing you using an ECW '
- 'layer. Either switch to a COG format or disable this safeguard.'
- ).format(layer.name())
+ results[SourceLayer(layer.name(), layer.id())] = Checks.PreventEcw
if is_vector_pg(layer):
# Make a copy by using a string, so we are sure to have user or password
datasource = QgsDataSourceUri(layer.source())
if datasource.authConfigId() != '' and prevent_auth_id:
- if lizmap_cloud:
- layer_error[layer.name()] = tr(
- 'The layer "{}" is using the QGIS authentication database. You must either use a PostgreSQL '
- 'service or store the login and password in the layer.'
- ).format(layer.name())
- else:
- layer_error[layer.name()] = tr(
- 'The layer "{}" is using the QGIS authentication database. You have activated a safeguard '
- 'preventing you using the QGIS authentication database. Either switch to another '
- 'authentication mechanism or disable this safeguard.'
- ).format(layer.name())
- connection_error = True
+ results[SourceLayer(layer.name(), layer.id())] = Checks.AuthenticationDb
# We can continue
continue
if datasource.service() != '' and prevent_service:
- layer_error[layer.name()] = tr(
- 'The layer "{}" is using the PostgreSQL service file. Using a service file can be recommended in '
- 'many cases, but it requires a configuration step. If you have done the configuration (on the '
- 'server side mainly), you can disable this safeguard.'
- ).format(layer.name())
+ results[SourceLayer(layer.name(), layer.id())] = Checks.PgService
# We can continue
continue
- if datasource.host().endswith(SAAS_DOMAIN) or force_pg_user_pass:
+ if datasource.host().endswith(CLOUD_DOMAIN) or force_pg_user_pass:
if not datasource.username() or not datasource.password():
- if lizmap_cloud:
- layer_error[layer.name()] = tr(
- 'The layer "{}" is missing some credentials. Either the user and/or the password is not in '
- 'the layer datasource.'
- ).format(layer.name())
- else:
- layer_error[layer.name()] = tr(
- 'The layer "{}" is missing some credentials. Either the user and/or the password is not in '
- 'the layer datasource, or disable the safeguard.'
- ).format(layer.name())
- connection_error = True
+ results[SourceLayer(layer.name(), layer.id())] = Checks.PgForceUserPass
# We can continue
continue
@@ -138,47 +98,44 @@ def project_safeguards_checks(
# https://docs.python.org/3/library/os.path.html#os.path.relpath
# On Windows, ValueError is raised when path and start are on different drives.
# For instance, H: and C:
- # Lizmap Cloud message must be prioritized
- if lizmap_cloud:
- layer_error[layer.name()] = tr(
- 'The layer "{}" can not be hosted on {} because the layer is hosted on a different drive.'
- ).format(layer.name(), SAAS_NAME)
- continue
- elif prevent_network_drive:
- layer_error[layer.name()] = tr(
- 'The layer "{}" is on another drive. Either move this file based layer or disable this safeguard.'
- ).format(layer.name())
+
+ if lizmap_cloud or prevent_other_drive:
+ results[SourceLayer(layer.name(), layer.id())] = Checks.PreventDrive
continue
# Not sure what to do for now...
# We can't compute a relative path, but the user didn't enable the safety check, so we must still skip
continue
- if parent_folder in relative_path and allow_parent_folder:
- if lizmap_cloud:
- # The layer can only be hosted the in "/qgis" directory
- layer_error[layer.name()] = tr(
- 'The layer "{}" can not be hosted on {} because the layer is located in too many '
- 'parent\'s folder. The current path from the project home path to the given layer is "{}".'
- ).format(layer.name(), SAAS_NAME, relative_path)
- else:
- layer_error[layer.name()] = tr(
- 'The layer "{}" is located in too many parent\'s folder. Either move this file based layer or '
- 'disable this safeguard. The current path from the project home path to the given layer is "{}".'
- ).format(layer.name(), relative_path)
-
- more = ''
- if connection_error:
- more = edit_connection_title + " "
- more += edit_connection + " "
- more += '
'
- more += right_click_step + " "
- more += tr(
- "When opening a QGIS project in your desktop, you mustn't have any "
- "prompt for a user&password."
- )
+ if allow_parent_folder:
+ # The user allow parent folder, so we check against the string provided in the function call
+ if parent_folder in relative_path:
+ results[SourceLayer(layer.name(), layer.id())] = Checks.PreventParentFolder
+ else:
+ # The user wants only local files, we only check for ".."
+ if '..' in relative_path:
+ results[SourceLayer(layer.name(), layer.id())] = Checks.PreventParentFolder
+
+ return results
+
+
+def project_invalid_pk(project: QgsProject) -> Tuple[List[SourceLayer], List[SourceLayer]]:
+ """ Check either non existing PK or bigint. """
+ autogenerated_keys = []
+ int8 = []
+ for layer in project.mapLayers().values():
+
+ if not isinstance(layer, QgsVectorLayer):
+ continue
+
+ result, field = auto_generated_primary_key_field(layer)
+ if result:
+ autogenerated_keys.append(SourceLayer(layer.name(), layer.id()))
+
+ if invalid_int8_primary_key(layer):
+ int8.append(SourceLayer(layer.name(), layer.id()))
- return layer_error, more
+ return autogenerated_keys, int8
def auto_generated_primary_key_field(layer: QgsVectorLayer) -> Tuple[bool, Optional[str]]:
@@ -304,7 +261,7 @@ def duplicated_layer_with_filter(project: QgsProject) -> Optional[str]:
return text
-def simplify_provider_side(project: QgsProject, fix=False) -> List[str]:
+def simplify_provider_side(project: QgsProject, fix=False) -> List[SourceLayer]:
""" Return the list of layer name which can be simplified on the server side. """
results = []
for layer in project.mapLayers().values():
@@ -317,7 +274,7 @@ def simplify_provider_side(project: QgsProject, fix=False) -> List[str]:
if not layer.simplifyMethod().forceLocalOptimization():
continue
- results.append(layer.name())
+ results.append(SourceLayer(layer.name(), layer.id()))
if fix:
simplify = layer.simplifyMethod()
@@ -327,7 +284,7 @@ def simplify_provider_side(project: QgsProject, fix=False) -> List[str]:
return results
-def use_estimated_metadata(project: QgsProject, fix: bool = False) -> List[str]:
+def use_estimated_metadata(project: QgsProject, fix: bool = False) -> List[SourceLayer]:
""" Return the list of layer name which can use estimated metadata. """
results = []
for layer in project.mapLayers().values():
@@ -336,7 +293,7 @@ def use_estimated_metadata(project: QgsProject, fix: bool = False) -> List[str]:
uri = layer.dataProvider().uri()
if not uri.useEstimatedMetadata():
- results.append(layer.name())
+ results.append(SourceLayer(layer.name(), layer.id()))
if fix:
uri.setUseEstimatedMetadata(True)
diff --git a/lizmap/resources/css/log.css b/lizmap/resources/css/log.css
index 7198f5a7..4acc9465 100644
--- a/lizmap/resources/css/log.css
+++ b/lizmap/resources/css/log.css
@@ -75,3 +75,9 @@ hr {
font-weight: bold;
padding-top:25px;
}
+
+table, th, td {
+ border: 1px solid black;
+ border-collapse: collapse;
+ padding:2px;
+}
diff --git a/lizmap/resources/ui/ui_lizmap.ui b/lizmap/resources/ui/ui_lizmap.ui
index 13a53b50..e956eca1 100755
--- a/lizmap/resources/ui/ui_lizmap.ui
+++ b/lizmap/resources/ui/ui_lizmap.ui
@@ -4256,8 +4256,8 @@ This is different to the map maximum extent (defined in QGIS project properties,
0
0
- 450
- 499
+ 601
+ 488
@@ -4288,6 +4288,13 @@ This is different to the map maximum extent (defined in QGIS project properties,
+ -
+
+
+ This feature is not linked to the QGIS native atlas feature. Both tools are independent.
+
+
+
-
@@ -4605,36 +4612,80 @@ This is different to the map maximum extent (defined in QGIS project properties,
-
- 1
+ 0
-
+
- Raw log
+ Checks
-
+
-
-
-
+
+
+ Checks which are failing. 'Blockings' are blocking :) To fix, either use the tooltip in the last column, or check the documentation in the next tab for all errors which can be reported
+
+
true
-
-
+
- Clear log
+ TEMPORARY, include in CFG ?
+
+
+ true
+ -
+
+
-
+
- Table log
+ Help about checks
-
+
-
-
+
+
+ TEMPORARY, this doc is autogenerated, polishing
+
+
+
+ -
+
+
+
+
+
+
+ Raw logs
+
+
+ -
+
+
+ Raw log, kind of deprecated later
+
+
+
+ -
+
+
+ true
+
+
+
+ -
+
+
+ Clear log
+
+
@@ -4769,13 +4820,13 @@ This is different to the map maximum extent (defined in QGIS project properties,
-
- Safe guards
+ Safeguards
-
- Only if you are sure about your server and if you are more comfortable with Lizmap, you can disable some safe guards
+ Only if you are sure about your server and if you are more comfortable with Lizmap, you can disable some safeguards
true
@@ -4855,9 +4906,9 @@ This is different to the map maximum extent (defined in QGIS project properties,
-
-
+
- SET PYTHON PREVENT ANOTHER NETWORK DRIVE
+ SET PYTHON PREVENT ANOTHER DRIVE
false
@@ -4938,23 +4989,23 @@ This is different to the map maximum extent (defined in QGIS project properties,
QgsCollapsibleGroupBox
QGroupBox
-
+
1
QgsFieldComboBox
QComboBox
-
+
QgsMapLayerComboBox
QComboBox
-
+
QgsPasswordLineEdit
QLineEdit
-
+
1
@@ -4978,7 +5029,7 @@ This is different to the map maximum extent (defined in QGIS project properties,
TableCheck
QTableWidget
- lizmap.models.check_project
+ lizmap.widgets.check_project
diff --git a/lizmap/saas.py b/lizmap/saas.py
index 3c656986..1963eaba 100644
--- a/lizmap/saas.py
+++ b/lizmap/saas.py
@@ -8,8 +8,10 @@
from qgis.core import QgsDataSourceUri, QgsProject
+from lizmap.definitions.lizmap_cloud import CLOUD_DOMAIN
from lizmap.qgis_plugin_tools.tools.i18n import tr
from lizmap.tools import is_vector_pg, update_uri
+from lizmap.widgets.check_project import SourceLayer
edit_connection_title = tr("You must edit the database connection.")
edit_connection = tr(
@@ -22,10 +24,6 @@
"legend and click 'Change datasource' to pick the layer again with the updated connection."
)
-SAAS_DOMAIN = 'lizmap.com'
-SAAS_NAME = 'Lizmap Cloud'
-SAAS_MAX_PARENT_FOLDER = 2 # TODO Check COG, is-it 3 ?
-
def is_lizmap_cloud(metadata: dict) -> bool:
""" Return True if the metadata is coming from Lizmap Cloud. """
@@ -33,12 +31,12 @@ def is_lizmap_cloud(metadata: dict) -> bool:
# Mainly in tests?
return False
- return metadata.get('hosting', '') == SAAS_DOMAIN
+ return metadata.get('hosting', '') == CLOUD_DOMAIN
-def check_project_ssl_postgis(project: QgsProject) -> Tuple[List[str], str]:
+def check_project_ssl_postgis(project: QgsProject) -> Tuple[List[SourceLayer], str]:
""" Check if the project is not using SSL on some PostGIS layers which are on a Lizmap Cloud database. """
- layer_error: List[str] = []
+ layer_error: List[SourceLayer] = []
for layer in project.mapLayers().values():
if not is_vector_pg(layer):
continue
@@ -52,11 +50,11 @@ def check_project_ssl_postgis(project: QgsProject) -> Tuple[List[str], str]:
continue
# Users might be hosted on Lizmap Cloud but using an external database
- if not datasource.host().endswith(SAAS_DOMAIN):
+ if not datasource.host().endswith(CLOUD_DOMAIN):
continue
if datasource.sslMode() in (QgsDataSourceUri.SslMode.SslDisable, QgsDataSourceUri.SslMode.SslAllow):
- layer_error.append(layer.name())
+ layer_error.append(SourceLayer(layer.name(), layer.id()))
more = edit_connection_title + " "
more += edit_connection + " "
@@ -77,7 +75,7 @@ def fix_ssl(project: QgsProject, force: bool = True) -> int:
if datasource.service():
continue
- if not datasource.host().endswith(SAAS_DOMAIN):
+ if not datasource.host().endswith(CLOUD_DOMAIN):
continue
if datasource.sslMode() in (QgsDataSourceUri.SslPrefer, QgsDataSourceUri.SslMode.SslRequire):
diff --git a/lizmap/test/manuel_table_checks.py b/lizmap/test/manuel_table_checks.py
deleted file mode 100644
index bcb8e6aa..00000000
--- a/lizmap/test/manuel_table_checks.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from qgis.PyQt.QtWidgets import QApplication
-
-from lizmap.models.check_project import Checks, Error, TableCheck
-
-# from lizmap.widgets.check_project import CheckProjectView
-
-
-app = QApplication([])
-view = TableCheck()
-# model = TableModel()
-# view = CheckProjectView()
-# view.setModel(model)
-identifier = '"bob" → count 2 layers'
-view.add_error(Error(identifier, Checks.DuplicatedLayerNameOrGroup))
-view.show()
-app.exec_()
diff --git a/lizmap/test/test_table_checks.py b/lizmap/test/test_table_checks.py
index 9e467943..6c34c449 100644
--- a/lizmap/test/test_table_checks.py
+++ b/lizmap/test/test_table_checks.py
@@ -1,30 +1,35 @@
import unittest
-from qgis.PyQt.QtWidgets import QApplication
-
-from lizmap.models.check_project import Levels, Severities, TableCheck
-from lizmap.widgets.check_project import CheckProjectView
+from lizmap.widgets.check_project import Checks, Error, TableCheck
class TestProjectTable(unittest.TestCase):
- def setUp(self):
- self.app = QApplication([])
-
def test(self):
- table = TableCheck(self.app.parent())
+ table = TableCheck(None)
+ table.setup()
self.assertEqual(table.horizontalHeader().count(), 4)
self.assertEqual(table.verticalHeader().count(), 0)
self.assertEqual(table.rowCount(), 0)
- table.add_row(Severities.Blocking, Levels.Project, "Classical mechanics", "bob")
+ table.add_error(Error('home-sweet-home', Checks.DuplicatedLayerNameOrGroup))
self.assertEqual(table.rowCount(), 1)
-
-if __name__ == '__main__':
- app = QApplication([])
- view = CheckProjectView()
- view.show()
- app.exec_()
+ expected = [
+ {
+ 'error': 'duplicated_layer_name_or_group',
+ 'level': 'project',
+ 'severity': 1,
+ 'source': 'home-sweet-home'
+ }
+ ]
+ self.assertListEqual(expected, table.to_json())
+
+
+# if __name__ == '__main__':
+# app = QApplication([])
+# # view = CheckProjectView()
+# # view.show()
+# app.exec_()
diff --git a/lizmap/test/test_ui.py b/lizmap/test/test_ui.py
index fd1a77b8..fa3d0fbc 100755
--- a/lizmap/test/test_ui.py
+++ b/lizmap/test/test_ui.py
@@ -82,7 +82,7 @@ def test_legend_options(self):
self.assertIsNone(output['layers']['legend_displayed_startup'].get('noLegendImage'))
# For LWC 3.5
- output = lizmap.project_config_file(LwcVersions.Lizmap_3_5, with_gui=False, check_server=False)
+ output = lizmap.project_config_file(LwcVersions.Lizmap_3_5, with_gui=False, check_server=False, ignore_error=True)
self.assertIsNone(output['layers']['legend_displayed_startup'].get('legend_image_option'))
self.assertEqual(output['layers']['legend_displayed_startup']['noLegendImage'], str(False))
diff --git a/lizmap/widgets/check_project.py b/lizmap/widgets/check_project.py
index f2facc06..c117921c 100644
--- a/lizmap/widgets/check_project.py
+++ b/lizmap/widgets/check_project.py
@@ -2,15 +2,726 @@
__license__ = 'GPL version 3'
__email__ = 'info@3liz.org'
-from qgis.PyQt.QtWidgets import QAbstractItemView, QTableView
+from enum import Enum
+from qgis.core import (
+ QgsMapLayerModel,
+ QgsMarkerSymbol,
+ QgsProject,
+ QgsSymbolLayerUtils,
+)
+from qgis.PyQt.QtCore import QSize, Qt
+from qgis.PyQt.QtGui import QIcon
+from qgis.PyQt.QtWidgets import (
+ QAbstractItemView,
+ QTableWidget,
+ QTableWidgetItem,
+)
-class CheckProjectView(QTableView):
+from lizmap.definitions.lizmap_cloud import CLOUD_MAX_PARENT_FOLDER, CLOUD_NAME
+from lizmap.definitions.qgis_settings import Settings
+from lizmap.qgis_plugin_tools.tools.i18n import tr
+from lizmap.tools import qgis_version
+
+
+class Header:
+
+ """ Header in tables. """
+ def __init__(self, data: str, label: str, tooltip: str):
+ self.data = data
+ self.label = label
+ self.tooltip = tooltip
+
+
+class Headers(Header, Enum):
+ """ List of headers in the table. """
+ Severity = 'severity', tr('Severity'), tr("Severity of the error")
+ Level = 'level', tr('Level'), tr("Level of the error")
+ Object = 'source', tr('Source'), tr("Source of the error")
+ Error = 'error', tr('Error'), tr('Description of the error')
+
+
+class Severity:
+
+ """ A level of severity, if it's blocking or not. """
+ def __init__(self, data: int, label: str, tooltip: str, color, size: int):
+ self.data = data
+ self.label = label
+ self.color = color
+ self.size = size
+ self.tooltip = tooltip
+
+ def marker(self) -> QIcon:
+ """ Marker used in the table. """
+ pixmap = QgsSymbolLayerUtils.symbolPreviewPixmap(
+ QgsMarkerSymbol.createSimple(
+ {
+ "name": "circle",
+ "color": self.color,
+ "size": "{}".format(self.size),
+ }
+ ),
+ QSize(16, 16)
+ )
+ return QIcon(pixmap)
+
+ def __str__(self):
+ return f'Severity {self.data} : {self.label}'
+
+
+class Severities(Severity, Enum):
+ """ List of severities. """
+ Blocking = 0, tr('Blocking'), tr('This is blocking the CFG file'), 'red', 3
+ Important = 1, tr('Important'), tr('This is important to fix, to improve performance'), 'orange', 2.5
+ # Normal = 2, tr('Normal'), tr('This would be nice to have look'), 'blue', 2
+ Low = 3, tr('Low'), tr('Nice to do'), 'yellow', 2
+ # Some severities can only done on runtime, QGIS version and/or Lizmap Cloud
+ Unknown = 99, 'Unknown', 'Severity will be determined on runtime', 'green', 1
+
+
+class Level:
+
+ """ Level which is raising the issue. Important to set the icon if possible. """
+ def __init__(self, data: str, label: str, tooltip: str, icon: QIcon):
+ self.data = data
+ self.label = label
+ self.icon = icon
+ self.tooltip = tooltip
+
+ def __str__(self):
+ return f'{self.data} : {self.label}'
+
+
+class Levels:
+
+ """ List of levels used. """
+
+ GlobalConfig = Level(
+ 'global',
+ tr('Global'),
+ tr('Issue in the global configuration, in QGIS or Lizmap settings'),
+ QIcon(':/images/themes/default/console/iconSettingsConsole.svg'),
+ )
+ Project = Level(
+ 'project',
+ tr('Project'),
+ tr('Issue at the project level'),
+ QIcon(':/images/themes/default/mIconQgsProjectFile.svg'),
+ )
+ Layer = Level(
+ 'layer',
+ tr('Layer'),
+ tr('Issue at the layer level'),
+ QIcon(':/images/themes/default/algorithms/mAlgorithmMergeLayers.svg'),
+ )
+
+
+class Check:
+
+ """ Definition of a check. """
+ def __init__(
+ self,
+ data: str,
+ title: str,
+ description: str,
+ helper: str,
+ level: Level,
+ severity: Severity,
+ icon: QIcon,
+ alt_description_lizmap_cloud: str = None,
+ alt_help_lizmap_cloud: str = None,
+ ):
+ self.data = data
+ self.title = title
+ self.description = description
+ self.alt_description = alt_description_lizmap_cloud
+ self.helper = helper
+ self.alt_help = alt_help_lizmap_cloud
+ self.level = level
+ self.severity = severity
+ self.icon = icon
+
+ def description_text(self, lizmap_cloud: bool) -> str:
+ """ Return the best description of the check, depending on Lizmap Cloud. """
+ if lizmap_cloud and self.alt_description:
+ return self.alt_description
+ else:
+ return self.description
+
+ def help_text(self, lizmap_cloud: bool) -> str:
+ """ Return the best help of the check, depending on Lizmap Cloud. """
+ if lizmap_cloud and self.alt_help:
+ return self.alt_help
+ else:
+ return self.helper
+
+ def html_help(self, index: int, severity: Severity, lizmap_cloud: False) -> str:
+ """ HTML string to show in an HTML table. """
+ row_class = ''
+ if index % 2:
+ row_class = "class=\"odd-row\""
+
+ html_str = (
+ "
"
+ "{title} | "
+ "{description} | "
+ "{how_to_fix} | "
+ "{level} | "
+ "{severity} | "
+ "
"
+ ).format(
+ row_class=row_class,
+ title=self.title,
+ description=self.description_text(lizmap_cloud),
+ how_to_fix=self.help_text(lizmap_cloud),
+ level=self.level.label,
+ severity=severity.label if self.severity == Severities.Unknown else self.severity.label,
+ )
+ return html_str
+
+ def html_tooltip(self, lizmap_cloud: bool = False) -> str:
+ """ HTML string to be used as a tooltip. """
+ html_str = (
+ "{description}"
+ "
"
+ "{how_to_fix}
"
+ ).format(
+ description=self.description_text(lizmap_cloud),
+ how_to_fix=self.help_text(lizmap_cloud),
+ )
+ return html_str
+
+ def __str__(self):
+ return f'{self.title} : {self.description_text(False)} :{self.level} → {self.severity}'
+
+
+qgis_32200 = tr(
+ 'Or with QGIS ≥ 3.22, you can use the auto-fix button in the "Settings" panel of the plugin'
+)
+other_auth = tr('Either switch to another authentication mechanism')
+safeguard = tr('Or disable this safeguard in your Lizmap plugin settings')
+global_connection = tr(
+ 'Edit your global PostgreSQL connection to enable this option, then change the datasource by right '
+ 'clicking on each layer above, then click "Change datasource" in the menu. Finally reselect your layer '
+ 'in the new dialog with the updated connection. When opening a QGIS project in your desktop, you mustn\'t have '
+ 'any prompt for a user or password. '
+ 'The edited connection will take effect only on newly added layer into a project that\'s why the right-click step '
+ 'is required.'
+)
+
+
+class Checks(Check, Enum):
+
+ """ List of checks defined. """
+
+ OgcValid = (
+ 'ogc_validity',
+ tr('OGC validity'),
+ tr(
+ "According to OGC rules, the project is not valid."
+ ),
+ (
+ ''
+ '- {project_properties}
'
+ '- {project_shortname}
'
+ '- {layer_shortname}
'
+ '
'
+ ).format(
+ project_properties=tr(
+ "Open the 'Project properties', then 'QGIS Server' tab, at the bottom, you can check your project "
+ "according to OGC standard"
+ ),
+ layer_shortname=tr(
+ "If you need to fix a layer shortname, go to the 'Layer properties' "
+ "for the given layer, then 'QGIS Server' tab, edit the shortname."
+ ),
+ project_shortname=tr(
+ "If you need to fix the project shortname, go to the 'Project properties', "
+ "then 'QGIS Server' tab, first tab, and change the shortname."
+ ),
+ ),
+ Levels.Project,
+ Severities.Low,
+ QIcon(':/images/themes/default/mIconWms.svg'),
+ )
+ PkInt8 = (
+ 'primary_key_bigint',
+ tr('Invalid bigint (integer8) field for QGIS Server as primary key'),
+ tr(
+ "Primary key should be an integer. If not fixed, expect layer to have some issues with some tools in "
+ "Lizmap Web Client: zoom to feature, filtering…"
+ ),
+ (
+ ''
+ ).format(
+ help=tr(
+ "We highly recommend you to set a proper integer field as a primary key, but neither a bigint nor "
+ "an integer8."
+ ),
+ ),
+ Levels.Layer,
+ Severities.Important,
+ QIcon(':/images/themes/default/mIconFieldInteger.svg'),
+ )
+ MissingPk = (
+ 'missing_primary_key',
+ tr('Missing a proper primary key in the database.'),
+ tr(
+ "The layer must have a proper primary key defined. When it's missing, QGIS Desktop tried to set a "
+ "temporary field called 'tid/ctid/…' to be a unique identifier. On QGIS Server, this will bring issues."
+ ),
+ (
+ ''
+ ).format(
+ help=tr(
+ "We highly recommend you to set a proper integer field as a primary key, but neither a bigint nor "
+ "an integer8."
+ ),
+ ),
+ Levels.Layer,
+ Severities.Important,
+ QIcon(':/images/themes/default/mSourceFields.svg'),
+ )
+ SSLConnection = (
+ 'ssl_connection',
+ tr('SSL connections to a PostgreSQL database'),
+ tr("Connections to a PostgreSQL database hosted on {} must use a SSL secured connection.").format(CLOUD_NAME),
+ (
+ ''
+ '- {help}
'
+ '- {auto_fix}
'
+ '
'
+ ).format(
+ help=global_connection,
+ auto_fix=qgis_32200,
+ ),
+ Levels.Layer,
+ Severities.Blocking if qgis_version() >= 32200 else Severities.Important,
+ QIcon(':/images/themes/default/mIconPostgis.svg'),
+ )
+ EstimatedMetadata = (
+ 'estimated_metadata',
+ tr('Estimated metadata'),
+ tr("PostgreSQL layer can have the use estimated metadata option enabled"),
+ (
+ ''
+ '- {help}
'
+ '- {auto_fix}
'
+ '
'
+ ).format(
+ help=global_connection,
+ auto_fix=qgis_32200,
+ ),
+ Levels.Layer,
+ Severities.Blocking if qgis_version() >= 32200 else Severities.Important,
+ QIcon(':/images/themes/default/mIconPostgis.svg'),
+ )
+ SimplifyGeometry = (
+ 'simplify_geometry',
+ tr('Simplify geometry on the provider side'),
+ tr("PostgreSQL layer can have the simplification on the provider side enabled"),
+ (
+ ''
+ '- {help}
'
+ '- {auto_fix}
'
+ '
'
+ ).format(
+ help=tr(
+ 'Visit the layer properties, then in the "Rendering" tab to enable it simplification on the provider '
+ 'side.'
+ ),
+ auto_fix=qgis_32200,
+ ),
+ Levels.Layer,
+ Severities.Blocking if qgis_version() >= 32200 else Severities.Important,
+ QIcon(':/images/themes/default/mIconGeometryCollectionLayer.svg'),
+ )
+ DuplicatedLayerNameOrGroup = (
+ 'duplicated_layer_name_or_group',
+ tr('Duplicated layer name or group'),
+ tr("It's not possible to store all the Lizmap configuration for these layer(s) or group(s)."),
+ (
+ ''
+ ).format(
+ tr('You must change them to make them unique'),
+ tr('Reconfigure their settings in the "Layers" tab of the plugin')
+ ),
+ Levels.Project,
+ Severities.Important,
+ QIcon(':/images/themes/default/propertyicons/editmetadata.svg'),
+ )
+ WmsUseLayerIds = (
+ 'wms_use_layer_id',
+ tr('Do not use layer IDs as name'),
+ tr(
+ "It's not possible anymore to use the option 'Use layer IDs as name' in the project properties dialog, "
+ "QGIS server tab, then WMS capabilities."
+ ),
+ ''.format(
+ help=tr("Uncheck this checkbox and re-save the Lizmap configuration file")
+ ),
+ Levels.Project,
+ Severities.Blocking,
+ QIcon(':/images/themes/default/mIconWms.svg'),
+ )
+ TrustProject = (
+ 'trust_project_metadata',
+ tr('Trust project metadata'),
+ tr('The project does not have the "Trust project metadata" enabled at the project level'),
+ (
+ ''
+ '- {help}
'
+ '- {auto_fix}
'
+ '
'.format(
+ help=tr(
+ 'In the project properties → Data sources → at the bottom, there is a checkbox to trust the '
+ 'project when the layer has no metadata.'
+ ),
+ auto_fix=qgis_32200,
+ )
+ ),
+ Levels.Project,
+ Severities.Blocking if qgis_version() >= 32200 else Severities.Important,
+ QIcon(':/images/themes/default/mIconQgsProjectFile.svg'),
+ )
+ PreventEcw = (
+ Settings.PreventEcw,
+ tr('Prevent ECW raster'),
+ tr(
+ 'The layer is using the ECW raster format. Because of the ECW\'s licence, this format is not compatible '
+ 'with most of QGIS server installations. You have activated a safeguard about preventing you using an '
+ 'ECW layer.'),
+ (
+ ''
+ '- {help}
'
+ '- {other}
'
+ '
'.format(
+ help=tr('Either switch to a COG format'),
+ other=safeguard,
+ )
+ ),
+ Levels.Layer,
+ Severities.Unknown,
+ QIcon(':/images/themes/default/mIconRasterLayer.svg'),
+ tr(
+ 'The layer is using an ECW raster format. Because of the ECW\'s licence, this format is not compatible '
+ 'with QGIS server.'
+ ),
+ (
+ ''
+ ).format(help=tr('Switch to a COG format'))
+ )
+ AuthenticationDb = (
+ Settings.PreventPgAuthDb,
+ tr('Authentication database'),
+ tr(
+ 'The layer is using the QGIS authentication database. You have activated a safeguard preventing you using '
+ 'the QGIS authentication database.'
+ ),
+ (
+ ''
+ '- {help}
'
+ '- {other}
'
+ '- {global_connection}
'
+ '
'.format(
+ help=other_auth,
+ other=safeguard,
+ global_connection=global_connection,
+ )
+ ),
+ Levels.Layer,
+ Severities.Unknown,
+ QIcon(':/images/themes/default/mIconPostgis.svg'),
+ tr('The layer is using the QGIS authentication database. This is not compatible with {}').format(CLOUD_NAME),
+ (
+ ''
+ '- {service}
'
+ '- {login_pass}
'
+ '
'
+ ).format(
+ service=tr('Either use a PostgreSQL service'),
+ login_pass=tr('Or store the login and password in the layer.')
+ )
+ )
+ PgService = (
+ Settings.PreventPgService,
+ tr('PostgreSQL service'),
+ tr(
+ 'Using a PostgreSQL service file can be recommended in many cases, but it requires a configuration step. '
+ 'If you have done the configuration (on the server side mainly), you can disable this safeguard.'
+ ),
+ (
+ ''
+ '- {help}
'
+ '- {other}
'
+ '- {global_connection}
'
+ '
'.format(
+ help=other_auth,
+ other=safeguard,
+ global_connection=global_connection,
+ )
+ ),
+ Levels.Layer,
+ Severities.Unknown,
+ QIcon(':/images/themes/default/mIconPostgis.svg'),
+ )
+ PgForceUserPass = (
+ Settings.ForcePgUserPass,
+ tr('PostgreSQL user and/or password'),
+ tr(
+ 'The layer is missing some credentials, either user and/or password.'
+ ),
+ (
+ ''
+ '- {edit_layer}
'
+ '- {help}
'
+ '- {other}
'
+ '- {global_connection}
'
+ '
'.format(
+ edit_layer=tr('Edit your layer configuration by force saving user&password'),
+ help=other_auth,
+ other=safeguard,
+ global_connection=global_connection,
+ )
+ ),
+ Levels.Layer,
+ Severities.Unknown,
+ QIcon(':/images/themes/default/mIconPostgis.svg'),
+ )
+ PreventDrive = (
+ Settings.PreventDrive,
+ tr('Other drive (network or local)'),
+ tr('The layer is stored on another drive.'),
+ (
+ ''
+ '- {help}
'
+ '- {other}
'
+ '
'.format(
+ help=tr('Either move this file based layer'),
+ other=safeguard,
+ )
+ ),
+ Levels.Layer,
+ Severities.Unknown,
+ QIcon(':/qt-project.org/styles/commonstyle/images/networkdrive-16.png'),
+ tr('The layer is stored on another drive, which is not possible using {}.').format(CLOUD_NAME),
+ (
+ ''
+ ).format(
+ help=tr('Move the layer'),
+ )
+ )
+ PreventParentFolder = (
+ Settings.AllowParentFolder,
+ tr('Parent folder'),
+ tr('The layer is stored in too many parent\'s folder, compare to the QGS file.'),
+ (
+ ''
+ '- {help}
'
+ '- {other}
'
+ '
'.format(
+ help=tr('Either move this file based layer'),
+ other=safeguard,
+ )
+ ),
+ Levels.Layer,
+ Severities.Unknown,
+ QIcon(':/images/themes/default/mIconFolderOpen.svg'),
+ tr('The layer is stored in too many parent\'s folder, compare to the QGS file.'),
+ (
+ ''
+ '- {help}
'
+ '- {other}
'
+ '- {fyi}
'
+ '
'
+ ).format(
+ help=tr('Either move the layer'),
+ other=safeguard,
+ fyi=tr(
+ 'For your information, the maximum of parents is {count} on {hosting_name}. This will be overriden '
+ 'on runtime if you use a higher value according to the server selected in the first panel.'
+ ).format(
+ count=CLOUD_MAX_PARENT_FOLDER,
+ hosting_name=CLOUD_NAME
+ ),
+ )
+ )
+
+ @classmethod
+ def html(cls, severity: Severity, lizmap_cloud: bool) -> str:
+ """ Generate an HTML table, according to the instance. """
+ html_str = ''
+ html_str += (
+ '{title} | {description} | {howto} | {level} | {severity} |
'
+ ).format(
+ title=tr('Title'),
+ description=tr('Description'),
+ howto=tr('How to fix'),
+ level=tr('Level'),
+ severity=tr('Severity'),
+ )
+ copy_sort = list(cls.__members__.values())
+ copy_sort.sort(key=lambda x: severity.data if x.severity == Severities.Unknown else x.severity.data)
+ for i, check in enumerate(copy_sort):
+ html_str += check.html_help(i, severity, lizmap_cloud)
+ html_str += '
'
+ return html_str
+
+
+class SourceLayer:
+
+ """ For identifying a layer in a project. """
+ def __init__(self, layer_name, layer_id):
+ self.layer_id = layer_id
+ self.layer_name = layer_name
+
+
+class SourceType:
+
+ """ List of sources in the project. """
+
+ Layer = SourceLayer
+
+
+class Error:
+
+ """ An error is defined by a check and a source. """
+ def __init__(self, source: str, check: Check, source_type=None):
+ self.source = source
+ self.check = check
+ self.source_type = source_type
+
+ def __str__(self):
+ return f'{self.source} : {self.check}'
+
+
+class TableCheck(QTableWidget):
+
+ """ Subclassing of QTableWidget in the plugin. """
+
+ # noinspection PyUnresolvedReferences
+ DATA = Qt.UserRole
+ JSON = DATA + 1
+
+ def setup(self):
+ """ Setting up parameters. """
+ # Do not use the constructor __init__, it's not working. Maybe because of UI files ?
- def __init__(self, parent=None):
- QTableView.__init__(self, parent=parent)
self.setSelectionMode(QAbstractItemView.SingleSelection)
self.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setAlternatingRowColors(True)
self.horizontalHeader().setStretchLastSection(True)
+ self.horizontalHeader().setVisible(True)
+ # Bug, same as self.sort()
+ # self.setSortingEnabled(True)
+
+ self.setColumnCount(len(Headers))
+ for i, header in enumerate(Headers):
+ column = QTableWidgetItem(header.label)
+ column.setToolTip(header.tooltip)
+ self.setHorizontalHeaderItem(i, column)
+
+ def truncate(self):
+ """ Truncate the table. """
+ self.setRowCount(0)
+
+ def has_blocking(self) -> bool:
+ """ If the table has at least one blocking issue. """
+ for row in range(self.rowCount()):
+ if self.item(row, 0).data(self.DATA) == 0:
+ return True
+ return False
+
+ def has_rows(self) -> int:
+ """ If the table has at least one row displayed. """
+ return self.rowCount()
+
+ def sort(self):
+ """ Sort the table by severity. """
+ # Strange bug occurring when we launch the analysis on the second time
+ # Lines are disappearing
+ # self.sortByColumn(0, Qt.AscendingOrder)
+ pass
+
+ def to_json(self) -> list:
+ """ Export data to JSON. """
+ result = []
+
+ for row in range(self.rowCount()):
+ data = dict()
+ for i, header in enumerate(Headers):
+ data[header.data] = self.item(row, i).data(self.JSON)
+ result.append(data)
+
+ return result
+
+ def add_error(self, error: Error, lizmap_cloud: bool = False, severity=None):
+ """ Add an error in the table. """
+ # By default, let's take the one in the error
+ used_severity = error.check.severity
+ if used_severity == Severities.Unknown:
+ if severity:
+ # The given severity is overriden the one in the error
+ used_severity = severity
+ else:
+ raise NotImplementedError('Missing severity level')
+
+ row = self.rowCount()
+ self.setRowCount(row + 1)
+
+ column = 0
+
+ # Severity
+ item = QTableWidgetItem(used_severity.label)
+ item.setData(self.DATA, used_severity.data)
+ item.setData(self.JSON, used_severity.data)
+ item.setToolTip(used_severity.tooltip)
+ item.setIcon(used_severity.marker())
+ self.setItem(row, column, item)
+ column += 1
+
+ # Level
+ item = QTableWidgetItem(error.check.level.label)
+ item.setData(self.DATA, error.check.level.data)
+ item.setData(self.JSON, error.check.level.data)
+ item.setToolTip(error.check.level.tooltip)
+ item.setIcon(error.check.level.icon)
+ self.setItem(row, column, item)
+ column += 1
+
+ # Source
+ item = QTableWidgetItem(error.source)
+ item.setData(self.DATA, error.source)
+ if isinstance(error.source_type, SourceType.Layer):
+ item.setToolTip(error.source_type.layer_id)
+ layer = QgsProject.instance().mapLayer(error.source_type.layer_id)
+ item.setIcon(QgsMapLayerModel.iconForLayer(layer))
+ item.setData(self.JSON, error.source_type.layer_id)
+ else:
+ # Project only for now
+ # TODO fix else
+ item.setData(self.JSON, error.source)
+ self.setItem(row, column, item)
+ column += 1
+
+ # Error
+ item = QTableWidgetItem(error.check.title)
+ item.setData(self.DATA, error.source)
+ item.setData(self.JSON, error.check.data)
+ item.setToolTip(error.check.html_tooltip(lizmap_cloud))
+ if error.check.icon:
+ item.setIcon(error.check.icon)
+ self.setItem(row, column, item)
+ column += 1