From f1656557cf3fc52ab3771db842759d4bb20a85ce Mon Sep 17 00:00:00 2001 From: Rohit Kamath Date: Sun, 19 Oct 2025 11:37:29 -0400 Subject: [PATCH 01/24] Calendar: add calendar views, templates, tests; integrate into URLs and sidebar --- tcf_core/context_processors.py | 6 ++ tcf_core/settings/base.py | 15 +++++ tcf_website/services/presence.py | 42 ++++++++++++++ tcf_website/templates/base/sidebar.html | 13 +++++ .../templates/calendar/calendar_overview.html | 55 +++++++++++++++++++ tcf_website/templates/calendar/disabled.html | 11 ++++ tcf_website/tests/test_calendar_overview.py | 14 +++++ tcf_website/urls.py | 1 + tcf_website/views/__init__.py | 1 + tcf_website/views/calendar.py | 53 ++++++++++++++++++ 10 files changed, 211 insertions(+) create mode 100644 tcf_website/services/presence.py create mode 100644 tcf_website/templates/calendar/calendar_overview.html create mode 100644 tcf_website/templates/calendar/disabled.html create mode 100644 tcf_website/tests/test_calendar_overview.py create mode 100644 tcf_website/views/calendar.py diff --git a/tcf_core/context_processors.py b/tcf_core/context_processors.py index b3897a173..642fb9d6e 100644 --- a/tcf_core/context_processors.py +++ b/tcf_core/context_processors.py @@ -29,3 +29,9 @@ def searchbar_context(request): "semesters": recent_semesters, } return context + + +def flags(_request): + return { + "ENABLE_CLUB_CALENDAR": getattr(settings, "ENABLE_CLUB_CALENDAR", False), + } diff --git a/tcf_core/settings/base.py b/tcf_core/settings/base.py index 71bed5652..e37e03200 100644 --- a/tcf_core/settings/base.py +++ b/tcf_core/settings/base.py @@ -132,6 +132,7 @@ "django.contrib.messages.context_processors.messages", "tcf_core.context_processors.base", "tcf_core.context_processors.searchbar_context", + "tcf_core.context_processors.flags", ], }, }, @@ -251,3 +252,17 @@ # Toxicity threshold for filtering reviews TOXICITY_THRESHOLD = 74 + +# Presence / Calendar feature +PRESENCE_SUBDOMAIN = env.str("PRESENCE_SUBDOMAIN", default="virginia") +ENABLE_CLUB_CALENDAR = env.bool("ENABLE_CLUB_CALENDAR", default=True) +PRESENCE_TIMEOUT_SECONDS = env.int("PRESENCE_TIMEOUT_SECONDS", default=8) +PRESENCE_CACHE_SECONDS = env.int("PRESENCE_CACHE_SECONDS", default=300) + +# Ensure a default cache exists if not configured elsewhere +CACHES = { + "default": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "LOCATION": "tcf-default", + } +} diff --git a/tcf_website/services/presence.py b/tcf_website/services/presence.py new file mode 100644 index 000000000..ba6c67ee7 --- /dev/null +++ b/tcf_website/services/presence.py @@ -0,0 +1,42 @@ +import requests +import backoff +from django.conf import settings +from django.core.cache import cache + +BASE = f"https://api.presence.io/{settings.PRESENCE_SUBDOMAIN}/v1" +CACHE_TTL = getattr(settings, "PRESENCE_CACHE_SECONDS", 300) +TIMEOUT = getattr(settings, "PRESENCE_TIMEOUT_SECONDS", 8) + + +def _cache_key(key: str) -> str: + return f"presence::{settings.PRESENCE_SUBDOMAIN}::{key}" + + +@backoff.on_exception(backoff.expo, (requests.RequestException,), max_tries=3) +def _get(url: str, params=None): + resp = requests.get(url, params=params or {}, timeout=TIMEOUT) + resp.raise_for_status() + return resp.json() + + +def get_events(): + """ + Returns a list of upcoming events from Presence. Cached briefly to avoid rate limits. + """ + key = _cache_key("events_all") + data = cache.get(key) + if data is None: + data = _get(f"{BASE}/events") + cache.set(key, data, CACHE_TTL) + return data + + +def get_event_details(event_uri: str): + key = _cache_key(f"event::{event_uri}") + data = cache.get(key) + if data is None: + data = _get(f"{BASE}/events/{event_uri}") + cache.set(key, data, CACHE_TTL) + return data + + diff --git a/tcf_website/templates/base/sidebar.html b/tcf_website/templates/base/sidebar.html index 5a50bd131..8ae28c147 100644 --- a/tcf_website/templates/base/sidebar.html +++ b/tcf_website/templates/base/sidebar.html @@ -60,6 +60,19 @@