Skip to content

Commit 179037b

Browse files
cross platform fix
1 parent 8b7057f commit 179037b

File tree

4 files changed

+68
-26
lines changed

4 files changed

+68
-26
lines changed

src/pretalx/agenda/urls.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ def get_schedule_urls(regex_prefix, name_prefix=""):
2323
(".xcal", schedule.ExporterView.as_view(), "export.schedule.xcal"),
2424
(".json", schedule.ExporterView.as_view(), "export.schedule.json"),
2525
(".ics", schedule.ExporterView.as_view(), "export.schedule.ics"),
26-
("/export/google-calendar", schedule.GoogleCalendarRedirectView.as_view(), "export.google-calendar"),
27-
("/export/my-google-calendar", schedule.GoogleCalendarRedirectView.as_view(), "export.my-google-calendar"),
26+
("/export/google-calendar", schedule.CalendarRedirectView.as_view(), "export.google-calendar"),
27+
("/export/my-google-calendar", schedule.CalendarRedirectView.as_view(), "export.my-google-calendar"),
28+
("/export/webcal", schedule.CalendarRedirectView.as_view(), "export.webcal"),
29+
("/export/my-webcal", schedule.CalendarRedirectView.as_view(), "export.my-webcal"),
2830
("/export/<name>", schedule.ExporterView.as_view(), "export"),
2931
("/widgets/schedule.json", widget.widget_data, "widget.data"),
3032
# Legacy widget data URL, but expected in old widget code.

src/pretalx/agenda/views/schedule.py

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,12 @@ def get_context_data(self, **kwargs):
127127
def get_exporter(self, public=True):
128128
url = resolve(self.request.path_info)
129129

130-
# Handle both export and export-tokenized URLs
130+
calendar_exports = ["export.google-calendar", "export.my-google-calendar", "export.other-calendar", "export.my-other-calendar"]
131131
if url.url_name in ["export", "export-tokenized"]:
132132
exporter = url.kwargs.get("name") or unquote(
133133
self.request.GET.get("exporter")
134134
)
135-
elif url.url_name in ["export.google-calendar", "export.my-google-calendar"]:
136-
# Handle our explicit Google Calendar URL patterns
135+
elif url.url_name in calendar_exports:
137136
exporter = url.url_name.replace("export.", "")
138137
else:
139138
exporter = url.url_name
@@ -360,20 +359,25 @@ class ChangelogView(EventPermissionRequired, TemplateView):
360359
permission_required = "agenda.view_schedule"
361360

362361

363-
class GoogleCalendarRedirectView(EventPermissionRequired, ScheduleMixin, TemplateView):
364-
# Define constant for session key
362+
class CalendarRedirectView(EventPermissionRequired, ScheduleMixin, TemplateView):
363+
"""Handles redirects for both Google Calendar and other calendar applications"""
365364
MY_STARRED_ICS_TOKEN_SESSION_KEY = 'my_starred_ics_token'
366365
permission_required = "agenda.view_schedule"
367366

368367
def get(self, request, *args, **kwargs):
369-
# Use resolver_match.url_name for robust route detection
368+
# Get URL name from resolver
370369
url_name = request.resolver_match.url_name if request.resolver_match else None
371-
if url_name == 'export.my-google-calendar':
372-
# Generate tokenized URL for my starred sessions
370+
371+
# Determine calendar type and starred status from URL pattern
372+
is_google = "google" in url_name
373+
is_my = "my" in url_name
374+
375+
if is_my:
376+
# For starred sessions
373377
if not request.user.is_authenticated:
374378
return HttpResponseRedirect(self.request.event.urls.login)
375379

376-
# Use constant instead of hardcoded string
380+
# Check for existing valid token
377381
existing_token = request.session.get(self.MY_STARRED_ICS_TOKEN_SESSION_KEY)
378382
generate_new_token = True
379383

@@ -384,12 +388,12 @@ def get(self, request, *args, **kwargs):
384388
token = existing_token
385389
generate_new_token = False
386390

387-
# Generate a new token if needed
391+
# Generate new token if needed
388392
if generate_new_token:
389393
token = self.generate_ics_token(request.user.id)
390-
# Use constant here too
391394
request.session[self.MY_STARRED_ICS_TOKEN_SESSION_KEY] = token
392395

396+
# Build tokenized URL for starred sessions
393397
ics_url = request.build_absolute_uri(
394398
reverse('agenda:export-tokenized', kwargs={
395399
'event': self.request.event.slug,
@@ -398,19 +402,32 @@ def get(self, request, *args, **kwargs):
398402
})
399403
)
400404
else:
401-
# Regular public calendar
405+
# Build public calendar URL
402406
ics_url = request.build_absolute_uri(
403407
reverse('agenda:export', kwargs={
404408
'event': self.request.event.slug,
405409
'name': 'schedule.ics'
406410
})
407411
)
408412

409-
# Change scheme to webcal
410-
parsed = urlparse(ics_url)
411-
ics_url = urlunparse(('webcal',) + parsed[1:])
412-
413-
# Create Google Calendar URL
414-
google_url = f"https://calendar.google.com/calendar/render?{urlencode({'cid': ics_url})}"
415-
416-
return HttpResponseRedirect(google_url)
413+
# Handle redirect based on calendar type
414+
if is_google:
415+
# Google Calendar requires special URL format
416+
google_url = f"https://calendar.google.com/calendar/render?{urlencode({'cid': ics_url})}"
417+
response = HttpResponse(
418+
f'<html><head><meta http-equiv="refresh" content="0;url={google_url}"></head>'
419+
f'<body><p style="text-align: center; padding:2vw; font-family: Roboto,Helvetica Neue,HelveticaNeue,Helvetica,Arial,sans-serif;">Redirecting to Google Calendar: {google_url}</p><script>window.location.href="{google_url}";</script></body></html>',
420+
content_type='text/html'
421+
)
422+
return response
423+
else:
424+
# Other calendars use webcal protocol
425+
parsed = urlparse(ics_url)
426+
webcal_url = urlunparse(('webcal',) + parsed[1:])
427+
# Create a simple HTML redirect with meta refresh
428+
response = HttpResponse(
429+
f'<html><head><meta http-equiv="refresh" content="0;url={webcal_url}"></head>'
430+
f'<body><p style="text-align: center; padding:2vw; font-family: Roboto,Helvetica Neue,HelveticaNeue,Helvetica,Arial,sans-serif;">Redirecting to: {webcal_url}</p><script>window.location.href="{webcal_url}";</script></body></html>',
431+
content_type='text/html'
432+
)
433+
return response

src/pretalx/schedule/exporters.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -429,20 +429,33 @@ def render(self, request, **kwargs):
429429
return f"{self.event.slug}-favs.ics", "text/calendar", cal.serialize()
430430

431431

432-
class BaseGoogleCalendarExporter(BaseExporter):
432+
class BaseCalendarExporter(BaseExporter):
433433
public = True
434434
show_qrcode = False
435-
icon = "fa-google"
435+
icon = "fa-calendar"
436+
436437
@property
437438
def show_public(self):
438439
return self.ical_exporter_cls(self.event).show_public
439440

440-
class GoogleCalendarExporter(BaseGoogleCalendarExporter):
441+
class GoogleCalendarExporter(BaseCalendarExporter):
441442
identifier = "google-calendar"
442443
verbose_name = "Add to Google Calendar"
444+
icon = "fa-google"
443445
ical_exporter_cls = ICalExporter
444446

445-
class MyGoogleCalendarExporter(BaseGoogleCalendarExporter):
447+
class MyGoogleCalendarExporter(BaseCalendarExporter):
446448
identifier = "my-google-calendar"
449+
icon = "fa-google"
447450
verbose_name = "Add My ⭐ Sessions to Google Calendar"
448451
ical_exporter_cls = MyICalExporter
452+
453+
class WebcalExporter(BaseCalendarExporter):
454+
identifier = "webcal"
455+
verbose_name = "Add to Other Calendar"
456+
ical_exporter_cls = ICalExporter
457+
458+
class MyWebcalExporter(BaseCalendarExporter):
459+
identifier = "my-webcal"
460+
verbose_name = "Add My ⭐ Sessions to Other Calendar"
461+
ical_exporter_cls = MyICalExporter

src/pretalx/schedule/signals.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,13 @@ def register_google_calendar_exporter(sender, **kwargs):
9090
def register_my_google_calendar_exporter(sender, **kwargs):
9191
from .exporters import MyGoogleCalendarExporter
9292
return MyGoogleCalendarExporter
93+
94+
@receiver(register_data_exporters, dispatch_uid="exporter_builtin_webcal")
95+
def register_webcal_exporter(sender, **kwargs):
96+
from .exporters import WebcalExporter
97+
return WebcalExporter
98+
99+
@receiver(register_my_data_exporters, dispatch_uid="exporter_builtin_my_webcal")
100+
def register_my_webcal_exporter(sender, **kwargs):
101+
from .exporters import MyWebcalExporter
102+
return MyWebcalExporter

0 commit comments

Comments
 (0)