diff --git a/backend/server/adventures/admin.py b/backend/server/adventures/admin.py index c736aa7..5cc7b4b 100644 --- a/backend/server/adventures/admin.py +++ b/backend/server/adventures/admin.py @@ -1,7 +1,7 @@ import os from django.contrib import admin from django.utils.html import mark_safe -from .models import Adventure, Collection +from .models import Adventure, Collection, Transportation from worldtravel.models import Country, Region, VisitedRegion @@ -74,6 +74,7 @@ admin.site.register(Collection, CollectionAdmin) admin.site.register(Country, CountryAdmin) admin.site.register(Region, RegionAdmin) admin.site.register(VisitedRegion) +admin.site.register(Transportation) admin.site.site_header = 'AdventureLog Admin' admin.site.site_title = 'AdventureLog Admin Site' diff --git a/backend/server/adventures/migrations/0013_alter_adventure_type_transportation.py b/backend/server/adventures/migrations/0013_alter_adventure_type_transportation.py new file mode 100644 index 0000000..ab7402f --- /dev/null +++ b/backend/server/adventures/migrations/0013_alter_adventure_type_transportation.py @@ -0,0 +1,41 @@ +# Generated by Django 5.0.7 on 2024-07-27 22:49 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('adventures', '0012_collection_end_date_collection_start_date'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterField( + model_name='adventure', + name='type', + field=models.CharField(choices=[('visited', 'Visited'), ('planned', 'Planned'), ('lodging', 'Lodging'), ('dining', 'Dining')], max_length=100), + ), + migrations.CreateModel( + name='Transportation', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('type', models.CharField(max_length=100)), + ('name', models.CharField(max_length=200)), + ('description', models.TextField(blank=True, null=True)), + ('rating', models.FloatField(blank=True, null=True)), + ('link', models.URLField(blank=True, null=True)), + ('date', models.DateTimeField(blank=True, null=True)), + ('flight_number', models.CharField(blank=True, max_length=100, null=True)), + ('from_location', models.CharField(blank=True, max_length=200, null=True)), + ('to_location', models.CharField(blank=True, max_length=200, null=True)), + ('is_public', models.BooleanField(default=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('collection', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='adventures.collection')), + ('user_id', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/backend/server/adventures/models.py b/backend/server/adventures/models.py index a872abd..e7027cd 100644 --- a/backend/server/adventures/models.py +++ b/backend/server/adventures/models.py @@ -12,6 +12,17 @@ ADVENTURE_TYPES = [ ('dining', 'Dining') ] +TRANSPORTATION_TYPES = [ + ('car', 'Car'), + ('plane', 'Plane'), + ('train', 'Train'), + ('bus', 'Bus'), + ('boat', 'Boat'), + ('bike', 'Bike'), + ('walking', 'Walking'), + ('other', 'Other') +] + # Assuming you have a default user ID you want to use default_user_id = 1 # Replace with an actual user ID @@ -70,3 +81,33 @@ class Collection(models.Model): def __str__(self): return self.name + +# make a class for transportaiotn and make it linked to a collection. Make it so it can be used for different types of transportations like car, plane, train, etc. + +class Transportation(models.Model): + id = models.AutoField(primary_key=True) + user_id = models.ForeignKey( + User, on_delete=models.CASCADE, default=default_user_id) + type = models.CharField(max_length=100, choices=TRANSPORTATION_TYPES) + name = models.CharField(max_length=200) + description = models.TextField(blank=True, null=True) + rating = models.FloatField(blank=True, null=True) + link = models.URLField(blank=True, null=True) + date = models.DateTimeField(blank=True, null=True) + flight_number = models.CharField(max_length=100, blank=True, null=True) + from_location = models.CharField(max_length=200, blank=True, null=True) + to_location = models.CharField(max_length=200, blank=True, null=True) + is_public = models.BooleanField(default=False) + collection = models.ForeignKey('Collection', on_delete=models.CASCADE, blank=True, null=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def clean(self): + if self.collection: + if self.collection.is_public and not self.is_public: + raise ValidationError('Transportations associated with a public collection must be public. Collection: ' + self.trip.name + ' Transportation: ' + self.name) + if self.user_id != self.collection.user_id: + raise ValidationError('Transportations must be associated with collections owned by the same user. Collection owner: ' + self.collection.user_id.username + ' Transportation owner: ' + self.user_id.username) + + def __str__(self): + return self.name diff --git a/backend/server/adventures/serializers.py b/backend/server/adventures/serializers.py index 84c38a2..1057eee 100644 --- a/backend/server/adventures/serializers.py +++ b/backend/server/adventures/serializers.py @@ -1,5 +1,5 @@ import os -from .models import Adventure, Collection +from .models import Adventure, Collection, Transportation from rest_framework import serializers class AdventureSerializer(serializers.ModelSerializer): @@ -31,5 +31,38 @@ class CollectionSerializer(serializers.ModelSerializer): # fields are all plus the adventures field fields = ['id', 'description', 'user_id', 'name', 'is_public', 'adventures', 'created_at', 'start_date', 'end_date'] +class TransportationSerializer(serializers.ModelSerializer): + + class Meta: + model = Transportation + fields = [ + 'id', 'user_id', 'type', 'name', 'description', 'rating', + 'link', 'date', 'flight_number', 'from_location', 'to_location', + 'is_public', 'collection', 'collection_id', 'created_at', 'updated_at' + ] + read_only_fields = ['id', 'created_at', 'updated_at'] + + def validate(self, data): + # Check if the collection is public and the transportation is not + collection = data.get('collection') + is_public = data.get('is_public', False) + if collection and collection.is_public and not is_public: + raise serializers.ValidationError( + 'Transportations associated with a public collection must be public.' + ) + + # Check if the user owns the collection + request = self.context.get('request') + if request and collection and collection.user_id != request.user: + raise serializers.ValidationError( + 'Transportations must be associated with collections owned by the same user.' + ) + + return data + + def create(self, validated_data): + # Set the user_id to the current user + validated_data['user_id'] = self.context['request'].user + return super().create(validated_data) \ No newline at end of file diff --git a/backend/server/adventures/urls.py b/backend/server/adventures/urls.py index 10cdb1a..7f876da 100644 --- a/backend/server/adventures/urls.py +++ b/backend/server/adventures/urls.py @@ -1,6 +1,6 @@ from django.urls import include, path from rest_framework.routers import DefaultRouter -from .views import AdventureViewSet, CollectionViewSet, StatsViewSet, GenerateDescription, ActivityTypesView +from .views import AdventureViewSet, CollectionViewSet, StatsViewSet, GenerateDescription, ActivityTypesView, TransportationViewSet router = DefaultRouter() router.register(r'adventures', AdventureViewSet, basename='adventures') @@ -8,6 +8,7 @@ router.register(r'collections', CollectionViewSet, basename='collections') router.register(r'stats', StatsViewSet, basename='stats') router.register(r'generate', GenerateDescription, basename='generate') router.register(r'activity-types', ActivityTypesView, basename='activity-types') +router.register(r'transportations', TransportationViewSet, basename='transportations') urlpatterns = [ diff --git a/backend/server/adventures/views.py b/backend/server/adventures/views.py index e06940f..8d427b4 100644 --- a/backend/server/adventures/views.py +++ b/backend/server/adventures/views.py @@ -4,9 +4,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, Collection +from .models import Adventure, Collection, Transportation from worldtravel.models import VisitedRegion, Region, Country -from .serializers import AdventureSerializer, CollectionSerializer +from .serializers import AdventureSerializer, CollectionSerializer, TransportationSerializer from rest_framework.permissions import IsAuthenticated from django.db.models import Q, Prefetch from .permissions import IsOwnerOrReadOnly, IsPublicReadOnly @@ -238,6 +238,9 @@ class CollectionViewSet(viewsets.ModelViewSet): # Update associated adventures to match the collection's is_public status Adventure.objects.filter(collection=instance).update(is_public=new_public_status) + # do the same for transportations + Transportation.objects.filter(collection=instance).update(is_public=new_public_status) + # Log the action (optional) action = "public" if new_public_status else "private" print(f"Collection {instance.id} and its adventures were set to {action}") @@ -379,3 +382,30 @@ class ActivityTypesView(viewsets.ViewSet): allTypes.append(x) return Response(allTypes) + +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) + + + 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) + + \ No newline at end of file diff --git a/frontend/src/lib/components/AdventureCard.svelte b/frontend/src/lib/components/AdventureCard.svelte index 8afa422..a907e16 100644 --- a/frontend/src/lib/components/AdventureCard.svelte +++ b/frontend/src/lib/components/AdventureCard.svelte @@ -163,9 +163,11 @@
Visited
{:else if user?.pk == adventure.user_id && adventure.type == 'planned'}
Planned
+ {:else if (user?.pk !== adventure.user_id && adventure.type == 'planned') || adventure.type == 'visited'} +
Adventure
{:else if user?.pk == adventure.user_id && adventure.type == 'lodging'}
Lodging
- {:else if user?.pk == adventure.user_id && adventure.type == 'dining'} + {:else if adventure.type == 'dining'}
Dining
{/if}