mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-07-22 14:29:36 +02:00
permisison fixes
This commit is contained in:
parent
d64abf2273
commit
67619aec57
8 changed files with 112 additions and 34 deletions
|
@ -6,8 +6,8 @@ from worldtravel.models import Country, Region, VisitedRegion
|
||||||
|
|
||||||
|
|
||||||
class AdventureAdmin(admin.ModelAdmin):
|
class AdventureAdmin(admin.ModelAdmin):
|
||||||
list_display = ('name', 'type', 'user_id', 'date', 'image_display')
|
list_display = ('name', 'type', 'user_id', 'date', 'is_public', 'image_display')
|
||||||
list_filter = ('type', 'user_id')
|
list_filter = ('type', 'user_id', 'is_public')
|
||||||
|
|
||||||
def image_display(self, obj):
|
def image_display(self, obj):
|
||||||
if obj.image:
|
if obj.image:
|
||||||
|
|
|
@ -12,4 +12,19 @@ class IsOwnerOrReadOnly(permissions.BasePermission):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Write permissions are only allowed to the owner of the object.
|
# Write permissions are only allowed to the owner of the object.
|
||||||
|
return obj.user_id == request.user
|
||||||
|
|
||||||
|
|
||||||
|
class IsPublicReadOnly(permissions.BasePermission):
|
||||||
|
"""
|
||||||
|
Custom permission to only allow read-only access to public objects,
|
||||||
|
and write access to the owner of the object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def has_object_permission(self, request, view, obj):
|
||||||
|
# Read permissions are allowed if the object is public
|
||||||
|
if request.method in permissions.SAFE_METHODS:
|
||||||
|
return obj.is_public or obj.user_id == request.user
|
||||||
|
|
||||||
|
# Write permissions are only allowed to the owner of the object
|
||||||
return obj.user_id == request.user
|
return obj.user_id == request.user
|
|
@ -5,11 +5,11 @@ from .models import Adventure, Trip
|
||||||
from .serializers import AdventureSerializer, TripSerializer
|
from .serializers import AdventureSerializer, TripSerializer
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from django.db.models import Q, Prefetch
|
from django.db.models import Q, Prefetch
|
||||||
from .permissions import IsOwnerOrReadOnly
|
from .permissions import IsOwnerOrReadOnly, IsPublicReadOnly
|
||||||
|
|
||||||
class AdventureViewSet(viewsets.ModelViewSet):
|
class AdventureViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = AdventureSerializer
|
serializer_class = AdventureSerializer
|
||||||
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
|
permission_classes = [IsOwnerOrReadOnly, IsPublicReadOnly]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return Adventure.objects.filter(
|
return Adventure.objects.filter(
|
||||||
|
@ -42,7 +42,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
class TripViewSet(viewsets.ModelViewSet):
|
class TripViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = TripSerializer
|
serializer_class = TripSerializer
|
||||||
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
|
permission_classes = [IsOwnerOrReadOnly, IsPublicReadOnly]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return Trip.objects.filter(
|
return Trip.objects.filter(
|
||||||
|
|
1
frontend/src/lib/assets/undraw_lost.svg
Normal file
1
frontend/src/lib/assets/undraw_lost.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 6.7 KiB |
|
@ -21,6 +21,7 @@
|
||||||
import Star from '~icons/mdi/star';
|
import Star from '~icons/mdi/star';
|
||||||
import Attachment from '~icons/mdi/attachment';
|
import Attachment from '~icons/mdi/attachment';
|
||||||
import PointSelectionModal from './PointSelectionModal.svelte';
|
import PointSelectionModal from './PointSelectionModal.svelte';
|
||||||
|
import Earth from '~icons/mdi/earth';
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
modal = document.getElementById('my_modal_1') as HTMLDialogElement;
|
modal = document.getElementById('my_modal_1') as HTMLDialogElement;
|
||||||
|
@ -245,6 +246,39 @@
|
||||||
Location</button
|
Location</button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="is_public">Public <Earth class="inline-block -mt-1 mb-1 w-6 h-6" /></label><br
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="toggle toggle-primary"
|
||||||
|
id="is_public"
|
||||||
|
name="is_public"
|
||||||
|
bind:checked={adventureToEdit.is_public}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if adventureToEdit.is_public}
|
||||||
|
<div class="bg-neutral p-4 rounded-md shadow-sm">
|
||||||
|
<p class=" font-semibold">Share this Adventure!</p>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<p class="text-card-foreground font-mono">
|
||||||
|
{window.location.origin}/adventures/{adventureToEdit.id}
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
on:click={() => {
|
||||||
|
navigator.clipboard.writeText(
|
||||||
|
`${window.location.origin}/adventures/${adventureToEdit.id}`
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 h-10 px-4 py-2"
|
||||||
|
>
|
||||||
|
Copy Link
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary mr-4 mt-4" on:click={submit}>Edit</button>
|
<button type="submit" class="btn btn-primary mr-4 mt-4" on:click={submit}>Edit</button>
|
||||||
<!-- if there is a button in form, it will close the modal -->
|
<!-- if there is a button in form, it will close the modal -->
|
||||||
|
|
|
@ -175,6 +175,13 @@ export const actions: Actions = {
|
||||||
let link = formData.get('link') as string | null;
|
let link = formData.get('link') as string | null;
|
||||||
let latitude = formData.get('latitude') as string | null;
|
let latitude = formData.get('latitude') as string | null;
|
||||||
let longitude = formData.get('longitude') as string | null;
|
let longitude = formData.get('longitude') as string | null;
|
||||||
|
let is_public = formData.get('is_public') as string | null | boolean;
|
||||||
|
|
||||||
|
if (is_public) {
|
||||||
|
is_public = true;
|
||||||
|
} else {
|
||||||
|
is_public = false;
|
||||||
|
}
|
||||||
|
|
||||||
// check if latitude and longitude are valid
|
// check if latitude and longitude are valid
|
||||||
if (latitude && longitude) {
|
if (latitude && longitude) {
|
||||||
|
@ -221,6 +228,7 @@ export const actions: Actions = {
|
||||||
formDataToSend.append('description', description || '');
|
formDataToSend.append('description', description || '');
|
||||||
formDataToSend.append('latitude', latitude || '');
|
formDataToSend.append('latitude', latitude || '');
|
||||||
formDataToSend.append('longitude', longitude || '');
|
formDataToSend.append('longitude', longitude || '');
|
||||||
|
formDataToSend.append('is_public', is_public.toString());
|
||||||
if (activity_types) {
|
if (activity_types) {
|
||||||
// Filter out empty and duplicate activity types, then trim each activity type
|
// Filter out empty and duplicate activity types, then trim each activity type
|
||||||
const cleanedActivityTypes = Array.from(
|
const cleanedActivityTypes = Array.from(
|
||||||
|
|
|
@ -5,33 +5,27 @@ import type { Adventure } 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) => {
|
||||||
if (!event.locals.user) {
|
const id = event.params as { id: string };
|
||||||
return redirect(302, '/login');
|
let request = await fetch(`${endpoint}/api/adventures/${id.id}/`, {
|
||||||
} else {
|
headers: {
|
||||||
const id = event.params as { id: string };
|
Cookie: `${event.cookies.get('auth')}`
|
||||||
let request = await fetch(`${endpoint}/api/adventures/${id.id}/`, {
|
|
||||||
headers: {
|
|
||||||
Cookie: `${event.cookies.get('auth')}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!request.ok) {
|
|
||||||
console.error('Failed to fetch adventure ' + id.id);
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
adventure: null
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
let adventure = (await request.json()) as Adventure;
|
|
||||||
if (!adventure.is_public && adventure.user_id !== event.locals.user.pk) {
|
|
||||||
return redirect(302, '/');
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
adventure
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
if (!request.ok) {
|
||||||
|
console.error('Failed to fetch adventure ' + id.id);
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
adventure: null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
let adventure = (await request.json()) as Adventure;
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
adventure
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}) satisfies PageServerLoad;
|
}) satisfies PageServerLoad;
|
||||||
|
|
||||||
|
|
|
@ -18,25 +18,51 @@
|
||||||
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';
|
||||||
|
import Lost from '$lib/assets/undraw_lost.svg';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
let adventure: Adventure;
|
let adventure: Adventure;
|
||||||
|
|
||||||
|
let notFound: boolean = false;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (data.props.adventure) {
|
if (data.props.adventure) {
|
||||||
adventure = data.props.adventure;
|
adventure = data.props.adventure;
|
||||||
} else {
|
} else {
|
||||||
goto('/404');
|
notFound = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !adventure}
|
{#if notFound}
|
||||||
|
<div
|
||||||
|
class="flex min-h-[100dvh] flex-col items-center justify-center bg-background px-4 py-12 sm:px-6 lg:px-8 -mt-20"
|
||||||
|
>
|
||||||
|
<div class="mx-auto max-w-md text-center">
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<img src={Lost} alt="Lost" class="w-1/2" />
|
||||||
|
</div>
|
||||||
|
<h1 class="mt-4 text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
|
||||||
|
Adventure not Found
|
||||||
|
</h1>
|
||||||
|
<p class="mt-4 text-muted-foreground">
|
||||||
|
The adventure you were looking for could not be found. Please try a different adventure or
|
||||||
|
check back later.
|
||||||
|
</p>
|
||||||
|
<div class="mt-6">
|
||||||
|
<button class="btn btn-primary" on:click={() => goto('/')}>Homepage</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if !adventure && !notFound}
|
||||||
<div class="flex justify-center items-center w-full mt-16">
|
<div class="flex justify-center items-center w-full mt-16">
|
||||||
<span class="loading loading-spinner w-24 h-24"></span>
|
<span class="loading loading-spinner w-24 h-24"></span>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{/if}
|
||||||
|
{#if adventure}
|
||||||
{#if adventure.name}
|
{#if adventure.name}
|
||||||
<h1 class="text-center font-extrabold text-4xl mb-2">{adventure.name}</h1>
|
<h1 class="text-center font-extrabold text-4xl mb-2">{adventure.name}</h1>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue