From e7360d46147e2f59367e33d09000930486bcf20f Mon Sep 17 00:00:00 2001 From: jmkerloch Date: Thu, 12 Sep 2024 11:12:18 +0200 Subject: [PATCH 01/17] feat(profile): add utils.py to get profile informations: - qgis profiles path - profile list - QGIS3.ini for profile - metadata from plugin in profile - metadata for plugin from QGIS --- profile_manager/profiles/utils.py | 143 ++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 profile_manager/profiles/utils.py diff --git a/profile_manager/profiles/utils.py b/profile_manager/profiles/utils.py new file mode 100644 index 0000000..fdf4ed2 --- /dev/null +++ b/profile_manager/profiles/utils.py @@ -0,0 +1,143 @@ +from pathlib import Path +from sys import platform +from typing import Any, Dict, List, Optional +from configparser import NoSectionError, RawConfigParser + + +from qgis.core import QgsUserProfileManager +from qgis.utils import iface + + +def qgis_profiles_path() -> Path: + """Get QGIS profiles paths from current platforms + + - Windows : $HOME / "AppData" / "Roaming" / "QGIS" / "QGIS3" / "profiles" + - MacOS : $HOME / "Library" / "Application Support" / "QGIS" / "QGIS3" / "profiles" + - Linux : $HOME / ".local" / "share" / "QGIS" / "QGIS3" / "profiles" + + Returns: + Path: QGIS profiles path + """ + home_path = Path.home() + # Windows + if platform.startswith("win32"): + return home_path / "AppData" / "Roaming" / "QGIS" / "QGIS3" / "profiles" + # MacOS + if platform == "darwin": + return ( + home_path + / "Library" + / "Application Support" + / "QGIS" + / "QGIS3" + / "profiles" + ) + # Linux + return home_path / ".local" / "share" / "QGIS" / "QGIS3" / "profiles" + + +def get_profile_qgis_ini_path(profile_name: str) -> Path: + """Get QGIS3.ini file path for a profile + + Args: + profile_name (str): profile name + + Returns: + Path: QGIS3.ini path + """ + # MacOS + if platform.startswith("darwin"): + return qgis_profiles_path() / profile_name / "qgis.org" / "QGIS3.ini" + # Windows / Linux + return qgis_profiles_path() / profile_name / "QGIS" / "QGIS3.ini" + + +def get_profile_plugin_metadata_path(profile_name: str, plugin_slug_name: str) -> Path: + """Get path to metadata.txt for a plugin inside a profile + + Args: + profile_name (str): profile name + plugin_slug_name (str): plugin slug name + + Returns: + Path: metadata.txt path + """ + return ( + qgis_profiles_path() + / profile_name + / "python" + / "plugins" + / plugin_slug_name + / "metadata.txt" + ) + + +def get_installed_plugin_list( + profile_name: str, only_activated: bool = True +) -> List[str]: + """Get installed plugin for a profile + + Args: + profile_name (str): profile name + only_activated (bool, optional): True to get only activated plugin, False to get all installed plugins. Defaults to True. + + Returns: + List[str]: plugin slug name list + """ + ini_parser = RawConfigParser() + ini_parser.optionxform = str # str = case-sensitive option names + ini_parser.read(get_profile_qgis_ini_path(profile_name)) + try: + plugins_in_profile = dict(ini_parser.items("PythonPlugins")) + except NoSectionError: + plugins_in_profile = {} + + if only_activated: + return [key for key, value in plugins_in_profile.items() if value == "true"] + else: + return plugins_in_profile.keys() + + +def get_installed_plugin_metadata( + profile_name: str, plugin_slug_name: str +) -> Dict[str, Any]: + """Get metadata information from metadata.txt file in profile installed plugin + + Args: + profile_name (str): profile name + plugin_slug_name (str): plugin slug name + + Returns: + Dict[str, Any]: metadata as dict. Empty dict if metadata unavailable + """ + ini_parser = RawConfigParser() + ini_parser.optionxform = str # str = case-sensitive option names + ini_parser.read(get_profile_plugin_metadata_path(profile_name, plugin_slug_name)) + try: + metadata = dict(ini_parser.items("general")) + except NoSectionError: + metadata = {} + return metadata + + +def get_plugin_info_from_qgis_manager( + plugin_slug_name: str, +) -> Optional[Dict[str, str]]: + """Get plugin informations from QGIS plugin manager + + Args: + plugin_slug_name (str): plugin slug name + + Returns: + Optional[Dict[str, str]]: metadata from plugin manager, None if plugin not found + """ + return iface.pluginManagerInterface().pluginMetadata(plugin_slug_name) + + +def get_profile_name_list() -> List[str]: + """Get profile name list from current installed QGIS + + Returns: + List[str]: profile name list + """ + return QgsUserProfileManager(qgis_profiles_path()).allProfiles() From ce5c2a1c6ef0ecb8c22a759b625dd40a1951496e Mon Sep 17 00:00:00 2001 From: jmkerloch Date: Thu, 12 Sep 2024 11:56:05 +0200 Subject: [PATCH 02/17] feat(profile): get profile plugin informations --- profile_manager/profiles/utils.py | 100 ++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/profile_manager/profiles/utils.py b/profile_manager/profiles/utils.py index fdf4ed2..d0ce505 100644 --- a/profile_manager/profiles/utils.py +++ b/profile_manager/profiles/utils.py @@ -1,3 +1,4 @@ +from dataclasses import dataclass from pathlib import Path from sys import platform from typing import Any, Dict, List, Optional @@ -141,3 +142,102 @@ def get_profile_name_list() -> List[str]: List[str]: profile name list """ return QgsUserProfileManager(qgis_profiles_path()).allProfiles() + + +@dataclass +class PluginInformation: + name: str + folder_name: str + official_repository: bool + plugin_id: str + version: str + + +def define_plugin_version_from_metadata( + manager_metadata: Dict[str, Any], plugin_metadata: Dict[str, Any] +) -> str: + """Define plugin version from available metadata + + Args: + manager_metadata (Dict[str, Any]): QGIS plugin manager metadata + plugin_metadata (Dict[str, Any]): installed plugin metadata + + Returns: + str: plugin version + """ + # Use version from plugin metadata + if "version" in plugin_metadata: + return plugin_metadata["version"] + + # Fallback to stable version + if manager_metadata["version_available_stable"]: + return manager_metadata["version_available_stable"] + # Fallback to experimental version + if manager_metadata["version_available_experimental"]: + return manager_metadata["version_available_experimental"] + # Fallback to available version + if manager_metadata["version_available"]: + return manager_metadata["version_available"] + # No version defined + return "" + + +def get_profile_plugin_information( + profile_name: str, plugin_slug_name: str +) -> Optional[PluginInformation]: + """Get plugin information from profile. Only official plugin are supported. + + Args: + profile_name (str): profile name + plugin_slug_name (str): plugin slug name + + Returns: + Optional[PluginInformation]: plugin information, None if plugin is not official + """ + manager_metadata = get_plugin_info_from_qgis_manager( + plugin_slug_name=plugin_slug_name + ) + plugin_metadata = get_installed_plugin_metadata( + profile_name=profile_name, plugin_slug_name=plugin_slug_name + ) + + # For now we don't support unofficial plugins + if manager_metadata is None: + return None + + return PluginInformation( + name=manager_metadata["name"], + folder_name=plugin_slug_name, + official_repository=True, # For now we only support official repository + plugin_id=manager_metadata["plugin_id"], + version=define_plugin_version_from_metadata( + manager_metadata=manager_metadata, + plugin_metadata=plugin_metadata, + ), + ) + + +def get_profile_plugin_list_information( + profile_name: str, only_activated: bool = True +) -> List[PluginInformation]: + """Get profile plugin information + + Args: + profile_name (str): profile name + only_activated (bool, optional): True to get only activated plugin, False to get all installed plugins. Defaults to True. + + Returns: + List[PluginInformation]: list of PluginInformation + """ + plugin_list: List[str] = get_installed_plugin_list( + profile_name=profile_name, only_activated=only_activated + ) + # Get information about installed plugin + profile_plugin_list: List[PluginInformation] = [] + + for plugin_name in plugin_list: + plugin_info = get_profile_plugin_information(profile_name, plugin_name) + if plugin_info and plugin_info.plugin_id != "": + profile_plugin_list.append(plugin_info) + + return profile_plugin_list From e4bbfee15b524f00af8a696eded93c8569b85404 Mon Sep 17 00:00:00 2001 From: jmkerloch Date: Thu, 12 Sep 2024 12:00:09 +0200 Subject: [PATCH 03/17] feat(qdt profile): add qdt profile export from QGIS profile: - copy all profile content - remove cache folders - remove python plugin folders - create profile.json file for QDT from profile plugins --- qdt_export/profile_export.py | 108 +++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 qdt_export/profile_export.py diff --git a/qdt_export/profile_export.py b/qdt_export/profile_export.py new file mode 100644 index 0000000..a22dc1d --- /dev/null +++ b/qdt_export/profile_export.py @@ -0,0 +1,108 @@ +from dataclasses import dataclass +import dataclasses +from pathlib import Path +from shutil import copytree, rmtree +from typing import Any, Dict + +import json + +from ..profiles.utils import ( + qgis_profiles_path, + get_profile_plugin_list_information, +) + + +QDT_PROFILE_SCHEMA = "https://raw.githubusercontent.com/Guts/qgis-deployment-cli/main/docs/schemas/profile/qgis_profile.json" + + +@dataclass +class QDTProfileInfos: + """Store informations for QDT profile creation""" + + description: str = "" + email: str = "" + version: str = "" + qgis_min_version: str = "" + qgis_max_version: str = "" + + +def qdt_profile_dict( + profile_name: str, + qdt_profile_infos: QDTProfileInfos, + export_inactive_plugin: bool = False, +) -> Dict[str, Any]: + """Create QDT profile dict from QGIS profile + + Get informations from installed plugin and QDT profile informations + + Args: + profile_name (str): profile name + qdt_profile_infos (QDTProfileInfos): information for QDT profile creation + export_inactive_plugin (bool, optional): True for inactive profile plugin export. Defaults to False. + + Returns: + Dict[str, Any]: QDT profile dict + """ + # Get profile installed plugin + only_activated = not export_inactive_plugin + profile_plugin_list = get_profile_plugin_list_information( + profile_name=profile_name, only_activated=only_activated + ) + + return { + "$schema": QDT_PROFILE_SCHEMA, + "name": profile_name, + "folder_name": profile_name, # TODO check for profile with space + "description": qdt_profile_infos.description, + "email": qdt_profile_infos.email, + "icon": "TDB", # TODO add icon + "qgisMinimumVersion": qdt_profile_infos.qgis_min_version, + "qgisMaximumVersion": qdt_profile_infos.qgis_max_version, + "version": qdt_profile_infos.version, + "plugins": [dataclasses.asdict(plugin) for plugin in profile_plugin_list], + } + + +def export_profile_for_qdt( + profile_name: str, + export_path: Path, + qdt_profile_infos: QDTProfileInfos, + clear_export_path: bool = False, + export_inactive_plugin: bool = False, +) -> None: + """Export QGIS profile for QDT + + Args: + profile_name (str): name of profile to export + export_path (Path): export path for QDT profile + qdt_profile_infos (QDTProfileInfos): information for QDT profile creation + clear_export_path (bool, optional): True for export path clear before export. Defaults to False. + export_inactive_plugin (bool, optional): True for inactive profile plugin export. Defaults to False. + """ + + if clear_export_path: + # Delete current export content + rmtree(export_path, ignore_errors=True) + + # Copy profile content to export path + copytree( + src=Path(qgis_profiles_path()) / profile_name, + dst=export_path, + dirs_exist_ok=True, + ) + + # Delete cache content + rmtree(export_path / "cache", ignore_errors=True) + rmtree(export_path / "oauth2-cache", ignore_errors=True) + + # Delete python/plugins content + rmtree(export_path / "python" / "plugins", ignore_errors=True) + + profile_dict = qdt_profile_dict( + profile_name=profile_name, + qdt_profile_infos=qdt_profile_infos, + export_inactive_plugin=export_inactive_plugin, + ) + + with open(export_path / "profile.json", "w", encoding="UTF-8") as f: + json.dump(profile_dict, f, indent=4) From d2e06bb2f3ea28406c2aaad612caef496b455cb7 Mon Sep 17 00:00:00 2001 From: jmkerloch Date: Thu, 12 Sep 2024 14:41:04 +0200 Subject: [PATCH 04/17] fix(qdt export): must reload plugin manager before qdt profile export --- profile_manager/profiles/utils.py | 9 +++++++-- qdt_export/profile_export.py | 3 +++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/profile_manager/profiles/utils.py b/profile_manager/profiles/utils.py index d0ce505..62a13c0 100644 --- a/profile_manager/profiles/utils.py +++ b/profile_manager/profiles/utils.py @@ -8,6 +8,8 @@ from qgis.core import QgsUserProfileManager from qgis.utils import iface +import pyplugin_installer + def qgis_profiles_path() -> Path: """Get QGIS profiles paths from current platforms @@ -122,16 +124,19 @@ def get_installed_plugin_metadata( def get_plugin_info_from_qgis_manager( - plugin_slug_name: str, + plugin_slug_name: str, reload_manager : bool = False ) -> Optional[Dict[str, str]]: """Get plugin informations from QGIS plugin manager Args: - plugin_slug_name (str): plugin slug name + plugin_slug_name (str): _description_ + reload_manager (bool, optional): reload manager for new plugins. Defaults to False. Returns: Optional[Dict[str, str]]: metadata from plugin manager, None if plugin not found """ + if reload_manager: + pyplugin_installer.instance().reloadAndExportData() return iface.pluginManagerInterface().pluginMetadata(plugin_slug_name) diff --git a/qdt_export/profile_export.py b/qdt_export/profile_export.py index a22dc1d..3e10e54 100644 --- a/qdt_export/profile_export.py +++ b/qdt_export/profile_export.py @@ -6,6 +6,8 @@ import json +import pyplugin_installer + from ..profiles.utils import ( qgis_profiles_path, get_profile_plugin_list_information, @@ -79,6 +81,7 @@ def export_profile_for_qdt( clear_export_path (bool, optional): True for export path clear before export. Defaults to False. export_inactive_plugin (bool, optional): True for inactive profile plugin export. Defaults to False. """ + pyplugin_installer.instance().reloadAndExportData() if clear_export_path: # Delete current export content From c2a28771620c76273dddc57ad258d78f7f89b815 Mon Sep 17 00:00:00 2001 From: jmkerloch Date: Thu, 12 Sep 2024 12:01:00 +0200 Subject: [PATCH 05/17] feat(qdt profile): add export in GUI --- profile_manager/gui/interface_handler.py | 9 ++ profile_manager/profile_manager_dialog.py | 43 ++++++ .../profile_manager_dialog_base.ui | 129 +++++++++++++++++- 3 files changed, 177 insertions(+), 4 deletions(-) diff --git a/profile_manager/gui/interface_handler.py b/profile_manager/gui/interface_handler.py index e83b594..d3c1db4 100644 --- a/profile_manager/gui/interface_handler.py +++ b/profile_manager/gui/interface_handler.py @@ -82,6 +82,7 @@ def populate_profile_listings(self): self.dlg.comboBoxNamesSource.clear() self.dlg.comboBoxNamesTarget.clear() + self.dlg.qdt_export_profile_cbx.clear() self.dlg.list_profiles.clear() for i, name in enumerate(profile_names): # Init source profiles combobox @@ -96,6 +97,14 @@ def populate_profile_listings(self): font = self.dlg.comboBoxNamesTarget.font() font.setItalic(True) self.dlg.comboBoxNamesTarget.setItemData(i, QVariant(font), Qt.FontRole) + # Init qdt export profiles combobox + self.dlg.qdt_export_profile_cbx.addItem(name) + if name == active_profile_name: + font = self.dlg.qdt_export_profile_cbx.font() + font.setItalic(True) + self.dlg.qdt_export_profile_cbx.setItemData( + i, QVariant(font), Qt.FontRole + ) # Add profiles to list view list_item = QListWidgetItem(QIcon("../icon.png"), name) if name == active_profile_name: diff --git a/profile_manager/profile_manager_dialog.py b/profile_manager/profile_manager_dialog.py index 12639bd..89d93cc 100644 --- a/profile_manager/profile_manager_dialog.py +++ b/profile_manager/profile_manager_dialog.py @@ -22,8 +22,12 @@ """ import os +from pathlib import Path +from typing import Optional from qgis.PyQt import QtWidgets, uic +from .userInterface.mdl_profiles import ProfileListModel +from .qdt_export.profile_export import export_profile_for_qdt, QDTProfileInfos # This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer FORM_CLASS, _ = uic.loadUiType( @@ -41,3 +45,42 @@ def __init__(self, parent=None): # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html # #widgets-and-dialogs-with-auto-connect self.setupUi(self) + + self.profile_mdl = ProfileListModel(self) + self.qdt_export_profile_cbx.setModel(self.profile_mdl) + self.export_qdt_button.clicked.connect(self.export_qdt_handler) + + self.comboBoxNamesSource.setModel(self.profile_mdl) + self.comboBoxNamesTarget.setModel(self.profile_mdl) + self.list_profiles.setModel(self.profile_mdl) + + def get_list_selection_profile_name(self) -> Optional[str]: + """Get selected profile name from list + + Returns: + Optional[str]: selected profile name, None if no profile selected + """ + index = self.list_profiles.selectionModel().currentIndex() + if index.isValid(): + return self.list_profiles.model().data(index, ProfileListModel.NAME_COL) + return None + + def export_qdt_handler(self): + """Export selected profile as QDT profile""" + profile_path = self.qdt_file_widget.filePath() + if profile_path: + source_profile_name = self.qdt_export_profile_cbx.currentText() + qdt_profile_infos = QDTProfileInfos( + description=self.qdt_description_edit.toPlainText(), + email=self.qdt_email_edit.text(), + version=self.qdt_version_edit.text(), + qgis_min_version=self.qdt_qgis_min_version_edit.text(), + qgis_max_version=self.qdt_qgis_max_version_edit.text(), + ) + export_profile_for_qdt( + profile_name=source_profile_name, + export_path=Path(profile_path), + qdt_profile_infos=qdt_profile_infos, + clear_export_path=self.qdt_clear_export_folder_checkbox.isChecked(), + export_inactive_plugin=self.qdt_inactive_plugin_export_checkbox.isChecked(), + ) diff --git a/profile_manager/profile_manager_dialog_base.ui b/profile_manager/profile_manager_dialog_base.ui index 41c9ef2..903e5cd 100644 --- a/profile_manager/profile_manager_dialog_base.ui +++ b/profile_manager/profile_manager_dialog_base.ui @@ -6,8 +6,8 @@ 0 0 - 733 - 622 + 761 + 451 @@ -20,7 +20,7 @@ - 0 + 2 @@ -193,7 +193,7 @@ - 0 + 2 @@ -345,6 +345,120 @@ + + + QDT Export + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Export + + + + + + + Email + + + + + + + QgsFileWidget::GetDirectory + + + + + + + + + + Export inactive plugins + + + + + + + + + + + + + + + + + 0 + 0 + + + + Profile + + + + + + + Description + + + + + + + Clear export folder + + + + + + + Version + + + + + + + QGIS min. version + + + + + + + QGIS max. version + + + + + + + + + + + @@ -356,6 +470,13 @@ + + + QgsFileWidget + QWidget +
qgsfilewidget.h
+
+
From 6aa672e598d1d3e361a31c34700aff2f24471992 Mon Sep 17 00:00:00 2001 From: jmkerloch Date: Thu, 12 Sep 2024 12:36:40 +0200 Subject: [PATCH 06/17] feat(profile): add QStandardtemModel for profile display: - use QgsUserProfileManager to get available profiles - connect to profilesChanged for model update --- profile_manager/gui/mdl_profiles.py | 73 +++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 profile_manager/gui/mdl_profiles.py diff --git a/profile_manager/gui/mdl_profiles.py b/profile_manager/gui/mdl_profiles.py new file mode 100644 index 0000000..f5667d6 --- /dev/null +++ b/profile_manager/gui/mdl_profiles.py @@ -0,0 +1,73 @@ +from pathlib import Path +from qgis.PyQt.QtCore import QObject, Qt, QModelIndex +from qgis.PyQt.QtGui import QStandardItemModel +from qgis.core import QgsUserProfileManager, QgsUserProfile, QgsApplication + +from ..profiles.utils import qgis_profiles_path + + +class ProfileListModel(QStandardItemModel): + """QStandardItemModel to display available QGIS profile list""" + + NAME_COL = 0 + + def __init__(self, parent: QObject = None): + """ + QStandardItemModel for profile list display + + Args: + parent: QObject parent + """ + super().__init__(parent) + self.setHorizontalHeaderLabels([self.tr("Name")]) + + self.profile_manager = QgsUserProfileManager(str(qgis_profiles_path())) + + # Connect to profile changes + self.profile_manager.setNewProfileNotificationEnabled(True) + self.profile_manager.profilesChanged.connect(self._update_available_profiles) + + # Initialization of available profiles + self._update_available_profiles() + + def flags(self, index: QModelIndex) -> Qt.ItemFlags: + """Define flags for an index. + Used to disable edition. + + Args: + index (QModelIndex): data index + + Returns: + Qt.ItemFlags: flags + """ + default_flags = super().flags(index) + return default_flags & ~Qt.ItemIsEditable # Disable editing + + def _update_available_profiles(self) -> None: + """Update model with all available profiles in manager""" + self.removeRows(0, self.rowCount()) + for profile_name in self.profile_manager.allProfiles(): + self.insert_profile(profile_name) + + def insert_profile(self, profile_name: str) -> None: + """Insert profile in model + + Args: + profile_name (str): profile name + """ + # Get user profile + profile: QgsUserProfile = self.profile_manager.profileForName(profile_name) + if profile: + row = self.rowCount() + self.insertRow(row) + self.setData(self.index(row, self.NAME_COL), profile.alias()) + self.setData( + self.index(row, self.NAME_COL), profile.icon(), Qt.DecorationRole + ) + + active_profile_folder_name = Path(QgsApplication.qgisSettingsDirPath()).name + profile_folder_name = Path(profile.folder()).name + if profile_folder_name == active_profile_folder_name: + font = QgsApplication.font() + font.setItalic(True) + self.setData(self.index(row, self.NAME_COL), font, Qt.FontRole) From af73ec75f782f931ff7249c3b5bb13a90743a7b0 Mon Sep 17 00:00:00 2001 From: jmkerloch Date: Thu, 12 Sep 2024 12:38:02 +0200 Subject: [PATCH 07/17] feat(qdt profile): use ProfileListModel to select profile --- profile_manager/gui/interface_handler.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/profile_manager/gui/interface_handler.py b/profile_manager/gui/interface_handler.py index d3c1db4..05d6041 100644 --- a/profile_manager/gui/interface_handler.py +++ b/profile_manager/gui/interface_handler.py @@ -82,7 +82,6 @@ def populate_profile_listings(self): self.dlg.comboBoxNamesSource.clear() self.dlg.comboBoxNamesTarget.clear() - self.dlg.qdt_export_profile_cbx.clear() self.dlg.list_profiles.clear() for i, name in enumerate(profile_names): # Init source profiles combobox @@ -97,14 +96,7 @@ def populate_profile_listings(self): font = self.dlg.comboBoxNamesTarget.font() font.setItalic(True) self.dlg.comboBoxNamesTarget.setItemData(i, QVariant(font), Qt.FontRole) - # Init qdt export profiles combobox - self.dlg.qdt_export_profile_cbx.addItem(name) - if name == active_profile_name: - font = self.dlg.qdt_export_profile_cbx.font() - font.setItalic(True) - self.dlg.qdt_export_profile_cbx.setItemData( - i, QVariant(font), Qt.FontRole - ) + # Add profiles to list view list_item = QListWidgetItem(QIcon("../icon.png"), name) if name == active_profile_name: From 348e72951b7e5eab2131de34f4b4fc594846973a Mon Sep 17 00:00:00 2001 From: jmkerloch Date: Thu, 12 Sep 2024 13:56:11 +0200 Subject: [PATCH 08/17] refactor(profile): use ProfileListModel as model for profile list and profile combobox - convert QListWidget to QListView - add function in ProfileManagerDialog to get current selected profile in list --- profile_manager/gui/interface_handler.py | 35 ++----------------- .../profile_manager_dialog_base.ui | 2 +- profile_manager/profiles/profile_copier.py | 4 +-- profile_manager/profiles/profile_editor.py | 2 +- profile_manager/profiles/profile_remover.py | 7 ++-- 5 files changed, 10 insertions(+), 40 deletions(-) diff --git a/profile_manager/gui/interface_handler.py b/profile_manager/gui/interface_handler.py index 05d6041..1b03f29 100644 --- a/profile_manager/gui/interface_handler.py +++ b/profile_manager/gui/interface_handler.py @@ -73,37 +73,8 @@ def populate_profile_listings(self): Also updates button states according to resulting selections. """ - profile_names = self.profile_manager.qgs_profile_manager.allProfiles() - active_profile_name = Path(QgsApplication.qgisSettingsDirPath()).name - self.dlg.comboBoxNamesSource.blockSignals(True) - self.dlg.comboBoxNamesTarget.blockSignals(True) - self.dlg.list_profiles.blockSignals(True) - - self.dlg.comboBoxNamesSource.clear() - self.dlg.comboBoxNamesTarget.clear() - self.dlg.list_profiles.clear() - for i, name in enumerate(profile_names): - # Init source profiles combobox - self.dlg.comboBoxNamesSource.addItem(name) - if name == active_profile_name: - font = self.dlg.comboBoxNamesSource.font() - font.setItalic(True) - self.dlg.comboBoxNamesSource.setItemData(i, QVariant(font), Qt.FontRole) - # Init target profiles combobox - self.dlg.comboBoxNamesTarget.addItem(name) - if name == active_profile_name: - font = self.dlg.comboBoxNamesTarget.font() - font.setItalic(True) - self.dlg.comboBoxNamesTarget.setItemData(i, QVariant(font), Qt.FontRole) - - # Add profiles to list view - list_item = QListWidgetItem(QIcon("../icon.png"), name) - if name == active_profile_name: - font = list_item.font() - font.setItalic(True) - list_item.setFont(font) - self.dlg.list_profiles.addItem(list_item) + active_profile_name = Path(QgsApplication.qgisSettingsDirPath()).name self.dlg.comboBoxNamesSource.setCurrentText(active_profile_name) @@ -224,7 +195,7 @@ def conditionally_enable_profile_buttons(self): Called when profile selection changes in the Profiles tab. """ # A profile must be selected - if self.dlg.list_profiles.currentItem() is None: + if self.dlg.get_list_selection_profile_name() is None: self.dlg.removeProfileButton.setToolTip( self.tr("Please choose a profile to remove") ) @@ -239,7 +210,7 @@ def conditionally_enable_profile_buttons(self): self.dlg.copyProfileButton.setEnabled(False) # Some actions can/should not be done on the currently active profile elif ( - self.dlg.list_profiles.currentItem().text() + self.dlg.get_list_selection_profile_name() == Path(QgsApplication.qgisSettingsDirPath()).name ): self.dlg.removeProfileButton.setToolTip( diff --git a/profile_manager/profile_manager_dialog_base.ui b/profile_manager/profile_manager_dialog_base.ui index 903e5cd..3d15bdd 100644 --- a/profile_manager/profile_manager_dialog_base.ui +++ b/profile_manager/profile_manager_dialog_base.ui @@ -97,7 +97,7 @@ - + diff --git a/profile_manager/profiles/profile_copier.py b/profile_manager/profiles/profile_copier.py index d1429b7..b5e037c 100644 --- a/profile_manager/profiles/profile_copier.py +++ b/profile_manager/profiles/profile_copier.py @@ -15,9 +15,9 @@ def __init__(self, profile_manager_dialog, qgis_path, *args, **kwargs): self.qgis_path = qgis_path def copy_profile(self): - source_profile = self.dlg.list_profiles.currentItem() + source_profile = self.dlg.get_list_selection_profile_name() assert source_profile is not None # should be forced by the GUI - source_profile_path = self.qgis_path + "/" + source_profile.text() + "/" + source_profile_path = self.qgis_path + "/" + source_profile + "/" dialog = NameProfileDialog() return_code = dialog.exec() diff --git a/profile_manager/profiles/profile_editor.py b/profile_manager/profiles/profile_editor.py index 8ffb2d9..1bbe061 100644 --- a/profile_manager/profiles/profile_editor.py +++ b/profile_manager/profiles/profile_editor.py @@ -21,7 +21,7 @@ def __init__( def edit_profile(self): """Renames profile with user input""" - old_profile_name = self.dlg.list_profiles.currentItem().text() + old_profile_name = self.dlg.get_list_selection_profile_name() # bad states that should be prevented by the GUI assert old_profile_name is not None assert old_profile_name != Path(QgsApplication.qgisSettingsDirPath()).name diff --git a/profile_manager/profiles/profile_remover.py b/profile_manager/profiles/profile_remover.py index a2bb5f8..eec47f1 100644 --- a/profile_manager/profiles/profile_remover.py +++ b/profile_manager/profiles/profile_remover.py @@ -23,12 +23,11 @@ def remove_profile(self): Aborts and shows an error message if no backup could be made. """ - profile_item = self.dlg.list_profiles.currentItem() + profile_name = self.dlg.get_list_selection_profile_name() # bad states that should be prevented by the GUI - assert profile_item is not None - assert profile_item.text() != Path(QgsApplication.qgisSettingsDirPath()).name + assert profile_name is not None + assert profile_name != Path(QgsApplication.qgisSettingsDirPath()).name - profile_name = profile_item.text() profile_path = adjust_to_operating_system(self.qgis_path + "/" + profile_name) clicked_button = QMessageBox.question( From e907c6e5c9a97c2c4ebd065c292fe2b4d28eebe6 Mon Sep 17 00:00:00 2001 From: jmkerloch Date: Thu, 12 Sep 2024 15:13:34 +0200 Subject: [PATCH 09/17] feat(qdt export): read QDT profile info from profile.json file --- profile_manager/profile_manager_dialog.py | 54 +++++++++++++++++++---- qdt_export/profile_export.py | 20 +++++++++ 2 files changed, 65 insertions(+), 9 deletions(-) diff --git a/profile_manager/profile_manager_dialog.py b/profile_manager/profile_manager_dialog.py index 89d93cc..15a97e4 100644 --- a/profile_manager/profile_manager_dialog.py +++ b/profile_manager/profile_manager_dialog.py @@ -27,7 +27,7 @@ from qgis.PyQt import QtWidgets, uic from .userInterface.mdl_profiles import ProfileListModel -from .qdt_export.profile_export import export_profile_for_qdt, QDTProfileInfos +from .qdt_export.profile_export import export_profile_for_qdt, get_qdt_profile_infos_from_file, QDTProfileInfos # This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer FORM_CLASS, _ = uic.loadUiType( @@ -49,6 +49,8 @@ def __init__(self, parent=None): self.profile_mdl = ProfileListModel(self) self.qdt_export_profile_cbx.setModel(self.profile_mdl) self.export_qdt_button.clicked.connect(self.export_qdt_handler) + self.export_qdt_button.setEnabled(False) + self.qdt_file_widget.fileChanged.connect(self._qdt_export_dir_changed) self.comboBoxNamesSource.setModel(self.profile_mdl) self.comboBoxNamesTarget.setModel(self.profile_mdl) @@ -64,23 +66,57 @@ def get_list_selection_profile_name(self) -> Optional[str]: if index.isValid(): return self.list_profiles.model().data(index, ProfileListModel.NAME_COL) return None + + def _qdt_export_dir_changed(self) -> None: + """Update UI when QDT export dir is changed: + - enabled/disable button + - define QDTProfileInformations if profile.json file is available + """ + export_dir = self.qdt_file_widget.filePath() + if export_dir: + self.export_qdt_button.setEnabled(True) + profile_json = Path(export_dir) / "profile.json" + if profile_json.exists(): + self._set_qdt_profile_infos(get_qdt_profile_infos_from_file(profile_json)) + else: + self.export_qdt_button.setEnabled(False) - def export_qdt_handler(self): - """Export selected profile as QDT profile""" - profile_path = self.qdt_file_widget.filePath() - if profile_path: - source_profile_name = self.qdt_export_profile_cbx.currentText() - qdt_profile_infos = QDTProfileInfos( + + def _get_qdt_profile_infos(self) -> QDTProfileInfos: + """Get QDTProfileInfos from UI + + Returns: + QDTProfileInfos: QDT Profile Information + """ + return QDTProfileInfos( description=self.qdt_description_edit.toPlainText(), email=self.qdt_email_edit.text(), version=self.qdt_version_edit.text(), qgis_min_version=self.qdt_qgis_min_version_edit.text(), qgis_max_version=self.qdt_qgis_max_version_edit.text(), - ) + ) + + def _set_qdt_profile_infos(self, qdt_profile_infos : QDTProfileInfos) -> None: + """Set QDTProfileInfos in UI + + Args: + qdt_profile_infos (QDTProfileInfos): QDT Profile Information + """ + self.qdt_description_edit.setPlainText(qdt_profile_infos.description) + self.qdt_email_edit.setText(qdt_profile_infos.email) + self.qdt_version_edit.setText(qdt_profile_infos.version) + self.qdt_qgis_min_version_edit.setText(qdt_profile_infos.qgis_min_version) + self.qdt_qgis_max_version_edit.setText(qdt_profile_infos.qgis_max_version) + + def export_qdt_handler(self) -> None: + """Export selected profile as QDT profile""" + profile_path = self.qdt_file_widget.filePath() + if profile_path: + source_profile_name = self.qdt_export_profile_cbx.currentText() export_profile_for_qdt( profile_name=source_profile_name, export_path=Path(profile_path), - qdt_profile_infos=qdt_profile_infos, + qdt_profile_infos=self._get_qdt_profile_infos(), clear_export_path=self.qdt_clear_export_folder_checkbox.isChecked(), export_inactive_plugin=self.qdt_inactive_plugin_export_checkbox.isChecked(), ) diff --git a/qdt_export/profile_export.py b/qdt_export/profile_export.py index 3e10e54..691383e 100644 --- a/qdt_export/profile_export.py +++ b/qdt_export/profile_export.py @@ -28,6 +28,26 @@ class QDTProfileInfos: qgis_max_version: str = "" +def get_qdt_profile_infos_from_file(profile_file : Path) -> QDTProfileInfos: + """Get QDT Profile informations from a profile.json file + File must exists + + Args: + profile_file (Path): profile.json path + + Returns: + QDTProfileInfos: QDT Profile informations + """ + with open(profile_file, 'r') as f: + qdt_profile_data = json.load(f) + return QDTProfileInfos( + description=qdt_profile_data.get("description", ""), + email=qdt_profile_data.get("email", ""), + version=qdt_profile_data.get("version", ""), + qgis_min_version=qdt_profile_data.get("qgisMinimumVersion", ""), + qgis_max_version=qdt_profile_data.get("qgisMaximumVersion", ""), + ) + def qdt_profile_dict( profile_name: str, qdt_profile_infos: QDTProfileInfos, From 8bf505a27ad1b477df738151b5767d6b46495793 Mon Sep 17 00:00:00 2001 From: jmkerloch Date: Thu, 12 Sep 2024 15:33:18 +0200 Subject: [PATCH 10/17] feat(qdt export): display messagebox after export --- profile_manager/profile_manager_dialog.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/profile_manager/profile_manager_dialog.py b/profile_manager/profile_manager_dialog.py index 15a97e4..145bf35 100644 --- a/profile_manager/profile_manager_dialog.py +++ b/profile_manager/profile_manager_dialog.py @@ -26,6 +26,7 @@ from typing import Optional from qgis.PyQt import QtWidgets, uic +from qgis.PyQt.QtWidgets import QMessageBox from .userInterface.mdl_profiles import ProfileListModel from .qdt_export.profile_export import export_profile_for_qdt, get_qdt_profile_infos_from_file, QDTProfileInfos @@ -120,3 +121,12 @@ def export_qdt_handler(self) -> None: clear_export_path=self.qdt_clear_export_folder_checkbox.isChecked(), export_inactive_plugin=self.qdt_inactive_plugin_export_checkbox.isChecked(), ) + QMessageBox.information( + self, + self.tr("QDT profile export"), + self.tr( + "QDT profile have been successfully exported." + ), + ) + + From e701fffb62dbf956a627de24a230b1ad953e6b37 Mon Sep 17 00:00:00 2001 From: jmkerloch Date: Thu, 12 Sep 2024 15:34:07 +0200 Subject: [PATCH 11/17] feat(profiles): support qgis run in other profiles path than default - closes Profile Manager does not respect --profiles-path start up option #5 --- profile_manager/profile_manager.py | 65 ++------------------- profile_manager/profiles/profile_creator.py | 7 +-- profile_manager/profiles/utils.py | 23 +------- 3 files changed, 9 insertions(+), 86 deletions(-) diff --git a/profile_manager/profile_manager.py b/profile_manager/profile_manager.py index d4f4892..2898112 100644 --- a/profile_manager/profile_manager.py +++ b/profile_manager/profile_manager.py @@ -50,6 +50,7 @@ from profile_manager.profile_manager_dialog import ProfileManagerDialog from profile_manager.profiles.profile_action_handler import ProfileActionHandler from profile_manager.utils import adjust_to_operating_system, wait_cursor +from profile_manager.profiles.utils import qgis_profiles_path, get_profile_qgis_ini_path class ProfileManager: @@ -251,38 +252,7 @@ def run(self): def set_paths(self): """Sets various OS and profile dependent paths""" - home_path = Path.home() - if platform.startswith("win32"): - self.qgis_profiles_path = ( - f"{home_path}/AppData/Roaming/QGIS/QGIS3/profiles".replace("\\", "/") - ) - self.ini_path = ( - self.qgis_profiles_path - + "/" - + self.dlg.comboBoxNamesSource.currentText() - + "/QGIS/QGIS3.ini" - ) - self.operating_system = "windows" - elif platform == "darwin": - self.qgis_profiles_path = ( - f"{home_path}/Library/Application Support/QGIS/QGIS3/profiles" - ) - self.ini_path = ( - self.qgis_profiles_path - + "/" - + self.dlg.comboBoxNamesSource.currentText() - + "/qgis.org/QGIS3.ini" - ) - self.operating_system = "mac" - else: - self.qgis_profiles_path = f"{home_path}/.local/share/QGIS/QGIS3/profiles" - self.ini_path = ( - self.qgis_profiles_path - + "/" - + self.dlg.comboBoxNamesSource.currentText() - + "/QGIS/QGIS3.ini" - ) - self.operating_system = "unix" + self.qgis_profiles_path = str(qgis_profiles_path()) self.backup_path = adjust_to_operating_system( str(Path.home()) + "/QGIS Profile Manager Backup/" @@ -487,36 +457,9 @@ def get_profile_paths(self) -> tuple[str, str]: def get_ini_paths(self): """Gets path to current chosen source and target qgis.ini file""" - if self.operating_system == "mac": - ini_path_source = adjust_to_operating_system( - self.qgis_profiles_path - + "/" - + self.dlg.comboBoxNamesSource.currentText() - + "/qgis.org/QGIS3.ini" - ) - ini_path_target = adjust_to_operating_system( - self.qgis_profiles_path - + "/" - + self.dlg.comboBoxNamesTarget.currentText() - + "/qgis.org/QGIS3.ini" - ) - else: - ini_path_source = adjust_to_operating_system( - self.qgis_profiles_path - + "/" - + self.dlg.comboBoxNamesSource.currentText() - + "/QGIS/QGIS3.ini" - ) - ini_path_target = adjust_to_operating_system( - self.qgis_profiles_path - + "/" - + self.dlg.comboBoxNamesTarget.currentText() - + "/QGIS/QGIS3.ini" - ) - ini_paths = { - "source": ini_path_source, - "target": ini_path_target, + "source": str(get_profile_qgis_ini_path(self.dlg.comboBoxNamesSource.currentText())), + "target": str(get_profile_qgis_ini_path(self.dlg.comboBoxNamesTarget.currentText())), } return ini_paths diff --git a/profile_manager/profiles/profile_creator.py b/profile_manager/profiles/profile_creator.py index 5abc03f..e1a53f6 100644 --- a/profile_manager/profiles/profile_creator.py +++ b/profile_manager/profiles/profile_creator.py @@ -1,4 +1,5 @@ from os import mkdir +from sys import platform from qgis.core import QgsUserProfileManager from qgis.PyQt.QtWidgets import QDialog, QMessageBox @@ -27,10 +28,8 @@ def create_new_profile(self): assert profile_name != "" # should be forced by the GUI self.qgs_profile_manager.createUserProfile(profile_name) try: - if self.profile_manager.operating_system == "mac": - profile_path = ( - self.qgis_path + "/" + profile_name + "/qgis.org/" - ) + if platform is 'darwin': + profile_path = self.qgis_path + "/" + profile_name + "/qgis.org/" else: profile_path = self.qgis_path + "/" + profile_name + "/QGIS/" diff --git a/profile_manager/profiles/utils.py b/profile_manager/profiles/utils.py index 62a13c0..fc8bef2 100644 --- a/profile_manager/profiles/utils.py +++ b/profile_manager/profiles/utils.py @@ -12,31 +12,12 @@ def qgis_profiles_path() -> Path: - """Get QGIS profiles paths from current platforms - - - Windows : $HOME / "AppData" / "Roaming" / "QGIS" / "QGIS3" / "profiles" - - MacOS : $HOME / "Library" / "Application Support" / "QGIS" / "QGIS3" / "profiles" - - Linux : $HOME / ".local" / "share" / "QGIS" / "QGIS3" / "profiles" + """Get QGIS profiles paths from current QGIS application Returns: Path: QGIS profiles path """ - home_path = Path.home() - # Windows - if platform.startswith("win32"): - return home_path / "AppData" / "Roaming" / "QGIS" / "QGIS3" / "profiles" - # MacOS - if platform == "darwin": - return ( - home_path - / "Library" - / "Application Support" - / "QGIS" - / "QGIS3" - / "profiles" - ) - # Linux - return home_path / ".local" / "share" / "QGIS" / "QGIS3" / "profiles" + return Path(iface.userProfileManager().rootLocation()) def get_profile_qgis_ini_path(profile_name: str) -> Path: From 8a7bf5beda90dfdee112c9bf6f36372c18caac6d Mon Sep 17 00:00:00 2001 From: jmkerloch Date: Thu, 12 Sep 2024 16:48:15 +0200 Subject: [PATCH 12/17] fix(profile model): use name and not alias because we use the value to get the folder --- profile_manager/gui/mdl_profiles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/profile_manager/gui/mdl_profiles.py b/profile_manager/gui/mdl_profiles.py index f5667d6..a7c6eff 100644 --- a/profile_manager/gui/mdl_profiles.py +++ b/profile_manager/gui/mdl_profiles.py @@ -60,7 +60,7 @@ def insert_profile(self, profile_name: str) -> None: if profile: row = self.rowCount() self.insertRow(row) - self.setData(self.index(row, self.NAME_COL), profile.alias()) + self.setData(self.index(row, self.NAME_COL), profile.name()) self.setData( self.index(row, self.NAME_COL), profile.icon(), Qt.DecorationRole ) From e9c381fbc94f89925381db9d9e39b0df90a2e4f8 Mon Sep 17 00:00:00 2001 From: jmkerloch Date: Tue, 1 Oct 2024 18:34:28 +0200 Subject: [PATCH 13/17] feat(qdt export): fix rebase of main --- profile_manager/gui/interface_handler.py | 2 +- profile_manager/gui/mdl_profiles.py | 2 +- profile_manager/profile_manager_dialog.py | 8 ++++++-- .../qdt_export}/profile_export.py | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) rename {qdt_export => profile_manager/qdt_export}/profile_export.py (98%) diff --git a/profile_manager/gui/interface_handler.py b/profile_manager/gui/interface_handler.py index 1b03f29..9ca2ade 100644 --- a/profile_manager/gui/interface_handler.py +++ b/profile_manager/gui/interface_handler.py @@ -122,7 +122,7 @@ def setup_connections(self): self.dlg.comboBoxNamesTarget.currentIndexChanged.connect( self.conditionally_enable_import_button ) - self.dlg.list_profiles.currentItemChanged.connect( + self.dlg.list_profiles.selectionModel().selectionChanged.connect( self.conditionally_enable_profile_buttons ) diff --git a/profile_manager/gui/mdl_profiles.py b/profile_manager/gui/mdl_profiles.py index a7c6eff..fd09fda 100644 --- a/profile_manager/gui/mdl_profiles.py +++ b/profile_manager/gui/mdl_profiles.py @@ -3,7 +3,7 @@ from qgis.PyQt.QtGui import QStandardItemModel from qgis.core import QgsUserProfileManager, QgsUserProfile, QgsApplication -from ..profiles.utils import qgis_profiles_path +from profile_manager.profiles.utils import qgis_profiles_path class ProfileListModel(QStandardItemModel): diff --git a/profile_manager/profile_manager_dialog.py b/profile_manager/profile_manager_dialog.py index 145bf35..317ca1d 100644 --- a/profile_manager/profile_manager_dialog.py +++ b/profile_manager/profile_manager_dialog.py @@ -21,14 +21,18 @@ ***************************************************************************/ """ +# standard import os from pathlib import Path from typing import Optional +# pyQGIS from qgis.PyQt import QtWidgets, uic from qgis.PyQt.QtWidgets import QMessageBox -from .userInterface.mdl_profiles import ProfileListModel -from .qdt_export.profile_export import export_profile_for_qdt, get_qdt_profile_infos_from_file, QDTProfileInfos + +# plugin +from profile_manager.gui.mdl_profiles import ProfileListModel +from profile_manager.qdt_export.profile_export import export_profile_for_qdt, get_qdt_profile_infos_from_file, QDTProfileInfos # This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer FORM_CLASS, _ = uic.loadUiType( diff --git a/qdt_export/profile_export.py b/profile_manager/qdt_export/profile_export.py similarity index 98% rename from qdt_export/profile_export.py rename to profile_manager/qdt_export/profile_export.py index 691383e..74c1f02 100644 --- a/qdt_export/profile_export.py +++ b/profile_manager/qdt_export/profile_export.py @@ -8,7 +8,7 @@ import pyplugin_installer -from ..profiles.utils import ( +from profile_manager.profiles.utils import ( qgis_profiles_path, get_profile_plugin_list_information, ) From a42e122af3a0840bde4f6235d60ccc7fcc8f8d91 Mon Sep 17 00:00:00 2001 From: jmkerloch Date: Wed, 2 Oct 2024 09:35:34 +0200 Subject: [PATCH 14/17] feat(qdt export): add new files in .pro for translation --- profile_manager/resources/i18n/plugin_translation.pro | 3 +++ 1 file changed, 3 insertions(+) diff --git a/profile_manager/resources/i18n/plugin_translation.pro b/profile_manager/resources/i18n/plugin_translation.pro index 944de25..a72c906 100644 --- a/profile_manager/resources/i18n/plugin_translation.pro +++ b/profile_manager/resources/i18n/plugin_translation.pro @@ -7,10 +7,13 @@ SOURCES = \ ../../profiles/profile_remover.py \ ../../profiles/profile_creator.py \ ../../profiles/profile_action_handler.py \ + ../../profiles/utils.py \ ../../profile_manager_dialog.py \ ../../gui/interface_handler.py \ ../../gui/name_profile_dialog.py \ + ../../gui/mdl_profiles.py \ ../../profile_manager.py \ + ../../qdt_export/profile_export.py \ ../../datasources/functions/function_handler.py \ ../../datasources/dataservices/datasource_distributor.py \ ../../datasources/dataservices/datasource_provider.py \ From 016484e7dccfb49c4acdb7b56357c2f54de2314a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 08:07:16 +0000 Subject: [PATCH 15/17] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- profile_manager/gui/interface_handler.py | 5 ++- profile_manager/gui/mdl_profiles.py | 5 +-- profile_manager/profile_manager.py | 13 +++++--- profile_manager/profile_manager_dialog.py | 35 ++++++++++---------- profile_manager/profiles/profile_creator.py | 6 ++-- profile_manager/profiles/utils.py | 8 ++--- profile_manager/qdt_export/profile_export.py | 13 ++++---- 7 files changed, 44 insertions(+), 41 deletions(-) diff --git a/profile_manager/gui/interface_handler.py b/profile_manager/gui/interface_handler.py index 9ca2ade..934be6d 100644 --- a/profile_manager/gui/interface_handler.py +++ b/profile_manager/gui/interface_handler.py @@ -1,9 +1,8 @@ from pathlib import Path from qgis.core import Qgis, QgsApplication, QgsMessageLog -from qgis.PyQt.QtCore import Qt, QVariant -from qgis.PyQt.QtGui import QIcon -from qgis.PyQt.QtWidgets import QDialog, QListWidgetItem +from qgis.PyQt.QtCore import Qt +from qgis.PyQt.QtWidgets import QDialog from profile_manager.datasources.dataservices.datasource_provider import ( DATA_SOURCE_SEARCH_LOCATIONS, diff --git a/profile_manager/gui/mdl_profiles.py b/profile_manager/gui/mdl_profiles.py index fd09fda..13a43ea 100644 --- a/profile_manager/gui/mdl_profiles.py +++ b/profile_manager/gui/mdl_profiles.py @@ -1,7 +1,8 @@ from pathlib import Path -from qgis.PyQt.QtCore import QObject, Qt, QModelIndex + +from qgis.core import QgsApplication, QgsUserProfile, QgsUserProfileManager +from qgis.PyQt.QtCore import QModelIndex, QObject, Qt from qgis.PyQt.QtGui import QStandardItemModel -from qgis.core import QgsUserProfileManager, QgsUserProfile, QgsApplication from profile_manager.profiles.utils import qgis_profiles_path diff --git a/profile_manager/profile_manager.py b/profile_manager/profile_manager.py index 2898112..f461a29 100644 --- a/profile_manager/profile_manager.py +++ b/profile_manager/profile_manager.py @@ -27,7 +27,6 @@ from os import path from pathlib import Path from shutil import copytree -from sys import platform # PyQGIS from qgis.core import Qgis, QgsMessageLog, QgsUserProfileManager @@ -49,8 +48,8 @@ from profile_manager.gui.interface_handler import InterfaceHandler from profile_manager.profile_manager_dialog import ProfileManagerDialog from profile_manager.profiles.profile_action_handler import ProfileActionHandler +from profile_manager.profiles.utils import get_profile_qgis_ini_path, qgis_profiles_path from profile_manager.utils import adjust_to_operating_system, wait_cursor -from profile_manager.profiles.utils import qgis_profiles_path, get_profile_qgis_ini_path class ProfileManager: @@ -252,7 +251,7 @@ def run(self): def set_paths(self): """Sets various OS and profile dependent paths""" - self.qgis_profiles_path = str(qgis_profiles_path()) + self.qgis_profiles_path = str(qgis_profiles_path()) self.backup_path = adjust_to_operating_system( str(Path.home()) + "/QGIS Profile Manager Backup/" @@ -458,8 +457,12 @@ def get_profile_paths(self) -> tuple[str, str]: def get_ini_paths(self): """Gets path to current chosen source and target qgis.ini file""" ini_paths = { - "source": str(get_profile_qgis_ini_path(self.dlg.comboBoxNamesSource.currentText())), - "target": str(get_profile_qgis_ini_path(self.dlg.comboBoxNamesTarget.currentText())), + "source": str( + get_profile_qgis_ini_path(self.dlg.comboBoxNamesSource.currentText()) + ), + "target": str( + get_profile_qgis_ini_path(self.dlg.comboBoxNamesTarget.currentText()) + ), } return ini_paths diff --git a/profile_manager/profile_manager_dialog.py b/profile_manager/profile_manager_dialog.py index 317ca1d..afc68ae 100644 --- a/profile_manager/profile_manager_dialog.py +++ b/profile_manager/profile_manager_dialog.py @@ -32,7 +32,11 @@ # plugin from profile_manager.gui.mdl_profiles import ProfileListModel -from profile_manager.qdt_export.profile_export import export_profile_for_qdt, get_qdt_profile_infos_from_file, QDTProfileInfos +from profile_manager.qdt_export.profile_export import ( + QDTProfileInfos, + export_profile_for_qdt, + get_qdt_profile_infos_from_file, +) # This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer FORM_CLASS, _ = uic.loadUiType( @@ -71,7 +75,7 @@ def get_list_selection_profile_name(self) -> Optional[str]: if index.isValid(): return self.list_profiles.model().data(index, ProfileListModel.NAME_COL) return None - + def _qdt_export_dir_changed(self) -> None: """Update UI when QDT export dir is changed: - enabled/disable button @@ -82,11 +86,12 @@ def _qdt_export_dir_changed(self) -> None: self.export_qdt_button.setEnabled(True) profile_json = Path(export_dir) / "profile.json" if profile_json.exists(): - self._set_qdt_profile_infos(get_qdt_profile_infos_from_file(profile_json)) + self._set_qdt_profile_infos( + get_qdt_profile_infos_from_file(profile_json) + ) else: self.export_qdt_button.setEnabled(False) - def _get_qdt_profile_infos(self) -> QDTProfileInfos: """Get QDTProfileInfos from UI @@ -94,14 +99,14 @@ def _get_qdt_profile_infos(self) -> QDTProfileInfos: QDTProfileInfos: QDT Profile Information """ return QDTProfileInfos( - description=self.qdt_description_edit.toPlainText(), - email=self.qdt_email_edit.text(), - version=self.qdt_version_edit.text(), - qgis_min_version=self.qdt_qgis_min_version_edit.text(), - qgis_max_version=self.qdt_qgis_max_version_edit.text(), - ) - - def _set_qdt_profile_infos(self, qdt_profile_infos : QDTProfileInfos) -> None: + description=self.qdt_description_edit.toPlainText(), + email=self.qdt_email_edit.text(), + version=self.qdt_version_edit.text(), + qgis_min_version=self.qdt_qgis_min_version_edit.text(), + qgis_max_version=self.qdt_qgis_max_version_edit.text(), + ) + + def _set_qdt_profile_infos(self, qdt_profile_infos: QDTProfileInfos) -> None: """Set QDTProfileInfos in UI Args: @@ -128,9 +133,5 @@ def export_qdt_handler(self) -> None: QMessageBox.information( self, self.tr("QDT profile export"), - self.tr( - "QDT profile have been successfully exported." - ), + self.tr("QDT profile have been successfully exported."), ) - - diff --git a/profile_manager/profiles/profile_creator.py b/profile_manager/profiles/profile_creator.py index e1a53f6..5dfa3e6 100644 --- a/profile_manager/profiles/profile_creator.py +++ b/profile_manager/profiles/profile_creator.py @@ -28,8 +28,10 @@ def create_new_profile(self): assert profile_name != "" # should be forced by the GUI self.qgs_profile_manager.createUserProfile(profile_name) try: - if platform is 'darwin': - profile_path = self.qgis_path + "/" + profile_name + "/qgis.org/" + if platform == "darwin": + profile_path = ( + self.qgis_path + "/" + profile_name + "/qgis.org/" + ) else: profile_path = self.qgis_path + "/" + profile_name + "/QGIS/" diff --git a/profile_manager/profiles/utils.py b/profile_manager/profiles/utils.py index fc8bef2..f7928e5 100644 --- a/profile_manager/profiles/utils.py +++ b/profile_manager/profiles/utils.py @@ -1,15 +1,13 @@ +from configparser import NoSectionError, RawConfigParser from dataclasses import dataclass from pathlib import Path from sys import platform from typing import Any, Dict, List, Optional -from configparser import NoSectionError, RawConfigParser - +import pyplugin_installer from qgis.core import QgsUserProfileManager from qgis.utils import iface -import pyplugin_installer - def qgis_profiles_path() -> Path: """Get QGIS profiles paths from current QGIS application @@ -105,7 +103,7 @@ def get_installed_plugin_metadata( def get_plugin_info_from_qgis_manager( - plugin_slug_name: str, reload_manager : bool = False + plugin_slug_name: str, reload_manager: bool = False ) -> Optional[Dict[str, str]]: """Get plugin informations from QGIS plugin manager diff --git a/profile_manager/qdt_export/profile_export.py b/profile_manager/qdt_export/profile_export.py index 74c1f02..fc3f84c 100644 --- a/profile_manager/qdt_export/profile_export.py +++ b/profile_manager/qdt_export/profile_export.py @@ -1,19 +1,17 @@ -from dataclasses import dataclass import dataclasses +import json +from dataclasses import dataclass from pathlib import Path from shutil import copytree, rmtree from typing import Any, Dict -import json - import pyplugin_installer from profile_manager.profiles.utils import ( - qgis_profiles_path, get_profile_plugin_list_information, + qgis_profiles_path, ) - QDT_PROFILE_SCHEMA = "https://raw.githubusercontent.com/Guts/qgis-deployment-cli/main/docs/schemas/profile/qgis_profile.json" @@ -28,7 +26,7 @@ class QDTProfileInfos: qgis_max_version: str = "" -def get_qdt_profile_infos_from_file(profile_file : Path) -> QDTProfileInfos: +def get_qdt_profile_infos_from_file(profile_file: Path) -> QDTProfileInfos: """Get QDT Profile informations from a profile.json file File must exists @@ -38,7 +36,7 @@ def get_qdt_profile_infos_from_file(profile_file : Path) -> QDTProfileInfos: Returns: QDTProfileInfos: QDT Profile informations """ - with open(profile_file, 'r') as f: + with open(profile_file, "r") as f: qdt_profile_data = json.load(f) return QDTProfileInfos( description=qdt_profile_data.get("description", ""), @@ -48,6 +46,7 @@ def get_qdt_profile_infos_from_file(profile_file : Path) -> QDTProfileInfos: qgis_max_version=qdt_profile_data.get("qgisMaximumVersion", ""), ) + def qdt_profile_dict( profile_name: str, qdt_profile_infos: QDTProfileInfos, From 8ed5e34038651e5ffa8e2fd555e5612d409b4062 Mon Sep 17 00:00:00 2001 From: jmkerloch Date: Fri, 4 Oct 2024 11:54:46 +0200 Subject: [PATCH 16/17] feat(profile manager) export folder at first position --- .../profile_manager_dialog_base.ui | 123 +++++++++--------- 1 file changed, 65 insertions(+), 58 deletions(-) diff --git a/profile_manager/profile_manager_dialog_base.ui b/profile_manager/profile_manager_dialog_base.ui index 3d15bdd..0c8d7fc 100644 --- a/profile_manager/profile_manager_dialog_base.ui +++ b/profile_manager/profile_manager_dialog_base.ui @@ -6,8 +6,8 @@ 0 0 - 761 - 451 + 701 + 503
@@ -350,113 +350,120 @@ QDT Export - - - - Qt::Vertical - - - - 20 - 40 - + + + + + 0 + 0 + - - - - - Export + Profile - - + + - Email + QGIS min. version - - - - QgsFileWidget::GetDirectory + + + + Description - + - - + + - Export inactive plugins + Email - + - - - - + - - - - - 0 - 0 - - + + - Profile + Clear export folder - - - - Description + + + + + + + Qt::Vertical - + + + 20 + 40 + + + - + - Clear export folder + Export inactive plugins - + Version - - + + - QGIS min. version + Export - + QGIS max. version - - - - + + + + + + + + QgsFileWidget::GetDirectory + + + + + + + Export folder + + + From e024193c67d2a5310488f618175b08965a11a811 Mon Sep 17 00:00:00 2001 From: jmkerloch Date: Fri, 4 Oct 2024 11:56:46 +0200 Subject: [PATCH 17/17] feat(changelog): add QDT profile export --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3cca0c..9248df6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Unreleased ## 0.5.0-beta1 - 2024-10-04 +- add tab to export profile for QGIS Deployment Toolbelt - add modern plugin's packaging using QGIS Plugin CI - apply Python coding rules to whole codebase (PEP8) - remove dead code