1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-07-22 14:29:36 +02:00

permisison fixes

This commit is contained in:
Sean Morley 2024-07-09 16:48:52 -04:00
parent d64abf2273
commit 67619aec57
8 changed files with 112 additions and 34 deletions

View file

@ -6,8 +6,8 @@ from worldtravel.models import Country, Region, VisitedRegion
class AdventureAdmin(admin.ModelAdmin):
list_display = ('name', 'type', 'user_id', 'date', 'image_display')
list_filter = ('type', 'user_id')
list_display = ('name', 'type', 'user_id', 'date', 'is_public', 'image_display')
list_filter = ('type', 'user_id', 'is_public')
def image_display(self, obj):
if obj.image:

View file

@ -13,3 +13,18 @@ class IsOwnerOrReadOnly(permissions.BasePermission):
# 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

View file

@ -5,11 +5,11 @@ from .models import Adventure, Trip
from .serializers import AdventureSerializer, TripSerializer
from rest_framework.permissions import IsAuthenticated
from django.db.models import Q, Prefetch
from .permissions import IsOwnerOrReadOnly
from .permissions import IsOwnerOrReadOnly, IsPublicReadOnly
class AdventureViewSet(viewsets.ModelViewSet):
serializer_class = AdventureSerializer
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
permission_classes = [IsOwnerOrReadOnly, IsPublicReadOnly]
def get_queryset(self):
return Adventure.objects.filter(
@ -42,7 +42,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
class TripViewSet(viewsets.ModelViewSet):
serializer_class = TripSerializer
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
permission_classes = [IsOwnerOrReadOnly, IsPublicReadOnly]
def get_queryset(self):
return Trip.objects.filter(

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.7 KiB

View file

@ -21,6 +21,7 @@
import Star from '~icons/mdi/star';
import Attachment from '~icons/mdi/attachment';
import PointSelectionModal from './PointSelectionModal.svelte';
import Earth from '~icons/mdi/earth';
onMount(async () => {
modal = document.getElementById('my_modal_1') as HTMLDialogElement;
@ -245,6 +246,39 @@
Location</button
>
</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>
<!-- if there is a button in form, it will close the modal -->

View file

@ -175,6 +175,13 @@ export const actions: Actions = {
let link = formData.get('link') as string | null;
let latitude = formData.get('latitude') 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
if (latitude && longitude) {
@ -221,6 +228,7 @@ export const actions: Actions = {
formDataToSend.append('description', description || '');
formDataToSend.append('latitude', latitude || '');
formDataToSend.append('longitude', longitude || '');
formDataToSend.append('is_public', is_public.toString());
if (activity_types) {
// Filter out empty and duplicate activity types, then trim each activity type
const cleanedActivityTypes = Array.from(

View file

@ -5,9 +5,6 @@ import type { Adventure } from '$lib/types';
const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
export const load = (async (event) => {
if (!event.locals.user) {
return redirect(302, '/login');
} else {
const id = event.params as { id: string };
let request = await fetch(`${endpoint}/api/adventures/${id.id}/`, {
headers: {
@ -23,16 +20,13 @@ export const load = (async (event) => {
};
} 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
}
};
}
}
}) satisfies PageServerLoad;
import type { Actions } from '@sveltejs/kit';

View file

@ -18,25 +18,51 @@
import { onMount } from 'svelte';
import type { PageData } from './$types';
import { goto } from '$app/navigation';
import Lost from '$lib/assets/undraw_lost.svg';
export let data: PageData;
let adventure: Adventure;
let notFound: boolean = false;
onMount(() => {
if (data.props.adventure) {
adventure = data.props.adventure;
} else {
goto('/404');
notFound = true;
}
});
</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">
<span class="loading loading-spinner w-24 h-24"></span>
</div>
{:else}
{/if}
{#if adventure}
{#if adventure.name}
<h1 class="text-center font-extrabold text-4xl mb-2">{adventure.name}</h1>
{/if}