From a6a790a0b9d5a4cdc2cc9bbfae782ec53607321b Mon Sep 17 00:00:00 2001 From: signedav Date: Tue, 3 Dec 2024 16:00:35 +0100 Subject: [PATCH 1/5] variables as toppings --- toppingmaker/exportsettings.py | 2 ++ toppingmaker/projecttopping.py | 26 +++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/toppingmaker/exportsettings.py b/toppingmaker/exportsettings.py index bff4966..ea9396c 100644 --- a/toppingmaker/exportsettings.py +++ b/toppingmaker/exportsettings.py @@ -88,6 +88,8 @@ def __init__(self): self.mapthemes = [] # keys of custom variables to be exported self.variables = [] + # list of variable keys that are defined as paths and should be resolved + self.path_variables = [] # names of layouts self.layouts = [] diff --git a/toppingmaker/projecttopping.py b/toppingmaker/projecttopping.py index 1eae40c..374f51f 100644 --- a/toppingmaker/projecttopping.py +++ b/toppingmaker/projecttopping.py @@ -421,13 +421,37 @@ def make_items( project: QgsProject, export_settings: ExportSettings, ): + self.clear() for variable_key in export_settings.variables: variable_value = QgsExpressionContextUtils.projectScope( project ).variable(variable_key) + + # if it's defined as path variable, we have to expose it as toppingfile + if variable_key in export_settings.path_variables: + os.makedirs(self.temporary_toppingfile_dir, exist_ok=True) + temporary_toppingfile_path = os.path.join( + self.temporary_toppingfile_dir, os.path.basename(variable_value) + ) + variable_value = temporary_toppingfile_path + self[variable_key] = variable_value or None + def item_dict(self, target: Target): + resolved_items = {} + for variable_name in self.keys(): + # here we need to know if it's a pathvariable or not + # this has to be overthought... it's not working like this + if path: + resolved_item = {} + resolved_item["templatefile"] = target.toppingfile_link( + ProjectTopping.LAYOUTTEMPLATE_TYPE, + self[layout_name]["templatefile"], + ) + resolved_items[layout_name] = resolved_item + return resolved_items + class Properties(dict): """ A dict object of dict items describing a selection of projet properties @@ -613,7 +637,7 @@ def _projecttopping_dict(self, target: Target): mapthemes_dict = dict(self.mapthemes) if mapthemes_dict: projecttopping_dict["mapthemes"] = mapthemes_dict - variables_dict = dict(self.variables) + variables_dict = self.variables.item_dict(target) if variables_dict: projecttopping_dict["variables"] = variables_dict properties_dict = dict(self.properties) From bb20149fec27fea83d93552b1c61af3f751e3bc2 Mon Sep 17 00:00:00 2001 From: signedav Date: Thu, 5 Dec 2024 16:23:19 +0100 Subject: [PATCH 2/5] resolvetoppings --- tests/test_toppingmaker.py | 12 +++++++++++- toppingmaker/projecttopping.py | 36 ++++++++++++++++++++-------------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/tests/test_toppingmaker.py b/tests/test_toppingmaker.py index 8327526..bc37645 100644 --- a/tests/test_toppingmaker.py +++ b/tests/test_toppingmaker.py @@ -703,6 +703,11 @@ def _make_project_and_export_settings(self): QgsExpressionContextUtils.setProjectVariable( project, "Variable with Structure", ["Not", "The", "Normal", 815, "Case"] ) + QgsExpressionContextUtils.setProjectVariable( + project, + "Path variable", + [os.path.join(self.projecttopping_test_path, "validconfig.ini")], + ) # create layouts layout = QgsPrintLayout(project) @@ -788,7 +793,12 @@ def _make_project_and_export_settings(self): export_settings.mapthemes = ["French Theme", "Robot Theme"] # define the custom variables to export - export_settings.variables = ["First Variable", "Variable with Structure"] + export_settings.variables = [ + "First Variable", + "Variable with Structure", + "Path variable", + ] + export_settings.path_variables = ["Path variable"] # define the layouts to export export_settings.layouts = ["Layout One", "Layout Three"] diff --git a/toppingmaker/projecttopping.py b/toppingmaker/projecttopping.py index 374f51f..397f346 100644 --- a/toppingmaker/projecttopping.py +++ b/toppingmaker/projecttopping.py @@ -61,6 +61,7 @@ class ProjectTopping(QObject): LAYERDEFINITION_TYPE = "layerdefinition" LAYERSTYLE_TYPE = "layerstyle" LAYOUTTEMPLATE_TYPE = "layouttemplate" + GENERIC_TYPE = "generic" class TreeItemProperties: """ @@ -414,6 +415,8 @@ def make_items( class Variables(dict): """ A dict object of dict items describing a variable according to the variable keys listed in the ExportSettings passed on parsing the QGIS project. + The items have the keys 'name' and (optional) 'ispath' for the case that it's a path that needs to be resolved for the topping. + To define what variables are paths this needs to be set in the ExportSettings path_variables. """ def make_items( @@ -424,6 +427,8 @@ def make_items( self.clear() for variable_key in export_settings.variables: + variable_item = {} + variable_value = QgsExpressionContextUtils.projectScope( project ).variable(variable_key) @@ -434,22 +439,23 @@ def make_items( temporary_toppingfile_path = os.path.join( self.temporary_toppingfile_dir, os.path.basename(variable_value) ) - variable_value = temporary_toppingfile_path + variable_item["value"] = temporary_toppingfile_path + variable_item["ispath"] = True + else: + variable_item["value"] = variable_value - self[variable_key] = variable_value or None + self[variable_key] = variable_item or None - def item_dict(self, target: Target): + def resolved_dict(self, target: Target): resolved_items = {} - for variable_name in self.keys(): - # here we need to know if it's a pathvariable or not - # this has to be overthought... it's not working like this - if path: - resolved_item = {} - resolved_item["templatefile"] = target.toppingfile_link( - ProjectTopping.LAYOUTTEMPLATE_TYPE, - self[layout_name]["templatefile"], + for variable_key in self.keys(): + resolved_value = self[variable_key].get("value") + if self[variable_key].get("ispath", False): + resolved_value = target.toppingfile_link( + ProjectTopping.GENERIC_TYPE, + self[variable_key].get("value"), ) - resolved_items[layout_name] = resolved_item + resolved_items[variable_key] = resolved_value return resolved_items class Properties(dict): @@ -637,9 +643,9 @@ def _projecttopping_dict(self, target: Target): mapthemes_dict = dict(self.mapthemes) if mapthemes_dict: projecttopping_dict["mapthemes"] = mapthemes_dict - variables_dict = self.variables.item_dict(target) - if variables_dict: - projecttopping_dict["variables"] = variables_dict + variables_resolved_dict = self.variables.resolved_dict(target) + if variables_resolved_dict: + projecttopping_dict["variables"] = variables_resolved_dict properties_dict = dict(self.properties) if properties_dict: projecttopping_dict["properties"] = properties_dict From 31784e237952f3ebc71b8f85d14cd556a9debbc2 Mon Sep 17 00:00:00 2001 From: signedav Date: Thu, 5 Dec 2024 17:38:18 +0100 Subject: [PATCH 3/5] make path absolute to use it as variablepath --- toppingmaker/projecttopping.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/toppingmaker/projecttopping.py b/toppingmaker/projecttopping.py index 397f346..3154b7a 100644 --- a/toppingmaker/projecttopping.py +++ b/toppingmaker/projecttopping.py @@ -435,11 +435,13 @@ def make_items( # if it's defined as path variable, we have to expose it as toppingfile if variable_key in export_settings.path_variables: - os.makedirs(self.temporary_toppingfile_dir, exist_ok=True) - temporary_toppingfile_path = os.path.join( - self.temporary_toppingfile_dir, os.path.basename(variable_value) - ) - variable_item["value"] = temporary_toppingfile_path + path = variable_value + if project.homePath() and not os.path.isabs(variable_value): + # if it's a saved project and the path is not absolute, make it absolute + path = os.path.join( + variable_value, project.homePath(), variable_value + ) + variable_item["value"] = path variable_item["ispath"] = True else: variable_item["value"] = variable_value From e938bac92abc38fed533ef73520c92c746669799 Mon Sep 17 00:00:00 2001 From: signedav Date: Fri, 6 Dec 2024 11:56:50 +0100 Subject: [PATCH 4/5] test readme --- tests/README.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/README.md b/tests/README.md index 64ddc6e..d201ad5 100644 --- a/tests/README.md +++ b/tests/README.md @@ -6,13 +6,7 @@ with a database, so your own postgres installation is not affected at all. To run the tests, go to the main directory of the project and do -```sh -export QGIS_TEST_VERSION=latest # See https://hub.docker.com/r/qgis/qgis/tags/ -export GITHUB_WORKSPACE=$PWD # only for local execution -docker run -v ${GITHUB_WORKSPACE}:/usr/src -w /usr/src opengisch/qgis:${QGIS_TEST_VERSION} sh -c 'xvfb-run pytest-3' -``` - In one line, removing all containers. ```sh -QGIS_TEST_VERSION=latest GITHUB_WORKSPACE=$PWD docker run -v ${GITHUB_WORKSPACE}:/usr/src -w /usr/src opengisch/qgis:${QGIS_TEST_VERSION} sh -c 'xvfb-run pytest-3' +docker run -v $PWD:/usr/src -w /usr/src opengisch/qgis:latest sh -c 'xvfb-run pytest-3' ``` From 55d571d095095310e362eab569ed5be3d423e2c3 Mon Sep 17 00:00:00 2001 From: signedav Date: Fri, 6 Dec 2024 12:31:37 +0100 Subject: [PATCH 5/5] tests --- tests/test_toppingmaker.py | 32 +++++++++++++++++++++++--------- tests/testdata/validConfig.ini | 7 +++++++ 2 files changed, 30 insertions(+), 9 deletions(-) create mode 100644 tests/testdata/validConfig.ini diff --git a/tests/test_toppingmaker.py b/tests/test_toppingmaker.py index bc37645..5ce3c61 100644 --- a/tests/test_toppingmaker.py +++ b/tests/test_toppingmaker.py @@ -43,6 +43,9 @@ class ToppingMakerTest(unittest.TestCase): def setUpClass(cls): """Run before all tests.""" cls.basetestpath = tempfile.mkdtemp() + cls.testdata_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "testdata" + ) cls.projecttopping_test_path = os.path.join(cls.basetestpath, "projecttopping") def test_target(self): @@ -143,9 +146,11 @@ def test_parse_project_with_mapthemes(self): # check variables variables = project_topping.variables # Anyway in practice no spaces should be used to be able to access them in the expressions like @first_variable - assert variables.get("First Variable") == "This is a test value." + assert variables.get("First Variable") + assert variables.get("First Variable")["value"] == "This is a test value." # QGIS is currently (3.29) not able to store structures in the project file. Still... - assert variables.get("Variable with Structure") == [ + assert variables.get("Variable with Structure") + assert variables.get("Variable with Structure")["value"] == [ "Not", "The", "Normal", @@ -416,11 +421,18 @@ def test_generate_files(self): "Case", ] foundVariableWithStructure = True + if variable_key == "Validation Path Variable": + assert ( + projecttopping_data["variables"][variable_key] + == "freddys_projects/this_specific_project/generic/freddys_validConfig.ini" + ) + foundVariableWithPath = True variable_count += 1 - assert variable_count == 2 + assert variable_count == 3 assert foundFirstVariable assert foundVariableWithStructure + assert foundVariableWithPath # check transaction mode with open(projecttopping_file_path) as yamlfile: @@ -471,12 +483,13 @@ def test_generate_files(self): countchecked = 0 - # there should be 21 toppingfiles: + # there should be 22 toppingfiles: # - one project topping # - 2 x 3 qlr files (two times since the layers are multiple times in the tree) # - 2 x 6 qml files (one layers with 3 styles, one layer with 2 styles and one layer with one style - and two times since the layers are multiple times in the tree) # - 2 qpt template files - assert len(target.toppingfileinfo_list) == 21 + # - 1 generic file (validation.ini) what is created by variable + assert len(target.toppingfileinfo_list) == 22 for toppingfileinfo in target.toppingfileinfo_list: self.print_info(toppingfileinfo["path"]) @@ -705,8 +718,8 @@ def _make_project_and_export_settings(self): ) QgsExpressionContextUtils.setProjectVariable( project, - "Path variable", - [os.path.join(self.projecttopping_test_path, "validconfig.ini")], + "Validation Path Variable", + os.path.join(self.testdata_path, "validConfig.ini"), ) # create layouts @@ -796,9 +809,10 @@ def _make_project_and_export_settings(self): export_settings.variables = [ "First Variable", "Variable with Structure", - "Path variable", + "Validation Path Variable", ] - export_settings.path_variables = ["Path variable"] + # content of this variable should be exported as toppingfile + export_settings.path_variables = ["Validation Path Variable"] # define the layouts to export export_settings.layouts = ["Layout One", "Layout Three"] diff --git a/tests/testdata/validConfig.ini b/tests/testdata/validConfig.ini new file mode 100644 index 0000000..1d2b550 --- /dev/null +++ b/tests/testdata/validConfig.ini @@ -0,0 +1,7 @@ +["PARAMETER"] +validation="on" +additionalModels="SH_ProjektdatenDB_Naturschutz_V1_0_AddChecks" + +["SH_ProjektdatenDB_Naturschutz_V1_0_AddChecks.SH_ProjektdatenDB_Naturschutz_V1_0_Validierung.v_Mitarbeitende.checkNameDuplikate"] +multiplicity="on" +type="on"