From 7c5b448c646c6f7b23209383b54ed3ab609e377b Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Sun, 22 Sep 2024 10:26:55 -0400 Subject: [PATCH 1/4] Revert "Add new model fields and remove old ones" This reverts commit eab312e94e681b667780e84c97232b0d25ea3a7e. --- backend/server/adventures/admin.py | 2 +- ...date_remove_adventure_end_date_and_more.py | 35 ------------- backend/server/adventures/models.py | 31 +++++------ backend/server/adventures/serializers.py | 52 +++++++------------ 4 files changed, 32 insertions(+), 88 deletions(-) delete mode 100644 backend/server/adventures/migrations/0007_remove_adventure_date_remove_adventure_end_date_and_more.py diff --git a/backend/server/adventures/admin.py b/backend/server/adventures/admin.py index de7227d..70eedc9 100644 --- a/backend/server/adventures/admin.py +++ b/backend/server/adventures/admin.py @@ -6,7 +6,7 @@ from worldtravel.models import Country, Region, VisitedRegion class AdventureAdmin(admin.ModelAdmin): - list_display = ('name', 'type', 'user_id', 'is_public', 'image_display') + list_display = ('name', 'type', 'user_id', 'date', 'is_public', 'image_display') list_filter = ('type', 'user_id', 'is_public') def image_display(self, obj): diff --git a/backend/server/adventures/migrations/0007_remove_adventure_date_remove_adventure_end_date_and_more.py b/backend/server/adventures/migrations/0007_remove_adventure_date_remove_adventure_end_date_and_more.py deleted file mode 100644 index 03f6b1e..0000000 --- a/backend/server/adventures/migrations/0007_remove_adventure_date_remove_adventure_end_date_and_more.py +++ /dev/null @@ -1,35 +0,0 @@ -# Generated by Django 5.0.8 on 2024-09-22 04:02 - -import django.db.models.deletion -import uuid -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('adventures', '0006_alter_adventure_link'), - ] - - operations = [ - migrations.RemoveField( - model_name='adventure', - name='date', - ), - migrations.RemoveField( - model_name='adventure', - name='end_date', - ), - migrations.CreateModel( - name='Visit', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), - ('start_date', models.DateField()), - ('end_date', models.DateField()), - ('notes', models.TextField(blank=True, null=True)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('adventure', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='visits', to='adventures.adventure')), - ], - ), - ] diff --git a/backend/server/adventures/models.py b/backend/server/adventures/models.py index 228b301..68c044e 100644 --- a/backend/server/adventures/models.py +++ b/backend/server/adventures/models.py @@ -30,23 +30,9 @@ default_user_id = 1 # Replace with an actual user ID User = get_user_model() -class Visit(models.Model): - id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) - adventure = models.ForeignKey('Adventure', on_delete=models.CASCADE, related_name='visits') - start_date = models.DateField() - end_date = models.DateField() - notes = models.TextField(blank=True, null=True) - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) - - def clean(self): - if self.start_date > self.end_date: - raise ValidationError('The start date must be before or equal to the end date.') - - def __str__(self): - return f"{self.adventure.name} - {self.start_date} to {self.end_date}" class Adventure(models.Model): + #id = models.AutoField(primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) user_id = models.ForeignKey( User, on_delete=models.CASCADE, default=default_user_id) @@ -59,6 +45,8 @@ class Adventure(models.Model): rating = models.FloatField(blank=True, null=True) link = models.URLField(blank=True, null=True, max_length=2083) image = ResizedImageField(force_format="WEBP", quality=75, null=True, blank=True, upload_to='images/') + date = models.DateField(blank=True, null=True) + end_date = models.DateField(blank=True, null=True) 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) @@ -67,7 +55,10 @@ class Adventure(models.Model): updated_at = models.DateTimeField(auto_now=True) def clean(self): - + if self.date and self.end_date and self.date > self.end_date: + raise ValidationError('The start date must be before the end date. Start date: ' + str(self.date) + ' End date: ' + str(self.end_date)) + if self.end_date and not self.date: + raise ValidationError('Adventures must have an end date. 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) @@ -78,6 +69,7 @@ class Adventure(models.Model): return self.name class Collection(models.Model): + #id = models.AutoField(primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) user_id = models.ForeignKey( User, on_delete=models.CASCADE, default=default_user_id) @@ -103,6 +95,7 @@ class Collection(models.Model): return self.name class Transportation(models.Model): + #id = models.AutoField(primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) user_id = models.ForeignKey( User, on_delete=models.CASCADE, default=default_user_id) @@ -136,6 +129,7 @@ class Transportation(models.Model): return self.name class Note(models.Model): + #id = models.AutoField(primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) user_id = models.ForeignKey( User, on_delete=models.CASCADE, default=default_user_id) @@ -159,6 +153,7 @@ class Note(models.Model): return self.name class Checklist(models.Model): + # id = models.AutoField(primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) user_id = models.ForeignKey( User, on_delete=models.CASCADE, default=default_user_id) @@ -180,6 +175,7 @@ class Checklist(models.Model): return self.name class ChecklistItem(models.Model): + #id = models.AutoField(primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) user_id = models.ForeignKey( User, on_delete=models.CASCADE, default=default_user_id) @@ -206,5 +202,4 @@ class AdventureImage(models.Model): adventure = models.ForeignKey(Adventure, related_name='images', on_delete=models.CASCADE) def __str__(self): - return self.image.url - + return self.image.url \ No newline at end of file diff --git a/backend/server/adventures/serializers.py b/backend/server/adventures/serializers.py index 99cc6c7..c012976 100644 --- a/backend/server/adventures/serializers.py +++ b/backend/server/adventures/serializers.py @@ -1,5 +1,5 @@ import os -from .models import Adventure, AdventureImage, ChecklistItem, Collection, Note, Transportation, Checklist, Visit +from .models import Adventure, AdventureImage, ChecklistItem, Collection, Note, Transportation, Checklist from rest_framework import serializers class AdventureImageSerializer(serializers.ModelSerializer): @@ -17,49 +17,20 @@ class AdventureImageSerializer(serializers.ModelSerializer): public_url = public_url.replace("'", "") representation['image'] = f"{public_url}/media/{instance.image.name}" return representation - -class VisitSerializer(serializers.ModelSerializer): - class Meta: - model = Visit - fields = ['id', 'start_date', 'end_date', 'notes'] - read_only_fields = ['id'] + + class AdventureSerializer(serializers.ModelSerializer): images = AdventureImageSerializer(many=True, read_only=True) - visits = VisitSerializer(many=True, read_only=False) class Meta: model = Adventure - fields = ['id', 'user_id', 'name', 'description', 'rating', 'activity_types', 'location', 'is_public', 'collection', 'created_at', 'updated_at', 'images', 'link', 'type', 'longitude', 'latitude', 'visits'] + fields = ['id', 'user_id', 'name', 'description', 'rating', 'activity_types', 'location', 'date', 'is_public', 'collection', 'created_at', 'updated_at', 'images', 'link', 'type', 'longitude', 'latitude', 'end_date'] read_only_fields = ['id', 'created_at', 'updated_at', 'user_id'] def to_representation(self, instance): representation = super().to_representation(instance) return representation - def create(self, validated_data): - visits_data = validated_data.pop('visits', []) - adventure = Adventure.objects.create(**validated_data) - for visit_data in visits_data: - Visit.objects.create(adventure=adventure, **visit_data) - return adventure - - def update(self, instance, validated_data): - visits_data = validated_data.pop('visits', []) - instance = super().update(instance, validated_data) - - # Update or create visits - for visit_data in visits_data: - visit_id = visit_data.get('id', None) - if visit_id: - visit = Visit.objects.get(id=visit_id, adventure=instance) - for attr, value in visit_data.items(): - setattr(visit, attr, value) - visit.save() - else: - Visit.objects.create(adventure=instance, **visit_data) - - return instance - class TransportationSerializer(serializers.ModelSerializer): class Meta: @@ -88,7 +59,17 @@ class ChecklistItemSerializer(serializers.ModelSerializer): 'id', 'user_id', 'name', 'is_checked', 'checklist', 'created_at', 'updated_at' ] read_only_fields = ['id', 'created_at', 'updated_at', 'user_id', 'checklist'] - + + # def validate(self, data): + # # Check if the checklist is public and the checklist item is not + # checklist = data.get('checklist') + # is_checked = data.get('is_checked', False) + # if checklist and checklist.is_public and not is_checked: + # raise serializers.ValidationError( + # 'Checklist items associated with a public checklist must be checked.' + # ) + + class ChecklistSerializer(serializers.ModelSerializer): items = ChecklistItemSerializer(many=True, source='checklistitem_set') class Meta: @@ -152,6 +133,9 @@ class ChecklistSerializer(serializers.ModelSerializer): return data + + + class CollectionSerializer(serializers.ModelSerializer): adventures = AdventureSerializer(many=True, read_only=True, source='adventure_set') transportations = TransportationSerializer(many=True, read_only=True, source='transportation_set') From c1664e82bf5a55c828f14150e97441a3e4a952b4 Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Fri, 27 Sep 2024 22:14:34 -0400 Subject: [PATCH 2/4] Fix multiple adventures when shared with multiple users! --- backend/server/adventures/views.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/server/adventures/views.py b/backend/server/adventures/views.py index 7d2a136..b1597dc 100644 --- a/backend/server/adventures/views.py +++ b/backend/server/adventures/views.py @@ -72,23 +72,22 @@ class AdventureViewSet(viewsets.ModelViewSet): return queryset.order_by(ordering) def get_queryset(self): - # if the user is not authenticated return only public adventures for retrieve action + # if the user is not authenticated return only public adventures for retrieve action if not self.request.user.is_authenticated: if self.action == 'retrieve': - return Adventure.objects.filter(is_public=True) + return Adventure.objects.filter(is_public=True).distinct() return Adventure.objects.none() - 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) | Q(collection__shared_with=self.request.user) - ) + ).distinct() else: # For other actions, include user's own adventures and shared adventures return Adventure.objects.filter( Q(user_id=self.request.user.id) | Q(collection__shared_with=self.request.user) - ) + ).distinct() def retrieve(self, request, *args, **kwargs): queryset = self.get_queryset() @@ -270,6 +269,7 @@ class AdventureViewSet(viewsets.ModelViewSet): serializer.save() # when creating an adventure, make sure the user is the owner of the collection or shared with the collection + @transaction.atomic def perform_create(self, serializer): # Retrieve the collection from the validated data collection = serializer.validated_data.get('collection') From 7e110d8670db61a414f0070aafdd6cdfe89e0d40 Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Fri, 27 Sep 2024 22:16:56 -0400 Subject: [PATCH 3/4] Fix multiple adventures when shared with multiple users! --- backend/server/adventures/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/server/adventures/views.py b/backend/server/adventures/views.py index b1597dc..dce3da0 100644 --- a/backend/server/adventures/views.py +++ b/backend/server/adventures/views.py @@ -75,19 +75,19 @@ class AdventureViewSet(viewsets.ModelViewSet): # if the user is not authenticated return only public adventures for retrieve action if not self.request.user.is_authenticated: if self.action == 'retrieve': - return Adventure.objects.filter(is_public=True).distinct() + return Adventure.objects.filter(is_public=True).distinct().order_by('-updated_at') return Adventure.objects.none() 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) | Q(collection__shared_with=self.request.user) - ).distinct() + ).distinct().order_by('-updated_at') else: # For other actions, include user's own adventures and shared adventures return Adventure.objects.filter( Q(user_id=self.request.user.id) | Q(collection__shared_with=self.request.user) - ).distinct() + ).distinct().order_by('-updated_at') def retrieve(self, request, *args, **kwargs): queryset = self.get_queryset() From e41d9382ab1910b2ca83c17becb52e2d274023c2 Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Fri, 27 Sep 2024 22:21:36 -0400 Subject: [PATCH 4/4] Fix for all other collection types --- backend/server/adventures/views.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/server/adventures/views.py b/backend/server/adventures/views.py index dce3da0..5a9dab6 100644 --- a/backend/server/adventures/views.py +++ b/backend/server/adventures/views.py @@ -619,7 +619,7 @@ class TransportationViewSet(viewsets.ModelViewSet): # if the user is not authenticated return only public transportations for retrieve action if not self.request.user.is_authenticated: if self.action == 'retrieve': - return Transportation.objects.filter(is_public=True) + return Transportation.objects.filter(is_public=True).distinct().order_by('-updated_at') return Transportation.objects.none() @@ -627,12 +627,12 @@ class TransportationViewSet(viewsets.ModelViewSet): # For individual adventure retrieval, include public adventures return Transportation.objects.filter( Q(is_public=True) | Q(user_id=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 return Transportation.objects.filter( Q(user_id=self.request.user.id) | Q(collection__shared_with=self.request.user) - ) + ).distinct().order_by('-updated_at') def partial_update(self, request, *args, **kwargs): # Retrieve the current object @@ -741,7 +741,7 @@ class NoteViewSet(viewsets.ModelViewSet): # if the user is not authenticated return only public transportations for retrieve action if not self.request.user.is_authenticated: if self.action == 'retrieve': - return Note.objects.filter(is_public=True) + return Note.objects.filter(is_public=True).distinct().order_by('-updated_at') return Note.objects.none() @@ -749,12 +749,12 @@ class NoteViewSet(viewsets.ModelViewSet): # For individual adventure retrieval, include public adventures return Note.objects.filter( Q(is_public=True) | Q(user_id=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 return Note.objects.filter( Q(user_id=self.request.user.id) | Q(collection__shared_with=self.request.user) - ) + ).distinct().order_by('-updated_at') def partial_update(self, request, *args, **kwargs): # Retrieve the current object @@ -863,7 +863,7 @@ class ChecklistViewSet(viewsets.ModelViewSet): # if the user is not authenticated return only public transportations for retrieve action if not self.request.user.is_authenticated: if self.action == 'retrieve': - return Checklist.objects.filter(is_public=True) + return Checklist.objects.filter(is_public=True).distinct().order_by('-updated_at') return Checklist.objects.none() @@ -871,12 +871,12 @@ class ChecklistViewSet(viewsets.ModelViewSet): # For individual adventure retrieval, include public adventures return Checklist.objects.filter( Q(is_public=True) | Q(user_id=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 return Checklist.objects.filter( Q(user_id=self.request.user.id) | Q(collection__shared_with=self.request.user) - ) + ).distinct().order_by('-updated_at') def partial_update(self, request, *args, **kwargs): # Retrieve the current object