Skip to content

Commit

Permalink
Merge pull request #8 from WhereGroup/cleanup_2023
Browse files Browse the repository at this point in the history
Clean up and fixes
  • Loading branch information
kannes authored Sep 24, 2024
2 parents 60a2706 + b70a2ca commit cee42f6
Show file tree
Hide file tree
Showing 60 changed files with 2,391 additions and 3,655 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
.idea
.pyc
__pycache__
.pyc
zip_build
37 changes: 0 additions & 37 deletions CHANGELOG

This file was deleted.

91 changes: 56 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,63 @@
### Profile Manager for QGIS3 ###
### Profile Manager for QGIS ###
A QGIS plugin for managing your profiles and data source connections.

### Plugin for managing your profiles and datasources ###
https://plugins.qgis.org/plugins/profile-manager/

### Installation ###
<a href="https://github.com/WhereGroup/profile-manager/assets/7661092/a64ea854-bff3-48a0-b1ff-83f3d4eaa5b7"><img src="https://github.com/WhereGroup/profile-manager/assets/7661092/a64ea854-bff3-48a0-b1ff-83f3d4eaa5b7" width="200"></a>
<a href="https://github.com/WhereGroup/profile-manager/assets/7661092/711faa86-c36c-40bc-92ab-7b73016996ae"><img src="https://github.com/WhereGroup/profile-manager/assets/7661092/711faa86-c36c-40bc-92ab-7b73016996ae" width="200"></a>
<a href="https://github.com/WhereGroup/profile-manager/assets/7661092/0c646930-88d8-45fe-81c8-4a5bf4501152"><img src="https://github.com/WhereGroup/profile-manager/assets/7661092/0c646930-88d8-45fe-81c8-4a5bf4501152" width="200"></a>
<a href="https://github.com/WhereGroup/profile-manager/assets/7661092/079665d6-e0ff-45fb-a65c-3b49cd9229de"><img src="https://github.com/WhereGroup/profile-manager/assets/7661092/079665d6-e0ff-45fb-a65c-3b49cd9229de" width="200"></a>

To install the plugin just copy the folder in your QGIS-Profile folder under ./python/plugins/
### Installation ###
To install the plugin manually just copy the folder into your QGIS profile directory under ./python/plugins/

- Windows Directory:
- C:\Users\{USER}\AppData\Roaming\QGIS\QGIS3\profiles\\{PROFILE}\python\plugins\
- Linux Directory:
- /home/{USER}/.local/share/QGIS/QGIS3/profiles/{PROFILE}/python/plugins
- Windows directory:
- `C:\Users\{USER}\AppData\Roaming\QGIS\QGIS3\profiles\{PROFILE}\python\plugins\`
- Linux directory:
- `~/.local/share/QGIS/QGIS3/profiles/{PROFILE}/python/plugins`
- MacOS directory:
- `~/Library/Application Support/QGIS/QGIS3/profiles`

### Features ###
- Create a new profile
- Creates and initiates a new profile
- Removing profile
- Removes a selected profile
- Copy profile
- Creates a copy of a selected profile with a new name
- Rename profile
- Renames the profile with a name provided by the user
- Importing data source connections from one profile to another
- Removing data source connections from a profile
- Removes the data source connection from the chosen SOURCE profile
- Importing (spatial) bookmarks
- Importing (data source) favourites
- Importing plugins
- Importing expression functions
- Importing models & scripts
- Importing some symbology types & label settings
- Importing QGIS UI settings (e.g. hidden toolbar items)

On all removal operations the user is being asked if they are certain
that he wants to delete given source/profile.
Additionally before every deletion a backup of the complete profiles
folder is created under the following directory:
- Windows directory:
- `C:\Users\{USER}\QGIS Profile Manager Backup\`
- Linux and MacOS directory:
- `~/QGIS Profile Manager Backup/`

### Known (current) limitations ###
- Not all data source connections might be recognized and imported/removed
- Not all data source connection types are supported
- Python expression functions are not supported
- Not all style things are supported, e.g. not 3D symbols, color ramps,
tags, etc.
- Errors might not always be communicated clearly so please TEST your
migrated configurations before discarding originals!

- General Features:
- Create a new profile
- Creates and initiates a new QGIS profile
- Reomving profile
- Removes the currently marked profile
- Copy profile
- Copies profile into a new one with a name provided by the user
- Rename profile
- Renames the profile with a name provided by the user
- Importing Datasources from one profile to another
(Datasources are chosen by checking them in a TreeView where each source is displayed)
- Removing Datasources from a profile
- Removes the datasources from the chosen SOURCE profile
- Importing bookmarks
- Importing favourites
- Importing plugins
- Importing functions
- Importing models & scripts
- Importing style & label settings
- Importing QGIS UI settings (f.e. hidden toolbar items)

On all removal operations the user is being asked if he is certain that he wants to delete given source/profile.
Additionally before every deletion a backup of the profiles folder is created under the follwing directory:
- Windows Directory:
- C:\Users\\{USER}\QGISBackup\
- Linux Directory:
- /home/{USER}/QGISBackup/
### Funding development ###
If you consider this plugin useful and would like to see it improved, e.g.
with support for more profile settings, becoming more stable, being more
thoroughly documented, leave the "experimental" plugin status or whatever
you desire, you can fund development. Contact us at [email protected]
1 change: 0 additions & 1 deletion __init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
/***************************************************************************
ProfileManager
Expand Down
7 changes: 0 additions & 7 deletions compile.bat

This file was deleted.

129 changes: 59 additions & 70 deletions datasources/Bookmarks/bookmark_handler.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,38 @@
from lxml import etree as et
from pathlib import Path
import sys


class BookmarkHandler:

def __init__(self, qgis_path, profile_manager):
self.profile_manager = profile_manager
self.qgis_path = qgis_path
self.bookmark_list = []
self.source_bookmark_file = ""
self.target_bookmark_file = ""
self.parser = et.XMLParser(remove_blank_text=True)

def parse_source_bookmarks(self):
"""Parses bookmarks from source xml"""
# get the element tree of the source file
try:
source_tree = et.parse(self.source_bookmark_file, self.parser)
self.insert_bookmarks_to_target_profile(source_tree)
except:
print("Oops!", sys.exc_info()[0], "occurred.")

def insert_bookmarks_to_target_profile(self, source_tree):
"""Inserts bookmarks into target xml file"""

from qgis.core import Qgis, QgsMessageLog


def import_bookmarks(source_bookmark_file: str, target_bookmark_file: str):
"""Imports spatial bookmarks from source to target profile.
Spatial bookmarks are stored in bookmarks.xml, e.g.:
<Bookmarks>
<Bookmark id="..." group="" extent="POLYGON((...))" name="Test Bookmark">
<spatialrefsys nativeFormat="Wkt">
...
</spatialrefsys>
</Bookmark>
...
</Bookmarks>
Args:
TODO
Returns:
error_message (str): An error message, if something XML related failed.
"""
# 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))

# check if target file exists
self.check_if_file_exists()
create_bookmark_file_if_not_exist(target_bookmark_file)
# get the element tree of the target file
# fill if empty
try:
target_tree = et.parse(self.target_bookmark_file, self.parser)
except et.XMLSyntaxError:
with open(self.target_bookmark_file, "w") as xmlTarget:
xmlTarget.write("<Bookmarks></Bookmarks>")

target_tree = et.parse(self.target_bookmark_file, self.parser)
target_tree = et.parse(target_bookmark_file, et.XMLParser(remove_blank_text=True))

# find all bookmark elements
source_root_tag = source_tree.findall('Bookmark')
Expand All @@ -43,48 +41,39 @@ def insert_bookmarks_to_target_profile(self, source_tree):
target_tree_root = target_tree.getroot()

# Remove duplicate entries to prevent piling data
target_tree_root = self.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:
target_tree_root.append(element)

# overwrite the xml file
et.ElementTree(target_tree_root)\
.write(self.target_bookmark_file, pretty_print=True, encoding='utf-8', xml_declaration=True)

@staticmethod
def remove_duplicates(source_toot_tag, target_tree, target_tree_root):
"""Removes duplicate entries"""
target_root_tag = target_tree.findall('Bookmark')
for s_element in source_toot_tag:
for t_element in target_root_tag:
if s_element.attrib["name"] == t_element.attrib["name"]:
target_tree_root.remove(t_element)

return target_tree_root

def check_if_file_exists(self):
"""Checks if file exists"""
target_file = Path(self.target_bookmark_file)
if target_file.is_file():
pass
else:
self.create_new_file()

def create_new_file(self):
"""Creates a new file"""
new_file = open(self.target_bookmark_file, "w")
new_file.write("<Bookmarks></Bookmarks>")
new_file.close()

def set_path_files(self, source_bookmark_file, target_bookmark_file):
"""Sets file path's"""
self.source_bookmark_file = source_bookmark_file
self.target_bookmark_file = target_bookmark_file





et.ElementTree(target_tree_root).write(
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
error = f"{type(e)}: {str(e)}"
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')
for s_element in source_root_tag:
for t_element in target_root_tag:
if s_element.attrib["name"] == t_element.attrib["name"]:
target_tree_root.remove(t_element)

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("<Bookmarks></Bookmarks>")

70 changes: 42 additions & 28 deletions datasources/Customizations/customization_handler.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,54 @@
from shutil import copy2
from os import path
from configparser import RawConfigParser
from os import path
from shutil import copy2

from ...utils import adjust_to_operating_system


def import_customizations(source_profile_path: str, target_profile_path: str):
"""Imports UI customizations from source to target profile.
Copies the whole QGISCUSTOMIZATION3.ini file and also transfers the [UI] section from QGIS3.ini if available
TODO fix discrepancy between [UI] and [Customization]! Which one(s) exist and what do we want to transfer?
class CustomizationHandler:
E.g.
[Customization]
Browser=true
Browser\AFS=false
...
def __init__(self, profile_manager):
self.profile_manager = profile_manager
self.path_source_customini = ""
self.path_target_customini = ""
self.parser = RawConfigParser()
self.parser.optionxform = str
Args:
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")
if path.exists(source_customini_path):
copy2(source_customini_path, target_customini_path)

def import_customizations(self):
if path.exists(self.path_source_customini):
copy2(self.path_source_customini, self.path_target_customini)
# 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")

ini_paths = self.profile_manager.get_ini_paths()
source_ini_parser = RawConfigParser()
source_ini_parser.optionxform = str # str = case-sensitive option names
source_ini_parser.read(source_qgis3ini_path)

self.parser.read(ini_paths["source"])
# 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 self.parser.has_section('UI'):
ui_data = dict(self.parser.items('UI'))
self.parser.clear()
self.parser.read(ini_paths["target"])
target_ini_parser = RawConfigParser()
target_ini_parser.optionxform = str # str = case-sensitive option names
target_ini_parser.read(target_qgis3ini_path)

for setting in ui_data:
if not self.parser.has_section("UI"):
self.parser["UI"] = {}

self.parser.set("UI", setting, ui_data[setting])
for setting in ui_data:
if not target_ini_parser.has_section("UI"):
target_ini_parser["UI"] = {}

with open(ini_paths["target"], 'w') as qgisconf:
self.parser.write(qgisconf)
target_ini_parser.set("UI", setting, ui_data[setting])

def set_path_files(self, source, target):
self.path_source_customini = self.profile_manager.adjust_to_operating_system(source + "QGIS/QGISCUSTOMIZATION3.ini")
self.path_target_customini = self.profile_manager.adjust_to_operating_system(target + "QGIS/QGISCUSTOMIZATION3.ini")
with open(target_qgis3ini_path, 'w') as qgisconf:
target_ini_parser.write(qgisconf, space_around_delimiters=False)

Loading

0 comments on commit cee42f6

Please sign in to comment.