mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-07-23 06:49:37 +02:00
Add download adventures as ICS calendar
This commit is contained in:
parent
4839edde7b
commit
0c27f4b8a4
14 changed files with 99 additions and 12 deletions
|
@ -1,6 +1,6 @@
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
from rest_framework.routers import DefaultRouter
|
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 = DefaultRouter()
|
||||||
router.register(r'adventures', AdventureViewSet, basename='adventures')
|
router.register(r'adventures', AdventureViewSet, basename='adventures')
|
||||||
|
@ -14,6 +14,7 @@ router.register(r'checklists', ChecklistViewSet, basename='checklists')
|
||||||
router.register(r'images', AdventureImageViewSet, basename='images')
|
router.register(r'images', AdventureImageViewSet, basename='images')
|
||||||
router.register(r'reverse-geocode', ReverseGeocodeViewSet, basename='reverse-geocode')
|
router.register(r'reverse-geocode', ReverseGeocodeViewSet, basename='reverse-geocode')
|
||||||
router.register(r'categories', CategoryViewSet, basename='categories')
|
router.register(r'categories', CategoryViewSet, basename='categories')
|
||||||
|
router.register(r'ics-calendar', IcsCalendarGeneratorViewSet, basename='ics-calendar')
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|
|
@ -17,6 +17,9 @@ from rest_framework.pagination import PageNumberPagination
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from django.contrib.auth import get_user_model
|
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()
|
User = get_user_model()
|
||||||
|
|
||||||
|
@ -1203,4 +1206,57 @@ class ReverseGeocodeViewSet(viewsets.ViewSet):
|
||||||
visited_region.save()
|
visited_region.save()
|
||||||
new_region_count += 1
|
new_region_count += 1
|
||||||
new_regions[region.id] = region.name
|
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
|
||||||
|
|
|
@ -15,4 +15,6 @@ gunicorn==23.0.0
|
||||||
qrcode==8.0
|
qrcode==8.0
|
||||||
# slippers==0.6.2
|
# slippers==0.6.2
|
||||||
# django-allauth-ui==1.5.1
|
# 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
|
|
@ -194,7 +194,8 @@
|
||||||
"adventure_calendar": "Abenteuerkalender",
|
"adventure_calendar": "Abenteuerkalender",
|
||||||
"emoji_picker": "Emoji-Picker",
|
"emoji_picker": "Emoji-Picker",
|
||||||
"hide": "Verstecken",
|
"hide": "Verstecken",
|
||||||
"show": "Zeigen"
|
"show": "Zeigen",
|
||||||
|
"download_calendar": "Kalender herunterladen"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"desc_1": "Entdecken, planen und erkunden Sie mit Leichtigkeit",
|
"desc_1": "Entdecken, planen und erkunden Sie mit Leichtigkeit",
|
||||||
|
|
|
@ -217,6 +217,7 @@
|
||||||
"show": "Show",
|
"show": "Show",
|
||||||
"hide": "Hide",
|
"hide": "Hide",
|
||||||
"emoji_picker": "Emoji Picker",
|
"emoji_picker": "Emoji Picker",
|
||||||
|
"download_calendar": "Download Calendar",
|
||||||
"days": "days",
|
"days": "days",
|
||||||
"activities": {
|
"activities": {
|
||||||
"general": "General 🌍",
|
"general": "General 🌍",
|
||||||
|
|
|
@ -241,7 +241,8 @@
|
||||||
"adventure_calendar": "Calendario de aventuras",
|
"adventure_calendar": "Calendario de aventuras",
|
||||||
"emoji_picker": "Selector de emojis",
|
"emoji_picker": "Selector de emojis",
|
||||||
"hide": "Esconder",
|
"hide": "Esconder",
|
||||||
"show": "Espectáculo"
|
"show": "Espectáculo",
|
||||||
|
"download_calendar": "Descargar Calendario"
|
||||||
},
|
},
|
||||||
"worldtravel": {
|
"worldtravel": {
|
||||||
"all": "Todo",
|
"all": "Todo",
|
||||||
|
|
|
@ -194,7 +194,8 @@
|
||||||
"adventure_calendar": "Calendrier d'aventure",
|
"adventure_calendar": "Calendrier d'aventure",
|
||||||
"emoji_picker": "Sélecteur d'émoticônes",
|
"emoji_picker": "Sélecteur d'émoticônes",
|
||||||
"hide": "Cacher",
|
"hide": "Cacher",
|
||||||
"show": "Montrer"
|
"show": "Montrer",
|
||||||
|
"download_calendar": "Télécharger le calendrier"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"desc_1": "Découvrez, planifiez et explorez en toute simplicité",
|
"desc_1": "Découvrez, planifiez et explorez en toute simplicité",
|
||||||
|
|
|
@ -194,7 +194,8 @@
|
||||||
"adventure_calendar": "Calendario delle avventure",
|
"adventure_calendar": "Calendario delle avventure",
|
||||||
"emoji_picker": "Selettore di emoji",
|
"emoji_picker": "Selettore di emoji",
|
||||||
"hide": "Nascondere",
|
"hide": "Nascondere",
|
||||||
"show": "Spettacolo"
|
"show": "Spettacolo",
|
||||||
|
"download_calendar": "Scarica Calendario"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"desc_1": "Scopri, pianifica ed esplora con facilità",
|
"desc_1": "Scopri, pianifica ed esplora con facilità",
|
||||||
|
|
|
@ -194,7 +194,8 @@
|
||||||
"adventure_calendar": "Avonturenkalender",
|
"adventure_calendar": "Avonturenkalender",
|
||||||
"emoji_picker": "Emoji-kiezer",
|
"emoji_picker": "Emoji-kiezer",
|
||||||
"hide": "Verbergen",
|
"hide": "Verbergen",
|
||||||
"show": "Show"
|
"show": "Show",
|
||||||
|
"download_calendar": "Agenda downloaden"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"desc_1": "Ontdek, plan en verken met gemak",
|
"desc_1": "Ontdek, plan en verken met gemak",
|
||||||
|
|
|
@ -241,7 +241,8 @@
|
||||||
"adventure_calendar": "Kalendarz przygód",
|
"adventure_calendar": "Kalendarz przygód",
|
||||||
"emoji_picker": "Wybór emoji",
|
"emoji_picker": "Wybór emoji",
|
||||||
"hide": "Ukrywać",
|
"hide": "Ukrywać",
|
||||||
"show": "Pokazywać"
|
"show": "Pokazywać",
|
||||||
|
"download_calendar": "Pobierz Kalendarz"
|
||||||
},
|
},
|
||||||
"worldtravel": {
|
"worldtravel": {
|
||||||
"country_list": "Lista krajów",
|
"country_list": "Lista krajów",
|
||||||
|
|
|
@ -194,7 +194,8 @@
|
||||||
"adventure_calendar": "Äventyrskalender",
|
"adventure_calendar": "Äventyrskalender",
|
||||||
"emoji_picker": "Emoji-väljare",
|
"emoji_picker": "Emoji-väljare",
|
||||||
"hide": "Dölja",
|
"hide": "Dölja",
|
||||||
"show": "Visa"
|
"show": "Visa",
|
||||||
|
"download_calendar": "Ladda ner kalender"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"desc_1": "Upptäck, planera och utforska med lätthet",
|
"desc_1": "Upptäck, planera och utforska med lätthet",
|
||||||
|
|
|
@ -194,7 +194,8 @@
|
||||||
"adventure_calendar": "冒险日历",
|
"adventure_calendar": "冒险日历",
|
||||||
"emoji_picker": "表情符号选择器",
|
"emoji_picker": "表情符号选择器",
|
||||||
"hide": "隐藏",
|
"hide": "隐藏",
|
||||||
"show": "展示"
|
"show": "展示",
|
||||||
|
"download_calendar": "下载日历"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"desc_1": "轻松发现、规划和探索",
|
"desc_1": "轻松发现、规划和探索",
|
||||||
|
|
|
@ -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 {
|
return {
|
||||||
props: {
|
props: {
|
||||||
adventures,
|
adventures,
|
||||||
dates
|
dates,
|
||||||
|
ics_calendar
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}) satisfies PageServerLoad;
|
}) satisfies PageServerLoad;
|
||||||
|
|
|
@ -14,6 +14,10 @@
|
||||||
let adventures = data.props.adventures;
|
let adventures = data.props.adventures;
|
||||||
let dates = data.props.dates;
|
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 plugins = [TimeGrid, DayGrid];
|
||||||
let options = {
|
let options = {
|
||||||
view: 'dayGridMonth',
|
view: 'dayGridMonth',
|
||||||
|
@ -24,3 +28,10 @@
|
||||||
<h1 class="text-center text-2xl font-bold">{$t('adventures.adventure_calendar')}</h1>
|
<h1 class="text-center text-2xl font-bold">{$t('adventures.adventure_calendar')}</h1>
|
||||||
|
|
||||||
<Calendar {plugins} {options} />
|
<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>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue