diff --git a/backend/server/adventures/permissions.py b/backend/server/adventures/permissions.py index 890b9b08..0b8218b2 100644 --- a/backend/server/adventures/permissions.py +++ b/backend/server/adventures/permissions.py @@ -40,6 +40,10 @@ class CollectionShared(permissions.BasePermission): - Shared users: full access to collections they're shared with and related objects - Public access: read-only for objects marked as public """ + def has_permission(self, request, view): + return (request.user and request.user.is_authenticated) or \ + request.method in permissions.SAFE_METHODS + def has_object_permission(self, request, view, obj): user = request.user if not user or not user.is_authenticated: diff --git a/backend/server/adventures/tests/test_share_image.py b/backend/server/adventures/tests/test_share_image.py index c7782eab..126691fe 100644 --- a/backend/server/adventures/tests/test_share_image.py +++ b/backend/server/adventures/tests/test_share_image.py @@ -164,3 +164,41 @@ class ShareImageApiTests(TestCase): res = self.client.get(url) self.assertEqual(res.status_code, 200) self.assertIn('attachment', res['Content-Disposition']) + + def test_shared_user_can_fetch_private_collection_share_image(self): + self.private_collection.shared_with.add(self.other) + self.client.logout() + self.client.login(username='apiother', password='testpass123') + url = f'/api/collections/{self.private_collection.id}/share-image/square/' + res = self.client.get(url) + self.assertEqual(res.status_code, 200) + self.assertEqual(res['Content-Type'], 'image/png') + + def test_anonymous_can_export_public_collection_pdf(self): + self.client.logout() + url = f'/api/collections/{self.public_collection.id}/export-pdf/' + res = self.client.get(url) + self.assertEqual(res.status_code, 200) + self.assertEqual(res['Content-Type'], 'application/pdf') + self.assertTrue(res.content.startswith(b'%PDF')) + + def test_owner_can_export_private_collection_pdf(self): + url = f'/api/collections/{self.private_collection.id}/export-pdf/' + res = self.client.get(url) + self.assertEqual(res.status_code, 200) + self.assertEqual(res['Content-Type'], 'application/pdf') + + def test_anonymous_cannot_export_private_collection_pdf(self): + self.client.logout() + url = f'/api/collections/{self.private_collection.id}/export-pdf/' + res = self.client.get(url) + self.assertIn(res.status_code, (403, 404)) + + def test_shared_user_can_export_private_collection_pdf(self): + self.private_collection.shared_with.add(self.other) + self.client.logout() + self.client.login(username='apiother', password='testpass123') + url = f'/api/collections/{self.private_collection.id}/export-pdf/' + res = self.client.get(url) + self.assertEqual(res.status_code, 200) + self.assertEqual(res['Content-Type'], 'application/pdf') diff --git a/backend/server/adventures/views/collection_view.py b/backend/server/adventures/views/collection_view.py index 6420ab5c..34fec458 100644 --- a/backend/server/adventures/views/collection_view.py +++ b/backend/server/adventures/views/collection_view.py @@ -160,7 +160,7 @@ class CollectionViewSet(viewsets.ModelViewSet): | Q(shared_with=self.request.user) | Q(invites__invited_user=self.request.user) ).distinct() - elif self.action == 'retrieve': + elif self.action in ('retrieve', 'export_pdf', 'share_image'): if not self.request.user.is_authenticated: queryset = Collection.objects.filter(is_public=True) else: diff --git a/backend/server/adventures/views/location_view.py b/backend/server/adventures/views/location_view.py index 9a6024e1..87595518 100644 --- a/backend/server/adventures/views/location_view.py +++ b/backend/server/adventures/views/location_view.py @@ -68,7 +68,7 @@ class LocationViewSet(viewsets.ModelViewSet): Public actions allow unauthenticated access to public locations. """ user = self.request.user - public_allowed_actions = {'retrieve', 'additional_info'} + public_allowed_actions = {'retrieve', 'additional_info', 'share_image'} if not user.is_authenticated: if self.action in public_allowed_actions: diff --git a/frontend/src/lib/components/collections/CollectionMap.svelte b/frontend/src/lib/components/collections/CollectionMap.svelte index 7edd625c..5a323087 100644 --- a/frontend/src/lib/components/collections/CollectionMap.svelte +++ b/frontend/src/lib/components/collections/CollectionMap.svelte @@ -16,6 +16,7 @@ export let collection: Collection; export let user: User | null = null; + export let canModify: boolean = false; // Allow disabling/enabling clustering for markers export let clusterEnabled: boolean = false; export let clusterOptions: any = { radius: 300, maxZoom: 8, minPoints: 2 }; @@ -642,65 +643,67 @@ -
- {$t('adventures.add_to_collection')} -
-- {$t('adventures.click_map_add_marker')} -
-