1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-08-02 11:45:17 +02:00

Rename Adventures to Locations (#696)

* Refactor user_id to user in adventures and related models, views, and components

- Updated all instances of user_id to user in the adventures app, including models, serializers, views, and frontend components.
- Adjusted queries and filters to reflect the new user field naming convention.
- Ensured consistency across the codebase for user identification in adventures, collections, notes, and transportation entities.
- Modified frontend components to align with the updated data structure, ensuring proper access control and rendering based on user ownership.

* Refactor adventure-related views and components to use "Location" terminology

- Updated GlobalSearchView to replace AdventureSerializer with LocationSerializer.
- Modified IcsCalendarGeneratorViewSet to use LocationSerializer instead of AdventureSerializer.
- Created new LocationImageViewSet for managing location images, including primary image toggling and image deletion.
- Introduced LocationViewSet for managing locations with enhanced filtering, sorting, and sharing capabilities.
- Updated ReverseGeocodeViewSet to utilize LocationSerializer.
- Added ActivityTypesView to retrieve distinct activity types from locations.
- Refactored user views to replace AdventureSerializer with LocationSerializer.
- Updated frontend components to reflect changes from "adventure" to "location", including AdventureCard, AdventureLink, AdventureModal, and others.
- Adjusted API endpoints in frontend routes to align with new location-based structure.
- Ensured all references to adventures are replaced with locations across the codebase.

* refactor: rename adventures to locations across the application

- Updated localization files to replace adventure-related terms with location-related terms.
- Refactored TypeScript types and variables from Adventure to Location in various routes and components.
- Adjusted UI elements and labels to reflect the change from adventures to locations.
- Ensured all references to adventures in the codebase are consistent with the new location terminology.

* Refactor code structure for improved readability and maintainability

* feat: Implement location details page with server-side loading and deletion functionality

- Added +page.server.ts to handle server-side loading of additional location info.
- Created +page.svelte for displaying location details, including images, visits, and maps.
- Integrated GPX file handling and rendering on the map.
- Updated map route to link to locations instead of adventures.
- Refactored profile and search routes to use LocationCard instead of AdventureCard.

* docs: Update terminology from "Adventure" to "Location" and enhance project overview

* docs: Clarify collection examples in usage documentation

* feat: Enable credentials for GPX file fetch and add CORS_ALLOW_CREDENTIALS setting

* Refactor adventure references to locations across the backend and frontend

- Updated CategoryViewSet to reflect location context instead of adventures.
- Modified ChecklistViewSet to include locations in retrieval logic.
- Changed GlobalSearchView to search for locations instead of adventures.
- Adjusted IcsCalendarGeneratorViewSet to handle locations instead of adventures.
- Refactored LocationImageViewSet to remove unused import.
- Updated LocationViewSet to clarify public access for locations.
- Changed LodgingViewSet to reference locations instead of adventures.
- Modified NoteViewSet to prevent listing all locations.
- Updated RecommendationsViewSet to handle locations in parsing and response.
- Adjusted ReverseGeocodeViewSet to search through user locations.
- Updated StatsViewSet to count locations instead of adventures.
- Changed TagsView to reflect activity types for locations.
- Updated TransportationViewSet to reference locations instead of adventures.
- Added new translations for search results related to locations in multiple languages.
- Updated dashboard and profile pages to reflect location counts instead of adventure counts.
- Adjusted search routes to handle locations instead of adventures.

* Update banner image

* style: Update stats component background and border for improved visibility

* refactor: Rename AdventureCard and AdventureModal to LocationCard and LocationModal for consistency
This commit is contained in:
Sean Morley 2025-06-25 11:49:34 -04:00 committed by GitHub
parent 5308ec21d6
commit 493a13995c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
115 changed files with 3148 additions and 2759 deletions

View file

@ -1,6 +1,6 @@
from .activity_types_view import *
from .adventure_image_view import *
from .adventure_view import *
from .tags_view import *
from .location_image_view import *
from .location_view import *
from .category_view import *
from .checklist_view import *
from .collection_view import *

View file

@ -2,7 +2,7 @@ from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from adventures.models import Adventure, Attachment
from adventures.models import Location, Attachment
from adventures.serializers import AttachmentSerializer
class AttachmentViewSet(viewsets.ModelViewSet):
@ -10,47 +10,43 @@ class AttachmentViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated]
def get_queryset(self):
return Attachment.objects.filter(user_id=self.request.user)
@action(detail=True, methods=['post'])
def attachment_delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
return Attachment.objects.filter(user=self.request.user)
def create(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
adventure_id = request.data.get('adventure')
location_id = request.data.get('location')
try:
adventure = Adventure.objects.get(id=adventure_id)
except Adventure.DoesNotExist:
return Response({"error": "Adventure not found"}, status=status.HTTP_404_NOT_FOUND)
if adventure.user_id != request.user:
# Check if the adventure has any collections
if adventure.collections.exists():
# Check if the user is in the shared_with list of any of the adventure's collections
location = Location.objects.get(id=location_id)
except Location.DoesNotExist:
return Response({"error": "Location not found"}, status=status.HTTP_404_NOT_FOUND)
if location.user != request.user:
# Check if the location has any collections
if location.collections.exists():
# Check if the user is in the shared_with list of any of the location's collections
user_has_access = False
for collection in adventure.collections.all():
for collection in location.collections.all():
if collection.shared_with.filter(id=request.user.id).exists():
user_has_access = True
break
if not user_has_access:
return Response({"error": "User does not have permission to access this adventure"}, status=status.HTTP_403_FORBIDDEN)
return Response({"error": "User does not have permission to access this location"}, status=status.HTTP_403_FORBIDDEN)
else:
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
return Response({"error": "User does not own this location"}, status=status.HTTP_403_FORBIDDEN)
return super().create(request, *args, **kwargs)
def perform_create(self, serializer):
adventure_id = self.request.data.get('adventure')
adventure = Adventure.objects.get(id=adventure_id)
# If the adventure belongs to collections, set the owner to the collection owner
if adventure.collections.exists():
location_id = self.request.data.get('location')
location = Location.objects.get(id=location_id)
# If the location belongs to collections, set the owner to the collection owner
if location.collections.exists():
# Get the first collection's owner (assuming all collections have the same owner)
collection = adventure.collections.first()
serializer.save(user_id=collection.user_id)
collection = location.collections.first()
serializer.save(user=collection.user)
else:
# Otherwise, set the owner to the request user
serializer.save(user_id=self.request.user)
serializer.save(user=self.request.user)

View file

@ -2,21 +2,19 @@ from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from adventures.models import Category, Adventure
from adventures.models import Category, Location
from adventures.serializers import CategorySerializer
class CategoryViewSet(viewsets.ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
return Category.objects.filter(user_id=self.request.user)
return Category.objects.filter(user=self.request.user)
@action(detail=False, methods=['get'])
def categories(self, request):
def list(self, request, *args, **kwargs):
"""
Retrieve a list of distinct categories for adventures associated with the current user.
Retrieve a list of distinct categories for locations associated with the current user.
"""
categories = self.get_queryset().distinct()
serializer = self.get_serializer(categories, many=True)
@ -24,19 +22,19 @@ class CategoryViewSet(viewsets.ModelViewSet):
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
if instance.user_id != request.user:
if instance.user != request.user:
return Response({"error": "User does not own this category"}, status
=400)
if instance.name == 'general':
return Response({"error": "Cannot delete the general category"}, status=400)
# set any adventures with this category to a default category called general before deleting the category, if general does not exist create it for the user
general_category = Category.objects.filter(user_id=request.user, name='general').first()
# set any locations with this category to a default category called general before deleting the category, if general does not exist create it for the user
general_category = Category.objects.filter(user=request.user, name='general').first()
if not general_category:
general_category = Category.objects.create(user_id=request.user, name='general', icon='🌍', display_name='General')
general_category = Category.objects.create(user=request.user, name='general', icon='🌍', display_name='General')
Adventure.objects.filter(category=instance).update(category=general_category)
Location.objects.filter(category=instance).update(category=general_category)
return super().destroy(request, *args, **kwargs)

View file

@ -6,32 +6,22 @@ from adventures.models import Checklist
from adventures.serializers import ChecklistSerializer
from rest_framework.exceptions import PermissionDenied
from adventures.permissions import IsOwnerOrSharedWithFullAccess
from rest_framework.permissions import IsAuthenticated
class ChecklistViewSet(viewsets.ModelViewSet):
queryset = Checklist.objects.all()
serializer_class = ChecklistSerializer
permission_classes = [IsOwnerOrSharedWithFullAccess]
permission_classes = [IsAuthenticated, IsOwnerOrSharedWithFullAccess]
filterset_fields = ['is_public', 'collection']
# return error message if user is not authenticated on the root endpoint
def list(self, request, *args, **kwargs):
# Prevent listing all adventures
return Response({"detail": "Listing all checklists is not allowed."},
status=status.HTTP_403_FORBIDDEN)
@action(detail=False, methods=['get'])
def all(self, request):
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400)
queryset = Checklist.objects.filter(
Q(user_id=request.user.id)
Q(user=request.user)
)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
def get_queryset(self):
# if the user is not authenticated return only public transportations for retrieve action
# if the user is not authenticated return only public checklists for retrieve action
if not self.request.user.is_authenticated:
if self.action == 'retrieve':
return Checklist.objects.filter(is_public=True).distinct().order_by('-updated_at')
@ -39,14 +29,14 @@ class ChecklistViewSet(viewsets.ModelViewSet):
if self.action == 'retrieve':
# For individual adventure retrieval, include public adventures
# For individual adventure retrieval, include public locations
return Checklist.objects.filter(
Q(is_public=True) | Q(user_id=self.request.user.id) | Q(collection__shared_with=self.request.user)
Q(is_public=True) | Q(user=self.request.user) | Q(collection__shared_with=self.request.user)
).distinct().order_by('-updated_at')
else:
# For other actions, include user's own adventures and shared adventures
# For other actions, include user's own locations and shared locations
return Checklist.objects.filter(
Q(user_id=self.request.user.id) | Q(collection__shared_with=self.request.user)
Q(user=self.request.user) | Q(collection__shared_with=self.request.user)
).distinct().order_by('-updated_at')
def partial_update(self, request, *args, **kwargs):
@ -65,11 +55,11 @@ class ChecklistViewSet(viewsets.ModelViewSet):
if new_collection is not None and new_collection!=instance.collection:
# Check if the user is the owner of the new collection
if new_collection.user_id != user or instance.user_id != user:
if new_collection.user != user or instance.user != user:
raise PermissionDenied("You do not have permission to use this collection.")
elif new_collection is None:
# Handle the case where the user is trying to set the collection to None
if instance.collection is not None and instance.collection.user_id != user:
if instance.collection is not None and instance.collection.user != user:
raise PermissionDenied("You cannot remove the collection as you are not the owner.")
# Perform the update
@ -94,11 +84,11 @@ class ChecklistViewSet(viewsets.ModelViewSet):
if new_collection is not None and new_collection!=instance.collection:
# Check if the user is the owner of the new collection
if new_collection.user_id != user or instance.user_id != user:
if new_collection.user != user or instance.user != user:
raise PermissionDenied("You do not have permission to use this collection.")
elif new_collection is None:
# Handle the case where the user is trying to set the collection to None
if instance.collection is not None and instance.collection.user_id != user:
if instance.collection is not None and instance.collection.user != user:
raise PermissionDenied("You cannot remove the collection as you are not the owner.")
# Perform the update
@ -119,12 +109,12 @@ class ChecklistViewSet(viewsets.ModelViewSet):
if collection:
user = self.request.user
# Check if the user is the owner or is in the shared_with list
if collection.user_id != user and not collection.shared_with.filter(id=user.id).exists():
if collection.user != user and not collection.shared_with.filter(id=user.id).exists():
# Return an error response if the user does not have permission
raise PermissionDenied("You do not have permission to use this collection.")
# if collection the owner of the adventure is the owner of the collection
serializer.save(user_id=collection.user_id)
serializer.save(user=collection.user)
return
# Save the adventure with the current user as the owner
serializer.save(user_id=self.request.user)
serializer.save(user=self.request.user)

View file

@ -4,7 +4,7 @@ from django.db import transaction
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from adventures.models import Collection, Adventure, Transportation, Note, Checklist
from adventures.models import Collection, Location, Transportation, Note, Checklist
from adventures.permissions import CollectionShared
from adventures.serializers import CollectionSerializer
from users.models import CustomUser as User
@ -15,8 +15,6 @@ class CollectionViewSet(viewsets.ModelViewSet):
permission_classes = [CollectionShared]
pagination_class = pagination.StandardResultsSetPagination
# def get_queryset(self):
# return Collection.objects.filter(Q(user_id=self.request.user.id) & Q(is_archived=False))
def apply_sorting(self, queryset):
order_by = self.request.query_params.get('order_by', 'name')
@ -47,15 +45,13 @@ class CollectionViewSet(viewsets.ModelViewSet):
if order_direction == 'asc':
ordering = '-updated_at'
#print(f"Ordering by: {ordering}") # For debugging
return queryset.order_by(ordering)
def list(self, request, *args, **kwargs):
# make sure the user is authenticated
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400)
queryset = Collection.objects.filter(user_id=request.user.id, is_archived=False)
queryset = Collection.objects.filter(user=request.user, is_archived=False)
queryset = self.apply_sorting(queryset)
collections = self.paginate_and_respond(queryset, request)
return collections
@ -66,7 +62,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
return Response({"error": "User is not authenticated"}, status=400)
queryset = Collection.objects.filter(
Q(user_id=request.user.id)
Q(user=request.user)
)
queryset = self.apply_sorting(queryset)
@ -80,7 +76,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
return Response({"error": "User is not authenticated"}, status=400)
queryset = Collection.objects.filter(
Q(user_id=request.user.id) & Q(is_archived=True)
Q(user=request.user.id) & Q(is_archived=True)
)
queryset = self.apply_sorting(queryset)
@ -88,7 +84,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
return Response(serializer.data)
# this make the is_public field of the collection cascade to the adventures
# this make the is_public field of the collection cascade to the locations
@transaction.atomic
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
@ -99,7 +95,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
if 'collection' in serializer.validated_data:
new_collection = serializer.validated_data['collection']
# if the new collection is different from the old one and the user making the request is not the owner of the new collection return an error
if new_collection != instance.collection and new_collection.user_id != request.user:
if new_collection != instance.collection and new_collection.user != request.user:
return Response({"error": "User does not own the new collection"}, status=400)
# Check if the 'is_public' field is present in the update data
@ -107,29 +103,29 @@ class CollectionViewSet(viewsets.ModelViewSet):
new_public_status = serializer.validated_data['is_public']
# if is_public has changed and the user is not the owner of the collection return an error
if new_public_status != instance.is_public and instance.user_id != request.user:
print(f"User {request.user.id} does not own the collection {instance.id} that is owned by {instance.user_id}")
if new_public_status != instance.is_public and instance.user != request.user:
print(f"User {request.user.id} does not own the collection {instance.id} that is owned by {instance.user}")
return Response({"error": "User does not own the collection"}, status=400)
# Get all adventures in this collection
adventures_in_collection = Adventure.objects.filter(collections=instance)
# Get all locations in this collection
locations_in_collection = Location.objects.filter(collections=instance)
if new_public_status:
# If collection becomes public, make all adventures public
adventures_in_collection.update(is_public=True)
# If collection becomes public, make all locations public
locations_in_collection.update(is_public=True)
else:
# If collection becomes private, check each adventure
# Only set an adventure to private if ALL of its collections are private
# Collect adventures that do NOT belong to any other public collection (excluding the current one)
adventure_ids_to_set_private = []
# If collection becomes private, check each location
# Only set a location to private if ALL of its collections are private
# Collect locations that do NOT belong to any other public collection (excluding the current one)
location_ids_to_set_private = []
for adventure in adventures_in_collection:
has_public_collection = adventure.collections.filter(is_public=True).exclude(id=instance.id).exists()
for location in locations_in_collection:
has_public_collection = location.collections.filter(is_public=True).exclude(id=instance.id).exists()
if not has_public_collection:
adventure_ids_to_set_private.append(adventure.id)
location_ids_to_set_private.append(location.id)
# Bulk update those adventures
Adventure.objects.filter(id__in=adventure_ids_to_set_private).update(is_public=False)
# Bulk update those locations
Location.objects.filter(id__in=location_ids_to_set_private).update(is_public=False)
# Update transportations, notes, and checklists related to this collection
# These still use direct ForeignKey relationships
@ -150,7 +146,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
return Response(serializer.data)
# make an action to retreive all adventures that are shared with the user
# make an action to retreive all locations that are shared with the user
@action(detail=False, methods=['get'])
def shared(self, request):
if not request.user.is_authenticated:
@ -162,7 +158,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
# Adds a new user to the shared_with field of an adventure
# Adds a new user to the shared_with field of a location
@action(detail=True, methods=['post'], url_path='share/(?P<uuid>[^/.]+)')
def share(self, request, pk=None, uuid=None):
collection = self.get_object()
@ -177,7 +173,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
return Response({"error": "Cannot share with yourself"}, status=400)
if collection.shared_with.filter(id=user.id).exists():
return Response({"error": "Adventure is already shared with this user"}, status=400)
return Response({"error": "Location is already shared with this user"}, status=400)
collection.shared_with.add(user)
collection.save()
@ -207,28 +203,28 @@ class CollectionViewSet(viewsets.ModelViewSet):
def get_queryset(self):
if self.action == 'destroy':
return Collection.objects.filter(user_id=self.request.user.id)
return Collection.objects.filter(user=self.request.user.id)
if self.action in ['update', 'partial_update']:
return Collection.objects.filter(
Q(user_id=self.request.user.id) | Q(shared_with=self.request.user)
Q(user=self.request.user.id) | Q(shared_with=self.request.user)
).distinct()
if self.action == 'retrieve':
if not self.request.user.is_authenticated:
return Collection.objects.filter(is_public=True)
return Collection.objects.filter(
Q(is_public=True) | Q(user_id=self.request.user.id) | Q(shared_with=self.request.user)
Q(is_public=True) | Q(user=self.request.user.id) | Q(shared_with=self.request.user)
).distinct()
# For list action, include collections owned by the user or shared with the user, that are not archived
return Collection.objects.filter(
(Q(user_id=self.request.user.id) | Q(shared_with=self.request.user)) & Q(is_archived=False)
(Q(user=self.request.user.id) | Q(shared_with=self.request.user)) & Q(is_archived=False)
).distinct()
def perform_create(self, serializer):
# This is ok because you cannot share a collection when creating it
serializer.save(user_id=self.request.user)
serializer.save(user=self.request.user)
def paginate_and_respond(self, queryset, request):
paginator = self.pagination_class()

View file

@ -3,8 +3,8 @@ from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from django.db.models import Q
from django.contrib.postgres.search import SearchVector, SearchQuery
from adventures.models import Adventure, Collection
from adventures.serializers import AdventureSerializer, CollectionSerializer
from adventures.models import Location, Collection
from adventures.serializers import LocationSerializer, CollectionSerializer
from worldtravel.models import Country, Region, City, VisitedCity, VisitedRegion
from worldtravel.serializers import CountrySerializer, RegionSerializer, CitySerializer, VisitedCitySerializer, VisitedRegionSerializer
from users.models import CustomUser as User
@ -20,7 +20,7 @@ class GlobalSearchView(viewsets.ViewSet):
# Initialize empty results
results = {
"adventures": [],
"locations": [],
"collections": [],
"users": [],
"countries": [],
@ -30,15 +30,15 @@ class GlobalSearchView(viewsets.ViewSet):
"visited_cities": []
}
# Adventures: Full-Text Search
adventures = Adventure.objects.annotate(
# Locations: Full-Text Search
locations = Location.objects.annotate(
search=SearchVector('name', 'description', 'location')
).filter(search=SearchQuery(search_term), user_id=request.user)
results["adventures"] = AdventureSerializer(adventures, many=True).data
).filter(search=SearchQuery(search_term), user=request.user)
results["locations"] = LocationSerializer(locations, many=True).data
# Collections: Partial Match Search
collections = Collection.objects.filter(
Q(name__icontains=search_term) & Q(user_id=request.user)
Q(name__icontains=search_term) & Q(user=request.user)
)
results["collections"] = CollectionSerializer(collections, many=True).data
@ -64,10 +64,10 @@ class GlobalSearchView(viewsets.ViewSet):
results["cities"] = CitySerializer(cities, many=True).data
# Visited Regions and Cities
visited_regions = VisitedRegion.objects.filter(user_id=request.user)
visited_regions = VisitedRegion.objects.filter(user=request.user)
results["visited_regions"] = VisitedRegionSerializer(visited_regions, many=True).data
visited_cities = VisitedCity.objects.filter(user_id=request.user)
visited_cities = VisitedCity.objects.filter(user=request.user)
results["visited_cities"] = VisitedCitySerializer(visited_cities, many=True).data
return Response(results)

View file

@ -4,27 +4,26 @@ from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from icalendar import Calendar, Event, vText, vCalAddress
from datetime import datetime, timedelta
from adventures.models import Adventure
from adventures.serializers import AdventureSerializer
from adventures.models import Location
from adventures.serializers import LocationSerializer
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)
locations = Location.objects.filter(user=request.user)
serializer = LocationSerializer(locations, 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']:
for location in serializer.data:
if location['visits']:
for visit in location['visits']:
# Skip if start_date is missing
if not visit.get('start_date'):
continue
@ -42,7 +41,7 @@ class IcsCalendarGeneratorViewSet(viewsets.ViewSet):
# Create event
event = Event()
event.add('summary', adventure['name'])
event.add('summary', location['name'])
event.add('dtstart', start_date)
event.add('dtend', end_date)
event.add('dtstamp', datetime.now())
@ -50,11 +49,11 @@ class IcsCalendarGeneratorViewSet(viewsets.ViewSet):
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'])
event.add('description', location['description'])
if location.get('location'):
event.add('location', location['location'])
if location.get('link'):
event.add('url', location['link'])
organizer = vCalAddress(f'MAILTO:{user.email}')
organizer.params['cn'] = vText(name)

View file

@ -4,15 +4,14 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from django.db.models import Q
from django.core.files.base import ContentFile
from adventures.models import Adventure, AdventureImage
from adventures.serializers import AdventureImageSerializer
from adventures.models import Location, LocationImage
from adventures.serializers import LocationImageSerializer
from integrations.models import ImmichIntegration
import uuid
import requests
import os
class AdventureImageViewSet(viewsets.ModelViewSet):
serializer_class = AdventureImageSerializer
serializer_class = LocationImageSerializer
permission_classes = [IsAuthenticated]
@action(detail=True, methods=['post'])
@ -21,21 +20,21 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
@action(detail=True, methods=['post'])
def toggle_primary(self, request, *args, **kwargs):
# Makes the image the primary image for the adventure, if there is already a primary image linked to the adventure, it is set to false and the new image is set to true. make sure that the permission is set to the owner of the adventure
# Makes the image the primary image for the location, if there is already a primary image linked to the location, it is set to false and the new image is set to true. make sure that the permission is set to the owner of the location
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
instance = self.get_object()
adventure = instance.adventure
if adventure.user_id != request.user:
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
location = instance.location
if location.user != request.user:
return Response({"error": "User does not own this location"}, status=status.HTTP_403_FORBIDDEN)
# Check if the image is already the primary image
if instance.is_primary:
return Response({"error": "Image is already the primary image"}, status=status.HTTP_400_BAD_REQUEST)
# Set the current primary image to false
AdventureImage.objects.filter(adventure=adventure, is_primary=True).update(is_primary=False)
LocationImage.objects.filter(location=location, is_primary=True).update(is_primary=False)
# Set the new image to true
instance.is_primary = True
@ -45,29 +44,29 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
def create(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
adventure_id = request.data.get('adventure')
location_id = request.data.get('location')
try:
adventure = Adventure.objects.get(id=adventure_id)
except Adventure.DoesNotExist:
return Response({"error": "Adventure not found"}, status=status.HTTP_404_NOT_FOUND)
location = Location.objects.get(id=location_id)
except Location.DoesNotExist:
return Response({"error": "location not found"}, status=status.HTTP_404_NOT_FOUND)
if adventure.user_id != request.user:
# Check if the adventure has any collections
if adventure.collections.exists():
# Check if the user is in the shared_with list of any of the adventure's collections
if location.user != request.user:
# Check if the location has any collections
if location.collections.exists():
# Check if the user is in the shared_with list of any of the location's collections
user_has_access = False
for collection in adventure.collections.all():
for collection in location.collections.all():
if collection.shared_with.filter(id=request.user.id).exists():
user_has_access = True
break
if not user_has_access:
return Response({"error": "User does not have permission to access this adventure"}, status=status.HTTP_403_FORBIDDEN)
return Response({"error": "User does not have permission to access this location"}, status=status.HTTP_403_FORBIDDEN)
else:
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
return Response({"error": "User does not own this location"}, status=status.HTTP_403_FORBIDDEN)
# Handle Immich ID for shared users by downloading the image
if (request.user != adventure.user_id and
if (request.user != location.user and
'immich_id' in request.data and
request.data.get('immich_id')):
@ -75,7 +74,7 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
# Get the shared user's Immich integration
try:
user_integration = ImmichIntegration.objects.get(user_id=request.user)
user_integration = ImmichIntegration.objects.get(user=request.user)
except ImmichIntegration.DoesNotExist:
return Response({
"error": "No Immich integration found for your account. Please set up Immich integration first.",
@ -122,8 +121,8 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
serializer.is_valid(raise_exception=True)
# Save with the downloaded image
adventure = serializer.validated_data['adventure']
serializer.save(user_id=adventure.user_id, image=image_file)
location = serializer.validated_data['location']
serializer.save(user=location.user, image=image_file)
return Response(serializer.data, status=status.HTTP_201_CREATED)
@ -144,14 +143,14 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
adventure_id = request.data.get('adventure')
location_id = request.data.get('location')
try:
adventure = Adventure.objects.get(id=adventure_id)
except Adventure.DoesNotExist:
return Response({"error": "Adventure not found"}, status=status.HTTP_404_NOT_FOUND)
location = Location.objects.get(id=location_id)
except Location.DoesNotExist:
return Response({"error": "location not found"}, status=status.HTTP_404_NOT_FOUND)
if adventure.user_id != request.user:
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
if location.user != request.user:
return Response({"error": "User does not own this location"}, status=status.HTTP_403_FORBIDDEN)
return super().update(request, *args, **kwargs)
@ -159,14 +158,13 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
return super().perform_destroy(instance)
def destroy(self, request, *args, **kwargs):
print("destroy")
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
instance = self.get_object()
adventure = instance.adventure
if adventure.user_id != request.user:
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
location = instance.location
if location.user != request.user:
return Response({"error": "User does not own this location"}, status=status.HTTP_403_FORBIDDEN)
return super().destroy(request, *args, **kwargs)
@ -175,27 +173,27 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
instance = self.get_object()
adventure = instance.adventure
if adventure.user_id != request.user:
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN)
location = instance.location
if location.user != request.user:
return Response({"error": "User does not own this location"}, status=status.HTTP_403_FORBIDDEN)
return super().partial_update(request, *args, **kwargs)
@action(detail=False, methods=['GET'], url_path='(?P<adventure_id>[0-9a-f-]+)')
def adventure_images(self, request, adventure_id=None, *args, **kwargs):
@action(detail=False, methods=['GET'], url_path='(?P<location_id>[0-9a-f-]+)')
def location_images(self, request, location_id=None, *args, **kwargs):
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
try:
adventure_uuid = uuid.UUID(adventure_id)
location_uuid = uuid.UUID(location_id)
except ValueError:
return Response({"error": "Invalid adventure ID"}, status=status.HTTP_400_BAD_REQUEST)
return Response({"error": "Invalid location ID"}, status=status.HTTP_400_BAD_REQUEST)
# Updated queryset to include images from adventures the user owns OR has shared access to
queryset = AdventureImage.objects.filter(
Q(adventure__id=adventure_uuid) & (
Q(adventure__user_id=request.user) | # User owns the adventure
Q(adventure__collections__shared_with=request.user) # User has shared access via collection
# Updated queryset to include images from locations the user owns OR has shared access to
queryset = LocationImage.objects.filter(
Q(location__id=location_uuid) & (
Q(location__user=request.user) | # User owns the location
Q(location__collections__shared_with=request.user) # User has shared access via collection
)
).distinct()
@ -203,13 +201,13 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
return Response(serializer.data)
def get_queryset(self):
# Updated to include images from adventures the user owns OR has shared access to
return AdventureImage.objects.filter(
Q(adventure__user_id=self.request.user) | # User owns the adventure
Q(adventure__collections__shared_with=self.request.user) # User has shared access via collection
# Updated to include images from locations the user owns OR has shared access to
return LocationImage.objects.filter(
Q(location__user=self.request.user) | # User owns the location
Q(location__collections__shared_with=self.request.user) # User has shared access via collection
).distinct()
def perform_create(self, serializer):
# Always set the image owner to the adventure owner, not the current user
adventure = serializer.validated_data['adventure']
serializer.save(user_id=adventure.user_id)
# Always set the image owner to the location owner, not the current user
location = serializer.validated_data['location']
serializer.save(user=location.user)

View file

@ -7,19 +7,18 @@ from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
import requests
from adventures.models import Adventure, Category, Transportation, Lodging
from adventures.models import Location, Category
from adventures.permissions import IsOwnerOrSharedWithFullAccess
from adventures.serializers import AdventureSerializer, TransportationSerializer, LodgingSerializer
from adventures.serializers import LocationSerializer
from adventures.utils import pagination
class AdventureViewSet(viewsets.ModelViewSet):
class LocationViewSet(viewsets.ModelViewSet):
"""
ViewSet for managing Adventure objects with support for filtering, sorting,
and sharing functionality.
"""
serializer_class = AdventureSerializer
serializer_class = LocationSerializer
permission_classes = [IsOwnerOrSharedWithFullAccess]
pagination_class = pagination.StandardResultsSetPagination
@ -28,20 +27,20 @@ class AdventureViewSet(viewsets.ModelViewSet):
def get_queryset(self):
"""
Returns queryset based on user authentication and action type.
Public actions allow unauthenticated access to public adventures.
Public actions allow unauthenticated access to public locations.
"""
user = self.request.user
public_allowed_actions = {'retrieve', 'additional_info'}
if not user.is_authenticated:
if self.action in public_allowed_actions:
return Adventure.objects.retrieve_adventures(
return Location.objects.retrieve_locations(
user, include_public=True
).order_by('-updated_at')
return Adventure.objects.none()
return Location.objects.none()
include_public = self.action in public_allowed_actions
return Adventure.objects.retrieve_adventures(
return Location.objects.retrieve_locations(
user,
include_public=include_public,
include_owned=True,
@ -67,7 +66,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
# Apply sorting logic
queryset = self._apply_ordering(queryset, order_by, order_direction)
# Filter adventures without collections if requested
# Filter locations without collections if requested
if include_collections == 'false':
queryset = queryset.filter(collections__isnull=True)
@ -116,7 +115,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
# Use the current user as owner since ManyToMany allows multiple collection owners
user_to_assign = self.request.user
serializer.save(user_id=user_to_assign)
serializer.save(user=user_to_assign)
def perform_update(self, serializer):
"""Update adventure."""
@ -147,18 +146,18 @@ class AdventureViewSet(viewsets.ModelViewSet):
@action(detail=False, methods=['get'])
def filtered(self, request):
"""Filter adventures by category types and visit status."""
"""Filter locations by category types and visit status."""
types = request.query_params.get('types', '').split(',')
# Handle 'all' types
if 'all' in types:
types = Category.objects.filter(
user_id=request.user
user=request.user
).values_list('name', flat=True)
else:
# Validate provided types
if not types or not all(
Category.objects.filter(user_id=request.user, name=type_name).exists()
Category.objects.filter(user=request.user, name=type_name).exists()
for type_name in types
):
return Response(
@ -167,9 +166,9 @@ class AdventureViewSet(viewsets.ModelViewSet):
)
# Build base queryset
queryset = Adventure.objects.filter(
category__in=Category.objects.filter(name__in=types, user_id=request.user),
user_id=request.user.id
queryset = Location.objects.filter(
category__in=Category.objects.filter(name__in=types, user=request.user),
user=request.user.id
)
# Apply visit status filtering
@ -180,19 +179,19 @@ class AdventureViewSet(viewsets.ModelViewSet):
@action(detail=False, methods=['get'])
def all(self, request):
"""Get all adventures (public and owned) with optional collection filtering."""
"""Get all locations (public and owned) with optional collection filtering."""
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400)
include_collections = request.query_params.get('include_collections', 'false') == 'true'
# Build queryset with collection filtering
base_filter = Q(user_id=request.user.id)
base_filter = Q(user=request.user.id)
if include_collections:
queryset = Adventure.objects.filter(base_filter)
queryset = Location.objects.filter(base_filter)
else:
queryset = Adventure.objects.filter(base_filter, collections__isnull=True)
queryset = Location.objects.filter(base_filter, collections__isnull=True)
queryset = self.apply_sorting(queryset)
serializer = self.get_serializer(queryset, many=True)
@ -225,7 +224,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
def _validate_collection_permissions(self, collections):
"""Validate user has permission to use all provided collections. Only the owner or shared users can use collections."""
for collection in collections:
if not (collection.user_id == self.request.user or
if not (collection.user == self.request.user or
collection.shared_with.filter(uuid=self.request.user.uuid).exists()):
raise PermissionDenied(
f"You do not have permission to use collection '{collection.name}'."
@ -235,7 +234,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
"""Validate permissions for collection updates (add/remove)."""
# Check permissions for new collections being added
for collection in new_collections:
if (collection.user_id != self.request.user and
if (collection.user != self.request.user and
not collection.shared_with.filter(uuid=self.request.user.uuid).exists()):
raise PermissionDenied(
f"You do not have permission to use collection '{collection.name}'."
@ -247,7 +246,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
collections_to_remove = current_collections - new_collections_set
for collection in collections_to_remove:
if (collection.user_id != self.request.user and
if (collection.user != self.request.user and
not collection.shared_with.filter(uuid=self.request.user.uuid).exists()):
raise PermissionDenied(
f"You cannot remove the adventure from collection '{collection.name}' "
@ -284,7 +283,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
return True
# Check ownership
if user.is_authenticated and adventure.user_id == user:
if user.is_authenticated and adventure.user == user:
return True
# Check shared collection access

View file

@ -17,7 +17,7 @@ class LodgingViewSet(viewsets.ModelViewSet):
if not request.user.is_authenticated:
return Response(status=status.HTTP_403_FORBIDDEN)
queryset = Lodging.objects.filter(
Q(user_id=request.user.id)
Q(user=request.user.id)
)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
@ -25,13 +25,13 @@ class LodgingViewSet(viewsets.ModelViewSet):
def get_queryset(self):
user = self.request.user
if self.action == 'retrieve':
# For individual adventure retrieval, include public adventures, user's own adventures and shared adventures
# For individual adventure retrieval, include public locations, user's own locations and shared locations
return Lodging.objects.filter(
Q(is_public=True) | Q(user_id=user.id) | Q(collection__shared_with=user.id)
Q(is_public=True) | Q(user=user.id) | Q(collection__shared_with=user.id)
).distinct().order_by('-updated_at')
# For other actions, include user's own adventures and shared adventures
# For other actions, include user's own locations and shared locations
return Lodging.objects.filter(
Q(user_id=user.id) | Q(collection__shared_with=user.id)
Q(user=user.id) | Q(collection__shared_with=user.id)
).distinct().order_by('-updated_at')
def partial_update(self, request, *args, **kwargs):
@ -48,11 +48,11 @@ class LodgingViewSet(viewsets.ModelViewSet):
if new_collection is not None and new_collection != instance.collection:
# Check if the user is the owner of the new collection
if new_collection.user_id != user or instance.user_id != user:
if new_collection.user != user or instance.user != user:
raise PermissionDenied("You do not have permission to use this collection.")
elif new_collection is None:
# Handle the case where the user is trying to set the collection to None
if instance.collection is not None and instance.collection.user_id != user:
if instance.collection is not None and instance.collection.user != user:
raise PermissionDenied("You cannot remove the collection as you are not the owner.")
# Perform the update
@ -73,12 +73,12 @@ class LodgingViewSet(viewsets.ModelViewSet):
if collection:
user = self.request.user
# Check if the user is the owner or is in the shared_with list
if collection.user_id != user and not collection.shared_with.filter(id=user.id).exists():
if collection.user != user and not collection.shared_with.filter(id=user.id).exists():
# Return an error response if the user does not have permission
raise PermissionDenied("You do not have permission to use this collection.")
# if collection the owner of the adventure is the owner of the collection
serializer.save(user_id=collection.user_id)
serializer.save(user=collection.user)
return
# Save the adventure with the current user as the owner
serializer.save(user_id=self.request.user)
serializer.save(user=self.request.user)

View file

@ -15,7 +15,7 @@ class NoteViewSet(viewsets.ModelViewSet):
# return error message if user is not authenticated on the root endpoint
def list(self, request, *args, **kwargs):
# Prevent listing all adventures
# Prevent listing all locations
return Response({"detail": "Listing all notes is not allowed."},
status=status.HTTP_403_FORBIDDEN)
@ -24,7 +24,7 @@ class NoteViewSet(viewsets.ModelViewSet):
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400)
queryset = Note.objects.filter(
Q(user_id=request.user.id)
Q(user=request.user.id)
)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
@ -39,14 +39,14 @@ class NoteViewSet(viewsets.ModelViewSet):
if self.action == 'retrieve':
# For individual adventure retrieval, include public adventures
# For individual adventure retrieval, include public locations
return Note.objects.filter(
Q(is_public=True) | Q(user_id=self.request.user.id) | Q(collection__shared_with=self.request.user)
Q(is_public=True) | Q(user=self.request.user.id) | Q(collection__shared_with=self.request.user)
).distinct().order_by('-updated_at')
else:
# For other actions, include user's own adventures and shared adventures
# For other actions, include user's own locations and shared locations
return Note.objects.filter(
Q(user_id=self.request.user.id) | Q(collection__shared_with=self.request.user)
Q(user=self.request.user.id) | Q(collection__shared_with=self.request.user)
).distinct().order_by('-updated_at')
def partial_update(self, request, *args, **kwargs):
@ -65,11 +65,11 @@ class NoteViewSet(viewsets.ModelViewSet):
if new_collection is not None and new_collection!=instance.collection:
# Check if the user is the owner of the new collection
if new_collection.user_id != user or instance.user_id != user:
if new_collection.user != user or instance.user != user:
raise PermissionDenied("You do not have permission to use this collection.")
elif new_collection is None:
# Handle the case where the user is trying to set the collection to None
if instance.collection is not None and instance.collection.user_id != user:
if instance.collection is not None and instance.collection.user != user:
raise PermissionDenied("You cannot remove the collection as you are not the owner.")
# Perform the update
@ -94,11 +94,11 @@ class NoteViewSet(viewsets.ModelViewSet):
if new_collection is not None and new_collection!=instance.collection:
# Check if the user is the owner of the new collection
if new_collection.user_id != user or instance.user_id != user:
if new_collection.user != user or instance.user != user:
raise PermissionDenied("You do not have permission to use this collection.")
elif new_collection is None:
# Handle the case where the user is trying to set the collection to None
if instance.collection is not None and instance.collection.user_id != user:
if instance.collection is not None and instance.collection.user != user:
raise PermissionDenied("You cannot remove the collection as you are not the owner.")
# Perform the update
@ -119,12 +119,12 @@ class NoteViewSet(viewsets.ModelViewSet):
if collection:
user = self.request.user
# Check if the user is the owner or is in the shared_with list
if collection.user_id != user and not collection.shared_with.filter(id=user.id).exists():
if collection.user != user and not collection.shared_with.filter(id=user.id).exists():
# Return an error response if the user does not have permission
raise PermissionDenied("You do not have permission to use this collection.")
# if collection the owner of the adventure is the owner of the collection
serializer.save(user_id=collection.user_id)
serializer.save(user=collection.user)
return
# Save the adventure with the current user as the owner
serializer.save(user_id=self.request.user)
serializer.save(user=self.request.user)

View file

@ -5,8 +5,6 @@ from rest_framework.response import Response
from django.conf import settings
import requests
from geopy.distance import geodesic
import time
class RecommendationsViewSet(viewsets.ViewSet):
permission_classes = [IsAuthenticated]
@ -14,7 +12,7 @@ class RecommendationsViewSet(viewsets.ViewSet):
HEADERS = {'User-Agent': 'AdventureLog Server'}
def parse_google_places(self, places, origin):
adventures = []
locations = []
for place in places:
location = place.get('location', {})
@ -45,16 +43,16 @@ class RecommendationsViewSet(viewsets.ViewSet):
"distance_km": round(distance_km, 2),
}
adventures.append(adventure)
locations.append(adventure)
# Sort by distance ascending
adventures.sort(key=lambda x: x["distance_km"])
locations.sort(key=lambda x: x["distance_km"])
return adventures
return locations
def parse_overpass_response(self, data, request):
nodes = data.get('elements', [])
adventures = []
locations = []
all = request.query_params.get('all', False)
origin = None
@ -102,13 +100,13 @@ class RecommendationsViewSet(viewsets.ViewSet):
"powered_by": "osm"
}
adventures.append(adventure)
locations.append(adventure)
# Sort by distance if available
if origin:
adventures.sort(key=lambda x: x.get("distance_km") or float("inf"))
locations.sort(key=lambda x: x.get("distance_km") or float("inf"))
return adventures
return locations
def query_overpass(self, lat, lon, radius, category, request):
@ -172,8 +170,8 @@ class RecommendationsViewSet(viewsets.ViewSet):
print("Overpass API error:", e)
return Response({"error": "Failed to retrieve data from Overpass API."}, status=500)
adventures = self.parse_overpass_response(data, request)
return Response(adventures)
locations = self.parse_overpass_response(data, request)
return Response(locations)
def query_google_nearby(self, lat, lon, radius, category, request):
"""Query Google Places API (New) for nearby places"""
@ -216,9 +214,9 @@ class RecommendationsViewSet(viewsets.ViewSet):
places = data.get('places', [])
origin = (float(lat), float(lon))
adventures = self.parse_google_places(places, origin)
locations = self.parse_google_places(places, origin)
return Response(adventures)
return Response(locations)
except requests.exceptions.RequestException as e:
print(f"Google Places API error: {e}")

View file

@ -3,11 +3,9 @@ from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from worldtravel.models import Region, City, VisitedRegion, VisitedCity
from adventures.models import Adventure
from adventures.serializers import AdventureSerializer
import requests
from adventures.models import Location
from adventures.serializers import LocationSerializer
from adventures.geocoding import reverse_geocode
from adventures.geocoding import extractIsoCode
from django.conf import settings
from adventures.geocoding import search_google, search_osm
@ -47,14 +45,14 @@ class ReverseGeocodeViewSet(viewsets.ViewSet):
@action(detail=False, methods=['post'])
def mark_visited_region(self, request):
# searches through all of the users adventures, if the serialized data is_visited, is true, runs reverse geocode on the adventures and if a region is found, marks it as visited. Use the extractIsoCode function to get the region
# searches through all of the users locations, if the serialized data is_visited, is true, runs reverse geocode on the locations and if a region is found, marks it as visited. Use the extractIsoCode function to get the region
new_region_count = 0
new_regions = {}
new_city_count = 0
new_cities = {}
adventures = Adventure.objects.filter(user_id=self.request.user)
serializer = AdventureSerializer(adventures, many=True)
for adventure, serialized_adventure in zip(adventures, serializer.data):
locations = Location.objects.filter(user=self.request.user)
serializer = LocationSerializer(locations, many=True)
for adventure, serialized_adventure in zip(locations, serializer.data):
if serialized_adventure['is_visited'] == True:
lat = adventure.latitude
lon = adventure.longitude
@ -69,18 +67,18 @@ class ReverseGeocodeViewSet(viewsets.ViewSet):
# data already contains region_id and city_id
if 'region_id' in data and data['region_id'] is not None:
region = Region.objects.filter(id=data['region_id']).first()
visited_region = VisitedRegion.objects.filter(region=region, user_id=self.request.user).first()
visited_region = VisitedRegion.objects.filter(region=region, user=self.request.user).first()
if not visited_region:
visited_region = VisitedRegion(region=region, user_id=self.request.user)
visited_region = VisitedRegion(region=region, user=self.request.user)
visited_region.save()
new_region_count += 1
new_regions[region.id] = region.name
if 'city_id' in data and data['city_id'] is not None:
city = City.objects.filter(id=data['city_id']).first()
visited_city = VisitedCity.objects.filter(city=city, user_id=self.request.user).first()
visited_city = VisitedCity.objects.filter(city=city, user=self.request.user).first()
if not visited_city:
visited_city = VisitedCity(city=city, user_id=self.request.user)
visited_city = VisitedCity(city=city, user=self.request.user)
visited_city.save()
new_city_count += 1
new_cities[city.id] = city.name

View file

@ -1,11 +1,9 @@
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.decorators import action
from django.shortcuts import get_object_or_404
from worldtravel.models import City, Region, Country, VisitedCity, VisitedRegion
from adventures.models import Adventure, Collection
from users.serializers import CustomUserDetailsSerializer as PublicUserSerializer
from adventures.models import Location, Collection
from django.contrib.auth import get_user_model
User = get_user_model()
@ -26,21 +24,21 @@ class StatsViewSet(viewsets.ViewSet):
user.email = None
# get the counts for the user
adventure_count = Adventure.objects.filter(
user_id=user.id).count()
location_count = Location.objects.filter(
user=user.id).count()
trips_count = Collection.objects.filter(
user_id=user.id).count()
user=user.id).count()
visited_city_count = VisitedCity.objects.filter(
user_id=user.id).count()
user=user.id).count()
total_cities = City.objects.count()
visited_region_count = VisitedRegion.objects.filter(
user_id=user.id).count()
user=user.id).count()
total_regions = Region.objects.count()
visited_country_count = VisitedRegion.objects.filter(
user_id=user.id).values('region__country').distinct().count()
user=user.id).values('region__country').distinct().count()
total_countries = Country.objects.count()
return Response({
'adventure_count': adventure_count,
'location_count': location_count,
'trips_count': trips_count,
'visited_city_count': visited_city_count,
'total_cities': total_cities,

View file

@ -2,7 +2,7 @@ from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from adventures.models import Adventure
from adventures.models import Location
class ActivityTypesView(viewsets.ViewSet):
permission_classes = [IsAuthenticated]
@ -10,7 +10,7 @@ class ActivityTypesView(viewsets.ViewSet):
@action(detail=False, methods=['get'])
def types(self, request):
"""
Retrieve a list of distinct activity types for adventures associated with the current user.
Retrieve a list of distinct activity types for locations associated with the current user.
Args:
request (HttpRequest): The HTTP request object.
@ -18,7 +18,7 @@ class ActivityTypesView(viewsets.ViewSet):
Returns:
Response: A response containing a list of distinct activity types.
"""
types = Adventure.objects.filter(user_id=request.user.id).values_list('activity_types', flat=True).distinct()
types = Location.objects.filter(user=request.user).values_list('tags', flat=True).distinct()
allTypes = []

View file

@ -1,12 +1,10 @@
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django.db.models import Q
from adventures.models import Transportation
from adventures.serializers import TransportationSerializer
from rest_framework.exceptions import PermissionDenied
from adventures.permissions import IsOwnerOrSharedWithFullAccess
from rest_framework.permissions import IsAuthenticated
class TransportationViewSet(viewsets.ModelViewSet):
queryset = Transportation.objects.all()
@ -17,7 +15,7 @@ class TransportationViewSet(viewsets.ModelViewSet):
if not request.user.is_authenticated:
return Response(status=status.HTTP_403_FORBIDDEN)
queryset = Transportation.objects.filter(
Q(user_id=request.user.id)
Q(user=request.user.id)
)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
@ -25,13 +23,13 @@ class TransportationViewSet(viewsets.ModelViewSet):
def get_queryset(self):
user = self.request.user
if self.action == 'retrieve':
# For individual adventure retrieval, include public adventures, user's own adventures and shared adventures
# For individual adventure retrieval, include public locations, user's own locations and shared locations
return Transportation.objects.filter(
Q(is_public=True) | Q(user_id=user.id) | Q(collection__shared_with=user.id)
Q(is_public=True) | Q(user=user.id) | Q(collection__shared_with=user.id)
).distinct().order_by('-updated_at')
# For other actions, include user's own adventures and shared adventures
# For other actions, include user's own locations and shared locations
return Transportation.objects.filter(
Q(user_id=user.id) | Q(collection__shared_with=user.id)
Q(user=user.id) | Q(collection__shared_with=user.id)
).distinct().order_by('-updated_at')
def partial_update(self, request, *args, **kwargs):
@ -48,11 +46,11 @@ class TransportationViewSet(viewsets.ModelViewSet):
if new_collection is not None and new_collection != instance.collection:
# Check if the user is the owner of the new collection
if new_collection.user_id != user or instance.user_id != user:
if new_collection.user != user or instance.user != user:
raise PermissionDenied("You do not have permission to use this collection.")
elif new_collection is None:
# Handle the case where the user is trying to set the collection to None
if instance.collection is not None and instance.collection.user_id != user:
if instance.collection is not None and instance.collection.user != user:
raise PermissionDenied("You cannot remove the collection as you are not the owner.")
# Perform the update
@ -73,12 +71,12 @@ class TransportationViewSet(viewsets.ModelViewSet):
if collection:
user = self.request.user
# Check if the user is the owner or is in the shared_with list
if collection.user_id != user and not collection.shared_with.filter(id=user.id).exists():
if collection.user != user and not collection.shared_with.filter(id=user.id).exists():
# Return an error response if the user does not have permission
raise PermissionDenied("You do not have permission to use this collection.")
# if collection the owner of the adventure is the owner of the collection
serializer.save(user_id=collection.user_id)
serializer.save(user=collection.user)
return
# Save the adventure with the current user as the owner
serializer.save(user_id=self.request.user)
serializer.save(user=self.request.user)