1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-07-19 21:09:37 +02:00
AdventureLog/backend/server/adventures/views.py

433 lines
17 KiB
Python
Raw Normal View History

2024-07-11 20:59:55 -04:00
import requests
2024-07-15 18:51:05 -04:00
from django.db import transaction
2024-07-08 11:44:39 -04:00
from rest_framework.decorators import action
from rest_framework import viewsets
2024-07-11 20:25:56 -04:00
from django.db.models.functions import Lower
2024-07-08 11:44:39 -04:00
from rest_framework.response import Response
2024-07-27 19:04:55 -04:00
from .models import Adventure, Collection, Transportation
2024-07-10 18:05:12 -04:00
from worldtravel.models import VisitedRegion, Region, Country
2024-07-27 19:04:55 -04:00
from .serializers import AdventureSerializer, CollectionSerializer, TransportationSerializer
2024-07-08 11:44:39 -04:00
from rest_framework.permissions import IsAuthenticated
from django.db.models import Q, Prefetch
2024-07-09 16:48:52 -04:00
from .permissions import IsOwnerOrReadOnly, IsPublicReadOnly
2024-07-11 19:27:03 -04:00
from rest_framework.pagination import PageNumberPagination
2024-07-15 19:53:20 -04:00
from django.shortcuts import get_object_or_404
from rest_framework import status
2024-07-11 19:27:03 -04:00
class StandardResultsSetPagination(PageNumberPagination):
2024-07-12 09:11:00 -04:00
page_size = 10
2024-07-11 19:27:03 -04:00
page_size_query_param = 'page_size'
max_page_size = 1000
from rest_framework.pagination import PageNumberPagination
from rest_framework.decorators import action
from rest_framework.response import Response
from django.db.models import Q
2024-07-08 11:44:39 -04:00
class AdventureViewSet(viewsets.ModelViewSet):
serializer_class = AdventureSerializer
2024-07-09 16:48:52 -04:00
permission_classes = [IsOwnerOrReadOnly, IsPublicReadOnly]
2024-07-11 19:27:03 -04:00
pagination_class = StandardResultsSetPagination
2024-07-08 11:44:39 -04:00
2024-07-13 10:28:45 -04:00
def apply_sorting(self, queryset):
2024-07-19 09:05:47 -04:00
order_by = self.request.query_params.get('order_by', 'updated_at')
2024-07-13 10:28:45 -04:00
order_direction = self.request.query_params.get('order_direction', 'asc')
2024-07-15 18:01:49 -04:00
include_collections = self.request.query_params.get('include_collections', 'true')
2024-07-13 10:28:45 -04:00
2024-07-19 09:05:47 -04:00
valid_order_by = ['name', 'type', 'date', 'rating', 'updated_at']
2024-07-13 10:28:45 -04:00
if order_by not in valid_order_by:
order_by = 'name'
if order_direction not in ['asc', 'desc']:
order_direction = 'asc'
# Apply case-insensitive sorting for the 'name' field
if order_by == 'name':
queryset = queryset.annotate(lower_name=Lower('name'))
ordering = 'lower_name'
else:
ordering = order_by
if order_direction == 'desc':
ordering = f'-{ordering}'
2024-07-19 09:05:47 -04:00
# reverse ordering for updated_at field
if order_by == 'updated_at':
if order_direction == 'asc':
2024-07-19 09:05:47 -04:00
ordering = '-updated_at'
else:
2024-07-19 09:05:47 -04:00
ordering = 'updated_at'
2024-07-13 10:28:45 -04:00
print(f"Ordering by: {ordering}") # For debugging
2024-07-15 12:09:20 -04:00
if include_collections == 'false':
queryset = queryset.filter(collection = None)
2024-07-13 10:28:45 -04:00
return queryset.order_by(ordering)
2024-07-08 11:44:39 -04:00
def get_queryset(self):
2024-07-15 19:53:20 -04:00
if self.action == 'retrieve':
# For individual adventure retrieval, include public adventures
return Adventure.objects.filter(
Q(is_public=True) | Q(user_id=self.request.user.id)
)
else:
# For other actions, only include user's own adventures
return Adventure.objects.filter(user_id=self.request.user.id)
def list(self, request, *args, **kwargs):
# Prevent listing all adventures
return Response({"detail": "Listing all adventures is not allowed."},
status=status.HTTP_403_FORBIDDEN)
def retrieve(self, request, *args, **kwargs):
queryset = self.get_queryset()
adventure = get_object_or_404(queryset, pk=kwargs['pk'])
serializer = self.get_serializer(adventure)
return Response(serializer.data)
2024-07-11 20:25:56 -04:00
2024-07-16 09:26:45 -04:00
def perform_create(self, serializer):
adventure = serializer.save(user_id=self.request.user)
if adventure.collection:
adventure.is_public = adventure.collection.is_public
adventure.save()
def perform_update(self, serializer):
adventure = serializer.save()
if adventure.collection:
adventure.is_public = adventure.collection.is_public
adventure.save()
2024-07-08 11:44:39 -04:00
def perform_create(self, serializer):
serializer.save(user_id=self.request.user)
@action(detail=False, methods=['get'])
2024-07-11 19:27:03 -04:00
def filtered(self, request):
types = request.query_params.get('types', '').split(',')
2024-07-15 09:36:07 -04:00
valid_types = ['visited', 'planned']
2024-07-11 19:27:03 -04:00
types = [t for t in types if t in valid_types]
2024-07-08 11:44:39 -04:00
2024-07-11 19:27:03 -04:00
if not types:
return Response({"error": "No valid types provided"}, status=400)
2024-07-08 11:44:39 -04:00
2024-07-11 19:27:03 -04:00
queryset = Adventure.objects.none()
for adventure_type in types:
if adventure_type in ['visited', 'planned']:
queryset |= Adventure.objects.filter(
2024-07-15 12:09:20 -04:00
type=adventure_type, user_id=request.user.id)
2024-07-11 19:27:03 -04:00
2024-07-13 10:28:45 -04:00
queryset = self.apply_sorting(queryset)
2024-07-11 20:25:56 -04:00
adventures = self.paginate_and_respond(queryset, request)
return adventures
@action(detail=False, methods=['get'])
def all(self, request):
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400)
2024-07-15 12:09:20 -04:00
# include_collections = request.query_params.get('include_collections', 'false')
# if include_collections not in ['true', 'false']:
# include_collections = 'false'
# if include_collections == 'true':
# queryset = Adventure.objects.filter(
# Q(is_public=True) | Q(user_id=request.user.id)
# )
# else:
# queryset = Adventure.objects.filter(
# Q(is_public=True) | Q(user_id=request.user.id), collection=None
# )
2024-07-27 15:41:26 -04:00
allowed_types = ['visited', 'planned']
2024-07-15 12:09:20 -04:00
queryset = Adventure.objects.filter(
2024-07-27 15:41:26 -04:00
Q(user_id=request.user.id) & Q(type__in=allowed_types)
2024-07-15 12:09:20 -04:00
)
queryset = self.apply_sorting(queryset)
2024-07-13 10:48:51 -04:00
serializer = self.get_serializer(queryset, many=True)
2024-07-15 12:09:20 -04:00
2024-07-13 10:48:51 -04:00
return Response(serializer.data)
2024-07-17 17:36:05 -04:00
@action(detail=False, methods=['get'])
def search(self, request):
query = self.request.query_params.get('query', '')
2024-07-18 14:55:23 -04:00
if len(query) < 2:
return Response({"error": "Query must be at least 2 characters long"}, status=400)
2024-07-17 17:36:05 -04:00
queryset = Adventure.objects.filter(
(Q(name__icontains=query) | Q(description__icontains=query) | Q(location__icontains=query) | Q(activity_types__icontains=query)) &
(Q(user_id=request.user.id) | Q(is_public=True))
)
queryset = self.apply_sorting(queryset)
2024-07-22 10:08:42 -04:00
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
2024-07-11 19:27:03 -04:00
def paginate_and_respond(self, queryset, request):
paginator = self.pagination_class()
page = paginator.paginate_queryset(queryset, request)
if page is not None:
serializer = self.get_serializer(page, many=True)
return paginator.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
2024-07-15 09:36:07 -04:00
class CollectionViewSet(viewsets.ModelViewSet):
serializer_class = CollectionSerializer
2024-07-09 16:48:52 -04:00
permission_classes = [IsOwnerOrReadOnly, IsPublicReadOnly]
2024-07-15 09:36:07 -04:00
pagination_class = StandardResultsSetPagination
def apply_sorting(self, queryset):
order_by = self.request.query_params.get('order_by', 'name')
order_direction = self.request.query_params.get('order_direction', 'asc')
valid_order_by = ['name']
if order_by not in valid_order_by:
order_by = 'name'
if order_direction not in ['asc', 'desc']:
order_direction = 'asc'
# Apply case-insensitive sorting for the 'name' field
if order_by == 'name':
queryset = queryset.annotate(lower_name=Lower('name'))
ordering = 'lower_name'
else:
ordering = order_by
if order_direction == 'desc':
ordering = f'-{ordering}'
print(f"Ordering by: {ordering}") # For debugging
return queryset.order_by(ordering)
2024-07-15 18:51:05 -04:00
2024-07-16 09:12:53 -04:00
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 = self.get_queryset()
queryset = self.apply_sorting(queryset)
collections = self.paginate_and_respond(queryset, request)
return collections
@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 = Collection.objects.filter(
Q(user_id=request.user.id)
)
queryset = self.apply_sorting(queryset)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
2024-07-15 18:51:05 -04:00
# this make the is_public field of the collection cascade to the adventures
@transaction.atomic
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
# Check if the 'is_public' field is present in the update data
if 'is_public' in serializer.validated_data:
new_public_status = serializer.validated_data['is_public']
# Update associated adventures to match the collection's is_public status
Adventure.objects.filter(collection=instance).update(is_public=new_public_status)
2024-07-27 19:04:55 -04:00
# do the same for transportations
Transportation.objects.filter(collection=instance).update(is_public=new_public_status)
2024-07-15 18:51:05 -04:00
# Log the action (optional)
action = "public" if new_public_status else "private"
print(f"Collection {instance.id} and its adventures were set to {action}")
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return Response(serializer.data)
def get_queryset(self):
2024-07-15 09:36:07 -04:00
collections = Collection.objects.filter(
Q(is_public=True) | Q(user_id=self.request.user.id)
2024-07-10 13:36:51 -04:00
).prefetch_related(
Prefetch('adventure_set', queryset=Adventure.objects.filter(
Q(is_public=True) | Q(user_id=self.request.user.id)
))
2024-07-27 19:22:01 -04:00
).prefetch_related(
Prefetch('transportation_set', queryset=Transportation.objects.filter(
Q(is_public=True) | Q(user_id=self.request.user.id)
))
)
2024-07-15 09:36:07 -04:00
return self.apply_sorting(collections)
def perform_create(self, serializer):
serializer.save(user_id=self.request.user)
2024-07-15 09:36:07 -04:00
# @action(detail=False, methods=['get'])
# def filtered(self, request):
# types = request.query_params.get('types', '').split(',')
# valid_types = ['visited', 'planned']
# types = [t for t in types if t in valid_types]
2024-07-15 09:36:07 -04:00
# if not types:
# return Response({"error": "No valid types provided"}, status=400)
2024-07-15 09:36:07 -04:00
# queryset = Collection.objects.none()
# for adventure_type in types:
# if adventure_type in ['visited', 'planned']:
# queryset |= Collection.objects.filter(
# type=adventure_type, user_id=request.user.id)
# queryset = self.apply_sorting(queryset)
# collections = self.paginate_and_respond(queryset, request)
# return collections
def paginate_and_respond(self, queryset, request):
paginator = self.pagination_class()
page = paginator.paginate_queryset(queryset, request)
if page is not None:
serializer = self.get_serializer(page, many=True)
return paginator.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
2024-07-10 17:27:43 -04:00
return Response(serializer.data)
2024-07-27 19:22:01 -04:00
# make a view to return all of the associated transportations with a collection
@action(detail=True, methods=['get'])
def transportations(self, request, pk=None):
collection = self.get_object()
transportations = Transportation.objects.filter(collection=collection)
serializer = TransportationSerializer(transportations, many=True)
return Response(serializer.data)
2024-07-10 17:27:43 -04:00
class StatsViewSet(viewsets.ViewSet):
permission_classes = [IsAuthenticated]
@action(detail=False, methods=['get'])
def counts(self, request):
visited_count = Adventure.objects.filter(
type='visited', user_id=request.user.id).count()
planned_count = Adventure.objects.filter(
type='planned', user_id=request.user.id).count()
2024-07-15 09:36:07 -04:00
trips_count = Collection.objects.filter(
2024-07-10 17:27:43 -04:00
user_id=request.user.id).count()
2024-07-10 18:05:12 -04:00
visited_region_count = VisitedRegion.objects.filter(
2024-07-10 17:27:43 -04:00
user_id=request.user.id).count()
2024-07-10 18:05:12 -04:00
total_regions = Region.objects.count()
2024-07-10 17:27:43 -04:00
country_count = VisitedRegion.objects.filter(
user_id=request.user.id).values('region__country').distinct().count()
2024-07-10 18:05:12 -04:00
total_countries = Country.objects.count()
2024-07-10 17:27:43 -04:00
return Response({
'visited_count': visited_count,
'planned_count': planned_count,
'trips_count': trips_count,
2024-07-10 18:05:12 -04:00
'visited_region_count': visited_region_count,
'total_regions': total_regions,
2024-07-10 17:27:43 -04:00
'country_count': country_count,
2024-07-10 18:05:12 -04:00
'total_countries': total_countries
2024-07-11 20:59:55 -04:00
})
class GenerateDescription(viewsets.ViewSet):
permission_classes = [IsAuthenticated]
@action(detail=False, methods=['get'],)
def desc(self, request):
name = self.request.query_params.get('name', '')
# un url encode the name
name = name.replace('%20', ' ')
print(name)
url = 'https://en.wikipedia.org/w/api.php?origin=*&action=query&prop=extracts&exintro&explaintext&format=json&titles=%s' % name
response = requests.get(url)
data = response.json()
data = response.json()
page_id = next(iter(data["query"]["pages"]))
extract = data["query"]["pages"][page_id]
if extract.get('extract') is None:
return Response({"error": "No description found"}, status=400)
return Response(extract)
@action(detail=False, methods=['get'],)
def img(self, request):
name = self.request.query_params.get('name', '')
# un url encode the name
name = name.replace('%20', ' ')
url = 'https://en.wikipedia.org/w/api.php?origin=*&action=query&prop=pageimages&format=json&piprop=original&titles=%s' % name
response = requests.get(url)
data = response.json()
page_id = next(iter(data["query"]["pages"]))
extract = data["query"]["pages"][page_id]
if extract.get('original') is None:
return Response({"error": "No image found"}, status=400)
return Response(extract["original"])
class ActivityTypesView(viewsets.ViewSet):
permission_classes = [IsAuthenticated]
@action(detail=False, methods=['get'])
def types(self, request):
"""
Retrieve a list of distinct activity types for adventures associated with the current user.
Args:
request (HttpRequest): The HTTP request object.
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()
allTypes = []
for i in types:
if not i:
continue
for x in i:
if x and x not in allTypes:
allTypes.append(x)
return Response(allTypes)
2024-07-27 19:04:55 -04:00
class TransportationViewSet(viewsets.ModelViewSet):
queryset = Transportation.objects.all()
serializer_class = TransportationSerializer
permission_classes = [IsAuthenticated]
filterset_fields = ['type', '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 adventures is not allowed."},
status=status.HTTP_403_FORBIDDEN)
2024-07-27 19:22:01 -04:00
@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 = Transportation.objects.filter(
Q(user_id=request.user.id)
)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
2024-07-27 19:04:55 -04:00
def get_queryset(self):
"""
This view should return a list of all transportations
for the currently authenticated user.
"""
user = self.request.user
return Transportation.objects.filter(user_id=user)
def perform_create(self, serializer):
serializer.save(user_id=self.request.user)