mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-07-30 18:29:37 +02:00
feat: Add additional adventure type and endpoint for sunrise/sunset information
This commit is contained in:
parent
13d3b24ec2
commit
16a7772003
5 changed files with 146 additions and 95 deletions
|
@ -10,6 +10,7 @@ from adventures.models import Adventure, Category, Transportation, Lodging
|
||||||
from adventures.permissions import IsOwnerOrSharedWithFullAccess
|
from adventures.permissions import IsOwnerOrSharedWithFullAccess
|
||||||
from adventures.serializers import AdventureSerializer, TransportationSerializer, LodgingSerializer
|
from adventures.serializers import AdventureSerializer, TransportationSerializer, LodgingSerializer
|
||||||
from adventures.utils import pagination
|
from adventures.utils import pagination
|
||||||
|
import requests
|
||||||
|
|
||||||
class AdventureViewSet(viewsets.ModelViewSet):
|
class AdventureViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = AdventureSerializer
|
serializer_class = AdventureSerializer
|
||||||
|
@ -170,48 +171,38 @@ class AdventureViewSet(viewsets.ModelViewSet):
|
||||||
serializer = self.get_serializer(queryset, many=True)
|
serializer = self.get_serializer(queryset, many=True)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
# @action(detail=True, methods=['post'])
|
@action(detail=True, methods=['get'], url_path='additional-info')
|
||||||
# def convert(self, request, pk=None):
|
def additional_info(self, request, pk=None):
|
||||||
# """
|
adventure = self.get_object()
|
||||||
# Convert an Adventure instance into a Transportation or Lodging instance.
|
|
||||||
# Expects a JSON body with "target_type": "transportation" or "lodging".
|
|
||||||
# """
|
|
||||||
# adventure = self.get_object()
|
|
||||||
# target_type = request.data.get("target_type", "").lower()
|
|
||||||
|
|
||||||
# if target_type not in ["transportation", "lodging"]:
|
# Permission check: owner or shared collection member
|
||||||
# return Response(
|
if adventure.user_id != request.user:
|
||||||
# {"error": "Invalid target type. Must be 'transportation' or 'lodging'."},
|
if not (adventure.collection and adventure.collection.shared_with.filter(id=request.user.id).exists()):
|
||||||
# status=400
|
return Response({"error": "User does not have permission to access this adventure"},
|
||||||
# )
|
status=status.HTTP_403_FORBIDDEN)
|
||||||
# if not adventure.collection:
|
|
||||||
# return Response(
|
|
||||||
# {"error": "Adventure must be part of a collection to be converted."},
|
|
||||||
# status=400
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # Define the overlapping fields that both the Adventure and target models share.
|
serializer = self.get_serializer(adventure)
|
||||||
# overlapping_fields = ["name", "description", "is_public", 'collection']
|
response_data = serializer.data
|
||||||
|
|
||||||
# # Gather the overlapping data from the adventure instance.
|
visits = response_data.get('visits', [])
|
||||||
# conversion_data = {}
|
sun_times = []
|
||||||
# for field in overlapping_fields:
|
|
||||||
# if hasattr(adventure, field):
|
|
||||||
# conversion_data[field] = getattr(adventure, field)
|
|
||||||
|
|
||||||
# # Make sure to include the user reference
|
for visit in visits:
|
||||||
# conversion_data["user_id"] = adventure.user_id
|
date = visit.get('start_date')
|
||||||
|
if date and adventure.longitude and adventure.latitude:
|
||||||
|
api_url = f'https://api.sunrisesunset.io/json?lat={adventure.latitude}&lng={adventure.longitude}&date={date}'
|
||||||
|
res = requests.get(api_url)
|
||||||
|
if res.status_code == 200:
|
||||||
|
data = res.json()
|
||||||
|
results = data.get('results', {})
|
||||||
|
if results.get('sunrise') and results.get('sunset'):
|
||||||
|
sun_times.append({
|
||||||
|
"date": date,
|
||||||
|
"visit_id": visit.get('id'),
|
||||||
|
"sunrise": results.get('sunrise'),
|
||||||
|
"sunset": results.get('sunset')
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
# # Convert the adventure instance within an atomic transaction.
|
response_data['sun_times'] = sun_times
|
||||||
# with transaction.atomic():
|
return Response(response_data)
|
||||||
# if target_type == "transportation":
|
|
||||||
# new_instance = Transportation.objects.create(**conversion_data)
|
|
||||||
# serializer = TransportationSerializer(new_instance)
|
|
||||||
# else: # target_type == "lodging"
|
|
||||||
# new_instance = Lodging.objects.create(**conversion_data)
|
|
||||||
# serializer = LodgingSerializer(new_instance)
|
|
||||||
|
|
||||||
# # Optionally, delete the original adventure to avoid duplicates.
|
|
||||||
# adventure.delete()
|
|
||||||
|
|
||||||
# return Response(serializer.data)
|
|
|
@ -44,6 +44,15 @@ export type Adventure = {
|
||||||
user?: User | null;
|
user?: User | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AdditionalAdventure = Adventure & {
|
||||||
|
sun_times: {
|
||||||
|
date: string;
|
||||||
|
visit_id: string;
|
||||||
|
sunrise: string;
|
||||||
|
sunset: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
export type Country = {
|
export type Country = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
|
|
@ -90,6 +90,8 @@
|
||||||
"visits": "Visits",
|
"visits": "Visits",
|
||||||
"create_new": "Create New...",
|
"create_new": "Create New...",
|
||||||
"adventure": "Adventure",
|
"adventure": "Adventure",
|
||||||
|
"additional_info": "Additional Information",
|
||||||
|
"sunrise_sunset": "Sunrise & Sunset",
|
||||||
"count_txt": "results matching your search",
|
"count_txt": "results matching your search",
|
||||||
"sort": "Sort",
|
"sort": "Sort",
|
||||||
"order_by": "Order By",
|
"order_by": "Order By",
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL'];
|
const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL'];
|
||||||
import type { Adventure, Collection } from '$lib/types';
|
import type { AdditionalAdventure, Adventure, Collection } from '$lib/types';
|
||||||
const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
|
const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
|
||||||
|
|
||||||
export const load = (async (event) => {
|
export const load = (async (event) => {
|
||||||
const id = event.params as { id: string };
|
const id = event.params as { id: string };
|
||||||
let request = await fetch(`${endpoint}/api/adventures/${id.id}/`, {
|
let request = await fetch(`${endpoint}/api/adventures/${id.id}/additional-info/`, {
|
||||||
headers: {
|
headers: {
|
||||||
Cookie: `sessionid=${event.cookies.get('sessionid')}`
|
Cookie: `sessionid=${event.cookies.get('sessionid')}`
|
||||||
},
|
},
|
||||||
|
@ -19,7 +19,7 @@ export const load = (async (event) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
let adventure = (await request.json()) as Adventure;
|
let adventure = (await request.json()) as AdditionalAdventure;
|
||||||
let collection: Collection | null = null;
|
let collection: Collection | null = null;
|
||||||
|
|
||||||
if (adventure.collection) {
|
if (adventure.collection) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Adventure } from '$lib/types';
|
import type { AdditionalAdventure, Adventure } from '$lib/types';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
@ -12,6 +12,7 @@
|
||||||
import toGeoJSON from '@mapbox/togeojson';
|
import toGeoJSON from '@mapbox/togeojson';
|
||||||
|
|
||||||
import LightbulbOn from '~icons/mdi/lightbulb-on';
|
import LightbulbOn from '~icons/mdi/lightbulb-on';
|
||||||
|
import WeatherSunset from '~icons/mdi/weather-sunset';
|
||||||
|
|
||||||
let geojson: any;
|
let geojson: any;
|
||||||
|
|
||||||
|
@ -75,7 +76,7 @@
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
|
||||||
let adventure: Adventure;
|
let adventure: AdditionalAdventure;
|
||||||
|
|
||||||
let currentSlide = 0;
|
let currentSlide = 0;
|
||||||
|
|
||||||
|
@ -112,7 +113,7 @@
|
||||||
await getGpxFiles();
|
await getGpxFiles();
|
||||||
});
|
});
|
||||||
|
|
||||||
async function saveEdit(event: CustomEvent<Adventure>) {
|
async function saveEdit(event: CustomEvent<AdditionalAdventure>) {
|
||||||
adventure = event.detail;
|
adventure = event.detail;
|
||||||
isEditModalOpen = false;
|
isEditModalOpen = false;
|
||||||
geojson = null;
|
geojson = null;
|
||||||
|
@ -522,60 +523,108 @@
|
||||||
</MapLibre>
|
</MapLibre>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if adventure.attachments && adventure.attachments.length > 0}
|
|
||||||
<div>
|
|
||||||
<!-- attachments -->
|
|
||||||
<h2 class="text-2xl font-bold mt-4">
|
|
||||||
{$t('adventures.attachments')}
|
|
||||||
<div class="tooltip z-10" data-tip={$t('adventures.gpx_tip')}>
|
|
||||||
<button class="btn btn-sm btn-circle btn-neutral">
|
|
||||||
<LightbulbOn class="w-6 h-6" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div class="grid gap-4 mt-4">
|
<!-- Additional Info Display Section -->
|
||||||
{#if adventure.attachments && adventure.attachments.length > 0}
|
|
||||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
<div>
|
||||||
{#each adventure.attachments as attachment}
|
{#if adventure.sun_times && adventure.sun_times.length > 0}
|
||||||
<AttachmentCard {attachment} />
|
<h2 class="text-2xl font-bold mt-4 mb-4">{$t('adventures.additional_info')}</h2>
|
||||||
{/each}
|
{#if adventure.sun_times && adventure.sun_times.length > 0}
|
||||||
|
<div class="collapse collapse-plus bg-base-200 mb-2 overflow-visible">
|
||||||
|
<input type="checkbox" />
|
||||||
|
<div class="collapse-title text-xl font-medium">
|
||||||
|
<span>
|
||||||
|
{$t('adventures.sunrise_sunset')}
|
||||||
|
<WeatherSunset class="w-6 h-6 inline-block ml-2 -mt-1" />
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
</div>
|
<div class="collapse-content">
|
||||||
</div>
|
<div class="grid gap-4 mt-4">
|
||||||
{/if}
|
<!-- Sunrise and Sunset times -->
|
||||||
{#if adventure.images && adventure.images.length > 0}
|
{#each adventure.sun_times as sun_time}
|
||||||
<div>
|
<div class="grid md:grid-cols-3 gap-4">
|
||||||
<h2 class="text-2xl font-bold mt-4">{$t('adventures.images')}</h2>
|
<div>
|
||||||
<div class="grid gap-4 mt-4">
|
<p class="text-sm text-muted-foreground">Date</p>
|
||||||
{#if adventure.images && adventure.images.length > 0}
|
<p class="text-base font-medium">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
{new Date(sun_time.date).toLocaleDateString()}
|
||||||
{#each adventure.images as image}
|
</p>
|
||||||
<div class="relative">
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
||||||
<!-- svelte-ignore a11y-missing-attribute -->
|
|
||||||
<!-- svelte-ignore a11y-missing-content -->
|
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
||||||
<div
|
|
||||||
class="w-full h-48 bg-cover bg-center rounded-lg"
|
|
||||||
style="background-image: url({image.image})"
|
|
||||||
on:click={() => (image_url = image.image)}
|
|
||||||
></div>
|
|
||||||
{#if image.is_primary}
|
|
||||||
<div
|
|
||||||
class="absolute top-0 right-0 bg-primary text-white px-2 py-1 rounded-bl-lg"
|
|
||||||
>
|
|
||||||
{$t('adventures.primary')}
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
<div>
|
||||||
</div>
|
<p class="text-sm text-muted-foreground">Sunrise</p>
|
||||||
{/each}
|
<p class="text-base font-medium">
|
||||||
|
{sun_time.sunrise}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm text-muted-foreground">Sunset</p>
|
||||||
|
<p class="text-base font-medium">
|
||||||
|
{sun_time.sunset}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if adventure.attachments && adventure.attachments.length > 0}
|
||||||
|
<div>
|
||||||
|
<!-- attachments -->
|
||||||
|
<h2 class="text-2xl font-bold mt-4">
|
||||||
|
{$t('adventures.attachments')}
|
||||||
|
<div class="tooltip z-10" data-tip={$t('adventures.gpx_tip')}>
|
||||||
|
<button class="btn btn-sm btn-circle btn-neutral">
|
||||||
|
<LightbulbOn class="w-6 h-6" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="grid gap-4 mt-4">
|
||||||
|
{#if adventure.attachments && adventure.attachments.length > 0}
|
||||||
|
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{#each adventure.attachments as attachment}
|
||||||
|
<AttachmentCard {attachment} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
{/if}
|
{#if adventure.images && adventure.images.length > 0}
|
||||||
|
<div>
|
||||||
|
<h2 class="text-2xl font-bold mt-4">{$t('adventures.images')}</h2>
|
||||||
|
<div class="grid gap-4 mt-4">
|
||||||
|
{#if adventure.images && adventure.images.length > 0}
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{#each adventure.images as image}
|
||||||
|
<div class="relative">
|
||||||
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
|
<!-- svelte-ignore a11y-missing-attribute -->
|
||||||
|
<!-- svelte-ignore a11y-missing-content -->
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<div
|
||||||
|
class="w-full h-48 bg-cover bg-center rounded-lg"
|
||||||
|
style="background-image: url({image.image})"
|
||||||
|
on:click={() => (image_url = image.image)}
|
||||||
|
></div>
|
||||||
|
{#if image.is_primary}
|
||||||
|
<div
|
||||||
|
class="absolute top-0 right-0 bg-primary text-white px-2 py-1 rounded-bl-lg"
|
||||||
|
>
|
||||||
|
{$t('adventures.primary')}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue