diff --git a/lizmap/dialogs/main.py b/lizmap/dialogs/main.py index 964ba64b..babcf54c 100755 --- a/lizmap/dialogs/main.py +++ b/lizmap/dialogs/main.py @@ -133,7 +133,7 @@ def __init__(self, parent=None, is_dev_version=True): "To know how to fix these checks, either use the tooltip (by hovering your mouse pointer on the table " "row) in the last column '{column_name}', or check the documentation in the next tab " "'{tab_name}' for all errors which can be reported." - ).format(column_name=Headers.Error.label, tab_name=self.tab_log.tabText(1)) + ).format(column_name=Headers().error.label, tab_name=self.tab_log.tabText(1)) ) auto_fix_panel = self.mOptionsListWidget.item(Panels.AutoFix).text() self.label_autofix.setText(tr( @@ -309,30 +309,32 @@ 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) + self.checks = Checks() + # 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) + self.safe_other_drive.setToolTip(self.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) + self.safe_pg_service.setToolTip(self.checks.PgService.description) # PG Auth DB 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) + self.safe_pg_auth_db.setToolTip(self.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) + self.safe_pg_user_password.setToolTip(self.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.safe_ecw.setToolTip(self.checks.PreventEcw.description) msg = tr( "Some safeguards are overridden by {host}. Even in 'normal' mode, some safeguards are becoming 'blocking' " @@ -438,17 +440,21 @@ def visit_settings_panel(self): def auto_fix_tooltip(self, lizmap_cloud): """ Set some tooltips on these auto-fix buttons, according to Lizmap Cloud status. """ - self.label_pg_ssl.setToolTip(Checks.SSLConnection.html_tooltip(lizmap_cloud)) - self.button_convert_ssl.setToolTip(Checks.SSLConnection.html_tooltip(lizmap_cloud)) + tooltip = self.checks.SSLConnection.html_tooltip(lizmap_cloud) + self.label_pg_ssl.setToolTip(tooltip) + self.button_convert_ssl.setToolTip(tooltip) - self.label_pg_estimated.setToolTip(Checks.EstimatedMetadata.html_tooltip(lizmap_cloud)) - self.button_use_estimated_md.setToolTip(Checks.EstimatedMetadata.html_tooltip(lizmap_cloud)) + tooltip = self.checks.EstimatedMetadata.html_tooltip(lizmap_cloud) + self.label_pg_estimated.setToolTip(tooltip) + self.button_use_estimated_md.setToolTip(tooltip) - self.label_trust_project.setToolTip(Checks.TrustProject.html_tooltip(lizmap_cloud)) - self.button_trust_project.setToolTip(Checks.TrustProject.html_tooltip(lizmap_cloud)) + tooltip = self.checks.TrustProject.html_tooltip(lizmap_cloud) + self.label_trust_project.setToolTip(tooltip) + self.button_trust_project.setToolTip(tooltip) - self.label_simplify.setToolTip(Checks.SimplifyGeometry.html_tooltip(lizmap_cloud)) - self.button_simplify_geom.setToolTip(Checks.SimplifyGeometry.html_tooltip(lizmap_cloud)) + tooltip = self.checks.SimplifyGeometry.html_tooltip(lizmap_cloud) + self.label_simplify.setToolTip(tooltip) + self.button_simplify_geom.setToolTip(tooltip) def has_auto_fix(self) -> bool: """ Return if an auto-fix is enabled. """ diff --git a/lizmap/plugin.py b/lizmap/plugin.py index 5fb748b0..6f57f7dd 100755 --- a/lizmap/plugin.py +++ b/lizmap/plugin.py @@ -272,13 +272,15 @@ def __init__(self, iface, lwc_version: LwcVersions = None): temp_dir = Path(tempfile.gettempdir()).joinpath('QGIS_Lizmap') if not temp_dir.exists(): temp_dir.mkdir() - file_handler = logging.FileHandler(temp_dir.joinpath("lizmap.log")) - file_handler.setLevel(logging.DEBUG) - formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') - file_handler.setFormatter(formatter) - add_logging_handler_once(LOGGER, file_handler) - LOGGER.debug( - "The directory {0} is currently used for file logging.".format(temp_dir)) + + if not to_bool(os.getenv("CI"), default_value=False): + file_handler = logging.FileHandler(temp_dir.joinpath("lizmap.log")) + file_handler.setLevel(logging.DEBUG) + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + file_handler.setFormatter(formatter) + add_logging_handler_once(LOGGER, file_handler) + LOGGER.debug( + "The directory {0} is currently used for file logging.".format(temp_dir)) # All logs def write_log_message(message, tag, level): @@ -2916,7 +2918,7 @@ def project_config_file( Severities, SourceLayer, ) - + checks = Checks() valid, _ = self.check_project_validity() if with_gui: @@ -2933,13 +2935,13 @@ def project_config_file( 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)) + 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): - self.dlg.check_results.add_error(Error(Path(self.project.fileName()).name, Checks.WmsUseLayerIds)) + 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: @@ -3017,16 +3019,18 @@ def project_config_file( self.dlg.log_panel.append("
") + severities = Severities() + for layer, error in results.items(): # Severity depends on beginner mode - severity = Severities.Blocking if beginner_mode else Severities.Important + 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) + checks.PreventEcw, checks.PgForceUserPass, checks.AuthenticationDb, checks.PreventDrive) if error in override: - severity = Severities.Blocking + severity = severities.bocking self.dlg.check_results.add_error( Error( @@ -3059,7 +3063,7 @@ def project_config_file( self.dlg.check_results.add_error( Error( layer.name, - Checks.SSLConnection, + checks.SSLConnection, source_type=SourceLayer(layer.name, layer.layer_id), ) ) @@ -3070,7 +3074,7 @@ def project_config_file( self.dlg.check_results.add_error( Error( layer.name, - Checks.MissingPk, + checks.MissingPk, source_type=SourceLayer(layer.name, layer.layer_id), ) ) @@ -3078,7 +3082,7 @@ def project_config_file( self.dlg.check_results.add_error( Error( layer.name, - Checks.PkInt8, + checks.PkInt8, source_type=SourceLayer(layer.name, layer.layer_id), ) ) @@ -3100,7 +3104,7 @@ def project_config_file( self.dlg.check_results.add_error( Error( layer.name, - Checks.SimplifyGeometry, + checks.SimplifyGeometry, source_type=SourceLayer(layer.name, layer.layer_id), ) ) @@ -3111,14 +3115,14 @@ def project_config_file( self.dlg.check_results.add_error( Error( layer.name, - Checks.EstimatedMetadata, + checks.EstimatedMetadata, source_type=SourceLayer(layer.name, layer.layer_id), ) ) self.dlg.enabled_estimated_md_button(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.check_results.add_error(Error(Path(self.project.fileName()).name, checks.TrustProject)) self.dlg.enabled_trust_project(True) # Not blocking, we change it in the background @@ -3576,7 +3580,7 @@ def check_project_validity(self): self.dlg.check_results.add_error( Error( Path(self.project.fileName()).name, - Checks.OgcValid, + Checks().OgcValid, ) ) @@ -3697,9 +3701,10 @@ def save_cfg_file( 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) + severities = Severities() self.dlg.html_help.setHtml( - Checks.html( - severity=Severities.Blocking if beginner_mode else Severities.Important, + Checks().html( + severity=severities.blocking if beginner_mode else severities.important, lizmap_cloud=is_lizmap_cloud(server_metadata) ) ) diff --git a/lizmap/project_checker_tools.py b/lizmap/project_checker_tools.py index 1ca4195a..499957f1 100644 --- a/lizmap/project_checker_tools.py +++ b/lizmap/project_checker_tools.py @@ -57,24 +57,25 @@ def project_safeguards_checks( # Do not use homePath, it's not designed for this if the user has set a custom home path project_home = Path(project.absolutePath()) results = {} + checks = Checks() for layer in project.mapLayers().values(): if isinstance(layer, QgsRasterLayer): if layer.source().lower().endswith('ecw') and prevent_ecw: - results[SourceLayer(layer.name(), layer.id())] = Checks.PreventEcw + 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: - results[SourceLayer(layer.name(), layer.id())] = Checks.AuthenticationDb + results[SourceLayer(layer.name(), layer.id())] = checks.AuthenticationDb # We can continue continue if datasource.service() != '' and prevent_service: - results[SourceLayer(layer.name(), layer.id())] = Checks.PgService + results[SourceLayer(layer.name(), layer.id())] = checks.PgService # We can continue continue @@ -82,7 +83,7 @@ def project_safeguards_checks( if not datasource.service(): if datasource.host().endswith(CLOUD_DOMAIN) or force_pg_user_pass: if not datasource.username() or not datasource.password(): - results[SourceLayer(layer.name(), layer.id())] = Checks.PgForceUserPass + results[SourceLayer(layer.name(), layer.id())] = checks.PgForceUserPass # We can continue continue @@ -107,7 +108,7 @@ def project_safeguards_checks( # For instance, H: and C: if lizmap_cloud or prevent_other_drive: - results[SourceLayer(layer.name(), layer.id())] = Checks.PreventDrive + results[SourceLayer(layer.name(), layer.id())] = checks.PreventDrive continue # Not sure what to do for now... @@ -117,11 +118,11 @@ def project_safeguards_checks( 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 + 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 + results[SourceLayer(layer.name(), layer.id())] = checks.PreventParentFolder return results @@ -348,12 +349,12 @@ def trailing_layer_group_name(layer_tree: QgsLayerTreeNode, project, results: Li layer = project.mapLayer(child.layerId()) if layer.name().strip() != layer.name(): results.append( - Error(layer.name(), Checks.LeadingTrailingSpaceLayerGroupName, SourceLayer(layer.name(), layer.id()))) + Error(layer.name(), Checks().LeadingTrailingSpaceLayerGroupName, SourceLayer(layer.name(), layer.id()))) else: child = cast_to_group(child) if child.name().strip() != child.name(): results.append( - Error(child.name(), Checks.LeadingTrailingSpaceLayerGroupName, SourceGroup)) + Error(child.name(), Checks().LeadingTrailingSpaceLayerGroupName, SourceGroup)) # Recursive call results = trailing_layer_group_name(child, project, results) diff --git a/lizmap/test/test_table_checks.py b/lizmap/test/test_table_checks.py index 01786fc5..c65206cf 100644 --- a/lizmap/test/test_table_checks.py +++ b/lizmap/test/test_table_checks.py @@ -17,10 +17,11 @@ def test(self): self.assertEqual(table.verticalHeader().count(), 0) self.assertEqual(table.rowCount(), 0) + checks = Checks() - table.add_error(Error('my-tailor-is-rich', Checks.DuplicatedLayerNameOrGroup)) - table.add_error(Error('home-sweet-home', Checks.DuplicatedLayerNameOrGroup)) - table.add_error(Error('home-sweet-home', Checks.MissingPk)) + table.add_error(Error('my-tailor-is-rich', checks.DuplicatedLayerNameOrGroup)) + table.add_error(Error('home-sweet-home', checks.DuplicatedLayerNameOrGroup)) + table.add_error(Error('home-sweet-home', checks.MissingPk)) self.assertEqual(table.rowCount(), 3) expected = [ diff --git a/lizmap/widgets/check_project.py b/lizmap/widgets/check_project.py index d2bfac2e..a257ff4c 100644 --- a/lizmap/widgets/check_project.py +++ b/lizmap/widgets/check_project.py @@ -2,7 +2,6 @@ __license__ = 'GPL version 3' __email__ = 'info@3liz.org' -from enum import Enum from qgis.core import ( QgsMapLayerModel, @@ -34,12 +33,19 @@ def __init__(self, data: str, label: str, tooltip: str): self.tooltip = tooltip -class Headers(Header, Enum): +class Headers: """ 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') + + def __init__(self): + self.members = [] + self.severity = Header('severity', tr('Severity'), tr("Severity of the error")) + self.level = Header('level', tr('Level'), tr("Level of the error")) + self.source = Header('source', tr('Source'), tr("Source of the error")) + self.error = Header('error', tr('Error'), tr('Description of the error')) + self.members.append(self.severity) + self.members.append(self.level) + self.members.append(self.source) + self.members.append(self.error) class Severity: @@ -70,14 +76,26 @@ def __str__(self): return f'' -class Severities(Severity, Enum): +class Severities: """ List of severities. """ - Blocking = 0, tr('Blocking'), tr('This is blocking the Lizmap configuration 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 + def __init__(self): + self.members = [] + self.blocking = Severity( + 0, tr('Blocking'), tr('This is blocking the Lizmap configuration file'), 'red', 3) + self.important = Severity( + 1, tr('Important'), tr('This is important to fix, to improve performance'), 'orange', 2.5) + self.normal = Severity( + 2, tr('Normal'), tr('This would be nice to have look'), 'blue', 2) + self.low = Severity( + 3, tr('Low'), tr('Nice to do'), 'yellow', 2) + # Some severities can only done on runtime, QGIS version and/or Lizmap Cloud + self.unknown = Severity( + 99, 'Unknown', 'Severity will be determined on runtime', 'green', 1) + self.members.append(self.blocking) + self.members.append(self.important) + self.members.append(self.normal) + self.members.append(self.low) + self.members.append(self.unknown) class Level: @@ -162,6 +180,7 @@ def html_help(self, index: int, severity: Severity, lizmap_cloud: False) -> str: if index % 2: row_class = "class=\"odd-row\"" + severities = Severities() html_str = ( "" "{title}" @@ -176,7 +195,7 @@ def html_help(self, index: int, severity: Severity, lizmap_cloud: False) -> str: 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, + severity=severity.label if self.severity == severities.unknown else self.severity.label, ) return html_str @@ -196,394 +215,393 @@ def __str__(self): return f'<{self.title} : {self.description_text(False)} :{self.level} → {self.severity}>' -# Check QGIS_VERSION_INT -qgis_32200 = tr( - 'With QGIS ≥ 3.22, you can use the auto-fix button in the "Settings" panel of the plugin to fix currently loaded ' - 'layers' -) -other_auth = tr('Either switch to another authentication mechanism') -safeguard = tr('Or disable this safeguard in your Lizmap plugin settings') -global_connection = tr( - 'To fix layers loaded later, 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 computer, with a ' - 'fresh launched QGIS software, 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.' -) -either_move_file = tr('Either move the file used for the layer') -move_file = tr('Move the file used for the layer') - - -class Checks(Check, Enum): +class Checks: """ List of checks defined. """ - OgcValid = ( - 'ogc_validity', - tr('OGC validity (QGIS server)'), - tr( - "According to OGC standard, the project is not valid." - ), - ( - '' - ).format( - project_properties=tr( - "Open the 'Project properties', then 'QGIS Server' tab, at the bottom, you can check your project " - "according to OGC standard" + def __init__(self): + # Check QGIS_VERSION_INT + qgis_32200 = tr( + 'With QGIS ≥ 3.22, you can use the auto-fix button in the "Settings" panel of the plugin to fix currently ' + 'loaded layers' + ) + other_auth = tr('Either switch to another authentication mechanism') + safeguard = tr('Or disable this safeguard in your Lizmap plugin settings') + global_connection = tr( + 'To fix layers loaded later, 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 computer, with a fresh launched QGIS software, 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.' + ) + either_move_file = tr('Either move the file used for the layer') + move_file = tr('Move the file used for the layer') + + self.OgcValid = Check( + 'ogc_validity', + tr('OGC validity (QGIS server)'), + tr( + "According to OGC standard, the project is not valid." ), - 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." + ( + '' + ).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." + ), ), - 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'), + ) + self.PkInt8 = Check( + '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…" ), - ), - 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." + ( + '' + ).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/mIconFieldInteger.svg'), + ) + self.MissingPk = Check( + '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." ), - ), - 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), - ( - '' - ).format( - auto_fix=qgis_32200, - help=global_connection, - ), - 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"), - ( - '' - ).format( - auto_fix=qgis_32200, - help=global_connection, - ), - 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 geometry simplification on the server side enabled"), - ( - '' - ).format( - auto_fix=qgis_32200, - help=tr( - 'Visit the layer properties, then in the "Rendering" tab to enable it simplification on the provider ' - 'side on the given layer.' + ( + '' + ).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.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'), - ( - ''.format( + Levels.Layer, + Severities().important, + QIcon(':/images/themes/default/mSourceFields.svg'), + ) + self.SSLConnection = Check( + '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), + ( + '' + ).format( + auto_fix=qgis_32200, + help=global_connection, + ), + Levels.Layer, + Severities().blocking if qgis_version() >= 32200 else Severities().important, + QIcon(':/images/themes/default/mIconPostgis.svg'), + ) + self.EstimatedMetadata = Check( + 'estimated_metadata', + tr('Estimated metadata'), + tr("PostgreSQL layer can have the use estimated metadata option enabled"), + ( + '' + ).format( + auto_fix=qgis_32200, + help=global_connection, + ), + Levels.Layer, + Severities().blocking if qgis_version() >= 32200 else Severities().important, + QIcon(':/images/themes/default/mIconPostgis.svg'), + ) + self.SimplifyGeometry = Check( + 'simplify_geometry', + tr('Simplify geometry on the provider side'), + tr("PostgreSQL layer can have the geometry simplification on the server side enabled"), + ( + '' + ).format( + auto_fix=qgis_32200, 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.' + 'Visit the layer properties, then in the "Rendering" tab to enable it simplification on the provider ' + 'side on the given layer.' ), - auto_fix=tr('With QGIS ≥ 3.22, you can use the auto-fix button in the "Settings" panel of the plugin'), - ) - ), - Levels.Project, - Severities.Blocking if qgis_version() >= 32200 else Severities.Important, - QIcon(':/images/themes/default/mIconQgsProjectFile.svg'), - ) - LeadingTrailingSpaceLayerGroupName = ( - 'leading_trailing_space', - tr('Leading/trailing space in layer/group name'), - tr( - 'The layer/group name has some leading/trailing spaces. It must be removed and the configuration in the ' - 'plugin might be needed.' - ), ( - ''.format( - edit_layer=tr('Rename your layer/group to remove leading/trailing spaces (left and right)'), - ) - ), - Levels.Layer, - Severities.Blocking, - QIcon(':/images/themes/default/algorithms/mAlgorithmMergeLayers.svg'), - ) - PreventEcw = ( - Settings.PreventEcw, - tr('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.'), - ( - ''.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('QGIS Authentication database'), - tr( - 'The layer is using the QGIS authentication database. You have activated a safeguard preventing you using ' - 'the QGIS authentication database.' - ), - ( - ''.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), - ( - '' - ).format( - service=tr('Either use a PostgreSQL service'), - login_pass=tr('Or store the login and password in the layer.') + ), + Levels.Layer, + Severities().blocking if qgis_version() >= 32200 else Severities().important, + QIcon(':/images/themes/default/mIconGeometryCollectionLayer.svg'), ) - ) - PgService = ( - Settings.PreventPgService, - tr('PostgreSQL service'), - tr( - 'Using a PostgreSQL service file is 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( - help=other_auth, - other=safeguard, - doc=pg_service_help().toString(), # Sorry, the link is not easily clickable in a QTextEdit - 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.' - ), - ( - ''.format( - edit_layer=tr('Edit your layer configuration by force saving user&password'), - help=other_auth, - other=safeguard, - global_connection=global_connection, + self.DuplicatedLayerNameOrGroup = Check( + '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'), + ) + self.WmsUseLayerIds = Check( + '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'), + ) + self.TrustProject = Check( + 'trust_project_metadata', + tr('Trust project metadata'), + tr('The project does not have the "Trust project metadata" enabled at the project level'), + ( + ''.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=tr('With QGIS ≥ 3.22, you can use the auto-fix button in the "Settings" panel of the plugin'), + ) + ), + Levels.Project, + Severities().blocking if qgis_version() >= 32200 else Severities().important, + QIcon(':/images/themes/default/mIconQgsProjectFile.svg'), + ) + self.LeadingTrailingSpaceLayerGroupName = Check( + 'leading_trailing_space', + tr('Leading/trailing space in layer/group name'), + tr( + 'The layer/group name has some leading/trailing spaces. It must be removed and the configuration in the ' + 'plugin might be needed.' + ), ( + ''.format( + edit_layer=tr('Rename your layer/group to remove leading/trailing spaces (left and right)'), + ) + ), + Levels.Layer, + Severities().blocking, + QIcon(':/images/themes/default/algorithms/mAlgorithmMergeLayers.svg'), + ) + self.PreventEcw = Check( + Settings.PreventEcw, + tr('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.'), + ( + ''.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')) + ) + self.AuthenticationDb = Check( + Settings.PreventPgAuthDb, + tr('QGIS Authentication database'), + tr( + 'The layer is using the QGIS authentication database. You have activated a safeguard preventing you using ' + 'the QGIS authentication database.' + ), + ( + ''.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), + ( + '' + ).format( + service=tr('Either use a PostgreSQL service'), + login_pass=tr('Or store the login and password in the layer.') ) - ), - 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.'), - ( - ''.format( - help=either_move_file, - other=safeguard, + ) + self.PgService = Check( + Settings.PreventPgService, + tr('PostgreSQL service'), + tr( + 'Using a PostgreSQL service file is 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( + help=other_auth, + other=safeguard, + doc=pg_service_help().toString(), # Sorry, the link is not easily clickable in a QTextEdit + global_connection=global_connection, + ) + ), + Levels.Layer, + Severities().unknown, + QIcon(':/images/themes/default/mIconPostgis.svg'), + ) + self.PgForceUserPass = Check( + Settings.ForcePgUserPass, + tr('PostgreSQL user and/or password'), + tr( + 'The layer is missing some credentials, either user and/or password.' + ), + ( + ''.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'), + ) + self.PreventDrive = Check( + Settings.PreventDrive, + tr('Other drive (network or local)'), + tr('The layer is stored on another drive.'), + ( + ''.format( + help=either_move_file, + 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=move_file, ) - ), - 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=move_file, ) - ) - PreventParentFolder = ( - Settings.AllowParentFolder, - tr('Parent folder'), - tr('The layer is stored in too many parent\'s folder, compare to the QGS file.'), - ( - ''.format( + self.PreventParentFolder = Check( + Settings.AllowParentFolder, + tr('Parent folder'), + tr('The layer is stored in too many parent\'s folder, compare to the QGS file.'), + ( + ''.format( + help=either_move_file, + 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.'), + ( + '' + ).format( help=either_move_file, 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 + ), ) - ), - 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.'), - ( - '' - ).format( - help=either_move_file, - 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: + def html(self, severity: Severity, lizmap_cloud: bool) -> str: """ Generate an HTML table, according to the instance. """ html_str = '' html_str += ( @@ -595,8 +613,9 @@ def html(cls, severity: Severity, lizmap_cloud: bool) -> str: 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) + copy_sort = list(self.__dict__.values()) + copy_sort = [c for c in copy_sort if isinstance(c, Check)] + 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 += '
' @@ -665,8 +684,9 @@ def setup(self): # Bug, same as self.sort() # self.setSortingEnabled(True) - self.setColumnCount(len(Headers)) - for i, header in enumerate(Headers): + headers = Headers() + self.setColumnCount(len(headers.members)) + for i, header in enumerate(headers.members): column = QTableWidgetItem(header.label) column.setToolTip(header.tooltip) self.setHorizontalHeaderItem(i, column) @@ -697,9 +717,10 @@ def to_json(self) -> list: """ Export data to JSON. """ result = [] + headers = Headers() for row in range(self.rowCount()): data = dict() - for i, header in enumerate(Headers): + for i, header in enumerate(headers.members): data[header.data] = self.item(row, i).data(self.JSON) result.append(data) @@ -736,7 +757,7 @@ 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 used_severity == Severities().unknown: if severity: # The given severity is overriden the one in the error used_severity = severity