From 55d68cb18f809c76a09ff0af0e47825668fb6988 Mon Sep 17 00:00:00 2001 From: bensteUEM Date: Wed, 29 Jul 2026 15:15:08 +0200 Subject: [PATCH 1/2] fix(events): agenda download with new endpoint and params (#282) --- .vscode/settings.json | 4 ++- churchtools_api/events.py | 44 +++++++++------------------- tests/test_churchtools_api_events.py | 32 ++++++++++---------- 3 files changed, 34 insertions(+), 46 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index faabffd..2886d7e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,5 +5,7 @@ "editor.defaultFormatter": "charliermarsh.ruff", "editor.formatOnSave": false }, - "python.formatting.provider": "none" + "python.formatting.provider": "none", + "python-envs.defaultEnvManager": "ms-python.python:venv", + "python-envs.defaultPackageManager": "ms-python.python:pip" } \ No newline at end of file diff --git a/churchtools_api/events.py b/churchtools_api/events.py index d414b97..f6441df 100644 --- a/churchtools_api/events.py +++ b/churchtools_api/events.py @@ -329,15 +329,15 @@ def set_event_services_counts_ajax( ) return False - def get_event_agenda(self, eventId: int) -> list: + def get_event_agenda(self, event_id: int) -> list: """Retrieve agenda for event by ID from ChurchTools. Arguments: - eventId: number of the event + event_id: number of the event Returns: list of event agenda items. """ - url = self.domain + f"/api/events/{eventId}/agenda" + url = self.domain + f"/api/events/{event_id}/agenda" headers = {"accept": "application/json"} response = self.session.get(url=url, headers=headers) @@ -354,23 +354,25 @@ def get_event_agenda(self, eventId: int) -> list: return None def export_event_agenda( - self, target_format: str, target_path: str = "./downloads", **kwargs: dict + self, + event_id: int, + target_format: str, + target_path: str = "./downloads", + **kwargs: dict, ) -> bool: """Exports the agenda as zip file for imports in presenter-programs. Parameters: + event_id: event id to check for agenda id should be exported target_format: fileformat or name of presentation software which should be supported. - Supported formats are 'SONG_BEAMER', 'PRO_PRESENTER6' - and 'PRO_PRESENTER7' + Supported formats are 'SONG_BEAMER', 'PRO_PRESENTER_6' + and 'PRO_PRESENTER_7' target_path: Filepath of the file which should be exported (including filename) kwargs: additional keywords as listed below Keywords: - eventId: event id to check for agenda id should be exported - agendaId: agenda id of the agenda which should be exported - DO NOT combine with eventId because it will be overwritten! append_arrangement: if True, the name of the arrangement will be included within the agenda caption export_Songs: if True, the songfiles will be in the @@ -379,37 +381,19 @@ def export_event_agenda( Returns: if successful. """ - if "eventId" in kwargs: - if "agendaId" in kwargs: - logger.warning( - "Invalid use of params - can not combine eventId and agendaId!", - ) - else: - agenda = self.get_event_agenda(eventId=kwargs["eventId"]) - agendaId = agenda["id"] - elif "agendaId" in kwargs: - agendaId = kwargs["agendaId"] - else: - logger.warning("Missing event or agendaId") - return False - # note: target path can be either a zip-file defined before function # call or just a folder is_zip = target_path.lower().endswith(".zip") if not is_zip: target_path = Path(target_path) target_path.mkdir(parents=True, exist_ok=True) - - if "eventId" in kwargs: - new_file_name = "{}_{}.zip".format(agenda["name"], target_format) - else: - new_file_name = f"{target_format}_agendaId_{agendaId}.zip" + new_file_name = f"{target_format}_event_id:{event_id}.zip" target_path = target_path / new_file_name - url = f"{self.domain}/api/agendas/{agendaId}/export" + url = f"{self.domain}/api/events/{event_id}/agenda/export" # NOTE the stream=True parameter below - params = {"target": target_format} + params = {"format": target_format} json_data = {} # The following 3 parameter 'appendArrangement', 'exportSongs' and # 'withCategory' are mandatory from the churchtools API side: diff --git a/tests/test_churchtools_api_events.py b/tests/test_churchtools_api_events.py index 8126028..4b94837 100644 --- a/tests/test_churchtools_api_events.py +++ b/tests/test_churchtools_api_events.py @@ -267,7 +267,17 @@ def test_get_event_agenda(self) -> None: result = self.api.get_event_agenda(eventId) assert result is not None - def test_export_event_agenda(self, caplog: pytest.LogCaptureFixture) -> None: + @pytest.mark.parametrize( + "target_format", + [ + pytest.param("SONG_BEAMER", id="song_beamer"), + pytest.param("PRO_PRESENTER_7", id="pro_presenter_7"), + pytest.param("PRO_PRESENTER_6", id="pro_presenter_6"), + ], + ) + def test_export_event_agenda( + self, target_format: str + ) -> None: """IMPORTANT - This test method and the parameters used depend on target system! Test function to download an Event Agenda file package for e.g. Songbeamer @@ -275,14 +285,7 @@ def test_export_event_agenda(self, caplog: pytest.LogCaptureFixture) -> None: On ELKW1610.KRZ.TOOLS event ID 484 is an existing Event with schedule (20th. Nov 2022) """ - eventId = 484 - agendaId = self.api.get_event_agenda(eventId)["id"] - - caplog.clear() - with caplog.at_level(level=logging.WARNING, logger="churchtools_api.events"): - download_result = self.api.export_event_agenda("SONG_BEAMER") - assert len(caplog.records) == 1 - assert not download_result + SAMPLE_EVENT_ID = 484 download_dir = Path("downloads") for root, dirs, files in download_dir.walk(top_down=False): @@ -291,13 +294,12 @@ def test_export_event_agenda(self, caplog: pytest.LogCaptureFixture) -> None: for name in dirs: (root / name).rmdir() - download_result = self.api.export_event_agenda("SONG_BEAMER", agendaId=agendaId) - assert download_result - - download_result = self.api.export_event_agenda("SONG_BEAMER", eventId=eventId) + download_result = self.api.export_event_agenda( + event_id=SAMPLE_EVENT_ID, target_format=target_format + ) assert download_result - EXPECTED_NUMBER_OF_FILES = 2 + EXPECTED_NUMBER_OF_FILES = 1 assert len(os.listdir("downloads")) == EXPECTED_NUMBER_OF_FILES def test_get_services(self) -> None: @@ -400,7 +402,7 @@ def test_get_event_agenda_docx(self) -> None: SAMPLE_EVENT_ID = 4102 SAMPLE_SELECTED_SERVICES = [1, 3, 4, 7, 5, 6] - agenda = self.api.get_event_agenda(eventId=SAMPLE_EVENT_ID) + agenda = self.api.get_event_agenda(event_id=SAMPLE_EVENT_ID) service_groups = self.api.get_event_masterdata( resultClass="serviceGroups", returnAsDict=True ) From 0e98fb312f733194102b6201fc37124dfa5a2db7 Mon Sep 17 00:00:00 2001 From: bensteUEM Date: Wed, 29 Jul 2026 15:31:17 +0200 Subject: [PATCH 2/2] fix: updated CT bugfix rtype changed --- churchtools_api/groups.py | 4 +--- tests/test_churchtools_api_groups.py | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/churchtools_api/groups.py b/churchtools_api/groups.py index bb8ab5f..ae69851 100644 --- a/churchtools_api/groups.py +++ b/churchtools_api/groups.py @@ -517,9 +517,7 @@ def add_group_member(self, group_id: int, person_id: int, **kwargs: dict) -> dic if response.status_code == requests.codes.ok: response_content = json.loads(response.content) - # For unknown reasons the endpoint returns a list of items instead - # of a single item as specified in the API documentation. - return response_content["data"][0].copy() + return response_content["data"].copy() logger.warning( "%s Something went wrong adding group member: %s", diff --git a/tests/test_churchtools_api_groups.py b/tests/test_churchtools_api_groups.py index 8415213..d295fdd 100644 --- a/tests/test_churchtools_api_groups.py +++ b/tests/test_churchtools_api_groups.py @@ -113,7 +113,7 @@ def test_get_group_permissions(self) -> None: IMPORTANT - This test method and the parameters used depend on target system! """ SAMPLE_GROUP_ID = 103 - EXPECTED_NUMNER_OF_PERMISSIONS = 2 + EXPECTED_NUMNER_OF_PERMISSIONS = 20 permissions = self.api.get_group_permissions(group_id=SAMPLE_GROUP_ID) assert permissions["churchdb"]["+see group"] == EXPECTED_NUMNER_OF_PERMISSIONS @@ -441,7 +441,7 @@ def test_get_parent_groups(self) -> None: parent_groups = self.api.get_parent_groups(group_id=SAMPLE_GROUP_ID_CHILD) - assert isinstance(parent_groups, (list, type(None))) + assert isinstance(parent_groups, list | type(None)) if parent_groups is not None and len(parent_groups) > 0: for parent_group in parent_groups: assert isinstance(parent_group, dict) @@ -458,7 +458,7 @@ def test_get_child_groups(self) -> None: SAMPLE_GROUP_ID_CHILD = 2170 child_groups = self.api.get_child_groups(group_id=SAMPLE_GROUP_ID_PARENT) - assert isinstance(child_groups, (list, type(None))) + assert isinstance(child_groups, list | type(None)) if child_groups is not None and len(child_groups) > 0: for child_group in child_groups: assert isinstance(child_group, dict)