introduce max search horizon config

Adds `MAX_SEARCH_HORIZON` environment variable to control the
maximum range of days for search operations.

Splits longer search periods into multiple requests, preventing
potential performance issues or timeouts with large datasets.
This commit is contained in:
Marc Koch 2025-08-19 17:04:24 +02:00
parent 13dc40302c
commit 46e62a94ed
Signed by: marc
GPG Key ID: 12406554CFB028B9
2 changed files with 38 additions and 20 deletions

View File

@ -16,6 +16,7 @@ services:
- SMTP_SERVER=your.smtp.server # adjust this - SMTP_SERVER=your.smtp.server # adjust this
- SMTP_PORT=587 # adjust this if necessary - SMTP_PORT=587 # adjust this if necessary
- SMTP_SENDER_NAME=Room Booking System # adjust this if you want - SMTP_SENDER_NAME=Room Booking System # adjust this if you want
- MAX_SEARCH_HORIZON=14 # adjust maximal range of days (longer search periods will get split into multiple requests)
volumes: volumes:
app-data: app-data:

View File

@ -12,10 +12,12 @@ from pprint import pprint
import caldav import caldav
import pytz import pytz
from caldav import CalendarObjectResource, Principal, Calendar from caldav import CalendarObjectResource, Principal, Calendar, DAVObject
from jinja2 import Template from jinja2 import Template
from django.core.exceptions import ObjectDoesNotExist
tz = pytz.timezone(os.getenv("TIME_ZONE", "Europe/Berlin")) tz = pytz.timezone(os.getenv("TIME_ZONE", "Europe/Berlin"))
MAX_SEARCH_HORIZON = int(os.getenv("MAX_SEARCH_HORIZON", 14))
class DavEvent: class DavEvent:
""" """
@ -29,7 +31,7 @@ class DavEvent:
created: datetime created: datetime
status: str status: str
organizer: str organizer: str
calendar: Calendar calendar: DAVObject
obj: CalendarObjectResource obj: CalendarObjectResource
missing_required_fields: list missing_required_fields: list
@ -119,11 +121,10 @@ class DavEvent:
if match: if match:
token = match.group() token = match.group()
print(f"Priority token found in event description: {token}") print(f"Priority token found in event description: {token}")
try:
token_obj = PriorityEventToken.objects.get(token=token) token_obj = PriorityEventToken.objects.get(token=token)
except ObjectDoesNotExist:
# Check if the token object exists in the database print(f"Priority token could not be found in database: {token}")
if not token_obj:
print(f"Priority token '{token}' not found in database.")
return False return False
# If token is already used, verify signature # If token is already used, verify signature
@ -263,17 +264,34 @@ def clear(target_calendars: list, is_test: bool=False) -> dict:
print(f"--- Clearing cancelled bookings and overlaps in calendar: {calendar.id}") print(f"--- Clearing cancelled bookings and overlaps in calendar: {calendar.id}")
horizon = tcal_by_name[calendar.id].auto_clear_overlap_horizon_days horizon = tcal_by_name[calendar.id].auto_clear_overlap_horizon_days
# Split horizon search into multiple requests if horizon is bigger
# than MAX_SEARCH_HORIZON
horizons = []
while horizon > 0:
if horizon >= MAX_SEARCH_HORIZON:
horizons.append(MAX_SEARCH_HORIZON)
else:
horizons.append(horizon)
horizon -= MAX_SEARCH_HORIZON
events_fetched = []
start_delta = 0
end_delta = 0
today = datetime.now(tz=tz).date()
for h in horizons:
end_delta += h
try: try:
events_fetched = calendar.search( events_fetched.extend(calendar.search(
start=datetime.now(), start=today + timedelta(days=start_delta),
end=date.today() + timedelta(days=horizon), end=today + timedelta(days=end_delta),
event=True, event=True,
expand=True, expand=True,
split_expanded=True, split_expanded=True,
) ))
except Exception as e: except Exception as e:
print(f"--- Failed to fetch events for calendar: {calendar.id}: {e}") print(f"--- Failed to fetch events for calendar: {calendar.id}: {e}")
continue continue
start_delta += h
# Create DavEvent objects from fetched events # Create DavEvent objects from fetched events
events = [] events = []
@ -348,7 +366,6 @@ def clear(target_calendars: list, is_test: bool=False) -> dict:
elif not event.is_cancelled: elif not event.is_cancelled:
if not is_test: if not is_test:
event.cancel() event.cancel()
is_cancelled = True
result["cancellation_type"] = "cancelled" result["cancellation_type"] = "cancelled"
results["cancelled_overlapping_recurring_events"].append(result) results["cancelled_overlapping_recurring_events"].append(result)
print("Cancelled overlapping recurring event:") print("Cancelled overlapping recurring event:")