1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-08-02 19:55:18 +02:00

collections v1

This commit is contained in:
Sean Morley 2024-07-15 09:36:07 -04:00
parent c446372bcb
commit 533453b764
21 changed files with 1170 additions and 207 deletions

View file

@ -1,7 +1,7 @@
import os
from django.contrib import admin
from django.utils.html import mark_safe
from .models import Adventure, Trip
from .models import Adventure, Collection
from worldtravel.models import Country, Region, VisitedRegion
@ -65,7 +65,7 @@ admin.site.register(Adventure, AdventureAdmin)
admin.site.register(Country, CountryAdmin)
admin.site.register(Region, RegionAdmin)
admin.site.register(VisitedRegion)
admin.site.register(Trip)
admin.site.register(Collection)
admin.site.site_header = 'AdventureLog Admin'
admin.site.site_title = 'AdventureLog Admin Site'

View file

@ -0,0 +1,42 @@
# Generated by Django 5.0.6 on 2024-07-15 12:57
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('adventures', '0006_alter_adventure_type_alter_trip_type'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.RemoveField(
model_name='adventure',
name='trip',
),
migrations.AlterField(
model_name='adventure',
name='type',
field=models.CharField(choices=[('visited', 'Visited'), ('planned', 'Planned')], max_length=100),
),
migrations.CreateModel(
name='Collection',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=200)),
('is_public', models.BooleanField(default=False)),
('user_id', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField(
model_name='adventure',
name='collection',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='adventures.collection'),
),
migrations.DeleteModel(
name='Trip',
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2024-07-15 13:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('adventures', '0007_remove_adventure_trip_alter_adventure_type_and_more'),
]
operations = [
migrations.AddField(
model_name='collection',
name='description',
field=models.TextField(blank=True, null=True),
),
]

View file

@ -7,7 +7,6 @@ from django.forms import ValidationError
ADVENTURE_TYPES = [
('visited', 'Visited'),
('planned', 'Planned'),
('featured', 'Featured')
]
@ -34,40 +33,31 @@ class Adventure(models.Model):
is_public = models.BooleanField(default=False)
longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
trip = models.ForeignKey('Trip', on_delete=models.CASCADE, blank=True, null=True)
collection = models.ForeignKey('Collection', on_delete=models.CASCADE, blank=True, null=True)
def clean(self):
if self.trip:
if self.trip.is_public and not self.is_public:
raise ValidationError('Adventures associated with a public trip must be public. Trip: ' + self.trip.name + ' Adventure: ' + self.name)
if self.user_id != self.trip.user_id:
raise ValidationError('Adventures must be associated with trips owned by the same user. Trip owner: ' + self.trip.user_id.username + ' Adventure owner: ' + self.user_id.username)
if self.type != self.trip.type:
raise ValidationError('Adventure type must match trip type. Trip type: ' + self.trip.type + ' Adventure type: ' + self.type)
if self.type == 'featured' and not self.is_public:
raise ValidationError('Featured adventures must be public. Adventure: ' + self.name)
if self.collection:
if self.collection.is_public and not self.is_public:
raise ValidationError('Adventures associated with a public collection must be public. Collection: ' + self.trip.name + ' Adventure: ' + self.name)
if self.user_id != self.collection.user_id:
raise ValidationError('Adventures must be associated with collections owned by the same user. Collection owner: ' + self.collection.user_id.username + ' Adventure owner: ' + self.user_id.username)
def __str__(self):
return self.name
class Trip(models.Model):
class Collection(models.Model):
id = models.AutoField(primary_key=True)
user_id = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id)
name = models.CharField(max_length=200)
type = models.CharField(max_length=100, choices=ADVENTURE_TYPES)
location = models.CharField(max_length=200, blank=True, null=True)
date = models.DateField(blank=True, null=True)
description = models.TextField(blank=True, null=True)
is_public = models.BooleanField(default=False)
# if connected adventures are private and trip is public, raise an error
# if connected adventures are private and collection is public, raise an error
def clean(self):
if self.is_public and self.pk: # Only check if the instance has a primary key
for adventure in self.adventure_set.all():
if not adventure.is_public:
raise ValidationError('Public trips cannot be associated with private adventures. Trip: ' + self.name + ' Adventure: ' + adventure.name)
if self.type == 'featured' and not self.is_public:
raise ValidationError('Featured trips must be public. Trip: ' + self.name)
raise ValidationError('Public collections cannot be associated with private adventures. Collection: ' + self.name + ' Adventure: ' + adventure.name)
def __str__(self):
return self.name

View file

@ -1,5 +1,5 @@
import os
from .models import Adventure, Trip
from .models import Adventure, Collection
from rest_framework import serializers
class AdventureSerializer(serializers.ModelSerializer):
@ -18,13 +18,13 @@ class AdventureSerializer(serializers.ModelSerializer):
representation['image'] = f"{public_url}/media/{instance.image.name}"
return representation
class TripSerializer(serializers.ModelSerializer):
class CollectionSerializer(serializers.ModelSerializer):
adventures = AdventureSerializer(many=True, read_only=True, source='adventure_set')
class Meta:
model = Trip
model = Collection
# fields are all plus the adventures field
fields = ['id', 'user_id', 'name', 'type', 'location', 'date', 'is_public', 'adventures']
fields = ['id', 'user_id', 'name', 'is_public', 'adventures']

View file

@ -1,10 +1,10 @@
from django.urls import include, path
from rest_framework.routers import DefaultRouter
from .views import AdventureViewSet, TripViewSet, StatsViewSet, GenerateDescription
from .views import AdventureViewSet, CollectionViewSet, StatsViewSet, GenerateDescription
router = DefaultRouter()
router.register(r'adventures', AdventureViewSet, basename='adventures')
router.register(r'trips', TripViewSet, basename='trips')
router.register(r'collections', CollectionViewSet, basename='collections')
router.register(r'stats', StatsViewSet, basename='stats')
router.register(r'generate', GenerateDescription, basename='generate')

View file

@ -3,9 +3,9 @@ from rest_framework.decorators import action
from rest_framework import viewsets
from django.db.models.functions import Lower
from rest_framework.response import Response
from .models import Adventure, Trip
from .models import Adventure, Collection
from worldtravel.models import VisitedRegion, Region, Country
from .serializers import AdventureSerializer, TripSerializer
from .serializers import AdventureSerializer, CollectionSerializer
from rest_framework.permissions import IsAuthenticated
from django.db.models import Q, Prefetch
from .permissions import IsOwnerOrReadOnly, IsPublicReadOnly
@ -65,7 +65,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
@action(detail=False, methods=['get'])
def filtered(self, request):
types = request.query_params.get('types', '').split(',')
valid_types = ['visited', 'planned', 'featured']
valid_types = ['visited', 'planned']
types = [t for t in types if t in valid_types]
if not types:
@ -76,10 +76,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
for adventure_type in types:
if adventure_type in ['visited', 'planned']:
queryset |= Adventure.objects.filter(
type=adventure_type, user_id=request.user.id, trip=None)
elif adventure_type == 'featured':
queryset |= Adventure.objects.filter(
type='featured', is_public=True, trip=None)
type=adventure_type, user_id=request.user.id, collection=None)
queryset = self.apply_sorting(queryset)
adventures = self.paginate_and_respond(queryset, request)
@ -89,7 +86,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
def all(self, request):
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400)
queryset = Adventure.objects.filter(user_id=request.user.id).exclude(type='featured')
queryset = Adventure.objects.filter(user_id=request.user.id)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
@ -101,39 +98,77 @@ class AdventureViewSet(viewsets.ModelViewSet):
return paginator.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
class TripViewSet(viewsets.ModelViewSet):
serializer_class = TripSerializer
class CollectionViewSet(viewsets.ModelViewSet):
serializer_class = CollectionSerializer
permission_classes = [IsOwnerOrReadOnly, IsPublicReadOnly]
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)
def get_queryset(self):
return Trip.objects.filter(
collections = Collection.objects.filter(
Q(is_public=True) | Q(user_id=self.request.user.id)
).prefetch_related(
Prefetch('adventure_set', queryset=Adventure.objects.filter(
Q(is_public=True) | Q(user_id=self.request.user.id)
))
)
return self.apply_sorting(collections)
def perform_create(self, serializer):
serializer.save(user_id=self.request.user)
@action(detail=False, methods=['get'])
@action(detail=False, methods=['get'])
def visited(self, request):
visited_adventures = Adventure.objects.filter(
type='visited', user_id=request.user.id, trip=None)
return self.get_paginated_response(visited_adventures)
# @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]
@action(detail=False, methods=['get'])
def planned(self, request):
trips = self.get_queryset().filter(type='planned', user_id=request.user.id)
serializer = self.get_serializer(trips, many=True)
return Response(serializer.data)
# if not types:
# return Response({"error": "No valid types provided"}, status=400)
@action(detail=False, methods=['get'])
def featured(self, request):
trips = self.get_queryset().filter(type='featured', is_public=True)
serializer = self.get_serializer(trips, many=True)
# 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)
return Response(serializer.data)
class StatsViewSet(viewsets.ViewSet):
@ -145,9 +180,7 @@ class StatsViewSet(viewsets.ViewSet):
type='visited', user_id=request.user.id).count()
planned_count = Adventure.objects.filter(
type='planned', user_id=request.user.id).count()
featured_count = Adventure.objects.filter(
type='featured', is_public=True).count()
trips_count = Trip.objects.filter(
trips_count = Collection.objects.filter(
user_id=request.user.id).count()
visited_region_count = VisitedRegion.objects.filter(
user_id=request.user.id).count()
@ -158,7 +191,6 @@ class StatsViewSet(viewsets.ViewSet):
return Response({
'visited_count': visited_count,
'planned_count': planned_count,
'featured_count': featured_count,
'trips_count': trips_count,
'visited_region_count': visited_region_count,
'total_regions': total_regions,