diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f7dceb7a..186db416 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.12" architecture: x64 cache: "pip" cache-dependency-path: "requirements/dev.txt" @@ -46,11 +46,10 @@ jobs: strategy: matrix: qgis_version: [ + # "latest", "release-3_34", "release-3_28", "release-3_22", - "release-3_16", -# "latest", ] steps: @@ -80,10 +79,10 @@ jobs: - uses: actions/checkout@v4 - - name: Set up Python 3.10 + - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.12" cache: "pip" cache-dependency-path: "requirements/packaging.txt" @@ -115,15 +114,6 @@ jobs: --osgeo-password ${{ secrets.OSGEO_PASSWORD }} --create-plugin-repo - - name: Tweet - uses: mugi111/tweet-trigger-release@v1.2 - with: - consumer_key: ${{ secrets.TWITTER_CONSUMER_KEY }} - consumer_secret: ${{ secrets.TWITTER_CONSUMER_SECRET }} - access_token_key: ${{ secrets.TWITTER_ACCESS_TOKEN_KEY }} - access_token_secret: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} - tweet_body: "New version of @LizmapForQgis desktop plugin ${{ env.RELEASE_VERSION }} 🦎 on #QGIS https://github.com/3liz/lizmap-plugin/releases" - - name: Repository Dispatch uses: peter-evans/repository-dispatch@v3 with: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 51329292..e0db7e91 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: check-added-large-files - repo: https://github.com/pycqa/flake8 - rev: 5.0.4 + rev: 6.1.0 hooks: - id: flake8 exclude: lizmap/lizmap_api/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 56d1a5d4..cb567b4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +* Raise QGIS minimum version to 3.22 + ## 4.3.24 - 2024-09-09 * Improve download of QGS project diff --git a/Makefile b/Makefile index 0e1c4c8e..0d5acc30 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ SHELL:=bash .ONESHELL: .PHONY: env -QGIS_VERSION ?= release-3_16 +QGIS_VERSION ?= release-3_22 start_tests: @echo 'Start docker compose' diff --git a/lizmap/dialogs/dock_html_preview.py b/lizmap/dialogs/dock_html_preview.py index e5a5c46f..6d097b4e 100644 --- a/lizmap/dialogs/dock_html_preview.py +++ b/lizmap/dialogs/dock_html_preview.py @@ -28,7 +28,6 @@ from lizmap.toolbelt.i18n import tr from lizmap.toolbelt.resources import resources_path -from lizmap.toolbelt.version import qgis_version try: from qgis.PyQt.QtWebKitWidgets import QWebView @@ -104,9 +103,8 @@ def __init__(self, parent, *__args): self.feature.featureChanged.connect(self.update_html) self.feature.setShowBrowserButtons(True) - if qgis_version() >= 32000: - # We don't have a better signal to listen to - QgsProject.instance().dirtySet.connect(self.update_html) + # We don't have a better signal to listen to + QgsProject.instance().dirtySet.connect(self.update_html) self.update_html() diff --git a/lizmap/dialogs/main.py b/lizmap/dialogs/main.py index c08440b0..8ffa29d4 100755 --- a/lizmap/dialogs/main.py +++ b/lizmap/dialogs/main.py @@ -109,10 +109,7 @@ def __init__(self, parent=None, is_dev_version=True, lwc_version: LwcVersions = self.label_lizmap_logo.setPixmap(pixmap) # Initial extent widget - if qgis_version() >= 31800: - self.widget_initial_extent.setMapCanvas(iface.mapCanvas(), False) - else: - self.widget_initial_extent.setMapCanvas(iface.mapCanvas()) + self.widget_initial_extent.setMapCanvas(iface.mapCanvas(), False) self.widget_initial_extent.setOutputCrs(self.project.crs()) self.widget_initial_extent.setOriginalExtent(iface.mapCanvas().extent(), self.project.crs()) self.project.crsChanged.connect(self.project_crs_changed) @@ -544,20 +541,10 @@ def has_auto_fix(self) -> bool: def enabled_ssl_button(self, status: bool): """ Enable or not the button. """ - if Qgis.QGIS_VERSION_INT <= 32200: - self.button_convert_ssl.setToolTip(tr("QGIS 3.22 minimum is required")) - self.button_convert_ssl.setEnabled(False) - return - self.button_convert_ssl.setEnabled(status) def enabled_estimated_md_button(self, status: bool): """ Enable or not the button. """ - if Qgis.QGIS_VERSION_INT <= 32200: - self.button_use_estimated_md.setToolTip(tr("QGIS 3.22 minimum is required")) - self.button_use_estimated_md.setEnabled(False) - return - self.button_use_estimated_md.setEnabled(status) def enabled_trust_project(self, status: bool): diff --git a/lizmap/dialogs/server_wizard.py b/lizmap/dialogs/server_wizard.py index be72c1d9..05839658 100644 --- a/lizmap/dialogs/server_wizard.py +++ b/lizmap/dialogs/server_wizard.py @@ -49,13 +49,10 @@ from lizmap.definitions.qgis_settings import Settings from lizmap.logger import log_function from lizmap.saas import is_lizmap_cloud, webdav_properties +from lizmap.server_dav import WebDav from lizmap.toolbelt.i18n import tr -from lizmap.toolbelt.version import qgis_version, version - -if qgis_version() >= 32200: - from lizmap.server_dav import WebDav - from lizmap.toolbelt.plugin import lizmap_user_folder, user_settings +from lizmap.toolbelt.version import version LOGGER = logging.getLogger('Lizmap') THUMBS = " 👍" @@ -945,11 +942,7 @@ def save_auth_id(self) -> bool: LOGGER.debug("Edit current information authentication ID : {}".format(self.auth_id)) # Edit config.setId(self.auth_id) - if qgis_version() < 32000: - auth_manager.removeAuthenticationConfig(self.auth_id) - result = auth_manager.storeAuthenticationConfig(config) - else: - result = auth_manager.storeAuthenticationConfig(config, True) + result = auth_manager.storeAuthenticationConfig(config, True) # The JSON will be saved later, in the table else: # Creation @@ -1120,15 +1113,13 @@ def request_check_url(self, url: str, login: str, password: str) -> Tuple[bool, if any(item in version() for item in UNSTABLE_VERSION_PREFIX): # Debug for devs self.has_repository = False - if qgis_version() < 32200: - # Missing PyQGIS class for managing webdav + + dav_metadata = webdav_properties(content) + if not dav_metadata: self.dav_url = None else: - dav_metadata = webdav_properties(content) - if not dav_metadata: - self.dav_url = None - else: - self.dav_url = self.trailing_slash(dav_metadata.get('url')) + dav_metadata.get('projects_path') + self.dav_url = self.trailing_slash(dav_metadata.get('url')) + dav_metadata.get('projects_path') + return True, '', True def _uri(self) -> QgsDataSourceUri: diff --git a/lizmap/metadata.txt b/lizmap/metadata.txt index 6666d162..d9a05a46 100755 --- a/lizmap/metadata.txt +++ b/lizmap/metadata.txt @@ -1,6 +1,6 @@ [general] name=Lizmap -qgisMinimumVersion=3.16 +qgisMinimumVersion=3.22 qgisMaximumVersion=3.99 author=3Liz email=info@3liz.com diff --git a/lizmap/plugin.py b/lizmap/plugin.py index 6851c970..8f1ac977 100755 --- a/lizmap/plugin.py +++ b/lizmap/plugin.py @@ -25,6 +25,7 @@ QgsEditFormConfig, QgsExpression, QgsFileDownloader, + QgsIconUtils, QgsLayerTree, QgsLayerTreeGroup, QgsMapLayer, @@ -38,17 +39,9 @@ QgsWkbTypes, ) from qgis.gui import QgsFileWidget -from qgis.PyQt.QtCore import QEventLoop -from qgis.PyQt.QtWidgets import QApplication, QFileDialog - -from lizmap.dialogs.news import NewConfigDialog -from lizmap.dialogs.server_wizard import CreateFolderWizard - -if Qgis.QGIS_VERSION_INT >= 32200: - from qgis.core import QgsIconUtils - from qgis.PyQt.QtCore import ( QCoreApplication, + QEventLoop, QStorageInfo, Qt, QTranslator, @@ -66,7 +59,9 @@ ) from qgis.PyQt.QtWidgets import ( QAction, + QApplication, QDialogButtonBox, + QFileDialog, QLineEdit, QMessageBox, QPushButton, @@ -127,6 +122,8 @@ from lizmap.dialogs.html_maptip import HtmlMapTipDialog from lizmap.dialogs.lizmap_popup import LizmapPopupDialog from lizmap.dialogs.main import LizmapDialog +from lizmap.dialogs.news import NewConfigDialog +from lizmap.dialogs.server_wizard import CreateFolderWizard from lizmap.dialogs.wizard_group import WizardGroupDialog from lizmap.drag_drop_dataviz_manager import DragDropDatavizManager from lizmap.forms.atlas_edition import AtlasEditionDialog @@ -185,6 +182,7 @@ from qgis.core import QgsProjectServerValidator from lizmap.qt_style_sheets import NEW_FEATURE_COLOR, NEW_FEATURE_CSS +from lizmap.server_dav import WebDav from lizmap.server_lwc import MAX_DAYS, ServerManager from lizmap.toolbelt.convert import to_bool from lizmap.toolbelt.custom_logging import ( @@ -212,9 +210,6 @@ from lizmap.tooltip import Tooltip from lizmap.version_checker import VersionChecker -if qgis_version() >= 32200: - from lizmap.server_dav import WebDav - LOGGER = logging.getLogger(plugin_name()) VERSION_URL = 'https://raw.githubusercontent.com/3liz/lizmap-web-client/versions/versions.json' # To try a local file @@ -301,12 +296,9 @@ def __init__(self, iface, lwc_version: LwcVersions = None): self.is_dev_version = any(item in self.version for item in UNSTABLE_VERSION_PREFIX) self.dlg = LizmapDialog(is_dev_version=self.is_dev_version, lwc_version=self._version) - if Qgis.QGIS_VERSION_INT >= 32200: - self.webdav = WebDav() - # Give the dialog only the first time - self.webdav.setup_webdav_dialog(self.dlg) - else: - self.webdav = None + self.webdav = WebDav() + # Give the dialog only the first time + self.webdav.setup_webdav_dialog(self.dlg) # self.check_webdav() self.dock_html_preview = None @@ -1501,27 +1493,26 @@ def initGui(self): "This value will be replaced on the server side when evaluating the expression thanks to " "the QGIS server Lizmap plugin.") # Register variable helps - if qgis_version() >= 32200: - QgsExpression.addVariableHelpText( - "lizmap_user", - "{}

{}

{}".format( - tr("The current Lizmap login as a string."), - tr("It might be an empty string if the user is not connected."), - server_side, - ) + QgsExpression.addVariableHelpText( + "lizmap_user", + "{}

{}

{}".format( + tr("The current Lizmap login as a string."), + tr("It might be an empty string if the user is not connected."), + server_side, ) - QgsExpression.addVariableHelpText( - "lizmap_user_groups", - "{}

{}

{}

{}".format( - tr("The current groups of the logged user as an array."), - tr("It might be an empty array if the user is not connected."), - tr( - "You might need to use functions in the Array expression category, such as " - "
array_to_string
to convert it to a string."), - server_side, - ) + ) + QgsExpression.addVariableHelpText( + "lizmap_user_groups", + "{}

{}

{}

{}".format( + tr("The current groups of the logged user as an array."), + tr("It might be an empty array if the user is not connected."), + tr( + "You might need to use functions in the Array expression category, such as " + "
array_to_string
to convert it to a string."), + server_side, ) - QgsExpression.addVariableHelpText("lizmap_repository", tr("The current repository ID on the server.")) + ) + QgsExpression.addVariableHelpText("lizmap_repository", tr("The current repository ID on the server.")) # Let's fix the dialog to the first panel self.dlg.mOptionsListWidget.setCurrentRow(Panels.Information) @@ -3401,7 +3392,7 @@ def check_project( # Icon for k, v in filters.items(): - if k == "_wkb_type" and Qgis.QGIS_VERSION_INT >= 32000: + if k == "_wkb_type": icon = QgsIconUtils.iconForWkbType(v) break else: diff --git a/lizmap/server_lwc.py b/lizmap/server_lwc.py index a7d475ee..90c5a105 100755 --- a/lizmap/server_lwc.py +++ b/lizmap/server_lwc.py @@ -1279,12 +1279,7 @@ def migrate_password_manager(self, servers: list): config.setConfig('username', user) config.setConfig('password', password) config.setConfig('realm', QUrl(url).host()) - - if qgis_version() < 32000: - auth_manager.removeAuthenticationConfig(auth_id) - result = auth_manager.storeAuthenticationConfig(config) - else: - result = auth_manager.storeAuthenticationConfig(config, True) + result = auth_manager.storeAuthenticationConfig(config, True) if not result: LOGGER.critical("Error while migrating the server") diff --git a/lizmap/test/test_dialog_edition.py b/lizmap/test/test_dialog_edition.py index 8e281c99..05271aaa 100755 --- a/lizmap/test/test_dialog_edition.py +++ b/lizmap/test/test_dialog_edition.py @@ -16,7 +16,6 @@ from lizmap.forms.time_manager_edition import TimeManagerEditionDialog from lizmap.forms.tooltip_edition import ToolTipEditionDialog from lizmap.toolbelt.resources import plugin_test_data_path -from lizmap.toolbelt.version import qgis_version __copyright__ = 'Copyright 2023, 3Liz' __license__ = 'GPL version 3' @@ -61,11 +60,7 @@ def test_load_save_collection_dataviz(self): """Test we can load collection.""" dialog = DatavizEditionDialog() self.assertFalse(dialog.error.isVisible()) - if qgis_version() < 32200: - self.assertEqual('', dialog.x_field.currentField()) - self.assertEqual(dialog.validate(), 'The field "x field" is mandatory.') - else: - self.assertEqual('id', dialog.x_field.currentField()) + self.assertEqual('id', dialog.x_field.currentField()) data = [ { @@ -92,15 +87,7 @@ def test_atlas_dialog(self): dialog = AtlasEditionDialog() self.assertFalse(dialog.error.isVisible()) - if qgis_version() >= 32200: - self.assertEqual(dialog.primary_key.currentField(), 'id') - else: - self.assertEqual(dialog.validate(), 'The field "primary key" is mandatory.') - dialog.primary_key.setCurrentIndex(1) - self.assertEqual(dialog.validate(), 'The field "feature label" is mandatory.') - dialog.feature_label.setCurrentIndex(1) - self.assertEqual(dialog.validate(), 'The field "sort field" is mandatory.') - dialog.sort_field.setCurrentIndex(1) + self.assertEqual(dialog.primary_key.currentField(), 'id') self.assertEqual( 'The layers you have chosen for this tool must be checked in the "WFS Capabilities"\n option of the QGIS ' @@ -126,14 +113,11 @@ def test_atlas_dialog(self): del dialog dialog = AtlasEditionDialog() - if qgis_version() < 32200: - self.assertEqual(dialog.validate(), 'The field "primary key" is mandatory.') - else: - self.assertEqual( - dialog.validate(), - 'The layers you have chosen for this tool must be checked in the "WFS Capabilities"\n option of the ' - 'QGIS Server tab in the "Project Properties" dialog.' - ) + self.assertEqual( + dialog.validate(), + 'The layers you have chosen for this tool must be checked in the "WFS Capabilities"\n option of the ' + 'QGIS Server tab in the "Project Properties" dialog.' + ) dialog.load_form(data) diff --git a/lizmap/test/test_trace_dialog.py b/lizmap/test/test_trace_dialog.py index 0322c4ef..f2193227 100755 --- a/lizmap/test/test_trace_dialog.py +++ b/lizmap/test/test_trace_dialog.py @@ -7,7 +7,6 @@ from lizmap.definitions.dataviz import GraphType from lizmap.forms.trace_dataviz_edition import TraceDatavizEditionDialog from lizmap.toolbelt.resources import plugin_test_data_path -from lizmap.toolbelt.version import qgis_version __copyright__ = 'Copyright 2020, 3Liz' __license__ = 'GPL version 3' @@ -44,32 +43,19 @@ def test_unique(self): """Test Y is unique when we add a new row.""" dialog = TraceDatavizEditionDialog(None, self.layer, GraphType.Histogram, []) self.assertFalse(dialog.error.isVisible()) - if qgis_version() < 32200: - self.assertEqual('', dialog.y_field.currentField()) - self.assertEqual('Y field is required.', dialog.validate()) - dialog.y_field.setCurrentIndex(0) - else: - self.assertEqual('id', dialog.y_field.currentField()) + self.assertEqual('id', dialog.y_field.currentField()) self.assertIsNone(dialog.validate()) # Same but with a unique value not existing dialog = TraceDatavizEditionDialog(None, self.layer, GraphType.Histogram, ['hello']) self.assertFalse(dialog.error.isVisible()) - if qgis_version() < 32200: - self.assertEqual('Y field is required.', dialog.validate()) - dialog.y_field.setCurrentIndex(0) - else: - self.assertEqual('id', dialog.y_field.currentField()) + self.assertEqual('id', dialog.y_field.currentField()) self.assertIsNone(dialog.validate()) # Same but with a unique value dialog = TraceDatavizEditionDialog(None, self.layer, GraphType.Histogram, ['id']) self.assertFalse(dialog.error.isVisible()) - if qgis_version() < 32200: - self.assertEqual('Y field is required.', dialog.validate()) - dialog.y_field.setCurrentIndex(0) - else: - self.assertEqual('id', dialog.y_field.currentField()) + self.assertEqual('id', dialog.y_field.currentField()) self.assertEqual('This Y field is already existing.', dialog.validate()) def test_trace_dialog(self): @@ -77,18 +63,11 @@ def test_trace_dialog(self): dialog = TraceDatavizEditionDialog(None, self.layer, GraphType.Histogram, []) self.assertFalse(dialog.error.isVisible()) - if qgis_version() < 32200: - self.assertEqual('', dialog.y_field.currentField()) - self.assertEqual('Y field is required.', dialog.validate()) - self.assertEqual('#086fa1', dialog.color.color().name()) - self.assertEqual('', dialog.color_field.currentField()) - self.assertTrue(dialog.color.isEnabled()) - else: - # TODO better to check these ones - self.assertEqual('id', dialog.y_field.currentField()) - self.assertEqual('#000000', dialog.color.color().name()) - self.assertEqual('id', dialog.color_field.currentField()) - self.assertFalse(dialog.color.isEnabled()) + # TODO better to check these ones + self.assertEqual('id', dialog.y_field.currentField()) + self.assertEqual('#000000', dialog.color.color().name()) + self.assertEqual('id', dialog.color_field.currentField()) + self.assertFalse(dialog.color.isEnabled()) self.assertTrue(dialog.color_field.allowEmptyFieldName()) diff --git a/lizmap/test/test_webdav.py b/lizmap/test/test_webdav.py index 66d77876..c491e41a 100644 --- a/lizmap/test/test_webdav.py +++ b/lizmap/test/test_webdav.py @@ -8,13 +8,9 @@ from qgis.PyQt.QtWidgets import QWizard from lizmap.dialogs.server_wizard import THUMBS, CreateFolderWizard -from lizmap.toolbelt.strings import random_string -from lizmap.toolbelt.version import qgis_version - -if qgis_version() >= 32200: - from lizmap.server_dav import WebDav, PropFindFileResponse, PropFindDirResponse - +from lizmap.server_dav import PropFindDirResponse, PropFindFileResponse, WebDav from lizmap.toolbelt.resources import plugin_test_data_path +from lizmap.toolbelt.strings import random_string try: from lizmap.test.credentials import ( @@ -47,7 +43,7 @@ def skip_test(): """ Check conditions for running tests. """ - return not all((CREDENTIALS, qgis_version() >= 32200, LIZMAP_HOST_DAV, LIZMAP_USER, LIZMAP_PASSWORD)) + return not all((CREDENTIALS, LIZMAP_HOST_DAV, LIZMAP_USER, LIZMAP_PASSWORD)) # # Important note, we are not using our own webdav library to create/remove fixtures. diff --git a/lizmap/toolbelt/version.py b/lizmap/toolbelt/version.py index 64170762..2e8d4c7c 100644 --- a/lizmap/toolbelt/version.py +++ b/lizmap/toolbelt/version.py @@ -13,10 +13,7 @@ def qgis_version(): """ Return the QGIS version as integers. """ - # The API has changed in QGIS 3.12 - # Use the function layer - # noinspection PyUnresolvedReferences - return Qgis.QGIS_VERSION_INT + return Qgis.versionInt() def version(remove_v_prefix=True) -> str: diff --git a/lizmap/widgets/check_project.py b/lizmap/widgets/check_project.py index 8c79d73f..307b551e 100644 --- a/lizmap/widgets/check_project.py +++ b/lizmap/widgets/check_project.py @@ -21,7 +21,6 @@ from lizmap.definitions.online_help import pg_service_help from lizmap.definitions.qgis_settings import Settings from lizmap.toolbelt.i18n import tr -from lizmap.toolbelt.version import qgis_version # 10 000 * 10 000 RASTER_COUNT_CELL = 100000000 @@ -245,9 +244,8 @@ class Checks: """ List of checks defined. """ def __init__(self): - # Check QGIS_VERSION_INT - qgis_32200 = tr( - 'With QGIS ≥ 3.22, you can use the auto-fix button in the dedicated panel of the plugin to fix currently ' + qgis_auto_fix_button = tr( + 'You can use the auto-fix button in the dedicated panel of the plugin to fix currently ' 'loaded layers' ) other_auth = tr('Either switch to another authentication mechanism') @@ -430,11 +428,11 @@ def __init__(self): '
  • {help}
  • ' '' ).format( - auto_fix=qgis_32200, + auto_fix=qgis_auto_fix_button, help=global_connection, ), Levels.Layer, - Severities().blocking if qgis_version() >= 32200 else Severities().important, + Severities().blocking, QIcon(':/images/themes/default/mIconPostgis.svg'), ) self.EstimatedMetadata = Check( @@ -447,11 +445,11 @@ def __init__(self): '
  • {help}
  • ' '' ).format( - auto_fix=qgis_32200, + auto_fix=qgis_auto_fix_button, help=global_connection, ), Levels.Layer, - Severities().blocking if qgis_version() >= 32200 else Severities().important, + Severities().blocking, QIcon(':/images/themes/default/mIconPostgis.svg'), ) self.SimplifyGeometry = Check( @@ -464,14 +462,14 @@ def __init__(self): '
  • {help}
  • ' '' ).format( - auto_fix=qgis_32200, + auto_fix=qgis_auto_fix_button, help=tr( 'Visit the layer properties, then in the "Rendering" tab to enable it simplification on the provider ' 'side on the given layer.' ), ), Levels.Layer, - Severities().blocking if qgis_version() >= 32200 else Severities().important, + Severities().blocking, QIcon(':/images/themes/default/mIconGeometryCollectionLayer.svg'), ) self.RasterWithoutPyramid = Check( @@ -731,11 +729,11 @@ def __init__(self): '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 dedicated panel of the plugin'), + auto_fix=tr('You can use the auto-fix button in the dedicated panel of the plugin'), ) ), Levels.Project, - Severities().blocking if qgis_version() >= 32200 else Severities().important, + Severities().blocking, QIcon(':/images/themes/default/mIconQgsProjectFile.svg'), ) self.EmptyBaseLayersGroup = Check(