Skip to content

Commit

Permalink
Add download adventures as ICS calendar
Browse files Browse the repository at this point in the history
  • Loading branch information
seanmorley15 committed Dec 14, 2024
1 parent 4839edd commit 0c27f4b
Show file tree
Hide file tree
Showing 14 changed files with 99 additions and 12 deletions.
3 changes: 2 additions & 1 deletion backend/server/adventures/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.urls import include, path
from rest_framework.routers import DefaultRouter
from .views import AdventureViewSet, ChecklistViewSet, CollectionViewSet, NoteViewSet, StatsViewSet, GenerateDescription, ActivityTypesView, TransportationViewSet, AdventureImageViewSet, ReverseGeocodeViewSet, CategoryViewSet
from .views import AdventureViewSet, ChecklistViewSet, CollectionViewSet, NoteViewSet, StatsViewSet, GenerateDescription, ActivityTypesView, TransportationViewSet, AdventureImageViewSet, ReverseGeocodeViewSet, CategoryViewSet, IcsCalendarGeneratorViewSet

router = DefaultRouter()
router.register(r'adventures', AdventureViewSet, basename='adventures')
Expand All @@ -14,6 +14,7 @@
router.register(r'images', AdventureImageViewSet, basename='images')
router.register(r'reverse-geocode', ReverseGeocodeViewSet, basename='reverse-geocode')
router.register(r'categories', CategoryViewSet, basename='categories')
router.register(r'ics-calendar', IcsCalendarGeneratorViewSet, basename='ics-calendar')


urlpatterns = [
Expand Down
58 changes: 57 additions & 1 deletion backend/server/adventures/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
from django.shortcuts import get_object_or_404
from rest_framework import status
from django.contrib.auth import get_user_model
from icalendar import Calendar, Event, vText, vCalAddress
from django.http import HttpResponse
from datetime import datetime

User = get_user_model()

Expand Down Expand Up @@ -1203,4 +1206,57 @@ def mark_visited_region(self, request):
visited_region.save()
new_region_count += 1
new_regions[region.id] = region.name
return Response({"new_regions": new_region_count, "regions": new_regions})
return Response({"new_regions": new_region_count, "regions": new_regions})


from django.http import HttpResponse
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from icalendar import Calendar, Event, vText, vCalAddress
from datetime import datetime, timedelta

class IcsCalendarGeneratorViewSet(viewsets.ViewSet):
permission_classes = [IsAuthenticated]

@action(detail=False, methods=['get'])
def generate(self, request):
adventures = Adventure.objects.filter(user_id=request.user)
serializer = AdventureSerializer(adventures, many=True)
user = request.user
name = f"{user.first_name} {user.last_name}"
print(serializer.data)

cal = Calendar()
cal.add('prodid', '-//My Adventure Calendar//example.com//')
cal.add('version', '2.0')

for adventure in serializer.data:
if adventure['visits']:
for visit in adventure['visits']:
event = Event()
event.add('summary', adventure['name'])
start_date = datetime.strptime(visit['start_date'], '%Y-%m-%d').date()
end_date = datetime.strptime(visit['end_date'], '%Y-%m-%d').date() + timedelta(days=1) if visit['end_date'] else start_date + timedelta(days=1)
event.add('dtstart', start_date)
event.add('dtend', end_date)
event.add('dtstamp', datetime.now())
event.add('transp', 'TRANSPARENT')
event.add('class', 'PUBLIC')
event.add('created', datetime.now())
event.add('last-modified', datetime.now())
event.add('description', adventure['description'])
if adventure.get('location'):
event.add('location', adventure['location'])
if adventure.get('link'):
event.add('url', adventure['link'])

organizer = vCalAddress(f'MAILTO:{user.email}')
organizer.params['cn'] = vText(name)
event.add('organizer', organizer)

cal.add_component(event)

response = HttpResponse(cal.to_ical(), content_type='text/calendar')
response['Content-Disposition'] = 'attachment; filename=adventures.ics'
return response
4 changes: 3 additions & 1 deletion backend/server/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ gunicorn==23.0.0
qrcode==8.0
# slippers==0.6.2
# django-allauth-ui==1.5.1
# django-widget-tweaks==1.5.0
# django-widget-tweaks==1.5.0
django-ical==1.9.2
icalendar==6.1.0
3 changes: 2 additions & 1 deletion frontend/src/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@
"adventure_calendar": "Abenteuerkalender",
"emoji_picker": "Emoji-Picker",
"hide": "Verstecken",
"show": "Zeigen"
"show": "Zeigen",
"download_calendar": "Kalender herunterladen"
},
"home": {
"desc_1": "Entdecken, planen und erkunden Sie mit Leichtigkeit",
Expand Down
1 change: 1 addition & 0 deletions frontend/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@
"show": "Show",
"hide": "Hide",
"emoji_picker": "Emoji Picker",
"download_calendar": "Download Calendar",
"days": "days",
"activities": {
"general": "General 🌍",
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,8 @@
"adventure_calendar": "Calendario de aventuras",
"emoji_picker": "Selector de emojis",
"hide": "Esconder",
"show": "Espectáculo"
"show": "Espectáculo",
"download_calendar": "Descargar Calendario"
},
"worldtravel": {
"all": "Todo",
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@
"adventure_calendar": "Calendrier d'aventure",
"emoji_picker": "Sélecteur d'émoticônes",
"hide": "Cacher",
"show": "Montrer"
"show": "Montrer",
"download_calendar": "Télécharger le calendrier"
},
"home": {
"desc_1": "Découvrez, planifiez et explorez en toute simplicité",
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/locales/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@
"adventure_calendar": "Calendario delle avventure",
"emoji_picker": "Selettore di emoji",
"hide": "Nascondere",
"show": "Spettacolo"
"show": "Spettacolo",
"download_calendar": "Scarica Calendario"
},
"home": {
"desc_1": "Scopri, pianifica ed esplora con facilità",
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/locales/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@
"adventure_calendar": "Avonturenkalender",
"emoji_picker": "Emoji-kiezer",
"hide": "Verbergen",
"show": "Show"
"show": "Show",
"download_calendar": "Agenda downloaden"
},
"home": {
"desc_1": "Ontdek, plan en verken met gemak",
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/locales/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,8 @@
"adventure_calendar": "Kalendarz przygód",
"emoji_picker": "Wybór emoji",
"hide": "Ukrywać",
"show": "Pokazywać"
"show": "Pokazywać",
"download_calendar": "Pobierz Kalendarz"
},
"worldtravel": {
"country_list": "Lista krajów",
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/locales/sv.json
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@
"adventure_calendar": "Äventyrskalender",
"emoji_picker": "Emoji-väljare",
"hide": "Dölja",
"show": "Visa"
"show": "Visa",
"download_calendar": "Ladda ner kalender"
},
"home": {
"desc_1": "Upptäck, planera och utforska med lätthet",
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@
"adventure_calendar": "冒险日历",
"emoji_picker": "表情符号选择器",
"hide": "隐藏",
"show": "展示"
"show": "展示",
"download_calendar": "下载日历"
},
"home": {
"desc_1": "轻松发现、规划和探索",
Expand Down
10 changes: 9 additions & 1 deletion frontend/src/routes/calendar/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,18 @@ export const load = (async (event) => {
});
});

let icsFetch = await fetch(`${endpoint}/api/ics-calendar/generate`, {
headers: {
Cookie: `sessionid=${sessionId}`
}
});
let ics_calendar = await icsFetch.text();

return {
props: {
adventures,
dates
dates,
ics_calendar
}
};
}) satisfies PageServerLoad;
11 changes: 11 additions & 0 deletions frontend/src/routes/calendar/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
let adventures = data.props.adventures;
let dates = data.props.dates;
let icsCalendar = data.props.ics_calendar;
// turn the ics calendar into a data URL
let icsCalendarDataUrl = URL.createObjectURL(new Blob([icsCalendar], { type: 'text/calendar' }));
let plugins = [TimeGrid, DayGrid];
let options = {
view: 'dayGridMonth',
Expand All @@ -24,3 +28,10 @@
<h1 class="text-center text-2xl font-bold">{$t('adventures.adventure_calendar')}</h1>

<Calendar {plugins} {options} />

<!-- download calendar -->
<div class="flex items-center justify-center mt-4">
<a href={icsCalendarDataUrl} download="adventures.ics" class="btn btn-primary"
>Download Calendar</a
>
</div>

0 comments on commit 0c27f4b

Please sign in to comment.