From 9cc951a9a72c47bfc11152e265ddd90b7959a39b Mon Sep 17 00:00:00 2001 From: David Slusser Date: Mon, 21 Sep 2026 21:54:44 -0700 Subject: [PATCH 1/2] updating eventbrite task to support slug and id based urls --- src/django_project/tests/unit/web/test_tasks.py | 11 +++++++++++ src/django_project/web/tasks.py | 16 ++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/django_project/tests/unit/web/test_tasks.py b/src/django_project/tests/unit/web/test_tasks.py index b03a543..17abb28 100644 --- a/src/django_project/tests/unit/web/test_tasks.py +++ b/src/django_project/tests/unit/web/test_tasks.py @@ -36,6 +36,17 @@ def test_returns_clear_result_when_platform_link_is_missing(self, mock_get_event self.assertEqual(result, f"no Eventbrite links found for {self.group.name}") mock_get_events_for_organization.assert_not_called() + @patch("web.tasks.get_events_for_organization") + def test_reads_organizer_id_from_numeric_only_eventbrite_url(self, mock_get_events_for_organization): + self.link.url = "https://www.eventbrite.com/o/26948291755" + self.link.save() + mock_get_events_for_organization.return_value = [] + + result = ingest_future_eventbrite_events(self.group.pk) + + self.assertEqual(result, f"added 0 new events for {self.group.name}") + mock_get_events_for_organization.assert_called_once_with("26948291755") + @patch("web.tasks.get_event_details") @patch("web.tasks.get_events_for_organization") def test_ingests_event_without_primary_venue(self, mock_get_events_for_organization, mock_get_event_details): diff --git a/src/django_project/web/tasks.py b/src/django_project/web/tasks.py index 432210c..786e6f1 100644 --- a/src/django_project/web/tasks.py +++ b/src/django_project/web/tasks.py @@ -6,6 +6,7 @@ import time from datetime import timedelta from typing import Any +from urllib.parse import urlparse import requests from bs4 import BeautifulSoup @@ -42,6 +43,13 @@ def _truncate_for_model(model: type[Event], field_name: str, value: str | None) return value[:max_length] +def _get_eventbrite_organization_id(url: str) -> str | None: + """Extract the numeric organizer ID from an Eventbrite organization URL.""" + organization_slug = urlparse(url).path.rstrip("/").rsplit("/", maxsplit=1)[-1] + organization_id = organization_slug.rsplit("-", maxsplit=1)[-1] + return organization_id if organization_id.isdigit() else None + + @shared_task(time_limit=30, max_retries=0, name="web.test_task") def test_task() -> str: logger.info("test task starting") @@ -72,7 +80,9 @@ def ingest_eventbrite_organization_details(group_pk) -> str: updated = False group = TechGroup.objects.get(pk=group_pk) link = group.links.filter(name=f"{group.name} {group.platform.name} page").distinct()[0] - eb_group_id = link.url.split("-")[-1] + eb_group_id = _get_eventbrite_organization_id(link.url) + if not eb_group_id: + return f"invalid Eventbrite organization URL for {group.name}" organization_details = get_organization_details(eb_group_id) description = organization_details["long_description"]["text"] @@ -145,7 +155,9 @@ def ingest_future_eventbrite_events(group_pk) -> str: link: Any = group.links.filter(name=f"{group.name} {group.platform.name} page").distinct().first() if not link: return f"no {group.platform.name} links found for {group.name}" - eb_group_id: str = link.url.split("-")[-1] + eb_group_id = _get_eventbrite_organization_id(link.url) + if not eb_group_id: + return f"invalid Eventbrite organization URL for {group.name}" event_list: list = get_events_for_organization(eb_group_id) for item in event_list: event_details: dict = get_event_details(item["id"]) From 632e6e710f5e0d96eeb94af96bef43206878ec8b Mon Sep 17 00:00:00 2001 From: David Slusser Date: Mon, 21 Sep 2026 21:58:46 -0700 Subject: [PATCH 2/2] updates per PR comments --- .../tests/unit/web/test_tasks.py | 20 +++++++++++++++++++ src/django_project/web/tasks.py | 8 +++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/django_project/tests/unit/web/test_tasks.py b/src/django_project/tests/unit/web/test_tasks.py index 17abb28..491d41d 100644 --- a/src/django_project/tests/unit/web/test_tasks.py +++ b/src/django_project/tests/unit/web/test_tasks.py @@ -47,6 +47,26 @@ def test_reads_organizer_id_from_numeric_only_eventbrite_url(self, mock_get_even self.assertEqual(result, f"added 0 new events for {self.group.name}") mock_get_events_for_organization.assert_called_once_with("26948291755") + @patch("web.tasks.get_events_for_organization") + def test_rejects_non_eventbrite_urls(self, mock_get_events_for_organization): + self.link.url = "https://example.com/o/spokane-angel-alliance-26948291755" + self.link.save() + + result = ingest_future_eventbrite_events(self.group.pk) + + self.assertEqual(result, f"invalid Eventbrite organization URL for {self.group.name}") + mock_get_events_for_organization.assert_not_called() + + @patch("web.tasks.get_events_for_organization") + def test_rejects_non_organization_eventbrite_urls(self, mock_get_events_for_organization): + self.link.url = "https://www.eventbrite.com/e/spokane-angel-alliance-26948291755" + self.link.save() + + result = ingest_future_eventbrite_events(self.group.pk) + + self.assertEqual(result, f"invalid Eventbrite organization URL for {self.group.name}") + mock_get_events_for_organization.assert_not_called() + @patch("web.tasks.get_event_details") @patch("web.tasks.get_events_for_organization") def test_ingests_event_without_primary_venue(self, mock_get_events_for_organization, mock_get_event_details): diff --git a/src/django_project/web/tasks.py b/src/django_project/web/tasks.py index 786e6f1..32fe8eb 100644 --- a/src/django_project/web/tasks.py +++ b/src/django_project/web/tasks.py @@ -45,7 +45,13 @@ def _truncate_for_model(model: type[Event], field_name: str, value: str | None) def _get_eventbrite_organization_id(url: str) -> str | None: """Extract the numeric organizer ID from an Eventbrite organization URL.""" - organization_slug = urlparse(url).path.rstrip("/").rsplit("/", maxsplit=1)[-1] + parsed_url = urlparse(url) + if parsed_url.hostname not in {"eventbrite.com", "www.eventbrite.com"}: + return None + path_parts = parsed_url.path.rstrip("/").split("/") + if len(path_parts) != 3 or path_parts[1] != "o": + return None + organization_slug = path_parts[2] organization_id = organization_slug.rsplit("-", maxsplit=1)[-1] return organization_id if organization_id.isdigit() else None