diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bc91912..a0a2603 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: - --markdown-linebreak-ext=md - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.5.0" + rev: "v0.6.7" hooks: - id: ruff args: @@ -29,7 +29,7 @@ repos: - --target-version=py39 - repo: https://github.com/psf/black - rev: 24.4.2 + rev: 24.8.0 hooks: - id: black args: @@ -53,7 +53,7 @@ repos: - --filter-files - repo: https://github.com/pycqa/flake8 - rev: 7.1.0 + rev: 7.1.1 hooks: - id: flake8 additional_dependencies: ["flake8-qgis"] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6197ebf..d6830f7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,4 +9,3 @@ These are mostly guidelines, not rules. Use your best judgment, and feel free to We use git hooks through [pre-commit](https://pre-commit.com/) to enforce and automatically check some "rules". Please install them (`pre-commit install`) before to push any commit. See the relevant configuration file: `.pre-commit-config.yaml`. - diff --git a/profile_manager/__init__.py b/profile_manager/__init__.py index c653fbc..6d84408 100644 --- a/profile_manager/__init__.py +++ b/profile_manager/__init__.py @@ -32,4 +32,5 @@ def classFactory(iface): # pylint: disable=invalid-name """ # from .profile_manager import ProfileManager + return ProfileManager(iface) diff --git a/profile_manager/darkdetect/__init__.py b/profile_manager/darkdetect/__init__.py index 4cce200..b508452 100644 --- a/profile_manager/darkdetect/__init__.py +++ b/profile_manager/darkdetect/__init__.py @@ -1,22 +1,23 @@ -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Copyright (C) 2019 Alberto Sottile # # Distributed under the terms of the 3-clause BSD License. -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- -__version__ = '0.1.1' +__version__ = "0.1.1" -import sys import platform +import sys if sys.platform != "darwin": from ._dummy import * else: from distutils.version import LooseVersion as V + if V(platform.mac_ver()[0]) < V("10.14"): from ._dummy import * else: from ._detect import * del V -del sys, platform \ No newline at end of file +del sys, platform diff --git a/profile_manager/darkdetect/_detect.py b/profile_manager/darkdetect/_detect.py index 9a79f7c..cc6dbbf 100644 --- a/profile_manager/darkdetect/_detect.py +++ b/profile_manager/darkdetect/_detect.py @@ -1,14 +1,14 @@ -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Copyright (C) 2019 Alberto Sottile # # Distributed under the terms of the 3-clause BSD License. -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- import ctypes import ctypes.util -appkit = ctypes.cdll.LoadLibrary(ctypes.util.find_library('AppKit')) -objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('objc')) +appkit = ctypes.cdll.LoadLibrary(ctypes.util.find_library("AppKit")) +objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library("objc")) void_p = ctypes.c_void_p ull = ctypes.c_uint64 @@ -20,45 +20,51 @@ msg = objc.objc_msgSend + def _utf8(s): if not isinstance(s, bytes): - s = s.encode('utf8') + s = s.encode("utf8") return s + def n(name): return objc.sel_registerName(_utf8(name)) + def C(classname): return objc.objc_getClass(_utf8(classname)) + def theme(): - NSAutoreleasePool = objc.objc_getClass('NSAutoreleasePool') - pool = msg(NSAutoreleasePool, n('alloc')) - pool = msg(pool, n('init')) + NSAutoreleasePool = objc.objc_getClass("NSAutoreleasePool") + pool = msg(NSAutoreleasePool, n("alloc")) + pool = msg(pool, n("init")) - NSUserDefaults = C('NSUserDefaults') - stdUserDef = msg(NSUserDefaults, n('standardUserDefaults')) + NSUserDefaults = C("NSUserDefaults") + stdUserDef = msg(NSUserDefaults, n("standardUserDefaults")) - NSString = C('NSString') + NSString = C("NSString") - key = msg(NSString, n("stringWithUTF8String:"), _utf8('AppleInterfaceStyle')) - appearanceNS = msg(stdUserDef, n('stringForKey:'), void_p(key)) - appearanceC = msg(appearanceNS, n('UTF8String')) + key = msg(NSString, n("stringWithUTF8String:"), _utf8("AppleInterfaceStyle")) + appearanceNS = msg(stdUserDef, n("stringForKey:"), void_p(key)) + appearanceC = msg(appearanceNS, n("UTF8String")) if appearanceC is not None: out = ctypes.string_at(appearanceC) else: out = None - msg(pool, n('release')) + msg(pool, n("release")) if out is not None: - return out.decode('utf-8') + return out.decode("utf-8") else: - return 'Light' + return "Light" + def isDark(): - return theme() == 'Dark' + return theme() == "Dark" + def isLight(): - return theme() == 'Light' + return theme() == "Light" diff --git a/profile_manager/darkdetect/_dummy.py b/profile_manager/darkdetect/_dummy.py index 1e99668..f256724 100644 --- a/profile_manager/darkdetect/_dummy.py +++ b/profile_manager/darkdetect/_dummy.py @@ -1,14 +1,17 @@ -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Copyright (C) 2019 Alberto Sottile # # Distributed under the terms of the 3-clause BSD License. -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- + def theme(): return None - + + def isDark(): return None - + + def isLight(): return None diff --git a/profile_manager/datasources/Bookmarks/__init__.py b/profile_manager/datasources/Bookmarks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/profile_manager/datasources/Bookmarks/bookmark_handler.py b/profile_manager/datasources/Bookmarks/bookmark_handler.py index 30d1e65..7ffa314 100644 --- a/profile_manager/datasources/Bookmarks/bookmark_handler.py +++ b/profile_manager/datasources/Bookmarks/bookmark_handler.py @@ -1,6 +1,6 @@ -from lxml import etree as et from pathlib import Path +from lxml import etree as et from qgis.core import Qgis, QgsMessageLog @@ -26,22 +26,28 @@ def import_bookmarks(source_bookmark_file: str, target_bookmark_file: str): # get the element tree of the source file QgsMessageLog.logMessage("import_bookmarks", "Profile Manager", level=Qgis.Critical) try: - source_tree = et.parse(source_bookmark_file, et.XMLParser(remove_blank_text=True)) + source_tree = et.parse( + source_bookmark_file, et.XMLParser(remove_blank_text=True) + ) # check if target file exists create_bookmark_file_if_not_exist(target_bookmark_file) # get the element tree of the target file # fill if empty - target_tree = et.parse(target_bookmark_file, et.XMLParser(remove_blank_text=True)) + target_tree = et.parse( + target_bookmark_file, et.XMLParser(remove_blank_text=True) + ) # find all bookmark elements - source_root_tag = source_tree.findall('Bookmark') + source_root_tag = source_tree.findall("Bookmark") # get the root element "Bookmarks" target_tree_root = target_tree.getroot() # Remove duplicate entries to prevent piling data - target_tree_root = remove_duplicates(source_root_tag, target_tree, target_tree_root) + target_tree_root = remove_duplicates( + source_root_tag, target_tree, target_tree_root + ) # append the elements for element in source_root_tag: @@ -49,7 +55,10 @@ def import_bookmarks(source_bookmark_file: str, target_bookmark_file: str): # overwrite the xml file et.ElementTree(target_tree_root).write( - target_bookmark_file, pretty_print=True, encoding='utf-8', xml_declaration=True + target_bookmark_file, + pretty_print=True, + encoding="utf-8", + xml_declaration=True, ) except et.Error as e: # TODO: It would be nice to have a smaller and more specific try block but until then we except broadly @@ -57,12 +66,13 @@ def import_bookmarks(source_bookmark_file: str, target_bookmark_file: str): QgsMessageLog.logMessage(error, "Profile Manager", level=Qgis.Warning) return error + def remove_duplicates(source_root_tag, target_tree, target_tree_root): """Removes bookmarks from target that exist in the (to be imported) source too.""" # TODO FIXME this only checks the name of the bookmarks which will lead to false positives # it is ok and supported by QGIS to have the same name for multiple bookmarks # TODO compare the complete content of the xml node! - target_root_tag = target_tree.findall('Bookmark') + target_root_tag = target_tree.findall("Bookmark") for s_element in source_root_tag: for t_element in target_root_tag: if s_element.attrib["name"] == t_element.attrib["name"]: @@ -70,10 +80,10 @@ def remove_duplicates(source_root_tag, target_tree, target_tree_root): return target_tree_root + def create_bookmark_file_if_not_exist(bookmark_file): """Checks if file exists and creates it if not""" target_file = Path(bookmark_file) if not target_file.is_file(): with open(bookmark_file, "w") as new_file: new_file.write("") - diff --git a/profile_manager/datasources/Customizations/__init__.py b/profile_manager/datasources/Customizations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/profile_manager/datasources/Customizations/customization_handler.py b/profile_manager/datasources/Customizations/customization_handler.py index 163d8e6..36b689b 100644 --- a/profile_manager/datasources/Customizations/customization_handler.py +++ b/profile_manager/datasources/Customizations/customization_handler.py @@ -2,7 +2,7 @@ from os import path from shutil import copy2 -from ...utils import adjust_to_operating_system +from profile_manager.utils import adjust_to_operating_system def import_customizations(source_profile_path: str, target_profile_path: str): @@ -22,22 +22,30 @@ def import_customizations(source_profile_path: str, target_profile_path: str): TODO """ # Copy (overwrite) the QGISCUSTOMIZATION3.ini if exist - source_customini_path = adjust_to_operating_system(source_profile_path + "QGIS/QGISCUSTOMIZATION3.ini") - target_customini_path = adjust_to_operating_system(target_profile_path + "QGIS/QGISCUSTOMIZATION3.ini") + source_customini_path = adjust_to_operating_system( + source_profile_path + "QGIS/QGISCUSTOMIZATION3.ini" + ) + target_customini_path = adjust_to_operating_system( + target_profile_path + "QGIS/QGISCUSTOMIZATION3.ini" + ) if path.exists(source_customini_path): copy2(source_customini_path, target_customini_path) # Copy [UI] section from QGIS3.ini - source_qgis3ini_path = adjust_to_operating_system(source_profile_path + "QGIS/QGIS3.ini") - target_qgis3ini_path = adjust_to_operating_system(target_profile_path + "QGIS/QGIS3.ini") + source_qgis3ini_path = adjust_to_operating_system( + source_profile_path + "QGIS/QGIS3.ini" + ) + target_qgis3ini_path = adjust_to_operating_system( + target_profile_path + "QGIS/QGIS3.ini" + ) source_ini_parser = RawConfigParser() source_ini_parser.optionxform = str # str = case-sensitive option names source_ini_parser.read(source_qgis3ini_path) # TODO this is broken, right? It looks for [UI] but even in QGIS 3.10 (didnt check older) the (single) section is named [Customization] - if source_ini_parser.has_section('UI'): - ui_data = dict(source_ini_parser.items('UI')) + if source_ini_parser.has_section("UI"): + ui_data = dict(source_ini_parser.items("UI")) target_ini_parser = RawConfigParser() target_ini_parser.optionxform = str # str = case-sensitive option names @@ -49,6 +57,5 @@ def import_customizations(source_profile_path: str, target_profile_path: str): target_ini_parser.set("UI", setting, ui_data[setting]) - with open(target_qgis3ini_path, 'w') as qgisconf: + with open(target_qgis3ini_path, "w") as qgisconf: target_ini_parser.write(qgisconf, space_around_delimiters=False) - diff --git a/profile_manager/datasources/Dataservices/__init__.py b/profile_manager/datasources/Dataservices/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/profile_manager/datasources/Dataservices/datasource_distributor.py b/profile_manager/datasources/Dataservices/datasource_distributor.py index 077396a..2288de3 100644 --- a/profile_manager/datasources/Dataservices/datasource_distributor.py +++ b/profile_manager/datasources/Dataservices/datasource_distributor.py @@ -1,8 +1,7 @@ from configparser import RawConfigParser from urllib.parse import quote -from ...utils import adjust_to_operating_system - +from profile_manager.utils import adjust_to_operating_system KNOWN_WEB_SOURCES = [ "Vector-Tile", @@ -15,14 +14,18 @@ "GeoNode", ] # must match the names in data source provider's DATA_SOURCE_SEARCH_LOCATIONS. TODO re-use a single rules object! + def import_data_sources( - source_qgis_ini_file: str, - target_qgis_ini_file: str, - dictionary_of_checked_database_sources: dict, - dictionary_of_checked_web_sources: dict + source_qgis_ini_file: str, + target_qgis_ini_file: str, + dictionary_of_checked_database_sources: dict, + dictionary_of_checked_web_sources: dict, ): """Handles data source import""" - dictionary_of_checked_sources = {**dictionary_of_checked_database_sources, **dictionary_of_checked_web_sources} + dictionary_of_checked_sources = { + **dictionary_of_checked_database_sources, + **dictionary_of_checked_web_sources, + } if dictionary_of_checked_sources: source_qgis_ini_file = adjust_to_operating_system(source_qgis_ini_file) @@ -40,30 +43,42 @@ def import_data_sources( iterator = 0 if key in KNOWN_WEB_SOURCES: - if source_ini_parser.has_section('qgis'): + if source_ini_parser.has_section("qgis"): for element in range(len(dictionary_of_checked_web_sources[key])): import_web_sources( - source_ini_parser, target_ini_parser, dictionary_of_checked_web_sources, key, iterator + source_ini_parser, + target_ini_parser, + dictionary_of_checked_web_sources, + key, + iterator, ) iterator += 1 else: # seems to be a database source for element in range(len(dictionary_of_checked_database_sources[key])): import_db_sources( - source_ini_parser, target_ini_parser, dictionary_of_checked_database_sources, key, iterator + source_ini_parser, + target_ini_parser, + dictionary_of_checked_database_sources, + key, + iterator, ) iterator += 1 - with open(target_qgis_ini_file, 'w') as qgisconf: + with open(target_qgis_ini_file, "w") as qgisconf: target_ini_parser.write(qgisconf, space_around_delimiters=False) + def remove_data_sources( - qgis_ini_file: str, - dictionary_of_checked_database_sources: dict, - dictionary_of_checked_web_sources: dict + qgis_ini_file: str, + dictionary_of_checked_database_sources: dict, + dictionary_of_checked_web_sources: dict, ): """Handles data source removal from file""" - dictionary_of_checked_sources = {**dictionary_of_checked_database_sources, **dictionary_of_checked_web_sources} + dictionary_of_checked_sources = { + **dictionary_of_checked_database_sources, + **dictionary_of_checked_web_sources, + } qgis_ini_file = adjust_to_operating_system(qgis_ini_file) @@ -76,25 +91,30 @@ def remove_data_sources( iterator = 0 if key in KNOWN_WEB_SOURCES: - if parser.has_section('qgis'): + if parser.has_section("qgis"): for element in range(len(dictionary_of_checked_web_sources[key])): - remove_web_sources(parser, dictionary_of_checked_web_sources, key, iterator) + remove_web_sources( + parser, dictionary_of_checked_web_sources, key, iterator + ) iterator += 1 else: # seems to be a database source for element in range(len(dictionary_of_checked_database_sources[key])): - remove_db_sources(parser, dictionary_of_checked_database_sources, key, iterator) + remove_db_sources( + parser, dictionary_of_checked_database_sources, key, iterator + ) iterator += 1 - with open(qgis_ini_file, 'w') as qgisconf: + with open(qgis_ini_file, "w") as qgisconf: parser.write(qgisconf, space_around_delimiters=False) + def import_web_sources( - source_ini_parser: RawConfigParser, - target_ini_parser: RawConfigParser, - dictionary_of_checked_web_sources: dict, # TODO specify internal structure, TODO rename - key: str, - iterator: int, + source_ini_parser: RawConfigParser, + target_ini_parser: RawConfigParser, + dictionary_of_checked_web_sources: dict, # TODO specify internal structure, TODO rename + key: str, + iterator: int, ): """Imports web source strings to target file""" @@ -115,30 +135,37 @@ def import_web_sources( # filter to all entries matching the provider key (e. g. wms) to_be_imported_dictionary_sources = dict( # FIXME store the key to lookup separately to allow different GUI display vs technical implementation - filter(lambda item: str("connections-" + key.lower()) in item[0], to_be_imported_dictionary_sources.items()) + filter( + lambda item: str("connections-" + key.lower()) in item[0], + to_be_imported_dictionary_sources.items(), + ) ) # filter to all remaining entries matching the data source name to_be_imported_dictionary_sources = dict( filter( - lambda item: "\\" + quote( - dictionary_of_checked_web_sources[key][iterator].encode('latin-1') - ) + "\\" in item[0], - to_be_imported_dictionary_sources.items() + lambda item: "\\" + + quote(dictionary_of_checked_web_sources[key][iterator].encode("latin-1")) + + "\\" + in item[0], + to_be_imported_dictionary_sources.items(), ) ) for data_source in to_be_imported_dictionary_sources: if not target_ini_parser.has_section("qgis"): target_ini_parser["qgis"] = {} - target_ini_parser.set("qgis", data_source, to_be_imported_dictionary_sources[data_source]) + target_ini_parser.set( + "qgis", data_source, to_be_imported_dictionary_sources[data_source] + ) + def import_db_sources( - source_ini_parser: RawConfigParser, - target_ini_parser: RawConfigParser, - dictionary_of_checked_database_sources: dict, # TODO specify internal structure, TODO rename - key: str, - iterator: int, + source_ini_parser: RawConfigParser, + target_ini_parser: RawConfigParser, + dictionary_of_checked_database_sources: dict, # TODO specify internal structure, TODO rename + key: str, + iterator: int, ): """Imports data base strings to target file""" @@ -148,23 +175,29 @@ def import_db_sources( # filter to all remaining entries matching the data source name to_be_imported_dictionary_sources = dict( filter( - lambda item: "\\" + quote( - dictionary_of_checked_database_sources[key][iterator].encode('latin-1') - ) + "\\" in item[0], - to_be_imported_dictionary_sources.items() + lambda item: "\\" + + quote( + dictionary_of_checked_database_sources[key][iterator].encode("latin-1") + ) + + "\\" + in item[0], + to_be_imported_dictionary_sources.items(), ) ) for data_source in to_be_imported_dictionary_sources: if not target_ini_parser.has_section(key): target_ini_parser[key] = {} - target_ini_parser.set(key, data_source, to_be_imported_dictionary_sources[data_source]) + target_ini_parser.set( + key, data_source, to_be_imported_dictionary_sources[data_source] + ) + def remove_web_sources( - parser: RawConfigParser, - dictionary_of_checked_web_sources: dict, # TODO specify internal structure, TODO rename - key: str, - iterator: int, + parser: RawConfigParser, + dictionary_of_checked_web_sources: dict, # TODO specify internal structure, TODO rename + key: str, + iterator: int, ): """Removes web source strings from target file""" @@ -173,27 +206,32 @@ def remove_web_sources( # filter to all entries matching the provider key (e. g. wms) to_be_deleted_dictionary_sources = dict( - filter(lambda item: str("connections-" + key.lower()) in item[0], to_be_deleted_dictionary_sources.items()) + filter( + lambda item: str("connections-" + key.lower()) in item[0], + to_be_deleted_dictionary_sources.items(), + ) ) # filter to all remaining entries matching the data source name to_be_deleted_dictionary_sources = dict( filter( - lambda item: "\\" + quote( - dictionary_of_checked_web_sources[key][iterator].encode('latin-1') - ) + "\\" in item[0], - to_be_deleted_dictionary_sources.items() + lambda item: "\\" + + quote(dictionary_of_checked_web_sources[key][iterator].encode("latin-1")) + + "\\" + in item[0], + to_be_deleted_dictionary_sources.items(), ) ) for data_source in to_be_deleted_dictionary_sources: parser.remove_option("qgis", data_source) + def remove_db_sources( - parser: RawConfigParser, - dictionary_of_checked_database_sources: dict, # TODO specify internal structure, TODO rename - key: str, - iterator: int + parser: RawConfigParser, + dictionary_of_checked_database_sources: dict, # TODO specify internal structure, TODO rename + key: str, + iterator: int, ): """Remove data base sources from target file""" @@ -203,10 +241,13 @@ def remove_db_sources( # filter to all remaining entries matching the data source name to_be_deleted_dictionary_sources = dict( filter( - lambda item: "\\" + quote( - dictionary_of_checked_database_sources[key][iterator].encode('latin-1') - ) + "\\" in item[0], - to_be_deleted_dictionary_sources.items() + lambda item: "\\" + + quote( + dictionary_of_checked_database_sources[key][iterator].encode("latin-1") + ) + + "\\" + in item[0], + to_be_deleted_dictionary_sources.items(), ) ) diff --git a/profile_manager/datasources/Dataservices/datasource_handler.py b/profile_manager/datasources/Dataservices/datasource_handler.py index 0620855..197078e 100644 --- a/profile_manager/datasources/Dataservices/datasource_handler.py +++ b/profile_manager/datasources/Dataservices/datasource_handler.py @@ -1,14 +1,23 @@ -from PyQt5.QtWidgets import QMessageBox -from .datasource_distributor import import_data_sources, remove_data_sources -from ..Bookmarks.bookmark_handler import import_bookmarks -from ..Customizations.customization_handler import import_customizations -from ..Favourites.favourites_handler import import_favourites -from ..Functions.function_handler import import_expression_functions -from ..Models.model_handler import import_models -from ..Models.script_handler import import_scripts -from ..Plugins.plugin_handler import PluginHandler -from ..Styles.style_handler import import_styles -from ...utils import adjust_to_operating_system +from qgis.PyQt.QtWidgets import QMessageBox + +from profile_manager.datasources.Bookmarks.bookmark_handler import import_bookmarks +from profile_manager.datasources.Customizations.customization_handler import ( + import_customizations, +) +from profile_manager.datasources.Dataservices.datasource_distributor import ( + import_data_sources, + remove_data_sources, +) +from profile_manager.datasources.Favourites.favourites_handler import import_favourites +from profile_manager.datasources.Functions.function_handler import ( + import_expression_functions, +) +from profile_manager.datasources.Models.model_handler import import_models +from profile_manager.datasources.Models.script_handler import import_scripts +from profile_manager.datasources.Plugins.plugin_handler import PluginHandler +from profile_manager.datasources.Styles.style_handler import import_styles +from profile_manager.utils import adjust_to_operating_system + class DataSourceHandler: @@ -26,10 +35,14 @@ def __init__(self, profile_manager_dialog, profile_manager): self.target_bookmark_file = "" self.plugin_handler = PluginHandler(self.profile_manager) - def set_data_sources(self, dictionary_of_checked_web_sources, dictionary_of_checked_data_base_sources): + def set_data_sources( + self, dictionary_of_checked_web_sources, dictionary_of_checked_data_base_sources + ): """Sets data sources""" self.dictionary_of_checked_web_sources = dictionary_of_checked_web_sources - self.dictionary_of_checked_data_base_sources = dictionary_of_checked_data_base_sources + self.dictionary_of_checked_data_base_sources = ( + dictionary_of_checked_data_base_sources + ) def import_all_the_things(self): # TODO rename @@ -47,40 +60,64 @@ def import_all_the_things(self): self.dictionary_of_checked_data_base_sources, ) - self.profile_manager.update_data_sources(False) # TODO moved from DS_D.import_data_sources, do we really need it?! + self.profile_manager.update_data_sources( + False + ) # TODO moved from DS_D.import_data_sources, do we really need it?! if self.dlg.bookmark_check.isChecked(): - error_message = import_bookmarks(self.source_bookmark_file, self.target_bookmark_file) + error_message = import_bookmarks( + self.source_bookmark_file, self.target_bookmark_file + ) if error_message: had_errors = True - QMessageBox.critical(None, "Error while importing bookmarks", error_message) + QMessageBox.critical( + None, "Error while importing bookmarks", error_message + ) if self.dlg.favourites_check.isChecked(): - error_message = import_favourites(self.source_qgis_ini_file, self.target_qgis_ini_file) + error_message = import_favourites( + self.source_qgis_ini_file, self.target_qgis_ini_file + ) if error_message: had_errors = True - QMessageBox.critical(None, "Error while importing favourites", error_message) + QMessageBox.critical( + None, "Error while importing favourites", error_message + ) if self.dlg.models_check.isChecked(): - import_models(self.source_profile_path, self.target_profile_path) # currently has no error handling + import_models( + self.source_profile_path, self.target_profile_path + ) # currently has no error handling if self.dlg.scripts_check.isChecked(): - import_scripts(self.source_profile_path, self.target_profile_path) # currently has no error handling + import_scripts( + self.source_profile_path, self.target_profile_path + ) # currently has no error handling if self.dlg.styles_check.isChecked(): - error_message = import_styles(self.source_profile_path, self.target_profile_path) + error_message = import_styles( + self.source_profile_path, self.target_profile_path + ) if error_message: had_errors = True - QMessageBox.critical(None, "Error while importing styles", error_message) + QMessageBox.critical( + None, "Error while importing styles", error_message + ) if self.dlg.functions_check.isChecked(): - error_message = import_expression_functions(self.source_qgis_ini_file, self.target_qgis_ini_file) + error_message = import_expression_functions( + self.source_qgis_ini_file, self.target_qgis_ini_file + ) if error_message: had_errors = True - QMessageBox.critical(None, "Error while importing expression functions", error_message) + QMessageBox.critical( + None, "Error while importing expression functions", error_message + ) if self.dlg.ui_check.isChecked(): - import_customizations(self.source_profile_path, self.target_profile_path) # currently has no error handling + import_customizations( + self.source_profile_path, self.target_profile_path + ) # currently has no error handling # TODO why does data source import also import plugins again? self.plugin_handler.import_selected_plugins() @@ -93,7 +130,9 @@ def import_plugins(self): def display_plugins(self, only_for_target_profile=False): """Displays plugins in treeWidget""" self.plugin_handler.set_path_files() - self.plugin_handler.populate_plugins_list(only_for_target_profile=only_for_target_profile) + self.plugin_handler.populate_plugins_list( + only_for_target_profile=only_for_target_profile + ) def remove_datasources_and_plugins(self): """Handles data removal""" @@ -105,20 +144,28 @@ def remove_datasources_and_plugins(self): self.dictionary_of_checked_data_base_sources, ) - self.profile_manager.update_data_sources(False) # TODO moved from DS_D.remove_data_sources, do we really need it?! + self.profile_manager.update_data_sources( + False + ) # TODO moved from DS_D.remove_data_sources, do we really need it?! def set_path_to_files(self, source_profile_name, target_profile_name): """Sets file paths""" ini_paths = self.profile_manager.get_ini_paths() - self.source_qgis_ini_file = ini_paths['source'] - self.target_qgis_ini_file = ini_paths['target'] + self.source_qgis_ini_file = ini_paths["source"] + self.target_qgis_ini_file = ini_paths["target"] - self.source_profile_path = adjust_to_operating_system(self.qgis_path + '/' + source_profile_name + '/') - self.target_profile_path = adjust_to_operating_system(self.qgis_path + '/' + target_profile_name + '/') + self.source_profile_path = adjust_to_operating_system( + self.qgis_path + "/" + source_profile_name + "/" + ) + self.target_profile_path = adjust_to_operating_system( + self.qgis_path + "/" + target_profile_name + "/" + ) def set_path_to_bookmark_files(self, source_profile_name, target_profile_name): """Sets file paths""" self.source_bookmark_file = adjust_to_operating_system( - self.qgis_path + '/' + source_profile_name + '/' + 'bookmarks.xml') + self.qgis_path + "/" + source_profile_name + "/" + "bookmarks.xml" + ) self.target_bookmark_file = adjust_to_operating_system( - self.qgis_path + '/' + target_profile_name + '/' + 'bookmarks.xml') + self.qgis_path + "/" + target_profile_name + "/" + "bookmarks.xml" + ) diff --git a/profile_manager/datasources/Dataservices/datasource_provider.py b/profile_manager/datasources/Dataservices/datasource_provider.py index 15f1b5c..b0fb689 100644 --- a/profile_manager/datasources/Dataservices/datasource_provider.py +++ b/profile_manager/datasources/Dataservices/datasource_provider.py @@ -2,14 +2,13 @@ from re import compile, search from urllib.parse import unquote +from qgis.core import Qgis, QgsMessageLog from qgis.PyQt.QtCore import Qt from qgis.PyQt.QtWidgets import QTreeWidgetItem -from qgis.core import Qgis, QgsMessageLog - # TODO document these! can we directly integrate them below somewhere? -SERVICE_NAME_REGEX = compile(r'\\(.*?)\\') -GPKG_SERVICE_NAME_REGEX = compile(r'\\(.+).\\') +SERVICE_NAME_REGEX = compile(r"\\(.*?)\\") +GPKG_SERVICE_NAME_REGEX = compile(r"\\(.+).\\") """ "providername-ish": [ # a list of searching rules, not just one, because qgis changed between versions @@ -109,7 +108,9 @@ } -def get_data_sources_tree(ini_path: str, provider: str, make_checkable: bool) -> QTreeWidgetItem: +def get_data_sources_tree( + ini_path: str, provider: str, make_checkable: bool +) -> QTreeWidgetItem: """Returns a tree of checkable items for all data sources of the specified provider in the INI file. The tree contains a checkable item per data source found. @@ -124,16 +125,22 @@ def get_data_sources_tree(ini_path: str, provider: str, make_checkable: bool) -> """ data_source_connections = gather_data_source_connections(ini_path, provider) if not data_source_connections: - QgsMessageLog.logMessage(f"- 0 {provider} connections found", "Profile Manager", Qgis.Info) + QgsMessageLog.logMessage( + f"- 0 {provider} connections found", "Profile Manager", Qgis.Info + ) return None else: QgsMessageLog.logMessage( - f"- {len(data_source_connections)} {provider} connections found", "Profile Manager", Qgis.Info + f"- {len(data_source_connections)} {provider} connections found", + "Profile Manager", + Qgis.Info, ) tree_root_item = QTreeWidgetItem([provider]) if make_checkable: - tree_root_item.setFlags(tree_root_item.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable) + tree_root_item.setFlags( + tree_root_item.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable + ) data_source_items = [] for data_source_connection in data_source_connections: @@ -146,6 +153,7 @@ def get_data_sources_tree(ini_path: str, provider: str, make_checkable: bool) -> tree_root_item.addChildren(data_source_items) return tree_root_item + def gather_data_source_connections(ini_path: str, provider: str) -> list[str]: """Returns the names of all data source connections of the specified provider in the INI file. @@ -181,16 +189,24 @@ def gather_data_source_connections(ini_path: str, provider: str) -> list[str]: regex_pattern = compile(regex) for key in section: if regex_pattern.search(key): - if provider == "GeoPackage": # TODO move this logic/condition into the rules if possible? + if ( + provider == "GeoPackage" + ): # TODO move this logic/condition into the rules if possible? source_name_raw = search(GPKG_SERVICE_NAME_REGEX, key) - source_name = source_name_raw.group(0).replace("\\GPKG\\connections\\", "").replace("\\", "") + source_name = ( + source_name_raw.group(0) + .replace("\\GPKG\\connections\\", "") + .replace("\\", "") + ) else: source_name_raw = search(SERVICE_NAME_REGEX, key) source_name = source_name_raw.group(0).replace("\\", "") # TODO what are the replacements needed for?! # TODO "Bing VirtualEarth 💩" is not rendered well, also fails to import to other profile... - source_name = unquote(source_name, 'latin-1') # needed for e.g. %20 in connection names + source_name = unquote( + source_name, "latin-1" + ) # needed for e.g. %20 in connection names data_source_connections.append(source_name) return data_source_connections diff --git a/profile_manager/datasources/Favourites/__init__.py b/profile_manager/datasources/Favourites/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/profile_manager/datasources/Favourites/favourites_handler.py b/profile_manager/datasources/Favourites/favourites_handler.py index 4e348a7..eb26a7c 100644 --- a/profile_manager/datasources/Favourites/favourites_handler.py +++ b/profile_manager/datasources/Favourites/favourites_handler.py @@ -41,11 +41,15 @@ def import_favourites(source_qgis_ini_file, target_qgis_ini_file): elif target_ini_parser.has_option("browser", "favourites"): favourites_to_be_preserved = target_ini_parser.get("browser", "favourites") - import_string = favourites_to_be_imported["favourites"].replace(favourites_to_be_preserved, "") + import_string = favourites_to_be_imported["favourites"].replace( + favourites_to_be_preserved, "" + ) - target_ini_parser.set("browser", "favourites", favourites_to_be_preserved + import_string) + target_ini_parser.set( + "browser", "favourites", favourites_to_be_preserved + import_string + ) - with open(target_qgis_ini_file, 'w') as qgisconf: + with open(target_qgis_ini_file, "w") as qgisconf: target_ini_parser.write(qgisconf, space_around_delimiters=False) except Exception as e: # TODO: It would be nice to have a smaller and more specific try block but until then we except broadly diff --git a/profile_manager/datasources/Functions/__init__.py b/profile_manager/datasources/Functions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/profile_manager/datasources/Functions/function_handler.py b/profile_manager/datasources/Functions/function_handler.py index e6b8148..dced917 100644 --- a/profile_manager/datasources/Functions/function_handler.py +++ b/profile_manager/datasources/Functions/function_handler.py @@ -3,8 +3,6 @@ from qgis.core import Qgis, QgsMessageLog - - def import_expression_functions(source_qgis_ini_file: str, target_qgis_ini_file: str): """Imports custom expression functions from source to target profile. @@ -25,7 +23,9 @@ def import_expression_functions(source_qgis_ini_file: str, target_qgis_ini_file: Returns: error_message (str): An error message, if something failed. """ - QgsMessageLog.logMessage(f"Importing expression functions...", "Profile Manager", Qgis.Info) + QgsMessageLog.logMessage( + "Importing expression functions...", "Profile Manager", Qgis.Info + ) source_ini_parser = RawConfigParser() source_ini_parser.optionxform = str # str = case-sensitive option names @@ -44,9 +44,11 @@ def import_expression_functions(source_qgis_ini_file: str, target_qgis_ini_file: for entry in get_functions: if "expression" in entry or "helpText" in entry: target_ini_parser.set("expressions", entry, get_functions[entry]) - QgsMessageLog.logMessage(f"Found '{entry}'", "Profile Manager", Qgis.Info) + QgsMessageLog.logMessage( + f"Found '{entry}'", "Profile Manager", Qgis.Info + ) - with open(target_qgis_ini_file, 'w') as qgisconf: + with open(target_qgis_ini_file, "w") as qgisconf: target_ini_parser.write(qgisconf, space_around_delimiters=False) except Exception as e: # TODO: It would be nice to have a smaller and more specific try block but until then we except broadly diff --git a/profile_manager/datasources/Models/__init__.py b/profile_manager/datasources/Models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/profile_manager/datasources/Plugins/__init__.py b/profile_manager/datasources/Plugins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/profile_manager/datasources/Plugins/plugin_handler.py b/profile_manager/datasources/Plugins/plugin_handler.py index b883bf4..aa80d57 100644 --- a/profile_manager/datasources/Plugins/plugin_handler.py +++ b/profile_manager/datasources/Plugins/plugin_handler.py @@ -1,8 +1,8 @@ from qgis.PyQt.QtCore import Qt -from .plugin_displayer import PluginDisplayer -from .plugin_importer import import_plugins -from .plugin_remover import remove_plugins +from profile_manager.datasources.Plugins.plugin_displayer import PluginDisplayer +from profile_manager.datasources.Plugins.plugin_importer import import_plugins +from profile_manager.datasources.Plugins.plugin_remover import remove_plugins class PluginHandler: @@ -16,19 +16,32 @@ def __init__(self, profile_manager): def populate_plugins_list(self, only_for_target_profile=False): """Gets active plugins from ini file and displays them in treeWidget""" self.set_path_files() - self.plugin_displayer.set_ini_paths(self.source_qgis_ini_file, self.target_qgis_ini_file) - self.plugin_displayer.populate_plugins_list(only_populate_target_profile=only_for_target_profile) + self.plugin_displayer.set_ini_paths( + self.source_qgis_ini_file, self.target_qgis_ini_file + ) + self.plugin_displayer.populate_plugins_list( + only_populate_target_profile=only_for_target_profile + ) def import_selected_plugins(self): """Import selected plugins into target profile""" - source_profile_path, target_profile_path = self.profile_manager.get_profile_paths() + source_profile_path, target_profile_path = ( + self.profile_manager.get_profile_paths() + ) plugin_names = [] - for item in self.profile_manager.dlg.list_plugins.findItems("", Qt.MatchContains | Qt.MatchRecursive): + for item in self.profile_manager.dlg.list_plugins.findItems( + "", Qt.MatchContains | Qt.MatchRecursive + ): if item.checkState() == Qt.Checked: plugin_names.append(item.text()) - import_plugins(source_profile_path, target_profile_path, self.target_qgis_ini_file, plugin_names) + import_plugins( + source_profile_path, + target_profile_path, + self.target_qgis_ini_file, + plugin_names, + ) self.populate_plugins_list() def remove_selected_plugins(self): @@ -36,7 +49,9 @@ def remove_selected_plugins(self): source_profile_path, _ = self.profile_manager.get_profile_paths() plugin_names = [] - for item in self.profile_manager.dlg.list_plugins.findItems("", Qt.MatchContains | Qt.MatchRecursive): + for item in self.profile_manager.dlg.list_plugins.findItems( + "", Qt.MatchContains | Qt.MatchRecursive + ): if item.checkState() == Qt.Checked: plugin_names.append(item.text()) diff --git a/profile_manager/datasources/Plugins/plugin_importer.py b/profile_manager/datasources/Plugins/plugin_importer.py index 16e405b..fd1b2e1 100644 --- a/profile_manager/datasources/Plugins/plugin_importer.py +++ b/profile_manager/datasources/Plugins/plugin_importer.py @@ -3,14 +3,14 @@ from pathlib import Path from shutil import copytree -from ...utils import adjust_to_operating_system +from profile_manager.utils import adjust_to_operating_system def import_plugins( - source_profile_path: str, - target_profile_path: str, - target_qgis_ini_file: str, - plugin_names: list[str], + source_profile_path: str, + target_profile_path: str, + target_qgis_ini_file: str, + plugin_names: list[str], ): """Copies the specified plugins from source to target profile. @@ -42,16 +42,22 @@ def import_plugins( for plugin_name in plugin_names: ini_parser.set("PythonPlugins", plugin_name, "true") - source_plugin_dir = adjust_to_operating_system(source_profile_path + 'python/plugins/' + plugin_name + '/') - target_plugin_dir = adjust_to_operating_system(target_profile_path + 'python/plugins/' + plugin_name + '/') + source_plugin_dir = adjust_to_operating_system( + source_profile_path + "python/plugins/" + plugin_name + "/" + ) + target_plugin_dir = adjust_to_operating_system( + target_profile_path + "python/plugins/" + plugin_name + "/" + ) if path.exists(source_plugin_dir): - if not path.exists(target_profile_path + 'python/plugins/'): - Path(target_profile_path + 'python/plugins/').mkdir(parents=True, exist_ok=True) + if not path.exists(target_profile_path + "python/plugins/"): + Path(target_profile_path + "python/plugins/").mkdir( + parents=True, exist_ok=True + ) if not path.isdir(target_plugin_dir): copytree(source_plugin_dir, target_plugin_dir) else: continue # TODO error, dont skip silently! - with open(target_qgis_ini_file, 'w') as qgisconf: + with open(target_qgis_ini_file, "w") as qgisconf: ini_parser.write(qgisconf, space_around_delimiters=False) diff --git a/profile_manager/datasources/Plugins/plugin_remover.py b/profile_manager/datasources/Plugins/plugin_remover.py index 0300f07..f79d4fd 100644 --- a/profile_manager/datasources/Plugins/plugin_remover.py +++ b/profile_manager/datasources/Plugins/plugin_remover.py @@ -3,13 +3,13 @@ from qgis.PyQt.QtWidgets import QMessageBox -from ...utils import adjust_to_operating_system, tr +from profile_manager.utils import adjust_to_operating_system, tr def remove_plugins( - profile_path: str, - qgis_ini_file: str, - plugin_names: list[str], + profile_path: str, + qgis_ini_file: str, + plugin_names: list[str], ): """Removes the specified plugins from the profile. @@ -29,7 +29,9 @@ def remove_plugins( if ini_parser.has_option("PythonPlugins", plugin_name): ini_parser.remove_option("PythonPlugins", plugin_name) - plugins_dir = adjust_to_operating_system(profile_path + 'python/plugins/' + plugin_name + '/') + plugins_dir = adjust_to_operating_system( + profile_path + "python/plugins/" + plugin_name + "/" + ) try: rmtree(plugins_dir) @@ -38,9 +40,11 @@ def remove_plugins( QMessageBox.critical( None, tr("Plugin could not be removed"), - tr("Plugin '{0}' could not be removed due to error:\n{1}").format(plugin_name, e) + tr("Plugin '{0}' could not be removed due to error:\n{1}").format( + plugin_name, e + ), ) continue - with open(qgis_ini_file, 'w') as qgisconf: + with open(qgis_ini_file, "w") as qgisconf: ini_parser.write(qgisconf, space_around_delimiters=False) diff --git a/profile_manager/datasources/Styles/__init__.py b/profile_manager/datasources/Styles/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/profile_manager/datasources/Styles/style_handler.py b/profile_manager/datasources/Styles/style_handler.py index c9a7fed..6e6c9b0 100644 --- a/profile_manager/datasources/Styles/style_handler.py +++ b/profile_manager/datasources/Styles/style_handler.py @@ -1,5 +1,4 @@ import sqlite3 - from os import path from shutil import copy @@ -35,14 +34,18 @@ def import_styles(source_profile_path: str, target_profile_path: str): try: # import label settings - custom_labels = source_db_cursor.execute('SELECT * FROM labelsettings') - target_db_cursor.executemany('INSERT OR REPLACE INTO labelsettings VALUES (?,?,?,?)', custom_labels) + custom_labels = source_db_cursor.execute("SELECT * FROM labelsettings") + target_db_cursor.executemany( + "INSERT OR REPLACE INTO labelsettings VALUES (?,?,?,?)", custom_labels + ) # import symbols # FIXME: This has a hard-coded assumption that symbols with ids <= 115 are builtin symbols, # this will fail as soon as a new builtin symbol is shipped by QGIS. - custom_symbols = source_db_cursor.execute('SELECT * FROM symbol WHERE id>115') - target_db_cursor.executemany('INSERT OR REPLACE INTO symbol VALUES (?,?,?,?)', custom_symbols) + custom_symbols = source_db_cursor.execute("SELECT * FROM symbol WHERE id>115") + target_db_cursor.executemany( + "INSERT OR REPLACE INTO symbol VALUES (?,?,?,?)", custom_symbols + ) source_db.commit() target_db.commit() diff --git a/profile_manager/datasources/__init__.py b/profile_manager/datasources/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/metadata.txt b/profile_manager/metadata.txt similarity index 100% rename from metadata.txt rename to profile_manager/metadata.txt diff --git a/profile_manager/profile_manager.py b/profile_manager/profile_manager.py index 283cee3..a1405aa 100644 --- a/profile_manager/profile_manager.py +++ b/profile_manager/profile_manager.py @@ -20,6 +20,7 @@ * * ***************************************************************************/ """ + # Import the code for the dialog import time from collections import defaultdict @@ -28,17 +29,26 @@ from shutil import copytree from sys import platform -from qgis.PyQt.QtCore import QCoreApplication, QLocale, QSettings, QSize, Qt, QTranslator +from qgis.core import Qgis, QgsMessageLog, QgsUserProfileManager +from qgis.PyQt.QtCore import ( + QCoreApplication, + QLocale, + QSettings, + QSize, + Qt, + QTranslator, +) from qgis.PyQt.QtGui import QIcon from qgis.PyQt.QtWidgets import QAction, QMessageBox, QWidget -from qgis.core import Qgis, QgsMessageLog, QgsUserProfileManager # Import subclasses -from .datasources.Dataservices.datasource_handler import DataSourceHandler -from .profile_manager_dialog import ProfileManagerDialog -from .profiles.profile_action_handler import ProfileActionHandler -from .userInterface.interface_handler import InterfaceHandler -from .utils import adjust_to_operating_system, wait_cursor +from profile_manager.datasources.Dataservices.datasource_handler import ( + DataSourceHandler, +) +from profile_manager.profile_manager_dialog import ProfileManagerDialog +from profile_manager.profiles.profile_action_handler import ProfileActionHandler +from profile_manager.userInterface.interface_handler import InterfaceHandler +from profile_manager.utils import adjust_to_operating_system, wait_cursor class ProfileManager: @@ -59,7 +69,9 @@ def __init__(self, iface): self.qgis_profiles_path = "" self.ini_path = "" self.operating_system = "" - self.qgs_profile_manager = None # TODO in QGIS 3.30 we could and should use iface.userProfileManager() + self.qgs_profile_manager = ( + None # TODO in QGIS 3.30 we could and should use iface.userProfileManager() + ) self.data_source_handler: DataSourceHandler = None self.profile_manager_action_handler: ProfileActionHandler = None self.interface_handler: InterfaceHandler = None @@ -71,11 +83,10 @@ def __init__(self, iface): self.plugin_dir = path.dirname(__file__) # initialize locale - locale = QSettings().value('locale/userLocale', QLocale().name())[0:2] + locale = QSettings().value("locale/userLocale", QLocale().name())[0:2] locale_path = path.join( - self.plugin_dir, - 'i18n', - 'ProfileManager_{}.qm'.format(locale)) + self.plugin_dir, "i18n", "ProfileManager_{}.qm".format(locale) + ) if path.exists(locale_path): self.translator = QTranslator() @@ -84,7 +95,7 @@ def __init__(self, iface): # Declare instance attributes self.actions = [] - self.menu = self.tr(u'&Profile Manager') + self.menu = self.tr("&Profile Manager") # Check if plugin was started the first time in current QGIS session # Must be set in initGui() to survive plugin reloads @@ -103,19 +114,20 @@ def tr(self, message): :rtype: QString """ # noinspection PyTypeChecker,PyArgumentList,PyCallByClass - return QCoreApplication.translate('ProfileManager', message) + return QCoreApplication.translate("ProfileManager", message) def add_action( - self, - icon_path, - text, - callback, - enabled_flag=True, - add_to_menu=True, - add_to_toolbar=True, - status_tip=None, - whats_this=None, - parent=None): + self, + icon_path, + text, + callback, + enabled_flag=True, + add_to_menu=True, + add_to_toolbar=True, + status_tip=None, + whats_this=None, + parent=None, + ): """Add a toolbar icon to the toolbar. :param icon_path: Path to the icon for this action. Can be a resource @@ -171,9 +183,7 @@ def add_action( self.iface.addToolBarIcon(action) if add_to_menu: - self.iface.addPluginToMenu( - self.menu, - action) + self.iface.addPluginToMenu(self.menu, action) self.actions.append(action) @@ -183,10 +193,11 @@ def initGui(self): """Create the menu entries and toolbar icons inside the QGIS GUI.""" self.add_action( - path.join(path.dirname(__file__), 'icon.png'), - text=self.tr('Profile Manager'), + path.join(path.dirname(__file__), "icon.png"), + text=self.tr("Profile Manager"), callback=self.run, - parent=self.iface.mainWindow()) + parent=self.iface.mainWindow(), + ) # will be set False in run() self.first_start = True @@ -194,9 +205,7 @@ def initGui(self): def unload(self): """Removes the plugin menu item and icon from QGIS GUI.""" for action in self.actions: - self.iface.removePluginMenu( - self.tr('&Profile Manager'), - action) + self.iface.removePluginMenu(self.tr("&Profile Manager"), action) self.iface.removeToolBarIcon(action) def run(self): @@ -212,47 +221,72 @@ def run(self): self.set_paths() - self.qgs_profile_manager = QgsUserProfileManager(self.qgis_profiles_path) + self.qgs_profile_manager = QgsUserProfileManager( + self.qgis_profiles_path + ) self.data_source_handler = DataSourceHandler(self.dlg, self) - self.profile_manager_action_handler = ProfileActionHandler(self.dlg, self.qgis_profiles_path, self) + self.profile_manager_action_handler = ProfileActionHandler( + self.dlg, self.qgis_profiles_path, self + ) self.interface_handler = InterfaceHandler(self, self.dlg) self.interface_handler.setup_connections() self.interface_handler.populate_profile_listings() - self.interface_handler.populate_data_source_tree(self.dlg.comboBoxNamesSource.currentText(), True) - self.interface_handler.populate_data_source_tree(self.dlg.comboBoxNamesTarget.currentText(), False) + self.interface_handler.populate_data_source_tree( + self.dlg.comboBoxNamesSource.currentText(), True + ) + self.interface_handler.populate_data_source_tree( + self.dlg.comboBoxNamesTarget.currentText(), False + ) self.data_source_handler.set_path_to_files( self.dlg.comboBoxNamesSource.currentText(), - self.dlg.comboBoxNamesTarget.currentText() + self.dlg.comboBoxNamesTarget.currentText(), ) self.data_source_handler.display_plugins() - self.dlg.exec() 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" + 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 is '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" + 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" self.interface_handler.adjust_to_macOSDark() 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.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.backup_path = adjust_to_operating_system(str(Path.home()) + "/QGIS Profile Manager Backup/") + self.backup_path = adjust_to_operating_system( + str(Path.home()) + "/QGIS Profile Manager Backup/" + ) def make_backup(self, profile: str): """Creates a backup of the specified profile. @@ -267,7 +301,9 @@ def make_backup(self, profile: str): target_path = self.backup_path + str(ts) source_path = f"{self.qgis_profiles_path}/{profile}" QgsMessageLog.logMessage( - f"Backing up profile '{source_path}' to '{target_path}'", "Profile Manager", level=Qgis.Info + f"Backing up profile '{source_path}' to '{target_path}'", + "Profile Manager", + level=Qgis.Info, ) copytree(source_path, target_path) @@ -281,18 +317,24 @@ def import_action_handler(self): self.get_checked_sources() source_profile_name = self.dlg.comboBoxNamesSource.currentText() target_profile_name = self.dlg.comboBoxNamesTarget.currentText() - assert source_profile_name != target_profile_name # should be forced by the GUI - self.data_source_handler.set_path_to_files(source_profile_name, - target_profile_name) - self.data_source_handler.set_path_to_bookmark_files(source_profile_name, - target_profile_name) + assert ( + source_profile_name != target_profile_name + ) # should be forced by the GUI + self.data_source_handler.set_path_to_files( + source_profile_name, target_profile_name + ) + self.data_source_handler.set_path_to_bookmark_files( + source_profile_name, target_profile_name + ) try: self.make_backup(target_profile_name) except OSError as e: error_message = self.tr("Aborting import due to error:\n{}").format(e) if error_message: - QMessageBox.critical(None, self.tr("Backup could not be created"), error_message) + QMessageBox.critical( + None, self.tr("Backup could not be created"), error_message + ) return with wait_cursor(): @@ -304,7 +346,9 @@ def import_action_handler(self): QMessageBox.critical( None, self.tr("Data Source Import"), - self.tr("There were errors on import."), # The user should have been shown dialogs or see a log + self.tr( + "There were errors on import." + ), # The user should have been shown dialogs or see a log ) else: QMessageBox.information( @@ -330,8 +374,9 @@ def remove_source_action_handler(self): clicked_button = QMessageBox.question( None, self.tr("Remove Data Sources"), - self.tr("Are you sure you want to remove these sources?\n\nA backup will be created at '{}'") \ - .format(self.backup_path), + self.tr( + "Are you sure you want to remove these sources?\n\nA backup will be created at '{}'" + ).format(self.backup_path), ) if clicked_button == QMessageBox.Yes: @@ -340,14 +385,18 @@ def remove_source_action_handler(self): try: self.make_backup(source_profile_name) except OSError as e: - error_message = self.tr("Aborting removal due to error:\n{}").format(e) + error_message = self.tr( + "Aborting removal due to error:\n{}" + ).format(e) if not error_message: self.data_source_handler.remove_datasources_and_plugins() self.update_data_sources(True) if error_message: - QMessageBox.critical(None, self.tr("Backup could not be created"), error_message) + QMessageBox.critical( + None, self.tr("Backup could not be created"), error_message + ) else: QMessageBox.information( None, @@ -360,7 +409,9 @@ def remove_source_action_handler(self): self.refresh_browser_model() self.interface_handler.uncheck_everything() - def update_data_sources(self, only_update_plugins_for_target_profile=False, update_source=True): + def update_data_sources( + self, only_update_plugins_for_target_profile=False, update_source=True + ): """Updates data sources and plugin lists in the UI""" source_profile = self.dlg.comboBoxNamesSource.currentText() target_profile = self.dlg.comboBoxNamesTarget.currentText() @@ -371,7 +422,9 @@ def update_data_sources(self, only_update_plugins_for_target_profile=False, upda else: self.interface_handler.populate_data_source_tree(target_profile, False) - self.data_source_handler.display_plugins(only_for_target_profile=only_update_plugins_for_target_profile) + self.data_source_handler.display_plugins( + only_for_target_profile=only_update_plugins_for_target_profile + ) def get_checked_sources(self): """Gets all checked data sources and communicates them to the data source handler""" @@ -382,20 +435,34 @@ def get_checked_sources(self): checked_web_sources = defaultdict(list) checked_database_sources = defaultdict(list) - for item in self.dlg.treeWidgetSource.findItems("", Qt.MatchContains | Qt.MatchRecursive): + for item in self.dlg.treeWidgetSource.findItems( + "", Qt.MatchContains | Qt.MatchRecursive + ): if item.childCount() == 0 and item.checkState(0) == Qt.Checked: parent_text = item.parent().text(0) # the provider group in the tree - item_text = item.text(0) # a specific data source in the provider's group + item_text = item.text( + 0 + ) # a specific data source in the provider's group # FIXME hardcoded list of GUI titles - if parent_text in ["SpatiaLite", "PostgreSQL", "MSSQL", "DB2", "Oracle"]: + if parent_text in [ + "SpatiaLite", + "PostgreSQL", + "MSSQL", + "DB2", + "Oracle", + ]: checked_database_sources[parent_text].append(item_text) # GeoPackage connections are stored under [providers] in the ini - elif parent_text == "GeoPackage": # FIXME hardcoded relationship between GeoPackage and 'providers' + elif ( + parent_text == "GeoPackage" + ): # FIXME hardcoded relationship between GeoPackage and 'providers' checked_database_sources["providers"].append(item_text) else: checked_web_sources[parent_text].append(item_text) - self.data_source_handler.set_data_sources(checked_web_sources, checked_database_sources) + self.data_source_handler.set_data_sources( + checked_web_sources, checked_database_sources + ) def get_profile_paths(self) -> tuple[str, str]: """Returns the paths to the currently chosen source and target profiles. @@ -404,9 +471,17 @@ def get_profile_paths(self) -> tuple[str, str]: tuple[str, str]: Path to source profile, path to target profile """ source = adjust_to_operating_system( - self.qgis_profiles_path + "/" + self.dlg.comboBoxNamesSource.currentText() + "/") + self.qgis_profiles_path + + "/" + + self.dlg.comboBoxNamesSource.currentText() + + "/" + ) target = adjust_to_operating_system( - self.qgis_profiles_path + "/" + self.dlg.comboBoxNamesTarget.currentText() + "/") + self.qgis_profiles_path + + "/" + + self.dlg.comboBoxNamesTarget.currentText() + + "/" + ) return source, target @@ -414,14 +489,30 @@ 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") + 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") + 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") + 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") + self.qgis_profiles_path + + "/" + + self.dlg.comboBoxNamesTarget.currentText() + + "/QGIS/QGIS3.ini" + ) ini_paths = { "source": ini_path_source, @@ -432,5 +523,5 @@ def get_ini_paths(self): def refresh_browser_model(self): """Refreshes the browser of the qgis instance from which this plugin was started""" - self.iface.mainWindow().findChildren(QWidget, 'Browser')[0].refresh() - self.iface.mainWindow().findChildren(QWidget, 'Browser2')[0].refresh() + self.iface.mainWindow().findChildren(QWidget, "Browser")[0].refresh() + self.iface.mainWindow().findChildren(QWidget, "Browser2")[0].refresh() diff --git a/profile_manager/profile_manager_dialog.py b/profile_manager/profile_manager_dialog.py index 5e627dd..12639bd 100644 --- a/profile_manager/profile_manager_dialog.py +++ b/profile_manager/profile_manager_dialog.py @@ -26,8 +26,9 @@ from qgis.PyQt import QtWidgets, uic # This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer -FORM_CLASS, _ = uic.loadUiType(os.path.join( - os.path.dirname(__file__), 'profile_manager_dialog_base.ui')) +FORM_CLASS, _ = uic.loadUiType( + os.path.join(os.path.dirname(__file__), "profile_manager_dialog_base.ui") +) class ProfileManagerDialog(QtWidgets.QDialog, FORM_CLASS): diff --git a/profile_manager/profiles/__init__.py b/profile_manager/profiles/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/profile_manager/profiles/profile_action_handler.py b/profile_manager/profiles/profile_action_handler.py index 34acc2e..23d4d8e 100644 --- a/profile_manager/profiles/profile_action_handler.py +++ b/profile_manager/profiles/profile_action_handler.py @@ -1,14 +1,16 @@ from qgis.PyQt.QtWidgets import QDialog -from .profile_copier import ProfileCopier -from .profile_creator import ProfileCreator -from .profile_editor import ProfileEditor -from .profile_remover import ProfileRemover +from profile_manager.profiles.profile_copier import ProfileCopier +from profile_manager.profiles.profile_creator import ProfileCreator +from profile_manager.profiles.profile_editor import ProfileEditor +from profile_manager.profiles.profile_remover import ProfileRemover class ProfileActionHandler(QDialog): - def __init__(self, profile_manager_dialog, qgis_path, profile_manager, *args, **kwargs): + def __init__( + self, profile_manager_dialog, qgis_path, profile_manager, *args, **kwargs + ): super().__init__(*args, **kwargs) self.is_cancel_button_clicked = False @@ -16,9 +18,13 @@ def __init__(self, profile_manager_dialog, qgis_path, profile_manager, *args, ** self.dlg = profile_manager_dialog self.qgis_path = qgis_path self.profile_manager = profile_manager - self.profile_remover = ProfileRemover(self.dlg, self.qgis_path, self.profile_manager) + self.profile_remover = ProfileRemover( + self.dlg, self.qgis_path, self.profile_manager + ) self.profile_creator = ProfileCreator(self.qgis_path, self.profile_manager) - self.profile_editor = ProfileEditor(self.dlg, self.qgis_path, self.profile_manager) + self.profile_editor = ProfileEditor( + self.dlg, self.qgis_path, self.profile_manager + ) self.profile_copier = ProfileCopier(self.dlg, self.qgis_path) def create_new_profile(self): diff --git a/profile_manager/profiles/profile_copier.py b/profile_manager/profiles/profile_copier.py index 8bca6f5..d897c35 100644 --- a/profile_manager/profiles/profile_copier.py +++ b/profile_manager/profiles/profile_copier.py @@ -2,8 +2,8 @@ from qgis.PyQt.QtWidgets import QDialog, QMessageBox -from ..userInterface.name_profile_dialog import NameProfileDialog -from ..utils import wait_cursor +from profile_manager.userInterface.name_profile_dialog import NameProfileDialog +from profile_manager.utils import wait_cursor class ProfileCopier(QDialog): @@ -30,10 +30,16 @@ def copy_profile(self): try: copytree(source_profile_path, profile_path) except FileExistsError: - error_message = self.tr("Profile directory '{}' already exists.").format(profile_name) + error_message = self.tr( + "Profile directory '{}' already exists." + ).format(profile_name) if error_message: - QMessageBox.critical(None, self.tr("Profile could not be copied"), error_message) + QMessageBox.critical( + None, self.tr("Profile could not be copied"), error_message + ) else: QMessageBox.information( - None, self.tr("Profile copied"), self.tr("Profile '{}' successfully copied.").format(profile_name) + None, + self.tr("Profile copied"), + self.tr("Profile '{}' successfully copied.").format(profile_name), ) diff --git a/profile_manager/profiles/profile_creator.py b/profile_manager/profiles/profile_creator.py index 96fd8b3..9b93bdc 100644 --- a/profile_manager/profiles/profile_creator.py +++ b/profile_manager/profiles/profile_creator.py @@ -1,10 +1,10 @@ from os import mkdir -from qgis.PyQt.QtWidgets import QDialog, QMessageBox from qgis.core import QgsUserProfileManager +from qgis.PyQt.QtWidgets import QDialog, QMessageBox -from ..userInterface.name_profile_dialog import NameProfileDialog -from ..utils import adjust_to_operating_system, wait_cursor +from profile_manager.userInterface.name_profile_dialog import NameProfileDialog +from profile_manager.utils import adjust_to_operating_system, wait_cursor class ProfileCreator(QDialog): @@ -27,23 +27,31 @@ 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 is "mac": - profile_path = self.qgis_path + "/" + profile_name + "/qgis.org/" + if self.profile_manager.operating_system == "mac": + profile_path = ( + self.qgis_path + "/" + profile_name + "/qgis.org/" + ) else: profile_path = self.qgis_path + "/" + profile_name + "/QGIS/" profile_path = adjust_to_operating_system(profile_path) mkdir(profile_path) - ini_path = profile_path + adjust_to_operating_system('QGIS3.ini') + ini_path = profile_path + adjust_to_operating_system("QGIS3.ini") qgis_ini_file = open(ini_path, "w") qgis_ini_file.close() except FileExistsError: - error_message = self.tr("Profile directory '{}' already exists.").format(profile_name) + error_message = self.tr( + "Profile directory '{}' already exists." + ).format(profile_name) if error_message: - QMessageBox.critical(None, self.tr("Profile could not be created"), error_message) + QMessageBox.critical( + None, self.tr("Profile could not be created"), error_message + ) else: QMessageBox.information( - None, self.tr("Profile created"), self.tr("Profile '{}' successfully created.").format(profile_name) + None, + self.tr("Profile created"), + self.tr("Profile '{}' successfully created.").format(profile_name), ) diff --git a/profile_manager/profiles/profile_editor.py b/profile_manager/profiles/profile_editor.py index 6d7096a..f0de194 100644 --- a/profile_manager/profiles/profile_editor.py +++ b/profile_manager/profiles/profile_editor.py @@ -1,16 +1,18 @@ from os import rename from pathlib import Path -from qgis.PyQt.QtWidgets import QDialog, QMessageBox from qgis.core import QgsApplication +from qgis.PyQt.QtWidgets import QDialog, QMessageBox -from ..userInterface.name_profile_dialog import NameProfileDialog -from ..utils import adjust_to_operating_system, wait_cursor +from profile_manager.userInterface.name_profile_dialog import NameProfileDialog +from profile_manager.utils import adjust_to_operating_system, wait_cursor class ProfileEditor(QDialog): - def __init__(self, profile_manager_dialog, qgis_path, profile_manager, *args, **kwargs): + def __init__( + self, profile_manager_dialog, qgis_path, profile_manager, *args, **kwargs + ): super().__init__(*args, **kwargs) self.dlg = profile_manager_dialog @@ -24,9 +26,13 @@ def edit_profile(self): assert old_profile_name is not None assert old_profile_name != Path(QgsApplication.qgisSettingsDirPath()).name - profile_before_change = adjust_to_operating_system(self.qgis_path + "/" + old_profile_name) + profile_before_change = adjust_to_operating_system( + self.qgis_path + "/" + old_profile_name + ) - dialog = NameProfileDialog(title=self.tr("Rename Profile '{}'").format(old_profile_name)) + dialog = NameProfileDialog( + title=self.tr("Rename Profile '{}'").format(old_profile_name) + ) return_code = dialog.exec() if return_code == QDialog.Accepted: @@ -34,7 +40,9 @@ def edit_profile(self): with wait_cursor(): new_profile_name = dialog.text_input.text() assert new_profile_name != "" # should be forced by the GUI - profile_after_change = adjust_to_operating_system(self.qgis_path + "/" + new_profile_name) + profile_after_change = adjust_to_operating_system( + self.qgis_path + "/" + new_profile_name + ) try: rename(profile_before_change, profile_after_change) @@ -42,10 +50,14 @@ def edit_profile(self): error_message = str(e) if error_message: - QMessageBox.critical(None, self.tr("Profile could not be renamed"), error_message) + QMessageBox.critical( + None, self.tr("Profile could not be renamed"), error_message + ) else: QMessageBox.information( None, self.tr("Profile renamed"), - self.tr("Profile '{0}' successfully renamed to '{1}'.").format(old_profile_name, new_profile_name), + self.tr("Profile '{0}' successfully renamed to '{1}'.").format( + old_profile_name, new_profile_name + ), ) diff --git a/profile_manager/profiles/profile_remover.py b/profile_manager/profiles/profile_remover.py index ed299f8..a2bb5f8 100644 --- a/profile_manager/profiles/profile_remover.py +++ b/profile_manager/profiles/profile_remover.py @@ -1,15 +1,17 @@ from pathlib import Path from shutil import rmtree -from qgis.PyQt.QtWidgets import QDialog, QMessageBox from qgis.core import QgsApplication +from qgis.PyQt.QtWidgets import QDialog, QMessageBox -from ..utils import adjust_to_operating_system, wait_cursor +from profile_manager.utils import adjust_to_operating_system, wait_cursor class ProfileRemover(QDialog): - def __init__(self, profile_manager_dialog, qgis_path, profile_manager, *args, **kwargs): + def __init__( + self, profile_manager_dialog, qgis_path, profile_manager, *args, **kwargs + ): super().__init__(*args, **kwargs) self.dlg = profile_manager_dialog @@ -32,8 +34,9 @@ def remove_profile(self): clicked_button = QMessageBox.question( None, self.tr("Remove Profile"), - self.tr("Are you sure you want to remove the profile '{0}'?\n\nA backup will be created at '{1}'") \ - .format(profile_name, self.profile_manager.backup_path), + self.tr( + "Are you sure you want to remove the profile '{0}'?\n\nA backup will be created at '{1}'" + ).format(profile_name, self.profile_manager.backup_path), ) if clicked_button == QMessageBox.Yes: @@ -43,18 +46,22 @@ def remove_profile(self): try: self.profile_manager.make_backup(profile_name) except OSError as e: - error_message = \ - self.tr("Aborting removal of profile '{0}' due to error:\n{1}").format(profile_name, e) + error_message = self.tr( + "Aborting removal of profile '{0}' due to error:\n{1}" + ).format(profile_name, e) if error_message: - QMessageBox.critical(None, self.tr("Backup could not be created"), error_message) + QMessageBox.critical( + None, self.tr("Backup could not be created"), error_message + ) return with wait_cursor(): try: rmtree(profile_path) except FileNotFoundError as e: - error_message = self.tr("Aborting removal of profile '{0}' due to error:\n{1}") \ - .format(profile_name, e) + error_message = self.tr( + "Aborting removal of profile '{0}' due to error:\n{1}" + ).format(profile_name, e) if error_message: QMessageBox.critical( @@ -62,5 +69,7 @@ def remove_profile(self): ) else: QMessageBox.information( - None, self.tr("Profile removed"), self.tr("Profile '{}' has been removed.").format(profile_name) + None, + self.tr("Profile removed"), + self.tr("Profile '{}' has been removed.").format(profile_name), ) diff --git a/profile_manager/userInterface/__init__.py b/profile_manager/userInterface/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/profile_manager/userInterface/interface_handler.py b/profile_manager/userInterface/interface_handler.py index df1c564..4c9e9b0 100644 --- a/profile_manager/userInterface/interface_handler.py +++ b/profile_manager/userInterface/interface_handler.py @@ -1,12 +1,14 @@ -import time from pathlib import Path -from qgis.PyQt.QtCore import QVariant, Qt +from qgis.core import Qgis, QgsApplication, QgsMessageLog +from qgis.PyQt.QtCore import Qt, QVariant from qgis.PyQt.QtGui import QColor, QIcon, QPalette from qgis.PyQt.QtWidgets import QDialog, QListWidgetItem -from qgis.core import Qgis, QgsApplication, QgsMessageLog -from ..datasources.Dataservices.datasource_provider import get_data_sources_tree, DATA_SOURCE_SEARCH_LOCATIONS +from profile_manager.datasources.Dataservices.datasource_provider import ( + DATA_SOURCE_SEARCH_LOCATIONS, + get_data_sources_tree, +) class InterfaceHandler(QDialog): @@ -26,7 +28,9 @@ def populate_data_source_tree(self, profile_name, populating_source_profile): populating_source_profile (bool): If the source profile is populated """ QgsMessageLog.logMessage( - f"Scanning profile '{profile_name}' for data source connections:", "Profile Manager", Qgis.Info + f"Scanning profile '{profile_name}' for data source connections:", + "Profile Manager", + Qgis.Info, ) ini_paths = self.profile_manager.get_ini_paths() if populating_source_profile: @@ -37,22 +41,30 @@ def populate_data_source_tree(self, profile_name, populating_source_profile): # collect data source tree items from ini file data_source_list = [] for provider in DATA_SOURCE_SEARCH_LOCATIONS.keys(): - tree_root_item = get_data_sources_tree(target_ini_path, provider, make_checkable=populating_source_profile) + tree_root_item = get_data_sources_tree( + target_ini_path, provider, make_checkable=populating_source_profile + ) if tree_root_item: data_source_list.append(tree_root_item) QgsMessageLog.logMessage( - f"Scanning profile '{profile_name}' for data source connections: Done!", "Profile Manager", Qgis.Info + f"Scanning profile '{profile_name}' for data source connections: Done!", + "Profile Manager", + Qgis.Info, ) # populate tree if populating_source_profile: self.dlg.treeWidgetSource.clear() - self.dlg.treeWidgetSource.setHeaderLabel(self.tr("Source Profile: {}").format(profile_name)) + self.dlg.treeWidgetSource.setHeaderLabel( + self.tr("Source Profile: {}").format(profile_name) + ) for tree_root_item in data_source_list: self.dlg.treeWidgetSource.addTopLevelItem(tree_root_item) else: self.dlg.treeWidgetTarget.clear() - self.dlg.treeWidgetTarget.setHeaderLabel(self.tr("Target Profile: {}").format(profile_name)) + self.dlg.treeWidgetTarget.setHeaderLabel( + self.tr("Target Profile: {}").format(profile_name) + ) for tree_root_item in data_source_list: self.dlg.treeWidgetTarget.addTopLevelItem(tree_root_item) @@ -85,7 +97,7 @@ def populate_profile_listings(self): font.setItalic(True) self.dlg.comboBoxNamesTarget.setItemData(i, QVariant(font), Qt.FontRole) # Add profiles to list view - list_item = QListWidgetItem(QIcon('../icon.png'), name) + list_item = QListWidgetItem(QIcon("../icon.png"), name) if name == active_profile_name: font = list_item.font() font.setItalic(True) @@ -100,11 +112,12 @@ def populate_profile_listings(self): self.conditionally_enable_profile_buttons() def adjust_to_macOSDark(self): - from ..darkdetect import _detect + from profile_manager.darkdetect import _detect + if _detect.isDark(): # Change ComboBox selected from black to white - self.dlg.comboBoxNamesSource.setStyleSheet('color: white') - self.dlg.comboBoxNamesTarget.setStyleSheet('color: white') + self.dlg.comboBoxNamesSource.setStyleSheet("color: white") + self.dlg.comboBoxNamesTarget.setStyleSheet("color: white") # Set checkbox indicator of the treewidget from black to white file_tree_palette = QPalette() @@ -119,15 +132,25 @@ def adjust_to_macOSDark(self): def setup_connections(self): """Set up connections""" # buttons - self.dlg.importButton.clicked.connect(self.profile_manager.import_action_handler) + self.dlg.importButton.clicked.connect( + self.profile_manager.import_action_handler + ) self.dlg.closeDialog.rejected.connect(self.dlg.close) self.dlg.createProfileButton.clicked.connect( self.profile_manager.profile_manager_action_handler.create_new_profile ) - self.dlg.removeProfileButton.clicked.connect(self.profile_manager.profile_manager_action_handler.remove_profile) - self.dlg.removeSourcesButton.clicked.connect(self.profile_manager.remove_source_action_handler) - self.dlg.editProfileButton.clicked.connect(self.profile_manager.profile_manager_action_handler.edit_profile) - self.dlg.copyProfileButton.clicked.connect(self.profile_manager.profile_manager_action_handler.copy_profile) + self.dlg.removeProfileButton.clicked.connect( + self.profile_manager.profile_manager_action_handler.remove_profile + ) + self.dlg.removeSourcesButton.clicked.connect( + self.profile_manager.remove_source_action_handler + ) + self.dlg.editProfileButton.clicked.connect( + self.profile_manager.profile_manager_action_handler.edit_profile + ) + self.dlg.copyProfileButton.clicked.connect( + self.profile_manager.profile_manager_action_handler.copy_profile + ) # checkbox self.dlg.checkBox_checkAll.stateChanged.connect(self.check_everything) @@ -139,18 +162,29 @@ def setup_connections(self): self.dlg.comboBoxNamesTarget.currentIndexChanged.connect( lambda: self.profile_manager.update_data_sources(True, False) ) - self.dlg.comboBoxNamesSource.currentIndexChanged.connect(self.conditionally_enable_import_button) - self.dlg.comboBoxNamesTarget.currentIndexChanged.connect(self.conditionally_enable_import_button) - self.dlg.list_profiles.currentItemChanged.connect(self.conditionally_enable_profile_buttons) + self.dlg.comboBoxNamesSource.currentIndexChanged.connect( + self.conditionally_enable_import_button + ) + self.dlg.comboBoxNamesTarget.currentIndexChanged.connect( + self.conditionally_enable_import_button + ) + self.dlg.list_profiles.currentItemChanged.connect( + self.conditionally_enable_profile_buttons + ) + def check_everything(self): """Checks/Unchecks every checkbox in the gui""" if self.checked: self.uncheck_everything() else: - for item in self.dlg.treeWidgetSource.findItems("", Qt.MatchContains | Qt.MatchRecursive): + for item in self.dlg.treeWidgetSource.findItems( + "", Qt.MatchContains | Qt.MatchRecursive + ): item.setCheckState(0, Qt.Checked) - for item in self.dlg.list_plugins.findItems("", Qt.MatchContains | Qt.MatchRecursive): + for item in self.dlg.list_plugins.findItems( + "", Qt.MatchContains | Qt.MatchRecursive + ): item.setCheckState(Qt.Checked) self.dlg.bookmark_check.setCheckState(Qt.Checked) @@ -174,7 +208,9 @@ def uncheck_everything(self): self.dlg.ui_check.setChecked(Qt.Unchecked) self.dlg.checkBox_checkAll.setChecked(Qt.Unchecked) - for item in self.dlg.treeWidgetSource.findItems("", Qt.MatchContains | Qt.MatchRecursive): + for item in self.dlg.treeWidgetSource.findItems( + "", Qt.MatchContains | Qt.MatchRecursive + ): item.setCheckState(0, Qt.Unchecked) for iterator in range(self.dlg.list_plugins.count()): @@ -187,8 +223,13 @@ def conditionally_enable_import_button(self): """ # Don't allow import of a profile into itself - if self.dlg.comboBoxNamesSource.currentText() == self.dlg.comboBoxNamesTarget.currentText(): - self.dlg.importButton.setToolTip(self.tr("Target profile can not be same as source profile")) + if ( + self.dlg.comboBoxNamesSource.currentText() + == self.dlg.comboBoxNamesTarget.currentText() + ): + self.dlg.importButton.setToolTip( + self.tr("Target profile can not be same as source profile") + ) self.dlg.importButton.setEnabled(False) else: self.dlg.importButton.setToolTip("") @@ -201,17 +242,30 @@ def conditionally_enable_profile_buttons(self): """ # A profile must be selected if self.dlg.list_profiles.currentItem() is None: - self.dlg.removeProfileButton.setToolTip(self.tr("Please choose a profile to remove")) + self.dlg.removeProfileButton.setToolTip( + self.tr("Please choose a profile to remove") + ) self.dlg.removeProfileButton.setEnabled(False) - self.dlg.editProfileButton.setToolTip(self.tr("Please choose a profile to rename")) + self.dlg.editProfileButton.setToolTip( + self.tr("Please choose a profile to rename") + ) self.dlg.editProfileButton.setEnabled(False) - self.dlg.copyProfileButton.setToolTip(self.tr("Please select a profile to copy from")) + self.dlg.copyProfileButton.setToolTip( + self.tr("Please select a profile to copy from") + ) self.dlg.copyProfileButton.setEnabled(False) # Some actions can/should not be done on the currently active profile - elif self.dlg.list_profiles.currentItem().text() == Path(QgsApplication.qgisSettingsDirPath()).name: - self.dlg.removeProfileButton.setToolTip(self.tr("The active profile cannot be removed")) + elif ( + self.dlg.list_profiles.currentItem().text() + == Path(QgsApplication.qgisSettingsDirPath()).name + ): + self.dlg.removeProfileButton.setToolTip( + self.tr("The active profile cannot be removed") + ) self.dlg.removeProfileButton.setEnabled(False) - self.dlg.editProfileButton.setToolTip(self.tr("The active profile cannot be renamed")) + self.dlg.editProfileButton.setToolTip( + self.tr("The active profile cannot be renamed") + ) self.dlg.editProfileButton.setEnabled(False) self.dlg.copyProfileButton.setToolTip("") self.dlg.copyProfileButton.setEnabled(True) diff --git a/profile_manager/userInterface/name_profile_dialog.py b/profile_manager/userInterface/name_profile_dialog.py index 2b23489..06f904e 100644 --- a/profile_manager/userInterface/name_profile_dialog.py +++ b/profile_manager/userInterface/name_profile_dialog.py @@ -1,4 +1,4 @@ -from qgis.PyQt.QtCore import QRegularExpression, Qt +from qgis.PyQt.QtCore import QRegularExpression from qgis.PyQt.QtGui import QRegularExpressionValidator from qgis.PyQt.QtWidgets import QDialog, QDialogButtonBox, QLineEdit, QVBoxLayout @@ -22,7 +22,9 @@ def __init__(self, title=None, *args, **kwargs): self.text_input = QLineEdit() self.text_input.setPlaceholderText(self.tr("Profile Name")) # validation rule from QGIS' QgsUserProfileSelectionDialog - self.text_input.setValidator(QRegularExpressionValidator(QRegularExpression("[^/\\\\]+"))) + self.text_input.setValidator( + QRegularExpressionValidator(QRegularExpression("[^/\\\\]+")) + ) self.button_box = QDialogButtonBox.Ok | QDialogButtonBox.Cancel self.button_box = QDialogButtonBox(self.button_box) diff --git a/profile_manager/utils.py b/profile_manager/utils.py index a9ec8b1..6e821cb 100644 --- a/profile_manager/utils.py +++ b/profile_manager/utils.py @@ -1,7 +1,7 @@ from contextlib import contextmanager from sys import platform -from qgis.PyQt.QtCore import Qt, QCoreApplication +from qgis.PyQt.QtCore import QCoreApplication, Qt from qgis.PyQt.QtGui import QCursor, QGuiApplication @@ -19,15 +19,18 @@ def adjust_to_operating_system(path_to_adjust): For MacOS it contains special logic to also replace the /QGIS/ -> /qgis.org/ directory name. """ - if platform.startswith('win32'): + if platform.startswith("win32"): return path_to_adjust.replace("/", "\\") elif platform.startswith("linux") or "bsd" in platform: return path_to_adjust.replace("\\", "/") elif platform.startswith("darwin"): # macos - return path_to_adjust.replace("\\", "/").replace("/QGIS/QGIS3.ini", "/qgis.org/QGIS3.ini") + return path_to_adjust.replace("\\", "/").replace( + "/QGIS/QGIS3.ini", "/qgis.org/QGIS3.ini" + ) else: raise NotImplementedError(f"Unsupported platform '{platform}'") + def tr(message): # for translating in non-QObject class contexts - return QCoreApplication.translate('ProfileManager', message) + return QCoreApplication.translate("ProfileManager", message) diff --git a/test/__init__.py b/test/__init__.py index 8feeb0b..2aea085 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,2 +1,2 @@ # import qgis libs so that ve set the correct sip api version -import qgis # pylint: disable=W0611 # NOQA \ No newline at end of file +import qgis # pylint: disable=W0611 # NOQA diff --git a/test/qgis_interface.py b/test/qgis_interface.py index a407052..c5bd8fd 100644 --- a/test/qgis_interface.py +++ b/test/qgis_interface.py @@ -1,4 +1,3 @@ -# coding=utf-8 """QGIS plugin implementation. .. note:: This program is free software; you can redistribute it and/or modify @@ -14,29 +13,32 @@ """ -__author__ = 'tim@linfiniti.com' -__revision__ = '$Format:%H$' -__date__ = '10/01/2011' +__author__ = "tim@linfiniti.com" +__revision__ = "$Format:%H$" +__date__ = "10/01/2011" __copyright__ = ( - 'Copyright (c) 2010 by Ivan Mincik, ivan.mincik@gista.sk and ' - 'Copyright (c) 2011 German Carrillo, geotux_tuxman@linuxmail.org' - 'Copyright (c) 2014 Tim Sutton, tim@linfiniti.com' + "Copyright (c) 2010 by Ivan Mincik, ivan.mincik@gista.sk and " + "Copyright (c) 2011 German Carrillo, geotux_tuxman@linuxmail.org" + "Copyright (c) 2014 Tim Sutton, tim@linfiniti.com" ) import logging -from qgis.PyQt.QtCore import QObject, pyqtSlot, pyqtSignal + from qgis.core import QgsMapLayerRegistry from qgis.gui import QgsMapCanvasLayer -LOGGER = logging.getLogger('QGIS') +from qgis.PyQt.QtCore import QObject, pyqtSignal, pyqtSlot + +LOGGER = logging.getLogger("QGIS") -#noinspection PyMethodMayBeStatic,PyPep8Naming +# noinspection PyMethodMayBeStatic,PyPep8Naming class QgisInterface(QObject): """Class to expose QGIS objects and functions to plugins. This class is here for enabling us to run unit tests only, so most methods are simply stubs. """ + currentLayerChanged = pyqtSignal(QgsMapCanvasLayer) def __init__(self, canvas): @@ -47,7 +49,7 @@ def __init__(self, canvas): self.canvas = canvas # Set up slots so we can mimic the behaviour of QGIS when layers # are added. - LOGGER.debug('Initialising canvas...') + LOGGER.debug("Initialising canvas...") # noinspection PyArgumentList QgsMapLayerRegistry.instance().layersAdded.connect(self.addLayers) # noinspection PyArgumentList @@ -58,7 +60,7 @@ def __init__(self, canvas): # For processing module self.destCrs = None - @pyqtSlot('QStringList') + @pyqtSlot("QStringList") def addLayers(self, layers): """Handle layers being added to the registry so they show up in canvas. @@ -67,9 +69,9 @@ def addLayers(self, layers): .. note:: The QgsInterface api does not include this method, it is added here as a helper to facilitate testing. """ - #LOGGER.debug('addLayers called on qgis_interface') - #LOGGER.debug('Number of layers being added: %s' % len(layers)) - #LOGGER.debug('Layer Count Before: %s' % len(self.canvas.layers())) + # LOGGER.debug('addLayers called on qgis_interface') + # LOGGER.debug('Number of layers being added: %s' % len(layers)) + # LOGGER.debug('Layer Count Before: %s' % len(self.canvas.layers())) current_layers = self.canvas.layers() final_layers = [] for layer in current_layers: @@ -78,9 +80,9 @@ def addLayers(self, layers): final_layers.append(QgsMapCanvasLayer(layer)) self.canvas.setLayerSet(final_layers) - #LOGGER.debug('Layer Count After: %s' % len(self.canvas.layers())) + # LOGGER.debug('Layer Count After: %s' % len(self.canvas.layers())) - @pyqtSlot('QgsMapLayer') + @pyqtSlot("QgsMapLayer") def addLayer(self, layer): """Handle a layer being added to the registry so it shows up in canvas. diff --git a/test/test_init.py b/test/test_init.py index 383303d..4120b56 100644 --- a/test/test_init.py +++ b/test/test_init.py @@ -1,19 +1,18 @@ -# coding=utf-8 """Tests QGIS plugin init.""" -__author__ = 'Tim Sutton ' -__revision__ = '$Format:%H$' -__date__ = '17/10/2010' +__author__ = "Tim Sutton " +__revision__ = "$Format:%H$" +__date__ = "17/10/2010" __license__ = "GPL" -__copyright__ = 'Copyright 2012, Australia Indonesia Facility for ' -__copyright__ += 'Disaster Reduction' +__copyright__ = "Copyright 2012, Australia Indonesia Facility for " +__copyright__ += "Disaster Reduction" +import configparser +import logging import os import unittest -import logging -import configparser -LOGGER = logging.getLogger('QGIS') +LOGGER = logging.getLogger("QGIS") class TestInit(unittest.TestCase): @@ -35,30 +34,34 @@ def test_read_init(self): # plugins/validator.py required_metadata = [ - 'name', - 'description', - 'version', - 'qgisMinimumVersion', - 'email', - 'author'] - - file_path = os.path.abspath(os.path.join( - os.path.dirname(__file__), os.pardir, - 'metadata.txt')) + "name", + "description", + "version", + "qgisMinimumVersion", + "email", + "author", + ] + + file_path = os.path.abspath( + os.path.join(os.path.dirname(__file__), os.pardir, "metadata.txt") + ) LOGGER.info(file_path) metadata = [] parser = configparser.ConfigParser() parser.optionxform = str # str = case sensitive option names parser.read(file_path) message = 'Cannot find a section named "general" in %s' % file_path - assert parser.has_section('general'), message - metadata.extend(parser.items('general')) + assert parser.has_section("general"), message + metadata.extend(parser.items("general")) for expectation in required_metadata: - message = ('Cannot find metadata "%s" in metadata source (%s).' % ( - expectation, file_path)) + message = 'Cannot find metadata "%s" in metadata source (%s).' % ( + expectation, + file_path, + ) self.assertIn(expectation, dict(metadata), message) -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/test/test_profile_manager_dialog.py b/test/test_profile_manager_dialog.py index 2c8e928..98bf12e 100644 --- a/test/test_profile_manager_dialog.py +++ b/test/test_profile_manager_dialog.py @@ -1,4 +1,3 @@ -# coding=utf-8 """Dialog test. .. note:: This program is free software; you can redistribute it and/or modify @@ -8,17 +7,17 @@ """ -__author__ = 'dominik.szill@wheregroup.com' -__date__ = '2020-03-17' -__copyright__ = 'Copyright 2020, Dominik Szill / WhereGroup GmbH' +__author__ = "dominik.szill@wheregroup.com" +__date__ = "2020-03-17" +__copyright__ = "Copyright 2020, Dominik Szill / WhereGroup GmbH" import unittest -from qgis.PyQt.QtGui import QDialogButtonBox, QDialog +from qgis.PyQt.QtGui import QDialog, QDialogButtonBox +from utilities import get_qgis_app -from profile_manager_dialog import ProfileManagerDialog +from profile_manager.profile_manager_dialog import ProfileManagerDialog -from utilities import get_qgis_app QGIS_APP = get_qgis_app() @@ -48,8 +47,8 @@ def test_dialog_cancel(self): result = self.dialog.result() self.assertEqual(result, QDialog.Rejected) + if __name__ == "__main__": suite = unittest.makeSuite(ProfileManagerDialogTest) runner = unittest.TextTestRunner(verbosity=2) runner.run(suite) - diff --git a/test/test_translations.py b/test/test_translations.py index 035dc62..2faabdd 100644 --- a/test/test_translations.py +++ b/test/test_translations.py @@ -1,4 +1,3 @@ -# coding=utf-8 """Safe Translations Test. .. note:: This program is free software; you can redistribute it and/or modify @@ -7,14 +6,14 @@ (at your option) any later version. """ + from .utilities import get_qgis_app -__author__ = 'ismailsunni@yahoo.co.id' -__date__ = '12/10/2011' -__copyright__ = ('Copyright 2012, Australia Indonesia Facility for ' - 'Disaster Reduction') -import unittest +__author__ = "ismailsunni@yahoo.co.id" +__date__ = "12/10/2011" +__copyright__ = "Copyright 2012, Australia Indonesia Facility for " "Disaster Reduction" import os +import unittest from qgis.PyQt.QtCore import QCoreApplication, QTranslator @@ -26,26 +25,25 @@ class SafeTranslationsTest(unittest.TestCase): def setUp(self): """Runs before each test.""" - if 'LANG' in iter(os.environ.keys()): - os.environ.__delitem__('LANG') + if "LANG" in iter(os.environ.keys()): + os.environ.__delitem__("LANG") def tearDown(self): """Runs after each test.""" - if 'LANG' in iter(os.environ.keys()): - os.environ.__delitem__('LANG') + if "LANG" in iter(os.environ.keys()): + os.environ.__delitem__("LANG") def test_qgis_translations(self): """Test that translations work.""" parent_path = os.path.join(__file__, os.path.pardir, os.path.pardir) dir_path = os.path.abspath(parent_path) - file_path = os.path.join( - dir_path, 'i18n', 'af.qm') + file_path = os.path.join(dir_path, "i18n", "af.qm") translator = QTranslator() translator.load(file_path) QCoreApplication.installTranslator(translator) - expected_message = 'Goeie more' - real_message = QCoreApplication.translate("@default", 'Good morning') + expected_message = "Goeie more" + real_message = QCoreApplication.translate("@default", "Good morning") self.assertEqual(real_message, expected_message) diff --git a/test/utilities.py b/test/utilities.py index be7ee3b..d441403 100644 --- a/test/utilities.py +++ b/test/utilities.py @@ -1,11 +1,9 @@ -# coding=utf-8 """Common functionality used by regression tests.""" -import sys import logging +import sys - -LOGGER = logging.getLogger('QGIS') +LOGGER = logging.getLogger("QGIS") QGIS_APP = None # Static variable used to hold hand to running QGIS app CANVAS = None PARENT = None @@ -13,7 +11,7 @@ def get_qgis_app(): - """ Start one QGIS application to test against. + """Start one QGIS application to test against. :returns: Handle to QGIS app, canvas, iface and parent. If there are any errors the tuple members will be returned as None. @@ -23,9 +21,10 @@ def get_qgis_app(): """ try: - from qgis.PyQt import QtGui, QtCore from qgis.core import QgsApplication from qgis.gui import QgsMapCanvas + from qgis.PyQt import QtCore, QtGui + from .qgis_interface import QgisInterface except ImportError: return None, None, None, None @@ -34,7 +33,7 @@ def get_qgis_app(): if QGIS_APP is None: gui_flag = True # All test will run qgis in gui mode - #noinspection PyPep8Naming + # noinspection PyPep8Naming QGIS_APP = QgsApplication(sys.argv, gui_flag) # Make sure QGIS_PREFIX_PATH is set in your env if needed! QGIS_APP.initQgis() @@ -43,19 +42,19 @@ def get_qgis_app(): global PARENT # pylint: disable=W0603 if PARENT is None: - #noinspection PyPep8Naming + # noinspection PyPep8Naming PARENT = QtGui.QWidget() global CANVAS # pylint: disable=W0603 if CANVAS is None: - #noinspection PyPep8Naming + # noinspection PyPep8Naming CANVAS = QgsMapCanvas(PARENT) CANVAS.resize(QtCore.QSize(400, 400)) global IFACE # pylint: disable=W0603 if IFACE is None: # QgisInterface is a stub implementation of the QGIS plugin interface - #noinspection PyPep8Naming + # noinspection PyPep8Naming IFACE = QgisInterface(CANVAS) return QGIS_APP, CANVAS, IFACE, PARENT